@samitouri / QOSamiQemu / commits / 8612deb3f4

target/i386: add de/compaction to xsave_helper

HyperV use XSAVES which stores extended state in compacted format in which components are packed contiguously, while QEMU's internal XSAVE representation use the standard format in which each component is places at a fixed offset. Hence for this purpose we add two conversion fn's to the xsave helper to roundtrip XSAVE state in a migration. - decompact_xsave_area(): converts compacted format to standard. XSTATE_BV is masked to host XCR0 since IA32_XSS is managed by the hypervisor. - compact_xsave_area(): converts standard format back to compacted format. XCOMP_BV is set from the host's CPUID 0xD.0 rather than the guest's XCR0, as this is what the hypervisor expects. Both functions use the host's CPUID leaf 0xD subleaves to determine component sizes, offsets, and alignment requirements. There are situations when the host advertises features that we want to disable for the guest, e.g. AMX TILE. In this case we cannot rely on the host's xcr0, but instead we use the feature mask that has been generated in as part of the CPU realization process (x86_cpu_expand_features). Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com> [Fixup: made xsave_offset a size_t to fix macOS and OpenBSD builds] Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr> Link: https://lore.kernel.org/r/20260422214225.2242-29-mohamed@unpredictable.fr Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

Magnus Kulke committed Apr 22, 2026 at 23:42 UTC 8612deb3f435157f00945a3d75fef994b31f08a7
3 files changed +260 -3
target/i386/cpu.h
+2
@@ -3024,6 +3024,8 @@ void x86_cpu_xrstor_all_areas(X86CPU *cpu, const void *buf, uint32_t buflen);
3024 void x86_cpu_xsave_all_areas(X86CPU *cpu, void *buf, uint32_t buflen);
3025 uint32_t xsave_area_size(uint64_t mask, bool compacted);
3026 void x86_update_hflags(CPUX86State* env);
3027 +int decompact_xsave_area(const void *buf, size_t buflen, CPUX86State *env);
3028 +int compact_xsave_area(CPUX86State *env, void *buf, size_t buflen);
3029
3030 static inline bool hyperv_feat_enabled(X86CPU *cpu, int feat)
3031 {
target/i386/meson.build
+2 -3
@@ -3,14 +3,13 @@ i386_ss.add(files(
3 'cpu.c',
4 'gdbstub.c',
5 'helper.c',
6 - 'xsave_helper.c',
6 'cpu-dump.c',
7 ))
8 i386_ss.add(when: 'CONFIG_SEV', if_true: files('host-cpu.c', 'confidential-guest.c'))
9
10 # x86 cpu type
12 -i386_ss.add(when: 'CONFIG_KVM', if_true: files('host-cpu.c'))
13 -i386_ss.add(when: 'CONFIG_HVF', if_true: files('host-cpu.c'))
11 +i386_ss.add(when: 'CONFIG_KVM', if_true: files('xsave_helper.c', 'host-cpu.c'))
12 +i386_ss.add(when: 'CONFIG_HVF', if_true: files('xsave_helper.c', 'host-cpu.c'))
13 i386_ss.add(when: 'CONFIG_WHPX', if_true: files('host-cpu.c'))
14 i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c'))
15 i386_ss.add(when: 'CONFIG_MSHV', if_true: files('host-cpu.c'))
target/i386/xsave_helper.c
+256
@@ -3,6 +3,7 @@
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
@@ -293,3 +294,258 @@ void x86_cpu_xrstor_all_areas(X86CPU *cpu, const void *buf, uint32_t buflen)
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;
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 & (1u << 1)) != 0;
386 +
387 + /* Component is in the layout but unknown to the guest CPUID model */
388 + if (size == 0) {
389 + /*
390 + * The hypervisor might expose a component that has no
391 + * representation in the guest CPUID model. We query the host to
392 + * retrieve the size of the component, so we can skip over it.
393 + */
394 + host_cpuid(0xD, i, &eax, &ebx, &ecx, &edx);
395 + size = eax;
396 + align64 = (ecx & (1u << 1)) != 0;
397 + if (size == 0) {
398 + error_report("xsave component %zu: size unknown to both "
399 + "guest and host CPUID", i);
400 + return -EINVAL;
401 + }
402 +
403 + if (align64) {
404 + xsave_offset = QEMU_ALIGN_UP(xsave_offset, 64);
405 + }
406 +
407 + if (xsave_offset + size > buflen) {
408 + error_report("xsave component %zu overruns source buffer: "
409 + "offset=%zu size=%u buflen=%zu",
410 + i, xsave_offset, size, buflen);
411 + return -E2BIG;
412 + }
413 +
414 + xsave_offset += size;
415 + continue;
416 + }
417 +
418 + if (align64) {
419 + xsave_offset = QEMU_ALIGN_UP(xsave_offset, 64);
420 + }
421 +
422 + if ((xsave_offset + size) > buflen) {
423 + error_report("xsave component %zu overruns source buffer: "
424 + "offset=%zu size=%u buflen=%zu",
425 + i, xsave_offset, size, buflen);
426 + return -E2BIG;
427 + }
428 +
429 + if ((dst_off + size) > env->xsave_buf_len) {
430 + error_report("xsave component %zu overruns destination buffer: "
431 + "offset=%u size=%u buflen=%zu",
432 + i, dst_off, size, (size_t)env->xsave_buf_len);
433 + return -E2BIG;
434 + }
435 +
436 + /* Copy components marked present in XSTATE_BV to guest model */
437 + if (((compacted_xstate_bv >> i) & 1) != 0) {
438 + memcpy(env->xsave_buf + dst_off, buf + xsave_offset, size);
439 + }
440 +
441 + xsave_offset += size;
442 + }
443 +
444 + return 0;
445 +}
446 +
447 +/**
448 + * compact_xsave_area - Convert standard XSAVE format to compacted format
449 + * @env: CPU state containing the standard format XSAVE buffer
450 + * @buf: Destination buffer for compacted XSAVE data (to send to hypervisor)
451 + * @buflen: Size of destination buffer
452 + *
453 + * Accelerator backends like MSHV might expect XSAVE state in compacted format
454 + * (XSAVEC). The state components are packed contiguously without gaps.
455 + * The XSAVE qemu buffers are in standard format where each component has a
456 + * fixed offset.
457 + *
458 + * This function converts from standard to compacted format, it accepts a
459 + * pre-allocated destination buffer of sufficient size, it is the
460 + * responsibility of the caller to ensure the buffer is big enough.
461 + *
462 + * Returns: total size of compacted XSAVE data written to @buf
463 + */
464 +int compact_xsave_area(CPUX86State *env, void *buf, size_t buflen)
465 +{
466 + uint64_t *xcomp_bv;
467 + size_t i;
468 + uint32_t eax, ebx, ecx, edx;
469 + uint32_t size, src_off;
470 + bool align64;
471 + size_t compact_offset;
472 + uint64_t host_xcr0_mask, guest_xcr0;
473 +
474 + /* Zero out buffer, then copy legacy region (FP + SSE) and header as-is */
475 + memset(buf, 0, buflen);
476 + memcpy(buf, env->xsave_buf, XSAVE_EXT_OFFSET);
477 +
478 + /*
479 + * Set XCOMP_BV to indicate compacted format (bit 63) and which
480 + * components are in the layout.
481 + *
482 + * We must explicitly set XCOMP_BV because x86_cpu_xsave_all_areas()
483 + * produces standard format with XCOMP_BV=0 (buffer is zeroed and only
484 + * XSTATE_BV is set in the header).
485 + *
486 + * XCOMP_BV must reflect the partition's XSAVE capability, not the
487 + * guest's current XCR0 (env->xcr0). These differ b/c:
488 + * - A guest's XCR0 is what the guest OS has enabled via XSETBV
489 + * - The partition's XCR0 mask is the hypervisor's save/restore capability
490 + *
491 + * The hypervisor uses XSAVES which saves based on its capability, so the
492 + * XCOMP_BV value in the buffer we send back must match that capability.
493 + *
494 + * We intersect the host XCR0 with the guest's supported XCR0 features
495 + * (FEAT_XSAVE_XCR0_*) so that features disabled at partition creation
496 + * (e.g. AMX) are excluded from the compacted layout.
497 + */
498 + host_cpuid(0xD, 0, &eax, &ebx, &ecx, &edx);
499 + host_xcr0_mask = ((uint64_t)edx << 32) | eax;
500 + guest_xcr0 = ((uint64_t)env->features[FEAT_XSAVE_XCR0_HI] << 32) |
501 + env->features[FEAT_XSAVE_XCR0_LO];
502 + host_xcr0_mask &= guest_xcr0;
503 + xcomp_bv = buf + XSAVE_XCOMP_BV_OFFSET;
504 + *xcomp_bv = host_xcr0_mask | (1ULL << 63);
505 +
506 + /*
507 + * Process each extended state component in the host's XCR0.
508 + * The compacted layout must match XCOMP_BV (host capability).
509 + *
510 + * For each component:
511 + * - Get its size and standard offset from host CPUID
512 + * - Apply 64-byte alignment if required
513 + * - Copy data only if guest has this component (bit set in env->xcr0)
514 + * - Always advance offset to maintain correct layout
515 + */
516 + compact_offset = XSAVE_EXT_OFFSET;
517 + for (i = 2; i < 63; i++) {
518 + if (!((host_xcr0_mask >> i) & 1)) {
519 + continue;
520 + }
521 +
522 + /* Query host CPUID for this component's size and standard offset */
523 + host_cpuid(0xD, i, &eax, &ebx, &ecx, &edx);
524 + size = eax;
525 + src_off = ebx;
526 + align64 = (ecx >> 1) & 1;
527 +
528 + if (size == 0) {
529 + /* Component in host xcr0 but unknown - shouldn't happen */
530 + continue;
531 + }
532 +
533 + /* Apply 64-byte alignment if required by this component */
534 + if (align64) {
535 + compact_offset = QEMU_ALIGN_UP(compact_offset, 64);
536 + }
537 +
538 + /*
539 + * Only copy data if guest has this component enabled in XCR0.
540 + * Otherwise the component remains zeroed (init state), but we
541 + * still advance the offset to maintain the correct layout.
542 + */
543 + if ((env->xcr0 >> i) & 1) {
544 + memcpy(buf + compact_offset, env->xsave_buf + src_off, size);
545 + }
546 +
547 + compact_offset += size;
548 + }
549 +
550 + return compact_offset;
551 +}