From: Leonid Bloch <lb.workbox@gmail.com>
To: "Michael S . Tsirkin" <mst@redhat.com>,
Igor Mammedov <imammedo@redhat.com>,
Ani Sinha <anisinha@redhat.com>,
Paolo Bonzini <pbonzini@redhat.com>,
Richard Henderson <richard.henderson@linaro.org>,
Eduardo Habkost <ehabkost@redhat.com>,
Eric Blake <eblake@redhat.com>,
Markus Armbruster <armbru@redhat.com>,
Marcel Apfelbaum <marcel.apfelbaum@gmail.com>,
Dmitry Fleytman <dmitry.fleytman@gmail.com>
Cc: Leonid Bloch <lb.workbox@gmail.com>, qemu-devel@nongnu.org
Subject: [PATCH v4 5/8] hw/acpi: Introduce the QEMU AC adapter
Date: Tue, 26 May 2026 07:29:24 +0300 [thread overview]
Message-ID: <20260526042928.9203-6-lb.workbox@gmail.com> (raw)
In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com>
The AC adapter device communicates AC power state to the guest via ACPI.
AC adapter state is controlled programmatically via QMP commands,
ensuring deterministic behavior.
Properties:
- 'ioport': I/O port base address (default: 0x53c)
The device implements the ACPI_DEV_AML_IF interface to generate its
own AML code, placing the ADP0 device directly under \_SB scope.
QMP commands:
- ac-adapter-set-state: Set AC adapter connection state
- query-ac-adapter: Query current AC adapter state
Signed-off-by: Leonid Bloch <lb.workbox@gmail.com>
---
MAINTAINERS | 2 +
hw/acpi/Kconfig | 4 +
hw/acpi/acad-stub.c | 20 +++
hw/acpi/acad.c | 251 +++++++++++++++++++++++++++
hw/acpi/meson.build | 2 +
hw/acpi/trace-events | 4 +
hw/i386/Kconfig | 1 +
include/hw/acpi/acad.h | 25 +++
include/hw/acpi/acpi_dev_interface.h | 1 +
qapi/acpi.json | 47 +++++
10 files changed, 357 insertions(+)
create mode 100644 hw/acpi/acad-stub.c
create mode 100644 hw/acpi/acad.c
create mode 100644 include/hw/acpi/acad.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 47436ec878..90941519e3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3047,6 +3047,8 @@ AC Adapter
M: Leonid Bloch <lb.workbox@gmail.com>
S: Maintained
F: docs/specs/acad.rst
+F: hw/acpi/acad*
+F: include/hw/acpi/acad.h
Subsystems
----------
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
index 6b2c46d37a..889ace2dfa 100644
--- a/hw/acpi/Kconfig
+++ b/hw/acpi/Kconfig
@@ -74,6 +74,10 @@ config ACPI_VIOT
bool
depends on ACPI
+config AC_ADAPTER
+ bool
+ depends on ACPI
+
config ACPI_HW_REDUCED
bool
select ACPI
diff --git a/hw/acpi/acad-stub.c b/hw/acpi/acad-stub.c
new file mode 100644
index 0000000000..92093b7682
--- /dev/null
+++ b/hw/acpi/acad-stub.c
@@ -0,0 +1,20 @@
+/*
+ * QEMU emulated AC adapter device - QMP stubs.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-acpi.h"
+
+void qmp_ac_adapter_set_state(bool connected, Error **errp)
+{
+ error_setg(errp, "No AC adapter device found");
+}
+
+AcAdapterInfo *qmp_query_ac_adapter(Error **errp)
+{
+ error_setg(errp, "No AC adapter device found");
+ return NULL;
+}
diff --git a/hw/acpi/acad.c b/hw/acpi/acad.c
new file mode 100644
index 0000000000..11777fbb3c
--- /dev/null
+++ b/hw/acpi/acad.c
@@ -0,0 +1,251 @@
+/*
+ * QEMU emulated AC adapter device.
+ *
+ * Copyright (c) 2019-2026 Janus Technologies, Inc. (http://janustech.com)
+ *
+ * Authors:
+ * Leonid Bloch <lb.workbox@gmail.com>
+ * Marcel Apfelbaum <marcel.apfelbaum@gmail.com>
+ * Dmitry Fleytman <dmitry.fleytman@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "trace.h"
+#include "hw/isa/isa.h"
+#include "hw/acpi/acpi.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/acpi/acpi_aml_interface.h"
+#include "qapi/qapi-commands-acpi.h"
+
+#include "hw/acpi/acad.h"
+
+#define AC_ADAPTER_DEVICE(obj) OBJECT_CHECK(ACADState, (obj), \
+ TYPE_AC_ADAPTER)
+
+#define AC_STA_ADDR 0
+
+enum {
+ AC_ADAPTER_OFFLINE = 0,
+ AC_ADAPTER_ONLINE = 1,
+};
+
+typedef struct ACADState {
+ ISADevice dev;
+ MemoryRegion io;
+ uint16_t ioport;
+ uint8_t state;
+ bool qmp_connected;
+} ACADState;
+
+static void acad_get_dynamic_status(ACADState *s)
+{
+ s->state = s->qmp_connected ? AC_ADAPTER_ONLINE : AC_ADAPTER_OFFLINE;
+
+ trace_acad_get_dynamic_status(s->state);
+}
+
+static void acad_realize(DeviceState *dev, Error **errp)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ ACADState *s = AC_ADAPTER_DEVICE(dev);
+ bool ambiguous;
+
+ trace_acad_realize();
+
+ object_resolve_path_type("", TYPE_AC_ADAPTER, &ambiguous);
+ if (ambiguous) {
+ error_setg(errp, "at most one %s device is permitted",
+ TYPE_AC_ADAPTER);
+ return;
+ }
+
+ /* Initialize to disconnected by default */
+ s->qmp_connected = false;
+
+ isa_register_ioport(d, &s->io, s->ioport);
+}
+
+static const Property acad_device_properties[] = {
+ DEFINE_PROP_UINT16(AC_ADAPTER_IOPORT_PROP, ACADState, ioport, 0x53c),
+};
+
+static const VMStateDescription acad_vmstate = {
+ .name = "acad",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(qmp_connected, ACADState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void build_acad_aml(AcpiDevAmlIf *adev, Aml *scope)
+{
+ Aml *dev, *field, *method, *pkg;
+ Aml *acad_state;
+ Aml *sb_scope;
+ ACADState *s = AC_ADAPTER_DEVICE(adev);
+
+ acad_state = aml_local(0);
+
+ sb_scope = aml_scope("\\_SB");
+ dev = aml_device("ADP0");
+ aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0003")));
+
+ aml_append(dev, aml_operation_region("ACST", AML_SYSTEM_IO,
+ aml_int(s->ioport),
+ AC_ADAPTER_LEN));
+ field = aml_field("ACST", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
+ aml_append(field, aml_named_field("PWRS", 8));
+ aml_append(dev, field);
+
+ method = aml_method("_PSR", 0, AML_NOTSERIALIZED);
+ aml_append(method, aml_store(aml_name("PWRS"), acad_state));
+ aml_append(method, aml_return(acad_state));
+ aml_append(dev, method);
+
+ method = aml_method("_PCL", 0, AML_NOTSERIALIZED);
+ pkg = aml_package(1);
+ aml_append(pkg, aml_name("_SB"));
+ aml_append(method, aml_return(pkg));
+ aml_append(dev, method);
+
+ method = aml_method("_PIF", 0, AML_NOTSERIALIZED);
+ pkg = aml_package(6);
+ /* Power Source State */
+ aml_append(pkg, aml_int(0)); /* Non-redundant, non-shared */
+ /* Maximum Output Power */
+ aml_append(pkg, aml_int(AC_ADAPTER_VAL_UNKNOWN));
+ /* Maximum Input Power */
+ aml_append(pkg, aml_int(AC_ADAPTER_VAL_UNKNOWN));
+ /* Model Number */
+ aml_append(pkg, aml_string("QADP001"));
+ /* Serial Number */
+ aml_append(pkg, aml_string("SN00000"));
+ /* OEM Information */
+ aml_append(pkg, aml_string("QEMU"));
+ aml_append(method, aml_return(pkg));
+ aml_append(dev, method);
+
+ aml_append(sb_scope, dev);
+ aml_append(scope, sb_scope);
+
+ /* Status Change */
+ method = aml_method("\\_GPE._E0B", 0, AML_NOTSERIALIZED);
+ aml_append(method, aml_notify(aml_name("\\_SB.ADP0"), aml_int(0x80)));
+ aml_append(scope, method);
+}
+
+static void acad_class_init(ObjectClass *class, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(class);
+
+ dc->realize = acad_realize;
+ dc->hotpluggable = false;
+ device_class_set_props(dc, acad_device_properties);
+ dc->vmsd = &acad_vmstate;
+ adevc->build_dev_aml = build_acad_aml;
+}
+
+static uint64_t acad_ioport_read(void *opaque, hwaddr addr, unsigned size)
+{
+ ACADState *s = opaque;
+
+ acad_get_dynamic_status(s);
+
+ switch (addr) {
+ case AC_STA_ADDR:
+ return s->state;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const MemoryRegionOps acad_ops = {
+ .read = acad_ioport_read,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void acad_instance_init(Object *obj)
+{
+ ACADState *s = AC_ADAPTER_DEVICE(obj);
+
+ memory_region_init_io(&s->io, obj, &acad_ops, s, "acad",
+ AC_ADAPTER_LEN);
+}
+
+static const TypeInfo acad_info = {
+ .name = TYPE_AC_ADAPTER,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ACADState),
+ .class_init = acad_class_init,
+ .instance_init = acad_instance_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_ACPI_DEV_AML_IF },
+ { },
+ },
+};
+
+static ACADState *find_acad_device(Error **errp)
+{
+ bool ambiguous;
+ Object *o = object_resolve_path_type("", TYPE_AC_ADAPTER, &ambiguous);
+
+ if (!o) {
+ error_setg(errp, "No AC adapter device found");
+ return NULL;
+ }
+ if (ambiguous) {
+ error_setg(errp, "More than one AC adapter device present");
+ return NULL;
+ }
+ return AC_ADAPTER_DEVICE(o);
+}
+
+void qmp_ac_adapter_set_state(bool connected, Error **errp)
+{
+ ACADState *s = find_acad_device(errp);
+ Object *obj;
+
+ if (!s) {
+ return;
+ }
+
+ s->qmp_connected = connected;
+
+ obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
+ if (obj) {
+ acpi_send_event(DEVICE(obj), ACPI_AC_ADAPTER_CHANGE_STATUS);
+ }
+}
+
+AcAdapterInfo *qmp_query_ac_adapter(Error **errp)
+{
+ ACADState *s = find_acad_device(errp);
+ AcAdapterInfo *ret;
+
+ if (!s) {
+ return NULL;
+ }
+
+ ret = g_new0(AcAdapterInfo, 1);
+ ret->connected = s->qmp_connected;
+
+ return ret;
+}
+
+static void acad_register_types(void)
+{
+ type_register_static(&acad_info);
+}
+
+type_init(acad_register_types)
diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build
index e6bc78274e..731e9477e3 100644
--- a/hw/acpi/meson.build
+++ b/hw/acpi/meson.build
@@ -37,6 +37,8 @@ stub_ss.add(files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c'))
stub_ss.add(files('pci-bridge-stub.c'))
acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c'))
stub_ss.add(files('battery-stub.c'))
+acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c'))
+stub_ss.add(files('acad-stub.c'))
system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss)
system_ss.add(when: 'CONFIG_GHES_CPER', if_true: files('ghes_cper.c'))
stub_ss.add(files('ghes_cper_stub.c'))
diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events
index 8a6ab91a13..67602000f3 100644
--- a/hw/acpi/trace-events
+++ b/hw/acpi/trace-events
@@ -91,3 +91,7 @@ acpi_nvdimm_invalid_revision(uint32_t revision) "Revision 0x%" PRIx32 " is not s
# battery.c
battery_realize(void) "Battery device realize entry"
battery_get_dynamic_status(uint32_t state, uint32_t rate, uint32_t charge) "Battery read state: 0x%"PRIx32", rate: %"PRIu32", charge: %"PRIu32
+
+# acad.c
+acad_realize(void) "AC adapter device realize entry"
+acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8
diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig
index 94004ffeb2..06f21cadb7 100644
--- a/hw/i386/Kconfig
+++ b/hw/i386/Kconfig
@@ -40,6 +40,7 @@ config PC
imply NVDIMM
imply FDC_ISA
imply BATTERY
+ imply AC_ADAPTER
select I8259
select I8254
select PCKBD
diff --git a/include/hw/acpi/acad.h b/include/hw/acpi/acad.h
new file mode 100644
index 0000000000..f163158f35
--- /dev/null
+++ b/include/hw/acpi/acad.h
@@ -0,0 +1,25 @@
+/*
+ * QEMU emulated AC adapter device.
+ *
+ * Copyright (c) 2019-2026 Janus Technologies, Inc. (http://janustech.com)
+ *
+ * Authors:
+ * Leonid Bloch <lb.workbox@gmail.com>
+ * Marcel Apfelbaum <marcel.apfelbaum@gmail.com>
+ * Dmitry Fleytman <dmitry.fleytman@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+
+#ifndef HW_ACPI_AC_ADAPTER_H
+#define HW_ACPI_AC_ADAPTER_H
+
+#define TYPE_AC_ADAPTER "acad"
+#define AC_ADAPTER_IOPORT_PROP "ioport"
+
+#define AC_ADAPTER_VAL_UNKNOWN 0xFFFFFFFF
+
+#define AC_ADAPTER_LEN 1
+
+#endif
diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h
index a6f9022c0b..00566c56a7 100644
--- a/include/hw/acpi/acpi_dev_interface.h
+++ b/include/hw/acpi/acpi_dev_interface.h
@@ -15,6 +15,7 @@ typedef enum {
ACPI_POWER_DOWN_STATUS = 64,
ACPI_GENERIC_ERROR = 128,
ACPI_BATTERY_CHANGE_STATUS = 256,
+ ACPI_AC_ADAPTER_CHANGE_STATUS = 2048,
} AcpiEventStatusBits;
#define TYPE_ACPI_DEVICE_IF "acpi-device-interface"
diff --git a/qapi/acpi.json b/qapi/acpi.json
index 4711a05614..025b5d8eaa 100644
--- a/qapi/acpi.json
+++ b/qapi/acpi.json
@@ -213,3 +213,50 @@
##
{ 'command': 'query-battery',
'returns': 'BatteryInfo' }
+
+##
+# @ac-adapter-set-state:
+#
+# Set the state of the emulated AC adapter device
+#
+# @connected: whether the AC adapter is connected
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+# -> { "execute": "ac-adapter-set-state",
+# "arguments": { "connected": true } }
+# <- { "return": {} }
+##
+{ 'command': 'ac-adapter-set-state',
+ 'data': { 'connected': 'bool' } }
+
+##
+# @AcAdapterInfo:
+#
+# AC adapter state information
+#
+# @connected: whether the AC adapter is connected
+#
+# Since: 11.1
+##
+{ 'struct': 'AcAdapterInfo',
+ 'data': { 'connected': 'bool' } }
+
+##
+# @query-ac-adapter:
+#
+# Query the current state of the emulated AC adapter device
+#
+# Returns: AC adapter connection state
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+# -> { "execute": "query-ac-adapter" }
+# <- { "return": { "connected": true } }
+##
+{ 'command': 'query-ac-adapter',
+ 'returns': 'AcAdapterInfo' }
--
2.54.0
next prev parent reply other threads:[~2026-05-26 4:30 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-26 4:29 [PATCH v4 0/8] Introduce a battery, AC adapter, and lid button Leonid Bloch
2026-05-26 4:29 ` [PATCH v4 1/8] hw/acpi: Support extended GPE handling for additional ACPI devices Leonid Bloch
2026-05-26 4:29 ` [PATCH v4 2/8] docs/specs: Introduce the QEMU Battery documentation Leonid Bloch
2026-05-26 4:29 ` [PATCH v4 3/8] hw/acpi: Introduce the QEMU Battery Leonid Bloch
2026-06-02 5:49 ` Markus Armbruster
2026-05-26 4:29 ` [PATCH v4 4/8] docs/specs: Introduce the QEMU AC adapter documentation Leonid Bloch
2026-05-26 4:29 ` Leonid Bloch [this message]
2026-06-02 5:51 ` [PATCH v4 5/8] hw/acpi: Introduce the QEMU AC adapter Markus Armbruster
2026-05-26 4:29 ` [PATCH v4 6/8] docs/specs: Introduce the QEMU lid button documentation Leonid Bloch
2026-05-26 4:29 ` [PATCH v4 7/8] hw/acpi: Introduce the QEMU lid button Leonid Bloch
2026-05-26 4:29 ` [PATCH v4 8/8] scripts: Add laptop-mirror reference script Leonid Bloch
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260526042928.9203-6-lb.workbox@gmail.com \
--to=lb.workbox@gmail.com \
--cc=anisinha@redhat.com \
--cc=armbru@redhat.com \
--cc=dmitry.fleytman@gmail.com \
--cc=eblake@redhat.com \
--cc=ehabkost@redhat.com \
--cc=imammedo@redhat.com \
--cc=marcel.apfelbaum@gmail.com \
--cc=mst@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=richard.henderson@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.