Skip to content

Instantly share code, notes, and snippets.

@Mintsuki
Created March 2, 2026 16:10
Show Gist options
  • Select an option

  • Save Mintsuki/25129025509e7f8e30521c1f7940aad4 to your computer and use it in GitHub Desktop.

Select an option

Save Mintsuki/25129025509e7f8e30521c1f7940aad4 to your computer and use it in GitHub Desktop.
diff -ruN '--exclude=build' '--exclude=*.o' '--exclude=*.pyc' '--exclude=__pycache__' a/hw/intc/apic.c b/hw/intc/apic.c
--- a/hw/intc/apic.c 2025-12-23 20:48:56.000000000 +0100
+++ b/hw/intc/apic.c 2026-03-02 00:44:42.007386449 +0100
@@ -41,6 +41,42 @@
static uint32_t max_apics;
static uint32_t max_apic_words;
+static const char *apic_reg_name(int index)
+{
+ switch (index) {
+ case 0x02: return "ID";
+ case 0x03: return "VERSION";
+ case 0x08: return "TPR";
+ case 0x09: return "APR";
+ case 0x0a: return "PPR";
+ case 0x0b: return "EOI";
+ case 0x0d: return "LDR";
+ case 0x0e: return "DFR";
+ case 0x0f: return "SIVR";
+ case 0x10: case 0x11: case 0x12: case 0x13:
+ case 0x14: case 0x15: case 0x16: case 0x17: return "ISR";
+ case 0x18: case 0x19: case 0x1a: case 0x1b:
+ case 0x1c: case 0x1d: case 0x1e: case 0x1f: return "TMR";
+ case 0x20: case 0x21: case 0x22: case 0x23:
+ case 0x24: case 0x25: case 0x26: case 0x27: return "IRR";
+ case 0x28: return "ESR";
+ case 0x2f: return "LVT_CMCI";
+ case 0x30: return "ICR_LO";
+ case 0x31: return "ICR_HI";
+ case 0x32: return "LVT_TIMER";
+ case 0x33: return "LVT_THERMAL";
+ case 0x34: return "LVT_PMC";
+ case 0x35: return "LVT_LINT0";
+ case 0x36: return "LVT_LINT1";
+ case 0x37: return "LVT_ERROR";
+ case 0x38: return "TIMER_ICR";
+ case 0x39: return "TIMER_CCR";
+ case 0x3e: return "TIMER_DCR";
+ case 0x3f: return "SELF_IPI";
+ default: return "???";
+ }
+}
+
#define TYPE_APIC "apic"
/*This is reusing the APICCommonState typedef from APIC_COMMON */
DECLARE_INSTANCE_CHECKER(APICCommonState, APIC,
@@ -312,6 +348,21 @@
return -1;
}
+ /* When x2APIC is locked, reject clearing EXTD or EN (#GP) */
+ if (s->x2apic_locked &&
+ (s->apicbase & MSR_IA32_APICBASE_EXTD)) {
+ if (!(val & MSR_IA32_APICBASE_EXTD) ||
+ !(val & MSR_IA32_APICBASE_ENABLE)) {
+ fprintf(stderr, "x2apic-locked: BLOCKED APICBASE write 0x%lx -> 0x%lx (#GP)\n",
+ (unsigned long)s->apicbase, (unsigned long)val);
+ return -1;
+ }
+ }
+ if (s->x2apic_locked) {
+ fprintf(stderr, "x2apic-locked: APICBASE write 0x%lx -> 0x%lx (allowed)\n",
+ (unsigned long)s->apicbase, (unsigned long)val);
+ }
+
/*
* Transition into invalid state
* (s->apicbase & MSR_IA32_APICBASE_ENABLE == 0) &&
@@ -874,6 +925,23 @@
return -1;
}
+ /* When x2APIC is locked and in x2APIC mode, MMIO is not decoded */
+ if (s->x2apic_locked && is_x2apic_mode(s)) {
+ int reg = (addr >> 4) & 0xff;
+ fprintf(stderr, "x2apic-locked: MMIO READ 0x%03lx %-10s cpu=%d apic_id=%u rip=0x%lx\n",
+ (unsigned long)addr, apic_reg_name(reg),
+ CPU(s->cpu)->cpu_index, s->initial_apic_id,
+ (unsigned long)s->cpu->env.eip);
+ if (s->x2apic_mce) {
+ cpu_x86_inject_mce(NULL, s->cpu, 0,
+ MCI_STATUS_VAL | MCI_STATUS_UC | MCI_STATUS_EN |
+ MCI_STATUS_ADDRV,
+ MCG_STATUS_MCIP,
+ APIC_DEFAULT_ADDRESS | addr, 0, 0);
+ }
+ return 0xffffffff;
+ }
+
index = (addr >> 4) & 0xff;
apic_register_read(s, index, &val);
@@ -1070,6 +1138,24 @@
return;
}
+ /* When x2APIC is locked and in x2APIC mode, MMIO writes are ignored */
+ if (s->x2apic_locked && is_x2apic_mode(s)) {
+ int reg = (addr >> 4) & 0xff;
+ fprintf(stderr, "x2apic-locked: MMIO WRITE 0x%03lx %-10s val=0x%08x cpu=%d apic_id=%u rip=0x%lx\n",
+ (unsigned long)addr, apic_reg_name(reg),
+ (unsigned int)val,
+ CPU(s->cpu)->cpu_index, s->initial_apic_id,
+ (unsigned long)s->cpu->env.eip);
+ if (s->x2apic_mce) {
+ cpu_x86_inject_mce(NULL, s->cpu, 0,
+ MCI_STATUS_VAL | MCI_STATUS_UC | MCI_STATUS_EN |
+ MCI_STATUS_ADDRV,
+ MCG_STATUS_MCIP,
+ APIC_DEFAULT_ADDRESS | addr, 0, 0);
+ }
+ return;
+ }
+
apic_register_write(s, index, val);
}
diff -ruN '--exclude=build' '--exclude=*.o' '--exclude=*.pyc' '--exclude=__pycache__' a/hw/intc/apic_common.c b/hw/intc/apic_common.c
--- a/hw/intc/apic_common.c 2025-12-23 20:48:56.000000000 +0100
+++ b/hw/intc/apic_common.c 2026-03-01 22:12:42.663438222 +0100
@@ -60,6 +60,14 @@
}
}
+bool cpu_is_apic_x2apic_locked(APICCommonState *s)
+{
+ if (!s) {
+ return false;
+ }
+ return s->x2apic_locked;
+}
+
bool cpu_is_apic_enabled(APICCommonState *s)
{
if (!s) {
@@ -397,6 +405,8 @@
true),
DEFINE_PROP_BOOL("legacy-instance-id", APICCommonState, legacy_instance_id,
false),
+ DEFINE_PROP_BOOL("x2apic-locked", APICCommonState, x2apic_locked, false),
+ DEFINE_PROP_BOOL("x2apic-mce", APICCommonState, x2apic_mce, false),
};
static void apic_common_get_id(Object *obj, Visitor *v, const char *name,
diff -ruN '--exclude=build' '--exclude=*.o' '--exclude=*.pyc' '--exclude=__pycache__' a/include/hw/i386/apic.h b/include/hw/i386/apic.h
--- a/include/hw/i386/apic.h 2025-12-23 20:48:56.000000000 +0100
+++ b/include/hw/i386/apic.h 2026-02-28 22:39:01.030027434 +0100
@@ -22,6 +22,7 @@
int apic_msr_read(APICCommonState *s, int index, uint64_t *val);
int apic_msr_write(APICCommonState *s, int index, uint64_t val);
bool is_x2apic_mode(APICCommonState *s);
+bool cpu_is_apic_x2apic_locked(APICCommonState *s);
/* pc.c */
APICCommonState *cpu_get_current_apic(void);
diff -ruN '--exclude=build' '--exclude=*.o' '--exclude=*.pyc' '--exclude=__pycache__' a/include/hw/i386/apic_internal.h b/include/hw/i386/apic_internal.h
--- a/include/hw/i386/apic_internal.h 2025-12-23 20:48:56.000000000 +0100
+++ b/include/hw/i386/apic_internal.h 2026-02-28 22:54:48.567856717 +0100
@@ -188,6 +188,8 @@
DeviceState *vapic;
hwaddr vapic_paddr; /* note: persistence via kvmvapic */
bool legacy_instance_id;
+ bool x2apic_locked;
+ bool x2apic_mce;
uint32_t extended_log_dest;
};
diff -ruN '--exclude=build' '--exclude=*.o' '--exclude=*.pyc' '--exclude=__pycache__' a/target/i386/cpu.c b/target/i386/cpu.c
--- a/target/i386/cpu.c 2025-12-23 20:48:57.000000000 +0100
+++ b/target/i386/cpu.c 2026-03-01 21:43:29.659400482 +0100
@@ -44,6 +44,7 @@
#include "system/address-spaces.h"
#include "hw/boards.h"
#include "hw/i386/sgx-epc.h"
+#include "hw/i386/apic.h"
#endif
#include "system/qtest.h"
#include "tcg/tcg-cpu.h"
@@ -1542,7 +1543,7 @@
"taa-no", NULL, NULL, NULL,
NULL, "sbdr-ssdp-no", "fbsdp-no", "psdp-no",
NULL, "fb-clear", NULL, NULL,
- "bhi-no", NULL, NULL, NULL,
+ "bhi-no", "xapic-disable", NULL, NULL,
"pbrsb-no", NULL, "gds-no", "rfds-no",
"rfds-clear", NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
@@ -8043,6 +8044,11 @@
*ecx |= CPUID_7_0_ECX_OSPKE;
}
*edx = env->features[FEAT_7_0_EDX]; /* Feature flags */
+#ifndef CONFIG_USER_ONLY
+ if (cpu->apic_state && cpu_is_apic_x2apic_locked(cpu->apic_state)) {
+ *edx |= CPUID_7_0_EDX_ARCH_CAPABILITIES;
+ }
+#endif
} else if (count == 1) {
*eax = env->features[FEAT_7_1_EAX];
*ecx = env->features[FEAT_7_1_ECX];
diff -ruN '--exclude=build' '--exclude=*.o' '--exclude=*.pyc' '--exclude=__pycache__' a/target/i386/cpu.h b/target/i386/cpu.h
--- a/target/i386/cpu.h 2025-12-23 20:48:57.000000000 +0100
+++ b/target/i386/cpu.h 2026-02-28 22:38:31.776097461 +0100
@@ -1245,6 +1245,10 @@
#define MSR_ARCH_CAP_PBRSB_NO (1U << 24)
#define MSR_ARCH_CAP_GDS_NO (1U << 26)
#define MSR_ARCH_CAP_RFDS_NO (1U << 27)
+#define MSR_ARCH_CAP_XAPIC_DISABLE (1U << 21)
+
+#define MSR_IA32_XAPIC_DISABLE_STATUS 0xBD
+#define XAPIC_DISABLE_STATUS_LEGACY_DISABLED (1U << 0)
#define MSR_CORE_CAP_SPLIT_LOCK_DETECT (1U << 5)
diff -ruN '--exclude=build' '--exclude=*.o' '--exclude=*.pyc' '--exclude=__pycache__' a/target/i386/tcg/system/misc_helper.c b/target/i386/tcg/system/misc_helper.c
--- a/target/i386/tcg/system/misc_helper.c 2025-12-23 20:48:57.000000000 +0100
+++ b/target/i386/tcg/system/misc_helper.c 2026-03-01 22:12:56.801353315 +0100
@@ -294,6 +294,9 @@
env->msr_bndcfgs = val;
cpu_sync_bndcs_hflags(env);
break;
+ case MSR_IA32_XAPIC_DISABLE_STATUS:
+ /* Read-only MSR: #GP on any write */
+ goto error;
case MSR_APIC_START ... MSR_APIC_END: {
int ret;
int index = (uint32_t)env->regs[R_ECX] - MSR_APIC_START;
@@ -472,6 +475,19 @@
val = cpu_x86_get_msr_core_thread_count(x86_cpu);
break;
}
+ case MSR_IA32_ARCH_CAPABILITIES:
+ val = env->features[FEAT_ARCH_CAPABILITIES];
+ if (cpu_is_apic_x2apic_locked(x86_cpu->apic_state)) {
+ val |= MSR_ARCH_CAP_XAPIC_DISABLE;
+ }
+ break;
+ case MSR_IA32_XAPIC_DISABLE_STATUS:
+ val = 0;
+ if (cpu_is_apic_x2apic_locked(x86_cpu->apic_state) &&
+ is_x2apic_mode(x86_cpu->apic_state)) {
+ val |= XAPIC_DISABLE_STATUS_LEGACY_DISABLED;
+ }
+ break;
case MSR_APIC_START ... MSR_APIC_END: {
int ret;
int index = (uint32_t)env->regs[R_ECX] - MSR_APIC_START;
@@ -497,6 +513,7 @@
val = 0;
break;
}
+
env->regs[R_EAX] = (uint32_t)(val);
env->regs[R_EDX] = (uint32_t)(val >> 32);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment