> On 4 Aug 2026, at 2:48 PM, Khushit Shah <[email protected]> wrote:
>
> Historically, under split irqchip, KVM advertised Suppress EOI Broadcast
> (SEOIB) support to the guest, but ignored the guest's decision and
> continued broadcasting Local APIC EOIs to the userspace I/O APIC. This was
> fixed in kernel commit 6517dfbcc918 ("KVM: x86: Add x2APIC "features" to
> control EOI broadcast suppression") by introducing two x2APIC flags:
> - KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST: Advertise Suppress EOI
> Broadcast support and honor the guest decision.
> - KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST: Do not advertise Suppress
> EOI Broadcast support.
>
> For compatibility, KVM continues to follow the quirky behavior by default
> unless any of the above two flags are passed.
>
> This patch fixes the behavior by dynamically configuring KVM based on the
> userspace I/O APIC version. If the I/O APIC version is 0x20 (which implements
> the EOI register used for directed EOI), enable proper Suppress EOI Broadcast
> support by passing KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST. Otherwise, if
> the I/O APIC version is 0x11, disable Suppress EOI Broadcast support by
> passing KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST.
>
> The fix is required because certain guests (for e.g. Windows 2022 with CG)
> fails to boot or exhibits extremely slow boot on legacy KVM behavior.
>
> Changing between the legacy quirky behavior and the ENABLE behavior does
> not cause any guest-visible state change; it only affects KVM's internal
> behavior. However, changing between the legacy quirky behavior and the
> DISABLE behavior does result in a guest-visible change.
>
> Hence, to maintain live migration compatibility, introduce a new x86 machine
> property `quirked-seoib`. When enabled, this property leaves the legacy KVM
> behavior in place and is enabled by default for PC and Q35 machine types
> version 11.0 and older.
>
> Users can explicitly override this default by passing
> `-machine quirked-seoib=on` (to force the legacy quirky behavior on newer
> machine types) or `-machine quirked-seoib=off` (to enforce the fix on
> older machine types).
>
> Signed-off-by: Khushit Shah <[email protected]>
> ---
> Note:
> Changes from v2:
> - Added a new x86 machine property `quirked-seoib` to control the SEOIB
> behavior.
> v2:
> https://patchew.org/QEMU/[email protected]/
> - This relies on the KVM flags introduced in Linux kernel commit
> 6517dfbcc918 ("KVM: x86: Add x2APIC "features" to control EOI
> broadcast suppression"), which landed in v6.18+. For hosts running
> older kernels where this kernel patch is not present, QEMU will
> gracefully exit with a hint to use `-machine quirked-seoib=on`.
>
> - I realise this is very late for 11.1 so will probably need to default
> quirked mode on 11.1 also. Sending the patch anyway for now.
> ---
Ping for reviews :)
> hw/i386/pc_piix.c | 3 +++
> hw/i386/pc_q35.c | 3 +++
> hw/i386/x86-common.c | 38 ++++++++++++++++++++++++++++++++++++++
> hw/i386/x86.c | 21 +++++++++++++++++++++
> include/hw/i386/x86.h | 12 ++++++++++++
> target/i386/kvm/kvm.c | 24 ++++++++++++++++++++++++
> target/i386/kvm/kvm_i386.h | 1 +
> 7 files changed, 102 insertions(+)
>
> diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
> index 82457bdb16..f8636d0b1a 100644
> --- a/hw/i386/pc_piix.c
> +++ b/hw/i386/pc_piix.c
> @@ -437,9 +437,12 @@ DEFINE_I440FX_MACHINE_AS_LATEST(11, 1);
>
> static void pc_i440fx_machine_11_0_options(MachineClass *m)
> {
> + X86MachineClass *x86mc = X86_MACHINE_CLASS(m);
> +
> pc_i440fx_machine_11_1_options(m);
> compat_props_add(m->compat_props, hw_compat_11_0, hw_compat_11_0_len);
> compat_props_add(m->compat_props, pc_compat_11_0, pc_compat_11_0_len);
> + x86mc->quirked_seoib = true;
> }
>
> DEFINE_I440FX_MACHINE(11, 0);
> diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c
> index 6c1e4eff5f..3f045f3582 100644
> --- a/hw/i386/pc_q35.c
> +++ b/hw/i386/pc_q35.c
> @@ -392,9 +392,12 @@ DEFINE_Q35_MACHINE_AS_LATEST(11, 1);
>
> static void pc_q35_machine_11_0_options(MachineClass *m)
> {
> + X86MachineClass *x86mc = X86_MACHINE_CLASS(m);
> +
> pc_q35_machine_11_1_options(m);
> compat_props_add(m->compat_props, hw_compat_11_0, hw_compat_11_0_len);
> compat_props_add(m->compat_props, pc_compat_11_0, pc_compat_11_0_len);
> + x86mc->quirked_seoib = true;
> }
>
> DEFINE_Q35_MACHINE(11, 0);
> diff --git a/hw/i386/x86-common.c b/hw/i386/x86-common.c
> index 8f9419e7d3..4f34360b87 100644
> --- a/hw/i386/x86-common.c
> +++ b/hw/i386/x86-common.c
> @@ -66,6 +66,24 @@ out:
> object_unref(cpu);
> }
>
> +static bool x86_ioapic_has_eoi_reg(void)
> +{
> + Object *obj;
> + uint64_t version;
> +
> + /*
> + * The I/O APIC device does not exist yet, as x86_cpus_init() runs
> + * before ioapic_init_gsi(). Hence, use a throwaway instance to check
> + * I/O APIC version.
> + */
> + obj = object_new(TYPE_IOAPIC);
> + version = object_property_get_uint(obj, "version", &error_abort);
> + object_unref(obj);
> +
> + /* Only version 0x20 implements the EOI register used for directed EOI.
> */
> + return version == 0x20;
> +}
> +
> void x86_cpus_init(X86MachineState *x86ms, int default_cpu_version)
> {
> int i;
> @@ -109,6 +127,26 @@ void x86_cpus_init(X86MachineState *x86ms, int
> default_cpu_version)
> apic_set_max_apic_id(x86ms->apic_id_limit);
> }
>
> + /*
> + * Under split irqchip KVM advertises x2APIC Suppress EOI Broadcast to
> + * the guest but historically ignored the guest's request and kept
> + * broadcasting LAPIC EOIs to the userspace IOAPIC. For compatibility,
> + * KVM still follows legacy behavior by default.
> + * Based on the I/O APIC version, use proper x2APIC Suppress EOI
> + * Broadcast flags (ENABLE or DISABLE) to fix KVM behavior.
> + * This needs to happen before any vCPUs are created.
> + */
> + if (kvm_enabled() && kvm_irqchip_is_split() && !x86ms->quirked_seoib) {
> + Error *local_err = NULL;
> +
> + if (!kvm_configure_x2apic_seoib(x86_ioapic_has_eoi_reg(),
> &local_err)) {
> + error_append_hint(&local_err, "Use -machine %s=on to keep the "
> + "legacy behaviour.\n",
> X86_MACHINE_QUIRKED_SEOIB);
> + error_report_err(local_err);
> + exit(1);
> + }
> + }
> +
> possible_cpus = mc->possible_cpu_arch_ids(ms);
> for (i = 0; i < ms->smp.cpus; i++) {
> x86_cpu_new(x86ms, possible_cpus->cpus[i].arch_id, &error_fatal);
> diff --git a/hw/i386/x86.c b/hw/i386/x86.c
> index dc7f0d56b0..75cc3eb3e3 100644
> --- a/hw/i386/x86.c
> +++ b/hw/i386/x86.c
> @@ -205,6 +205,20 @@ static void x86_machine_set_smm(Object *obj, Visitor *v,
> const char *name,
> visit_type_OnOffAuto(v, name, &x86ms->smm, errp);
> }
>
> +static bool x86_machine_get_quirked_seoib(Object *obj, Error **errp)
> +{
> + X86MachineState *x86ms = X86_MACHINE(obj);
> +
> + return x86ms->quirked_seoib;
> +}
> +
> +static void x86_machine_set_quirked_seoib(Object *obj, bool value, Error
> **errp)
> +{
> + X86MachineState *x86ms = X86_MACHINE(obj);
> +
> + x86ms->quirked_seoib = value;
> +}
> +
> bool x86_machine_is_acpi_enabled(const X86MachineState *x86ms)
> {
> if (x86ms->acpi == ON_OFF_AUTO_OFF) {
> @@ -370,6 +384,7 @@ static void x86_machine_initfn(Object *obj)
> x86ms->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
> x86ms->bus_lock_ratelimit = 0;
> x86ms->above_4g_mem_start = 4 * GiB;
> + x86ms->quirked_seoib = X86_MACHINE_GET_CLASS(obj)->quirked_seoib;
> }
>
> static void x86_machine_finalize(Object *obj)
> @@ -445,6 +460,12 @@ static void x86_machine_class_init(ObjectClass *oc,
> const void *data)
> NULL, NULL);
> object_class_property_set_description(oc, "sgx-epc",
> "SGX EPC device");
> +
> + object_class_property_add_bool(oc, X86_MACHINE_QUIRKED_SEOIB,
> + x86_machine_get_quirked_seoib,
> + x86_machine_set_quirked_seoib);
> + object_class_property_set_description(oc, X86_MACHINE_QUIRKED_SEOIB,
> + "Leave KVM's Suppress EOI Broadcast quirky behavior in place");
> }
>
> static const TypeInfo x86_machine_info = {
> diff --git a/include/hw/i386/x86.h b/include/hw/i386/x86.h
> index 71fe6b5e12..bf3075d6f8 100644
> --- a/include/hw/i386/x86.h
> +++ b/include/hw/i386/x86.h
> @@ -32,6 +32,14 @@ struct X86MachineClass {
>
> /* CPU and apic information: */
> bool apic_xrupt_override;
> +
> + /*
> + * Leave KVM's Suppress EOI Broadcast quirk in place rather than
> + * configuring proper behaviour from the userspace IOAPIC version.
> + *
> + * Only applicable under split-irqchip.
> + */
> + bool quirked_seoib;
> };
>
> struct X86MachineState {
> @@ -93,6 +101,9 @@ struct X86MachineState {
> uint64_t bus_lock_ratelimit;
>
> IgvmCfg *igvm;
> +
> + /* Defaults from X86MachineClass, overridable by the user. */
> + bool quirked_seoib;
> };
>
> #define X86_MACHINE_SMM "smm"
> @@ -102,6 +113,7 @@ struct X86MachineState {
> #define X86_MACHINE_OEM_ID "x-oem-id"
> #define X86_MACHINE_OEM_TABLE_ID "x-oem-table-id"
> #define X86_MACHINE_BUS_LOCK_RATELIMIT "bus-lock-ratelimit"
> +#define X86_MACHINE_QUIRKED_SEOIB "quirked-seoib"
>
> #define TYPE_X86_MACHINE MACHINE_TYPE_NAME("x86")
> OBJECT_DECLARE_TYPE(X86MachineState, X86MachineClass, X86_MACHINE)
> diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
> index 4272b6770c..c9348d69e7 100644
> --- a/target/i386/kvm/kvm.c
> +++ b/target/i386/kvm/kvm.c
> @@ -307,6 +307,30 @@ bool kvm_enable_x2apic(void)
> has_x2apic_api);
> }
>
> +bool kvm_configure_x2apic_seoib(bool enable, Error **errp)
> +{
> + KVMState *s = KVM_STATE(current_accel());
> + uint64_t flag = enable ? KVM_X2APIC_ENABLE_SUPPRESS_EOI_BROADCAST
> + : KVM_X2APIC_DISABLE_SUPPRESS_EOI_BROADCAST;
> + int supported, ret;
> +
> + supported = kvm_vm_check_extension(s, KVM_CAP_X2APIC_API);
> + if (!(supported & flag)) {
> + error_setg(errp, "KVM does not have x2APIC Suppress EOI Broadcast "
> + "flags");
> + return false;
> + }
> +
> + ret = kvm_vm_enable_cap(s, KVM_CAP_X2APIC_API, 0, flag);
> + if (ret < 0) {
> + error_setg_errno(errp, -ret, "failed to configure x2APIC Suppress "
> + "EOI Broadcast flags");
> + return false;
> + }
> +
> + return true;
> +}
> +
> bool kvm_hv_vpindex_settable(void)
> {
> return hv_vpindex_settable;
> diff --git a/target/i386/kvm/kvm_i386.h b/target/i386/kvm/kvm_i386.h
> index 2b653442f4..1e154e536b 100644
> --- a/target/i386/kvm/kvm_i386.h
> +++ b/target/i386/kvm/kvm_i386.h
> @@ -25,6 +25,7 @@
>
> bool kvm_has_smm(void);
> bool kvm_enable_x2apic(void);
> +bool kvm_configure_x2apic_seoib(bool enable, Error **errp);
> bool kvm_hv_vpindex_settable(void);
> bool kvm_enable_hypercall(uint64_t enable_mask);
>
> --
> 2.39.3
>