> 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
> 

Reply via email to