From: Salil Mehta <salil.mehta@huawei.com>
Some devices cannot be hot-unplugged, either because removal is not meaningful
(e.g. on-board devices) or not supported (e.g. certain PCIe devices). Others,
such as CPUs on architectures like ARM, lack native hotplug support but can
still have their availability controlled through host policy. In all these
cases, a mechanism is needed to track and control a device’s *administrative*
power state — independent of its runtime operational state — so QEMU can:
- Disable a device while keeping it described in firmware, ACPI, or other
configuration.
- Prevent guest use until explicitly re-enabled.
- Coordinate transitions with platform-specific power handlers and migration
logic.
This patch introduces the core qdev support for administrative power state —
defining the property, enum, and accessors — without yet applying it to any
device. Later patches in this series integrate it with helper APIs
(qdev_disable(), qdev_enable(), etc.) and specific device types such as CPUs,
completing the flow with platform-specific handlers.
Key additions:
- New enum DeviceAdminPowerState with ENABLED, DISABLED, and REMOVED states,
defaulting to ENABLED.
- New DeviceClass flag admin_power_state_supported to advertise support for
administrative transitions.
- New QOM property "admin_power_state" to query or set the state on supported
devices.
- Internal accessors device_get_admin_power_state() and
device_set_admin_power_state() to manage state changes, including safe
handling when the device is not yet realized.
The enum models *policy* rather than electrical or functional power state, and
is distinct from runtime mechanisms (e.g. PSCI for ARM CPUs). The actual
operational state of a device is maintained by platform-specific or device-
specific code, which enforces runtime behaviour based on the administrative
setting. Every device starts administratively ENABLED by default. A DISABLED
device remains logically present but blocked from operation; a REMOVED device
is logically absent.
Signed-off-by: Salil Mehta <salil.mehta@huawei.com>
---
hw/core/qdev.c | 62 ++++++++++++++++++++++++++++++++++++++++++
include/hw/qdev-core.h | 54 ++++++++++++++++++++++++++++++++++++
target/arm/cpu.c | 1 +
3 files changed, 117 insertions(+)
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index f600226176..8502d6216f 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -633,6 +633,53 @@ static bool device_get_hotplugged(Object *obj, Error **errp)
return dev->hotplugged;
}
+static int device_get_admin_power_state(Object *obj, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+
+ return dev->admin_power_state;
+}
+
+static void
+device_set_admin_power_state(Object *obj, int new_state, Error **errp)
+{
+ DeviceState *dev = DEVICE(obj);
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+
+ if (!dc->admin_power_state_supported) {
+ error_setg(errp, "Device '%s' admin power state change not supported",
+ object_get_typename(obj));
+ return;
+ }
+
+ switch (new_state) {
+ case DEVICE_ADMIN_POWER_STATE_DISABLED: {
+ /*
+ * TODO: Operational state transition triggered by administrative action
+ * Powering off the realized device either synchronously or via OSPM.
+ */
+
+ qatomic_set(&dev->admin_power_state, DEVICE_ADMIN_POWER_STATE_DISABLED);
+ smp_wmb();
+ break;
+ }
+ case DEVICE_ADMIN_POWER_STATE_ENABLED: {
+ /*
+ * TODO: Operational state transition triggered by administrative action
+ * Powering on the device and restoring migration registration.
+ */
+
+ qatomic_set(&dev->admin_power_state, DEVICE_ADMIN_POWER_STATE_ENABLED);
+ smp_wmb();
+ break;
+ }
+ default:
+ error_setg(errp, "Invalid admin power state %d for device '%s'",
+ new_state, dev->id);
+ break;
+ }
+}
+
static void device_initfn(Object *obj)
{
DeviceState *dev = DEVICE(obj);
@@ -644,6 +691,7 @@ static void device_initfn(Object *obj)
dev->instance_id_alias = -1;
dev->realized = false;
+ dev->admin_power_state = DEVICE_ADMIN_POWER_STATE_ENABLED;
dev->allow_unplug_during_migration = false;
QLIST_INIT(&dev->gpios);
@@ -731,6 +779,15 @@ device_vmstate_if_get_id(VMStateIf *obj)
return qdev_get_dev_path(dev);
}
+static const QEnumLookup device_admin_power_state_lookup = {
+ .array = (const char *const[]) {
+ [DEVICE_ADMIN_POWER_STATE_ENABLED] = "enabled",
+ [DEVICE_ADMIN_POWER_STATE_REMOVED] = "removed",
+ [DEVICE_ADMIN_POWER_STATE_DISABLED] = "disabled",
+ },
+ .size = DEVICE_ADMIN_POWER_STATE_MAX,
+};
+
static void device_class_init(ObjectClass *class, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(class);
@@ -765,6 +822,11 @@ static void device_class_init(ObjectClass *class, const void *data)
device_get_hotpluggable, NULL);
object_class_property_add_bool(class, "hotplugged",
device_get_hotplugged, NULL);
+ object_class_property_add_enum(class, "admin_power_state",
+ "DeviceAdminPowerState",
+ &device_admin_power_state_lookup,
+ device_get_admin_power_state,
+ device_set_admin_power_state);
object_class_property_add_link(class, "parent_bus", TYPE_BUS,
offsetof(DeviceState, parent_bus), NULL, 0);
}
diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index 530f3da702..3bc212ab3a 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -159,6 +159,7 @@ struct DeviceClass {
*/
bool user_creatable;
bool hotpluggable;
+ bool admin_power_state_supported;
/* callbacks */
/**
@@ -217,6 +218,55 @@ typedef QLIST_HEAD(, NamedGPIOList) NamedGPIOListHead;
typedef QLIST_HEAD(, NamedClockList) NamedClockListHead;
typedef QLIST_HEAD(, BusState) BusStateHead;
+/**
+ * enum DeviceAdminPowerState - Administrative control states for a device
+ *
+ * This enum defines abstract administrative states used by QEMU to enable,
+ * disable, or logically remove a device from the virtual machine. These
+ * states reflect administrative control over a device's power availability
+ * and presence in the system. These administrative states are distinct from
+ * runtime operational power states (e.g., PSCI states for ARM CPUs). They
+ * represent administrative *policy* rather than physical, electrical, or
+ * functional state.
+ *
+ * Administrative state is managed externally "via QMP, firmware, or other
+ * host-side policy agents" and acts as a gating policy that determines
+ * whether guest software is permitted to interact with the device. Most
+ * devices default to the ENABLED state unless explicitly disabled or removed.
+ *
+ * Changing a device administrative state may directly or indirectly affect
+ * its operational behavior. For example, a DISABLED device will reject guest
+ * attempts to power it on or transition it out of a suspended state. Not all
+ * devices support dynamic transitions between administrative states.
+ *
+ * - DEVICE_ADMIN_POWER_STATE_ENABLED:
+ * The device is administratively enabled (i.e., logically present and
+ * permitted to operate). Guest software may change its operational state
+ * (e.g., activate, deactivate, suspend) within allowed architectural
+ * semantics. This is the default state for most devices unless explicitly
+ * disabled or unplugged.
+ *
+ * - DEVICE_ADMIN_POWER_STATE_DISABLED:
+ * The device is administratively disabled. It remains logically present
+ * but is blocked from functional operation. Guest-initiated transitions
+ * are either suppressed or ignored. This is typically used to enforce
+ * shutdown, deny execution, or offline the device without removing it.
+ *
+ * - DEVICE_ADMIN_POWER_STATE_REMOVED:
+ * The device has been logically removed (e.g., via hot-unplug). It is no
+ * longer considered present or visible to the guest. This state exists
+ * for representational or transitional purposes only. In most cases,
+ * once removed, the corresponding DeviceState object is destroyed and
+ * no longer tracked. This concept may not apply to some devices as
+ * architectural limitations might make unplug not meaningful.
+ */
+typedef enum DeviceAdminPowerState {
+ DEVICE_ADMIN_POWER_STATE_ENABLED = 0,
+ DEVICE_ADMIN_POWER_STATE_DISABLED,
+ DEVICE_ADMIN_POWER_STATE_REMOVED,
+ DEVICE_ADMIN_POWER_STATE_MAX
+} DeviceAdminPowerState;
+
/**
* struct DeviceState - common device state, accessed with qdev helpers
*
@@ -240,6 +290,10 @@ struct DeviceState {
* @realized: has device been realized?
*/
bool realized;
+ /**
+ * @admin_power_state: device administrative power state
+ */
+ DeviceAdminPowerState admin_power_state;
/**
* @pending_deleted_event: track pending deletion events during unplug
*/
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index e2b2337399..0c9a2e7ea4 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -2765,6 +2765,7 @@ static void arm_cpu_class_init(ObjectClass *oc, const void *data)
cc->gdb_get_core_xml_file = arm_gdb_get_core_xml_file;
cc->gdb_stop_before_watchpoint = true;
cc->disas_set_info = arm_disas_set_info;
+ dc->admin_power_state_supported = true;
#ifdef CONFIG_TCG
cc->tcg_ops = &arm_tcg_ops;
--
2.34.1
Hi Salil,
> On 1 Oct 2025, at 01:01, salil.mehta@opnsrc.net wrote:
>
> From: Salil Mehta <salil.mehta@huawei.com>
>
> Some devices cannot be hot-unplugged, either because removal is not meaningful
> (e.g. on-board devices) or not supported (e.g. certain PCIe devices). Others,
> such as CPUs on architectures like ARM, lack native hotplug support but can
> still have their availability controlled through host policy. In all these
> cases, a mechanism is needed to track and control a device’s *administrative*
> power state — independent of its runtime operational state — so QEMU can:
>
> - Disable a device while keeping it described in firmware, ACPI, or other
> configuration.
> - Prevent guest use until explicitly re-enabled.
> - Coordinate transitions with platform-specific power handlers and migration
> logic.
>
> This patch introduces the core qdev support for administrative power state —
> defining the property, enum, and accessors — without yet applying it to any
> device. Later patches in this series integrate it with helper APIs
> (qdev_disable(), qdev_enable(), etc.) and specific device types such as CPUs,
> completing the flow with platform-specific handlers.
>
> Key additions:
> - New enum DeviceAdminPowerState with ENABLED, DISABLED, and REMOVED states,
> defaulting to ENABLED.
> - New DeviceClass flag admin_power_state_supported to advertise support for
> administrative transitions.
> - New QOM property "admin_power_state" to query or set the state on supported
> devices.
> - Internal accessors device_get_admin_power_state() and
> device_set_admin_power_state() to manage state changes, including safe
> handling when the device is not yet realized.
>
> The enum models *policy* rather than electrical or functional power state, and
> is distinct from runtime mechanisms (e.g. PSCI for ARM CPUs). The actual
> operational state of a device is maintained by platform-specific or device-
> specific code, which enforces runtime behaviour based on the administrative
> setting. Every device starts administratively ENABLED by default. A DISABLED
> device remains logically present but blocked from operation; a REMOVED device
> is logically absent.
>
> Signed-off-by: Salil Mehta <salil.mehta@huawei.com>
> ---
> hw/core/qdev.c | 62 ++++++++++++++++++++++++++++++++++++++++++
> include/hw/qdev-core.h | 54 ++++++++++++++++++++++++++++++++++++
> target/arm/cpu.c | 1 +
I’d suggest separating this in two patches, one which adds functionality and
another one which enables functionality for the arch. It may ease integration overall,
moreover both may be independent of one another.
Thanks
Miguel
> 3 files changed, 117 insertions(+)
>
> diff --git a/hw/core/qdev.c b/hw/core/qdev.c
> index f600226176..8502d6216f 100644
> --- a/hw/core/qdev.c
> +++ b/hw/core/qdev.c
> @@ -633,6 +633,53 @@ static bool device_get_hotplugged(Object *obj, Error **errp)
> return dev->hotplugged;
> }
>
> +static int device_get_admin_power_state(Object *obj, Error **errp)
> +{
> + DeviceState *dev = DEVICE(obj);
> +
> + return dev->admin_power_state;
> +}
> +
> +static void
> +device_set_admin_power_state(Object *obj, int new_state, Error **errp)
> +{
> + DeviceState *dev = DEVICE(obj);
> + DeviceClass *dc = DEVICE_GET_CLASS(dev);
> +
> + if (!dc->admin_power_state_supported) {
> + error_setg(errp, "Device '%s' admin power state change not supported",
> + object_get_typename(obj));
> + return;
> + }
> +
> + switch (new_state) {
> + case DEVICE_ADMIN_POWER_STATE_DISABLED: {
> + /*
> + * TODO: Operational state transition triggered by administrative action
> + * Powering off the realized device either synchronously or via OSPM.
> + */
> +
> + qatomic_set(&dev->admin_power_state, DEVICE_ADMIN_POWER_STATE_DISABLED);
> + smp_wmb();
> + break;
> + }
> + case DEVICE_ADMIN_POWER_STATE_ENABLED: {
> + /*
> + * TODO: Operational state transition triggered by administrative action
> + * Powering on the device and restoring migration registration.
> + */
> +
> + qatomic_set(&dev->admin_power_state, DEVICE_ADMIN_POWER_STATE_ENABLED);
> + smp_wmb();
> + break;
> + }
> + default:
> + error_setg(errp, "Invalid admin power state %d for device '%s'",
> + new_state, dev->id);
> + break;
> + }
> +}
> +
> static void device_initfn(Object *obj)
> {
> DeviceState *dev = DEVICE(obj);
> @@ -644,6 +691,7 @@ static void device_initfn(Object *obj)
>
> dev->instance_id_alias = -1;
> dev->realized = false;
> + dev->admin_power_state = DEVICE_ADMIN_POWER_STATE_ENABLED;
> dev->allow_unplug_during_migration = false;
>
> QLIST_INIT(&dev->gpios);
> @@ -731,6 +779,15 @@ device_vmstate_if_get_id(VMStateIf *obj)
> return qdev_get_dev_path(dev);
> }
>
> +static const QEnumLookup device_admin_power_state_lookup = {
> + .array = (const char *const[]) {
> + [DEVICE_ADMIN_POWER_STATE_ENABLED] = "enabled",
> + [DEVICE_ADMIN_POWER_STATE_REMOVED] = "removed",
> + [DEVICE_ADMIN_POWER_STATE_DISABLED] = "disabled",
> + },
> + .size = DEVICE_ADMIN_POWER_STATE_MAX,
> +};
> +
> static void device_class_init(ObjectClass *class, const void *data)
> {
> DeviceClass *dc = DEVICE_CLASS(class);
> @@ -765,6 +822,11 @@ static void device_class_init(ObjectClass *class, const void *data)
> device_get_hotpluggable, NULL);
> object_class_property_add_bool(class, "hotplugged",
> device_get_hotplugged, NULL);
> + object_class_property_add_enum(class, "admin_power_state",
> + "DeviceAdminPowerState",
> + &device_admin_power_state_lookup,
> + device_get_admin_power_state,
> + device_set_admin_power_state);
> object_class_property_add_link(class, "parent_bus", TYPE_BUS,
> offsetof(DeviceState, parent_bus), NULL, 0);
> }
> diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
> index 530f3da702..3bc212ab3a 100644
> --- a/include/hw/qdev-core.h
> +++ b/include/hw/qdev-core.h
> @@ -159,6 +159,7 @@ struct DeviceClass {
> */
> bool user_creatable;
> bool hotpluggable;
> + bool admin_power_state_supported;
>
> /* callbacks */
> /**
> @@ -217,6 +218,55 @@ typedef QLIST_HEAD(, NamedGPIOList) NamedGPIOListHead;
> typedef QLIST_HEAD(, NamedClockList) NamedClockListHead;
> typedef QLIST_HEAD(, BusState) BusStateHead;
>
> +/**
> + * enum DeviceAdminPowerState - Administrative control states for a device
> + *
> + * This enum defines abstract administrative states used by QEMU to enable,
> + * disable, or logically remove a device from the virtual machine. These
> + * states reflect administrative control over a device's power availability
> + * and presence in the system. These administrative states are distinct from
> + * runtime operational power states (e.g., PSCI states for ARM CPUs). They
> + * represent administrative *policy* rather than physical, electrical, or
> + * functional state.
> + *
> + * Administrative state is managed externally "via QMP, firmware, or other
> + * host-side policy agents" and acts as a gating policy that determines
> + * whether guest software is permitted to interact with the device. Most
> + * devices default to the ENABLED state unless explicitly disabled or removed.
> + *
> + * Changing a device administrative state may directly or indirectly affect
> + * its operational behavior. For example, a DISABLED device will reject guest
> + * attempts to power it on or transition it out of a suspended state. Not all
> + * devices support dynamic transitions between administrative states.
> + *
> + * - DEVICE_ADMIN_POWER_STATE_ENABLED:
> + * The device is administratively enabled (i.e., logically present and
> + * permitted to operate). Guest software may change its operational state
> + * (e.g., activate, deactivate, suspend) within allowed architectural
> + * semantics. This is the default state for most devices unless explicitly
> + * disabled or unplugged.
> + *
> + * - DEVICE_ADMIN_POWER_STATE_DISABLED:
> + * The device is administratively disabled. It remains logically present
> + * but is blocked from functional operation. Guest-initiated transitions
> + * are either suppressed or ignored. This is typically used to enforce
> + * shutdown, deny execution, or offline the device without removing it.
> + *
> + * - DEVICE_ADMIN_POWER_STATE_REMOVED:
> + * The device has been logically removed (e.g., via hot-unplug). It is no
> + * longer considered present or visible to the guest. This state exists
> + * for representational or transitional purposes only. In most cases,
> + * once removed, the corresponding DeviceState object is destroyed and
> + * no longer tracked. This concept may not apply to some devices as
> + * architectural limitations might make unplug not meaningful.
> + */
> +typedef enum DeviceAdminPowerState {
> + DEVICE_ADMIN_POWER_STATE_ENABLED = 0,
> + DEVICE_ADMIN_POWER_STATE_DISABLED,
> + DEVICE_ADMIN_POWER_STATE_REMOVED,
> + DEVICE_ADMIN_POWER_STATE_MAX
> +} DeviceAdminPowerState;
> +
> /**
> * struct DeviceState - common device state, accessed with qdev helpers
> *
> @@ -240,6 +290,10 @@ struct DeviceState {
> * @realized: has device been realized?
> */
> bool realized;
> + /**
> + * @admin_power_state: device administrative power state
> + */
> + DeviceAdminPowerState admin_power_state;
> /**
> * @pending_deleted_event: track pending deletion events during unplug
> */
> diff --git a/target/arm/cpu.c b/target/arm/cpu.c
> index e2b2337399..0c9a2e7ea4 100644
> --- a/target/arm/cpu.c
> +++ b/target/arm/cpu.c
> @@ -2765,6 +2765,7 @@ static void arm_cpu_class_init(ObjectClass *oc, const void *data)
> cc->gdb_get_core_xml_file = arm_gdb_get_core_xml_file;
> cc->gdb_stop_before_watchpoint = true;
> cc->disas_set_info = arm_disas_set_info;
> + dc->admin_power_state_supported = true;
>
> #ifdef CONFIG_TCG
> cc->tcg_ops = &arm_tcg_ops;
> --
> 2.34.1
>
© 2016 - 2025 Red Hat, Inc.