From: Salil Mehta <salil.mehta@huawei.com>
Changing a device's administrative power state must trigger a concrete
operational transition at the platform layer via ACPI coordination with OSPM.
The platform is responsible for actually powering devices off or on and for
notifying the guest when required.
Some machines can coordinate transitions asynchronously with OSPM using ACPI
methods and events (e.g. _EJx, device-check, _OST), while others cannot or
may not be ready when policy flips. Without a defined linkage, admin policy
can drift from runtime reality, leaving devices active while 'disabled', or
disappearing without guest notification, and migration metadata out of sync.
This change establishes that linkage: administrative DISABLED/ENABLED requests
first drive the platform's operational transition via ACPI (prefer OSPM
coordination; otherwise fall back to a synchronous in-QEMU path) and only
then update QOM state and migration registration. This provides uniform
semantics and a reliable contract for management and tests.
Signed-off-by: Salil Mehta <salil.mehta@huawei.com>
---
hw/core/qdev.c | 68 ++++++++++++++++++++++++++++++++++++-----
include/hw/powerstate.h | 6 ++++
include/hw/qdev-core.h | 17 +++++++++++
3 files changed, 84 insertions(+), 7 deletions(-)
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index 23b84a7756..3aba99b912 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -326,6 +326,30 @@ bool qdev_disable(DeviceState *dev, BusState *bus, Error **errp)
errp);
}
+void qdev_sync_disable(DeviceState *dev, Error **errp)
+{
+ g_assert(dev);
+ g_assert(powerstate_handler(dev));
+
+ /*
+ * Administrative disable triggered either after OSPM completes _EJx
+ * (post Notify(..., 0x03)), or due to lack of async shutdown support.
+ *
+ * Device may still appear in ACPI namespace but remains disabled at
+ * the platform level. Guest cannot re-enable it until host allows.
+ */
+
+ /* Perform operational shutdown */
+ device_post_poweroff(dev, errp);
+ if (*errp) {
+ return;
+ }
+
+ /* Mark the device administratively disabled */
+ qatomic_set(&dev->admin_power_state, DEVICE_ADMIN_POWER_STATE_DISABLED);
+ smp_wmb();
+}
+
bool qdev_enable(DeviceState *dev, BusState *bus, Error **errp)
{
g_assert(dev);
@@ -705,6 +729,7 @@ device_set_admin_power_state(Object *obj, int new_state, Error **errp)
{
DeviceState *dev = DEVICE(obj);
DeviceClass *dc = DEVICE_GET_CLASS(dev);
+ DeviceAdminPowerState old_state;
if (!dc->admin_power_state_supported) {
error_setg(errp, "Device '%s' admin power state change not supported",
@@ -712,25 +737,54 @@ device_set_admin_power_state(Object *obj, int new_state, Error **errp)
return;
}
+ g_assert(powerstate_handler(dev));
+ old_state = qatomic_read(&dev->admin_power_state);
+
switch (new_state) {
case DEVICE_ADMIN_POWER_STATE_DISABLED: {
+ if (old_state == DEVICE_ADMIN_POWER_STATE_DISABLED) {
+ break;
+ }
+
/*
- * TODO: Operational state transition triggered by administrative action
+ * Operational state transition triggered by administrative action
* Powering off the realized device either synchronously or via OSPM.
*/
+ if (device_graceful_poweroff_supported(dev)) {
+ /* Graceful shutdown via guest coordination */
+ device_request_poweroff(dev, errp);
+ if (*errp) {
+ return;
+ }
- qatomic_set(&dev->admin_power_state, DEVICE_ADMIN_POWER_STATE_DISABLED);
- smp_wmb();
+ qatomic_set(&dev->admin_power_state,
+ DEVICE_ADMIN_POWER_STATE_DISABLED);
+ smp_wmb();
+ } else {
+ /* Immediate shutdown within QEMU synchronously */
+ qdev_sync_disable(dev, errp);
+ if (*errp) {
+ return;
+ }
+ }
break;
}
case DEVICE_ADMIN_POWER_STATE_ENABLED: {
- /*
- * TODO: Operational state transition triggered by administrative action
- * Powering on the device and restoring migration registration.
- */
+ if (old_state == DEVICE_ADMIN_POWER_STATE_ENABLED) {
+ break;
+ }
qatomic_set(&dev->admin_power_state, DEVICE_ADMIN_POWER_STATE_ENABLED);
smp_wmb();
+
+ /*
+ * Operational state transition triggered by administrative action
+ * Powering on the device and restoring migration registration.
+ */
+ device_pre_poweron(dev, errp);
+ if (*errp) {
+ return;
+ }
break;
}
default:
diff --git a/include/hw/powerstate.h b/include/hw/powerstate.h
index c16da0f24d..b35650bac4 100644
--- a/include/hw/powerstate.h
+++ b/include/hw/powerstate.h
@@ -168,4 +168,10 @@ void device_post_poweroff(DeviceState *dev, Error **errp);
void device_pre_poweron(DeviceState *dev, Error **errp);
void device_request_standby(DeviceState *dev, Error **errp);
+
+static inline bool device_graceful_poweroff_supported(DeviceState *dev)
+{
+ PowerStateHandler *h = powerstate_handler(dev);
+ return h && POWERSTATE_HANDLER_GET_CLASS(h)->request_poweroff;
+}
#endif /* POWERSTATE_H */
diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index 855ff865ba..3e08cfb59f 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -8,6 +8,7 @@
#include "qemu/rcu_queue.h"
#include "qom/object.h"
#include "hw/hotplug.h"
+#include "hw/powerstate.h"
#include "hw/resettable.h"
/**
@@ -589,6 +590,22 @@ bool qdev_realize_and_unref(DeviceState *dev, BusState *bus, Error **errp);
*/
bool qdev_disable(DeviceState *dev, BusState *bus, Error **errp);
+/**
+ * qdev_sync_disable - Force immediate power-off and administrative disable
+ * @dev: The device to be powered off and administratively disabled
+ * @errp: Pointer to a location where an error can be reported
+ *
+ * This function performs a synchronous power-off of the device and marks it
+ * as administratively DISABLED. It assumes that prior graceful handling (e.g.,
+ * ACPI _EJx) has already been completed, or that asynchronous mechanisms are
+ * unsupported.
+ *
+ * After execution, the device remains visible to the guest (e.g. via ACPI),
+ * but cannot be brought back online unless explicitly re-enabled via admin
+ * policy. This function also removes the device from the migration stream.
+ */
+void qdev_sync_disable(DeviceState *dev, Error **errp);
+
/**
* qdev_enable - Power on and administratively enable a device
* @dev: The device to be powered on and administratively enabled
--
2.34.1