aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Mammedov <imammedo@redhat.com>2024-12-10 17:39:44 +0100
committerMichael S. Tsirkin <mst@redhat.com>2025-01-15 13:05:41 -0500
commit8aa35bebeeaed19ae57afbc3e110b8e7fe8587d0 (patch)
treede611af628881e2a01e028797091a4a86aab4e37
parente043be2290ffdd666ad2ab35d2341c137b21a3b4 (diff)
downloadqemu-8aa35bebeeaed19ae57afbc3e110b8e7fe8587d0.zip
qemu-8aa35bebeeaed19ae57afbc3e110b8e7fe8587d0.tar.gz
qemu-8aa35bebeeaed19ae57afbc3e110b8e7fe8587d0.tar.bz2
cpuhp: make sure that remove events are handled within the same SCI
CPU_SCAN_METHOD was processing insert events first and only if insert event was not present then it would check remove event. Normally it's not an issue as it doesn't make much sense tho hotplug and immediately unplug it. In this corner case, which can be reproduced with: qemu -smp 1,maxcpus=2 -cpu host -monitor stdio \ -drive if=pflash,format=raw,readonly,file=edk2-x86_64-code.fd * boot till GRUB prompt and pause guest (either via monitor or stop GRUB from automatic boot) * at monitor prompt add CPU: device_add host-x86_64-cpu,socket-id=0,core-id=1,thread-id=0,id=foo * let guest OS boot completely, and unplug CPU from monitor prompt: device_del foo which triggers GPE event that leads to CPU_SCAN_METHOD on guest side as result of above cpu 'foo' will not be hotunplugged, since QEMU sees insert event and ignores remove event (leaving it in pending state) for the GPE event. Any follow up CPU hotplug/unplug action from QEMU side will handle previously ignored event, so as workaround user can repeat device_del. Fix this corner-case by queuing remove events independently from insert events, aka the same way as we do with insert events. And then go over remove queue to send eject notify events to OSPM within the same GPE event. PS: Process remove queue after the cpu add queue has been processed 1st to ensure that OSPM gets hotadd evets after hotremove ones. PS2: Case where it's still borken happens when guest OS is Linux and device_del happens before guest OS initializes ACPI subsystem. Culprit in this case though is the guest kernel, which mangles GPE.sts (by clearing them up) and thus pending SCI turns to NOP leaving insert/remove events in pending state. That is the guest bug and should be fixed there. Signed-off-by: Igor Mammedov <imammedo@redhat.com> Reported-by: Eric Mackay <eric.mackay@oracle.com> Message-Id: <20241210163945.3422623-3-imammedo@redhat.com> Tested-by: Eric Mackay <eric.mackay@oracle.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
-rw-r--r--hw/acpi/cpu.c43
1 files changed, 34 insertions, 9 deletions
diff --git a/hw/acpi/cpu.c b/hw/acpi/cpu.c
index 9d530a2..f70a2c0 100644
--- a/hw/acpi/cpu.c
+++ b/hw/acpi/cpu.c
@@ -327,6 +327,7 @@ const VMStateDescription vmstate_cpu_hotplug = {
#define CPU_EJECT_METHOD "CEJ0"
#define CPU_OST_METHOD "COST"
#define CPU_ADDED_LIST "CNEW"
+#define CPU_EJ_LIST "CEJL"
#define CPU_ENABLED "CPEN"
#define CPU_SELECTOR "CSEL"
@@ -488,7 +489,6 @@ void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
method = aml_method(CPU_SCAN_METHOD, 0, AML_SERIALIZED);
{
const uint8_t max_cpus_per_pass = 255;
- Aml *else_ctx;
Aml *while_ctx, *while_ctx2;
Aml *has_event = aml_local(0);
Aml *dev_chk = aml_int(1);
@@ -499,6 +499,8 @@ void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
Aml *uid = aml_local(3);
Aml *has_job = aml_local(4);
Aml *new_cpus = aml_name(CPU_ADDED_LIST);
+ Aml *ej_cpus = aml_name(CPU_EJ_LIST);
+ Aml *num_ej_cpus = aml_local(5);
aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
@@ -513,6 +515,8 @@ void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
*/
aml_append(method, aml_name_decl(CPU_ADDED_LIST,
aml_package(max_cpus_per_pass)));
+ aml_append(method, aml_name_decl(CPU_EJ_LIST,
+ aml_package(max_cpus_per_pass)));
aml_append(method, aml_store(zero, uid));
aml_append(method, aml_store(one, has_job));
@@ -527,6 +531,7 @@ void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
aml_append(while_ctx2, aml_store(one, has_event));
aml_append(while_ctx2, aml_store(zero, num_added_cpus));
+ aml_append(while_ctx2, aml_store(zero, num_ej_cpus));
/*
* Scan CPUs, till there are CPUs with events or
@@ -559,8 +564,10 @@ void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
* if CPU_ADDED_LIST is full, exit inner loop and process
* collected CPUs
*/
- ifctx = aml_if(
- aml_equal(num_added_cpus, aml_int(max_cpus_per_pass)));
+ ifctx = aml_if(aml_lor(
+ aml_equal(num_added_cpus, aml_int(max_cpus_per_pass)),
+ aml_equal(num_ej_cpus, aml_int(max_cpus_per_pass))
+ ));
{
aml_append(ifctx, aml_store(one, has_job));
aml_append(ifctx, aml_break());
@@ -577,16 +584,16 @@ void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
aml_append(ifctx, aml_store(one, has_event));
}
aml_append(while_ctx, ifctx);
- else_ctx = aml_else();
+
ifctx = aml_if(aml_equal(rm_evt, one));
{
- aml_append(ifctx,
- aml_call2(CPU_NOTIFY_METHOD, uid, eject_req));
- aml_append(ifctx, aml_store(one, rm_evt));
+ /* cache to be removed CPUs to Notify later */
+ aml_append(ifctx, aml_store(uid,
+ aml_index(ej_cpus, num_ej_cpus)));
+ aml_append(ifctx, aml_increment(num_ej_cpus));
aml_append(ifctx, aml_store(one, has_event));
}
- aml_append(else_ctx, ifctx);
- aml_append(while_ctx, else_ctx);
+ aml_append(while_ctx, ifctx);
aml_append(while_ctx, aml_increment(uid));
}
aml_append(while_ctx2, while_ctx);
@@ -620,6 +627,24 @@ void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
aml_append(while_ctx, aml_increment(cpu_idx));
}
aml_append(while_ctx2, while_ctx);
+
+ /*
+ * Notify OSPM about to be removed CPUs and clear remove flag
+ */
+ aml_append(while_ctx2, aml_store(zero, cpu_idx));
+ while_ctx = aml_while(aml_lless(cpu_idx, num_ej_cpus));
+ {
+ aml_append(while_ctx,
+ aml_store(aml_derefof(aml_index(ej_cpus, cpu_idx)),
+ uid));
+ aml_append(while_ctx,
+ aml_call2(CPU_NOTIFY_METHOD, uid, eject_req));
+ aml_append(while_ctx, aml_store(uid, cpu_selector));
+ aml_append(while_ctx, aml_store(one, rm_evt));
+ aml_append(while_ctx, aml_increment(cpu_idx));
+ }
+ aml_append(while_ctx2, while_ctx);
+
/*
* If another batch is needed, then it will resume scanning
* exactly at -- and not after -- the last CPU that's currently