| 1 | /* |
| 2 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 3 | * See the COPYING file in the top-level directory. |
| 4 | */ |
| 5 | #include "qemu/osdep.h" |
| 6 | #include "qemu/error-report.h" |
| 7 | |
| 8 | #include "cpu.h" |
| 9 | |
| 10 | void x86_cpu_xsave_all_areas(X86CPU *cpu, void *buf, uint32_t buflen) |
| 11 | { |
| 12 | CPUX86State *env = &cpu->env; |
| 13 | const ExtSaveArea *e, *f; |
| 14 | int i; |
| 15 | |
| 16 | X86LegacyXSaveArea *legacy; |
| 17 | X86XSaveHeader *header; |
| 18 | uint16_t cwd, swd, twd; |
| 19 | |
| 20 | memset(buf, 0, buflen); |
| 21 | |
| 22 | e = &x86_ext_save_areas[XSTATE_FP_BIT]; |
| 23 | |
| 24 | legacy = buf + e->offset; |
| 25 | header = buf + e->offset + sizeof(*legacy); |
| 26 | |
| 27 | twd = 0; |
| 28 | swd = env->fpus & ~(7 << 11); |
| 29 | swd |= (env->fpstt & 7) << 11; |
| 30 | cwd = env->fpuc; |
| 31 | for (i = 0; i < 8; ++i) { |
| 32 | twd |= (!env->fptags[i]) << i; |
| 33 | } |
| 34 | legacy->fcw = cwd; |
| 35 | legacy->fsw = swd; |
| 36 | legacy->ftw = twd; |
| 37 | legacy->fpop = env->fpop; |
| 38 | legacy->fpip = env->fpip; |
| 39 | legacy->fpdp = env->fpdp; |
| 40 | memcpy(&legacy->fpregs, env->fpregs, |
| 41 | sizeof(env->fpregs)); |
| 42 | legacy->mxcsr = env->mxcsr; |
| 43 | |
| 44 | for (i = 0; i < CPU_NB_REGS; i++) { |
| 45 | uint8_t *xmm = legacy->xmm_regs[i]; |
| 46 | |
| 47 | stq_p(xmm, env->xmm_regs[i].ZMM_Q(0)); |
| 48 | stq_p(xmm + 8, env->xmm_regs[i].ZMM_Q(1)); |
| 49 | } |
| 50 | |
| 51 | header->xstate_bv = env->xstate_bv; |
| 52 | |
| 53 | e = &x86_ext_save_areas[XSTATE_YMM_BIT]; |
| 54 | if (e->size && e->offset) { |
| 55 | XSaveAVX *avx; |
| 56 | |
| 57 | avx = buf + e->offset; |
| 58 | |
| 59 | for (i = 0; i < CPU_NB_REGS; i++) { |
| 60 | uint8_t *ymmh = avx->ymmh[i]; |
| 61 | |
| 62 | stq_p(ymmh, env->xmm_regs[i].ZMM_Q(2)); |
| 63 | stq_p(ymmh + 8, env->xmm_regs[i].ZMM_Q(3)); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | e = &x86_ext_save_areas[XSTATE_BNDREGS_BIT]; |
| 68 | if (e->size && e->offset) { |
| 69 | XSaveBNDREG *bndreg; |
| 70 | XSaveBNDCSR *bndcsr; |
| 71 | |
| 72 | f = &x86_ext_save_areas[XSTATE_BNDCSR_BIT]; |
| 73 | assert(f->size); |
| 74 | assert(f->offset); |
| 75 | |
| 76 | bndreg = buf + e->offset; |
| 77 | bndcsr = buf + f->offset; |
| 78 | |
| 79 | memcpy(&bndreg->bnd_regs, env->bnd_regs, |
| 80 | sizeof(env->bnd_regs)); |
| 81 | bndcsr->bndcsr = env->bndcs_regs; |
| 82 | } |
| 83 | |
| 84 | e = &x86_ext_save_areas[XSTATE_OPMASK_BIT]; |
| 85 | if (e->size && e->offset) { |
| 86 | XSaveOpmask *opmask; |
| 87 | XSaveZMM_Hi256 *zmm_hi256; |
| 88 | #ifdef TARGET_X86_64 |
| 89 | XSaveHi16_ZMM *hi16_zmm; |
| 90 | #endif |
| 91 | |
| 92 | f = &x86_ext_save_areas[XSTATE_ZMM_Hi256_BIT]; |
| 93 | assert(f->size); |
| 94 | assert(f->offset); |
| 95 | |
| 96 | opmask = buf + e->offset; |
| 97 | zmm_hi256 = buf + f->offset; |
| 98 | |
| 99 | memcpy(&opmask->opmask_regs, env->opmask_regs, |
| 100 | sizeof(env->opmask_regs)); |
| 101 | |
| 102 | for (i = 0; i < CPU_NB_REGS; i++) { |
| 103 | uint8_t *zmmh = zmm_hi256->zmm_hi256[i]; |
| 104 | |
| 105 | stq_p(zmmh, env->xmm_regs[i].ZMM_Q(4)); |
| 106 | stq_p(zmmh + 8, env->xmm_regs[i].ZMM_Q(5)); |
| 107 | stq_p(zmmh + 16, env->xmm_regs[i].ZMM_Q(6)); |
| 108 | stq_p(zmmh + 24, env->xmm_regs[i].ZMM_Q(7)); |
| 109 | } |
| 110 | |
| 111 | #ifdef TARGET_X86_64 |
| 112 | f = &x86_ext_save_areas[XSTATE_Hi16_ZMM_BIT]; |
| 113 | assert(f->size); |
| 114 | assert(f->offset); |
| 115 | |
| 116 | hi16_zmm = buf + f->offset; |
| 117 | |
| 118 | memcpy(&hi16_zmm->hi16_zmm, &env->xmm_regs[16], |
| 119 | 16 * sizeof(env->xmm_regs[16])); |
| 120 | #endif |
| 121 | } |
| 122 | |
| 123 | #ifdef TARGET_X86_64 |
| 124 | e = &x86_ext_save_areas[XSTATE_PKRU_BIT]; |
| 125 | if (e->size && e->offset) { |
| 126 | XSavePKRU *pkru = buf + e->offset; |
| 127 | |
| 128 | memcpy(pkru, &env->pkru, sizeof(env->pkru)); |
| 129 | } |
| 130 | |
| 131 | e = &x86_ext_save_areas[XSTATE_XTILE_CFG_BIT]; |
| 132 | if (e->size && e->offset) { |
| 133 | XSaveXTILECFG *tilecfg = buf + e->offset; |
| 134 | |
| 135 | memcpy(tilecfg, &env->xtilecfg, sizeof(env->xtilecfg)); |
| 136 | } |
| 137 | |
| 138 | e = &x86_ext_save_areas[XSTATE_XTILE_DATA_BIT]; |
| 139 | if (e->size && e->offset && buflen >= e->size + e->offset) { |
| 140 | XSaveXTILEDATA *tiledata = buf + e->offset; |
| 141 | |
| 142 | memcpy(tiledata, &env->xtiledata, sizeof(env->xtiledata)); |
| 143 | } |
| 144 | |
| 145 | e = &x86_ext_save_areas[XSTATE_APX_BIT]; |
| 146 | if (e->size && e->offset && buflen) { |
| 147 | XSaveAPX *apx = buf + e->offset; |
| 148 | |
| 149 | memcpy(apx, &env->regs[CPU_NB_REGS], |
| 150 | sizeof(env->regs[CPU_NB_REGS]) * (CPU_NB_EREGS - CPU_NB_REGS)); |
| 151 | } |
| 152 | #endif |
| 153 | } |
| 154 | |
| 155 | void x86_cpu_xrstor_all_areas(X86CPU *cpu, const void *buf, uint32_t buflen) |
| 156 | { |
| 157 | CPUX86State *env = &cpu->env; |
| 158 | const ExtSaveArea *e, *f, *g; |
| 159 | int i; |
| 160 | |
| 161 | const X86LegacyXSaveArea *legacy; |
| 162 | const X86XSaveHeader *header; |
| 163 | uint16_t cwd, swd, twd; |
| 164 | |
| 165 | e = &x86_ext_save_areas[XSTATE_FP_BIT]; |
| 166 | |
| 167 | legacy = buf + e->offset; |
| 168 | header = buf + e->offset + sizeof(*legacy); |
| 169 | |
| 170 | cwd = legacy->fcw; |
| 171 | swd = legacy->fsw; |
| 172 | twd = legacy->ftw; |
| 173 | env->fpop = legacy->fpop; |
| 174 | env->fpstt = (swd >> 11) & 7; |
| 175 | env->fpus = swd; |
| 176 | env->fpuc = cwd; |
| 177 | for (i = 0; i < 8; ++i) { |
| 178 | env->fptags[i] = !((twd >> i) & 1); |
| 179 | } |
| 180 | env->fpip = legacy->fpip; |
| 181 | env->fpdp = legacy->fpdp; |
| 182 | env->mxcsr = legacy->mxcsr; |
| 183 | memcpy(env->fpregs, &legacy->fpregs, |
| 184 | sizeof(env->fpregs)); |
| 185 | |
| 186 | for (i = 0; i < CPU_NB_REGS; i++) { |
| 187 | const uint8_t *xmm = legacy->xmm_regs[i]; |
| 188 | |
| 189 | env->xmm_regs[i].ZMM_Q(0) = ldq_p(xmm); |
| 190 | env->xmm_regs[i].ZMM_Q(1) = ldq_p(xmm + 8); |
| 191 | } |
| 192 | |
| 193 | env->xstate_bv = header->xstate_bv; |
| 194 | |
| 195 | e = &x86_ext_save_areas[XSTATE_YMM_BIT]; |
| 196 | if (e->size && e->offset) { |
| 197 | const XSaveAVX *avx; |
| 198 | |
| 199 | avx = buf + e->offset; |
| 200 | for (i = 0; i < CPU_NB_REGS; i++) { |
| 201 | const uint8_t *ymmh = avx->ymmh[i]; |
| 202 | |
| 203 | env->xmm_regs[i].ZMM_Q(2) = ldq_p(ymmh); |
| 204 | env->xmm_regs[i].ZMM_Q(3) = ldq_p(ymmh + 8); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | e = &x86_ext_save_areas[XSTATE_BNDREGS_BIT]; |
| 209 | if (e->size && e->offset) { |
| 210 | const XSaveBNDREG *bndreg; |
| 211 | const XSaveBNDCSR *bndcsr; |
| 212 | |
| 213 | f = &x86_ext_save_areas[XSTATE_BNDCSR_BIT]; |
| 214 | assert(f->size); |
| 215 | assert(f->offset); |
| 216 | |
| 217 | bndreg = buf + e->offset; |
| 218 | bndcsr = buf + f->offset; |
| 219 | |
| 220 | memcpy(env->bnd_regs, &bndreg->bnd_regs, |
| 221 | sizeof(env->bnd_regs)); |
| 222 | env->bndcs_regs = bndcsr->bndcsr; |
| 223 | } |
| 224 | |
| 225 | e = &x86_ext_save_areas[XSTATE_OPMASK_BIT]; |
| 226 | if (e->size && e->offset) { |
| 227 | const XSaveOpmask *opmask; |
| 228 | const XSaveZMM_Hi256 *zmm_hi256; |
| 229 | #ifdef TARGET_X86_64 |
| 230 | const XSaveHi16_ZMM *hi16_zmm; |
| 231 | #endif |
| 232 | |
| 233 | f = &x86_ext_save_areas[XSTATE_ZMM_Hi256_BIT]; |
| 234 | assert(f->size); |
| 235 | assert(f->offset); |
| 236 | |
| 237 | g = &x86_ext_save_areas[XSTATE_Hi16_ZMM_BIT]; |
| 238 | assert(g->size); |
| 239 | assert(g->offset); |
| 240 | |
| 241 | opmask = buf + e->offset; |
| 242 | zmm_hi256 = buf + f->offset; |
| 243 | #ifdef TARGET_X86_64 |
| 244 | hi16_zmm = buf + g->offset; |
| 245 | #endif |
| 246 | |
| 247 | memcpy(env->opmask_regs, &opmask->opmask_regs, |
| 248 | sizeof(env->opmask_regs)); |
| 249 | |
| 250 | for (i = 0; i < CPU_NB_REGS; i++) { |
| 251 | const uint8_t *zmmh = zmm_hi256->zmm_hi256[i]; |
| 252 | |
| 253 | env->xmm_regs[i].ZMM_Q(4) = ldq_p(zmmh); |
| 254 | env->xmm_regs[i].ZMM_Q(5) = ldq_p(zmmh + 8); |
| 255 | env->xmm_regs[i].ZMM_Q(6) = ldq_p(zmmh + 16); |
| 256 | env->xmm_regs[i].ZMM_Q(7) = ldq_p(zmmh + 24); |
| 257 | } |
| 258 | |
| 259 | #ifdef TARGET_X86_64 |
| 260 | memcpy(&env->xmm_regs[16], &hi16_zmm->hi16_zmm, |
| 261 | 16 * sizeof(env->xmm_regs[16])); |
| 262 | #endif |
| 263 | } |
| 264 | |
| 265 | #ifdef TARGET_X86_64 |
| 266 | e = &x86_ext_save_areas[XSTATE_PKRU_BIT]; |
| 267 | if (e->size && e->offset) { |
| 268 | const XSavePKRU *pkru; |
| 269 | |
| 270 | pkru = buf + e->offset; |
| 271 | memcpy(&env->pkru, pkru, sizeof(env->pkru)); |
| 272 | } |
| 273 | |
| 274 | e = &x86_ext_save_areas[XSTATE_XTILE_CFG_BIT]; |
| 275 | if (e->size && e->offset) { |
| 276 | const XSaveXTILECFG *tilecfg = buf + e->offset; |
| 277 | |
| 278 | memcpy(&env->xtilecfg, tilecfg, sizeof(env->xtilecfg)); |
| 279 | } |
| 280 | |
| 281 | e = &x86_ext_save_areas[XSTATE_XTILE_DATA_BIT]; |
| 282 | if (e->size && e->offset && buflen >= e->size + e->offset) { |
| 283 | const XSaveXTILEDATA *tiledata = buf + e->offset; |
| 284 | |
| 285 | memcpy(&env->xtiledata, tiledata, sizeof(env->xtiledata)); |
| 286 | } |
| 287 | |
| 288 | e = &x86_ext_save_areas[XSTATE_APX_BIT]; |
| 289 | if (e->size && e->offset) { |
| 290 | const XSaveAPX *apx = buf + e->offset; |
| 291 | |
| 292 | memcpy(&env->regs[CPU_NB_REGS], apx, |
| 293 | sizeof(env->regs[CPU_NB_REGS]) * (CPU_NB_EREGS - CPU_NB_REGS)); |
| 294 | } |
| 295 | #endif |
| 296 | } |
| 297 | |
| 298 | #define XSTATE_BV_IN_HDR offsetof(X86XSaveHeader, xstate_bv) |
| 299 | #define XCOMP_BV_IN_HDR offsetof(X86XSaveHeader, xcomp_bvo) |
| 300 | |
| 301 | typedef struct X86XSaveAreaView { |
| 302 | /* 512 bytes */ |
| 303 | X86LegacyXSaveArea legacy; |
| 304 | /* 64 bytes */ |
| 305 | X86XSaveHeader header; |
| 306 | /* ...followed by individual xsave areas */ |
| 307 | } X86XSaveAreaView; |
| 308 | |
| 309 | #define XSAVE_XSTATE_BV_OFFSET offsetof(X86XSaveAreaView, header.xstate_bv) |
| 310 | #define XSAVE_XCOMP_BV_OFFSET offsetof(X86XSaveAreaView, header.xcomp_bv) |
| 311 | #define XSAVE_EXT_OFFSET (sizeof(X86LegacyXSaveArea) + \ |
| 312 | sizeof(X86XSaveHeader)) |
| 313 | |
| 314 | /** |
| 315 | * decompact_xsave_area - Convert compacted XSAVE format to standard format |
| 316 | * @buf: Source buffer containing compacted XSAVE data |
| 317 | * @buflen: Size of source buffer |
| 318 | * @env: CPU state where the standard format buffer will be written to |
| 319 | * |
| 320 | * Accelerator backends like MSHV might return XSAVE state in compacted format |
| 321 | * (XSAVEC). The state components have to be packed contiguously without gaps. |
| 322 | * The XSAVE qemu buffers are in standard format where each component has a |
| 323 | * fixed offset. |
| 324 | * |
| 325 | * Returns: 0 on success, negative errno on failure |
| 326 | */ |
| 327 | int decompact_xsave_area(const void *buf, size_t buflen, CPUX86State *env) |
| 328 | { |
| 329 | uint64_t compacted_xstate_bv, compacted_xcomp_bv, compacted_layout_bv; |
| 330 | size_t xsave_offset; |
| 331 | uint64_t *xcomp_bv; |
| 332 | size_t i; |
| 333 | uint32_t eax, ebx, ecx, edx; |
| 334 | uint32_t size, dst_off; |
| 335 | bool align64, supervisor; |
| 336 | uint64_t guest_xcr0, *xstate_bv; |
| 337 | |
| 338 | compacted_xstate_bv = *(uint64_t *)(buf + XSAVE_XSTATE_BV_OFFSET); |
| 339 | compacted_xcomp_bv = *(uint64_t *)(buf + XSAVE_XCOMP_BV_OFFSET); |
| 340 | |
| 341 | /* This function only handles compacted format (bit 63 set) */ |
| 342 | assert((compacted_xcomp_bv >> 63) & 1); |
| 343 | |
| 344 | /* Low bits of XCOMP_BV describe which components are in the layout */ |
| 345 | compacted_layout_bv = compacted_xcomp_bv & ~(1ULL << 63); |
| 346 | |
| 347 | /* Zero out buffer, then copy legacy region (FP + SSE) and header as-is */ |
| 348 | memset(env->xsave_buf, 0, env->xsave_buf_len); |
| 349 | memcpy(env->xsave_buf, buf, XSAVE_EXT_OFFSET); |
| 350 | |
| 351 | /* |
| 352 | * We mask XSTATE_BV with the guest's supported XCR0 because: |
| 353 | * 1. Supervisor state (IA32_XSS) is hypervisor-managed, we don't use |
| 354 | * this state for migration. |
| 355 | * 2. Features disabled at partition creation (e.g. AMX) must be excluded |
| 356 | */ |
| 357 | guest_xcr0 = ((uint64_t)env->features[FEAT_XSAVE_XCR0_HI] << 32) | |
| 358 | env->features[FEAT_XSAVE_XCR0_LO]; |
| 359 | xstate_bv = (uint64_t *)(env->xsave_buf + XSAVE_XSTATE_BV_OFFSET); |
| 360 | *xstate_bv &= guest_xcr0; |
| 361 | |
| 362 | /* Clear bit 63 - output is standard format, not compacted */ |
| 363 | xcomp_bv = (uint64_t *)(env->xsave_buf + XSAVE_XCOMP_BV_OFFSET); |
| 364 | *xcomp_bv = *xcomp_bv & ~(1ULL << 63); |
| 365 | |
| 366 | /* |
| 367 | * Process each extended state component in the compacted layout. |
| 368 | * Components 0 and 1 (FP and SSE) are in the legacy region, so we |
| 369 | * start at component 2. For each component: |
| 370 | * - Calculate its offset in the compacted source (contiguous layout) |
| 371 | * - Get its fixed offset in the standard destination from CPUID |
| 372 | * - Copy if the component has non-init state (bit set in XSTATE_BV) |
| 373 | */ |
| 374 | xsave_offset = XSAVE_EXT_OFFSET; |
| 375 | for (i = 2; i < 63; i++) { |
| 376 | if (((compacted_layout_bv >> i) & 1) == 0) { |
| 377 | continue; |
| 378 | } |
| 379 | |
| 380 | /* Query guest CPUID for this component's size and standard offset */ |
| 381 | cpu_x86_cpuid(env, 0xD, i, &eax, &ebx, &ecx, &edx); |
| 382 | |
| 383 | size = eax; |
| 384 | dst_off = ebx; |
| 385 | align64 = (ecx & ESA_FEATURE_ALIGN64_MASK) != 0; |
| 386 | supervisor = (ecx & ESA_FEATURE_XSS_MASK) != 0; |
| 387 | |
| 388 | /* Component is in the layout but unknown to the guest CPUID model */ |
| 389 | if (size == 0) { |
| 390 | /* |
| 391 | * The hypervisor might expose a component that has no |
| 392 | * representation in the guest CPUID model. We query the host to |
| 393 | * retrieve the size of the component, so we can skip over it. |
| 394 | */ |
| 395 | host_cpuid(0xD, i, &eax, &ebx, &ecx, &edx); |
| 396 | size = eax; |
| 397 | align64 = (ecx & ESA_FEATURE_ALIGN64_MASK) != 0; |
| 398 | if (size == 0) { |
| 399 | error_report("xsave component %zu: size unknown to both " |
| 400 | "guest and host CPUID", i); |
| 401 | return -EINVAL; |
| 402 | } |
| 403 | |
| 404 | if (align64) { |
| 405 | xsave_offset = QEMU_ALIGN_UP(xsave_offset, 64); |
| 406 | } |
| 407 | |
| 408 | if (xsave_offset + size > buflen) { |
| 409 | error_report("xsave component %zu overruns source buffer: " |
| 410 | "offset=%zu size=%u buflen=%zu", |
| 411 | i, xsave_offset, size, buflen); |
| 412 | return -E2BIG; |
| 413 | } |
| 414 | |
| 415 | xsave_offset += size; |
| 416 | continue; |
| 417 | } |
| 418 | |
| 419 | if (align64) { |
| 420 | xsave_offset = QEMU_ALIGN_UP(xsave_offset, 64); |
| 421 | } |
| 422 | |
| 423 | if ((xsave_offset + size) > buflen) { |
| 424 | error_report("xsave component %zu overruns source buffer: " |
| 425 | "offset=%zu size=%u buflen=%zu", |
| 426 | i, xsave_offset, size, buflen); |
| 427 | return -E2BIG; |
| 428 | } |
| 429 | |
| 430 | if ((dst_off + size) > env->xsave_buf_len) { |
| 431 | error_report("xsave component %zu overruns destination buffer: " |
| 432 | "offset=%u size=%u buflen=%zu", |
| 433 | i, dst_off, size, (size_t)env->xsave_buf_len); |
| 434 | return -E2BIG; |
| 435 | } |
| 436 | |
| 437 | /* |
| 438 | * Copy components marked present in XSTATE_BV to guest model. |
| 439 | * |
| 440 | * NB: Supervisor state is skipped b/c there is no slot in the |
| 441 | * standard format XSAVE buffer (CET state is migrated via MSRs, |
| 442 | * others supervisor state isn't migrated). |
| 443 | */ |
| 444 | if (((compacted_xstate_bv >> i) & 1) != 0 && !supervisor) { |
| 445 | memcpy(env->xsave_buf + dst_off, buf + xsave_offset, size); |
| 446 | } |
| 447 | |
| 448 | xsave_offset += size; |
| 449 | } |
| 450 | |
| 451 | return 0; |
| 452 | } |
| 453 | |
| 454 | /** |
| 455 | * compact_xsave_area - Convert standard XSAVE format to compacted format |
| 456 | * @env: CPU state containing the standard format XSAVE buffer |
| 457 | * @buf: Destination buffer for compacted XSAVE data (to send to hypervisor) |
| 458 | * @buflen: Size of destination buffer |
| 459 | * |
| 460 | * Accelerator backends like MSHV might expect XSAVE state in compacted format |
| 461 | * (XSAVEC). The state components are packed contiguously without gaps. |
| 462 | * The XSAVE qemu buffers are in standard format where each component has a |
| 463 | * fixed offset. |
| 464 | * |
| 465 | * This function converts from standard to compacted format, it accepts a |
| 466 | * pre-allocated destination buffer of sufficient size, it is the |
| 467 | * responsibility of the caller to ensure the buffer is big enough. |
| 468 | * |
| 469 | * Returns: total size of compacted XSAVE data written to @buf |
| 470 | */ |
| 471 | int compact_xsave_area(CPUX86State *env, void *buf, size_t buflen) |
| 472 | { |
| 473 | uint64_t *xcomp_bv; |
| 474 | size_t i; |
| 475 | uint32_t eax, ebx, ecx, edx; |
| 476 | uint32_t size, src_off; |
| 477 | bool align64; |
| 478 | size_t compact_offset; |
| 479 | uint64_t host_xcr0_mask, guest_xcr0; |
| 480 | |
| 481 | /* Zero out buffer, then copy legacy region (FP + SSE) and header as-is */ |
| 482 | memset(buf, 0, buflen); |
| 483 | memcpy(buf, env->xsave_buf, XSAVE_EXT_OFFSET); |
| 484 | |
| 485 | /* |
| 486 | * Set XCOMP_BV to indicate compacted format (bit 63) and which |
| 487 | * components are in the layout. |
| 488 | * |
| 489 | * We must explicitly set XCOMP_BV because x86_cpu_xsave_all_areas() |
| 490 | * produces standard format with XCOMP_BV=0 (buffer is zeroed and only |
| 491 | * XSTATE_BV is set in the header). |
| 492 | * |
| 493 | * XCOMP_BV must reflect the partition's XSAVE capability, not the |
| 494 | * guest's current XCR0 (env->xcr0). These differ b/c: |
| 495 | * - A guest's XCR0 is what the guest OS has enabled via XSETBV |
| 496 | * - The partition's XCR0 mask is the hypervisor's save/restore capability |
| 497 | * |
| 498 | * The hypervisor uses XSAVES which saves based on its capability, so the |
| 499 | * XCOMP_BV value in the buffer we send back must match that capability. |
| 500 | * |
| 501 | * We intersect the host XCR0 with the guest's supported XCR0 features |
| 502 | * (FEAT_XSAVE_XCR0_*) so that features disabled at partition creation |
| 503 | * (e.g. AMX) are excluded from the compacted layout. |
| 504 | */ |
| 505 | host_cpuid(0xD, 0, &eax, &ebx, &ecx, &edx); |
| 506 | host_xcr0_mask = ((uint64_t)edx << 32) | eax; |
| 507 | guest_xcr0 = ((uint64_t)env->features[FEAT_XSAVE_XCR0_HI] << 32) | |
| 508 | env->features[FEAT_XSAVE_XCR0_LO]; |
| 509 | host_xcr0_mask &= guest_xcr0; |
| 510 | xcomp_bv = buf + XSAVE_XCOMP_BV_OFFSET; |
| 511 | *xcomp_bv = host_xcr0_mask | (1ULL << 63); |
| 512 | |
| 513 | /* |
| 514 | * Process each extended state component in the host's XCR0. |
| 515 | * The compacted layout must match XCOMP_BV (host capability). |
| 516 | * |
| 517 | * For each component: |
| 518 | * - Get its size and standard offset from host CPUID |
| 519 | * - Apply 64-byte alignment if required |
| 520 | * - Copy data only if guest has this component (bit set in env->xcr0) |
| 521 | * - Always advance offset to maintain correct layout |
| 522 | */ |
| 523 | compact_offset = XSAVE_EXT_OFFSET; |
| 524 | for (i = 2; i < 63; i++) { |
| 525 | if (!((host_xcr0_mask >> i) & 1)) { |
| 526 | continue; |
| 527 | } |
| 528 | |
| 529 | /* Query host CPUID for this component's size and standard offset */ |
| 530 | host_cpuid(0xD, i, &eax, &ebx, &ecx, &edx); |
| 531 | size = eax; |
| 532 | src_off = ebx; |
| 533 | align64 = (ecx & ESA_FEATURE_ALIGN64_MASK) != 0; |
| 534 | |
| 535 | if (size == 0) { |
| 536 | /* Component in host xcr0 but unknown - shouldn't happen */ |
| 537 | continue; |
| 538 | } |
| 539 | |
| 540 | /* Apply 64-byte alignment if required by this component */ |
| 541 | if (align64) { |
| 542 | compact_offset = QEMU_ALIGN_UP(compact_offset, 64); |
| 543 | } |
| 544 | |
| 545 | /* |
| 546 | * Only copy data if guest has this component enabled in XCR0. |
| 547 | * Otherwise the component remains zeroed (init state), but we |
| 548 | * still advance the offset to maintain the correct layout. |
| 549 | */ |
| 550 | if ((env->xcr0 >> i) & 1) { |
| 551 | memcpy(buf + compact_offset, env->xsave_buf + src_off, size); |
| 552 | } |
| 553 | |
| 554 | compact_offset += size; |
| 555 | } |
| 556 | |
| 557 | return compact_offset; |
| 558 | } |