All of lore.kernel.org
 help / color / mirror / Atom feed
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 v2 3/4] hw/acpi: Introduce the QEMU AC adapter
Date: Thu, 21 Aug 2025 20:45:51 +0300	[thread overview]
Message-ID: <20250821174554.40607-4-lb.workbox@gmail.com> (raw)
In-Reply-To: <20250821174554.40607-1-lb.workbox@gmail.com>

The AC adapter device communicates AC power state to the guest via ACPI.
It supports two modes of operation:

1. QMP control mode (default): AC adapter state is controlled
   programmatically via QMP commands, ensuring deterministic behavior.

2. Host mirroring mode (optional): The device reflects the host's AC
   adapter state from sysfs. Probing occurs on guest ACPI requests and
   at timed intervals. State changes trigger ACPI notifications.

Properties:
- 'use-qmp': Enable QMP control mode (default: true)
- 'enable-sysfs': Enable host AC adapter mirroring (default: false)
- 'probe_interval': Probe interval in ms for sysfs mode (default: 2000)
- 'sysfs_path': Override default sysfs path /sys/class/power_supply/

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                          |   6 +
 docs/specs/acad.rst                  | 195 ++++++++++++
 docs/specs/index.rst                 |   1 +
 hw/acpi/Kconfig                      |   4 +
 hw/acpi/acad.c                       | 447 +++++++++++++++++++++++++++
 hw/acpi/meson.build                  |   1 +
 hw/acpi/trace-events                 |   5 +
 hw/i386/Kconfig                      |   1 +
 include/hw/acpi/acad.h               |  27 ++
 include/hw/acpi/acpi_dev_interface.h |   1 +
 qapi/acpi.json                       |  49 +++
 11 files changed, 737 insertions(+)
 create mode 100644 docs/specs/acad.rst
 create mode 100644 hw/acpi/acad.c
 create mode 100644 include/hw/acpi/acad.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 51af9b7366..612efcb686 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2927,6 +2927,12 @@ S: Maintained
 F: hw/acpi/battery.*
 F: docs/specs/battery.rst
 
+AC Adapter
+M: Leonid Bloch <lb.workbox@gmail.com>
+S: Maintained
+F: hw/acpi/acad.*
+F: docs/specs/acad.rst
+
 Subsystems
 ----------
 Overall Audio backends
diff --git a/docs/specs/acad.rst b/docs/specs/acad.rst
new file mode 100644
index 0000000000..73d5501b8f
--- /dev/null
+++ b/docs/specs/acad.rst
@@ -0,0 +1,195 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+=================
+AC Adapter Device
+=================
+
+The AC adapter device provides AC power state information to the guest. It
+supports two operating modes:
+
+1. **QMP Control Mode** (default): AC adapter state is controlled via QMP
+   commands, providing deterministic control for testing and migration safety.
+2. **Sysfs Mode**: AC adapter state mirrors the host's physical AC adapter,
+   useful for desktop virtualization where the guest should see the host's
+   power state.
+
+Configuration
+-------------
+
+The AC adapter device is created as an ISA device using ``-device acad``.
+
+Operating Modes
+~~~~~~~~~~~~~~~
+
+**QMP Control Mode** (``use-qmp=true``, default)
+  AC adapter state is controlled via QMP commands. This mode is recommended for:
+
+  * Production environments requiring migration support
+  * Testing with predictable power states
+  * Environments without host AC adapter access
+  * Security-sensitive deployments
+
+**Sysfs Mode** (``enable-sysfs=true``)
+  AC adapter mirrors the host's physical AC adapter. This mode is useful for:
+
+  * Desktop virtualization on laptops
+  * Development and testing with real AC adapter behavior
+
+  Note: Sysfs mode reads host files and runs timers, which may impact
+  security and migration. Use with caution in production.
+
+Properties
+~~~~~~~~~~
+
+``ioport`` (default: 0x53c)
+  I/O port base address for the AC adapter device register.
+
+``use-qmp`` (default: true)
+  Enable QMP control mode. When true, AC adapter state is controlled via
+  QMP commands. Cannot be used together with ``enable-sysfs=true``.
+
+``enable-sysfs`` (default: false)
+  Enable sysfs mode to mirror the host's AC adapter. Cannot be used together
+  with ``use-qmp=true``.
+
+``probe_interval`` (default: 2000)
+  Time interval between periodic probes in milliseconds (sysfs mode only).
+  A zero value disables the periodic probes, and makes the AC adapter state
+  updates occur on guest requests only.
+
+``sysfs_path`` (default: auto-detected)
+  Path to the host's AC adapter sysfs directory (sysfs mode only). By default,
+  the device auto-detects the first AC adapter of type "Mains" in
+  ``/sys/class/power_supply/``. Use this property to specify a different
+  AC adapter, or to provide a custom path for testing purposes.
+
+Host AC Adapter Detection
+-------------------------
+
+The host's AC adapter information is taken from the sysfs AC adapter
+data, located in::
+
+    /sys/class/power_supply/[device of type "Mains"]
+
+The device automatically scans for the first AC adapter with:
+
+- A ``type`` file containing "Mains"
+- An ``online`` file that can be read
+
+If the sysfs path differs, a different AC adapter needs to be probed,
+or even if a "fake" host AC adapter is to be provided, the ``sysfs_path``
+property allows overriding the default detection.
+
+ACPI Interface
+--------------
+
+The AC adapter device is exposed to the guest as an ACPI device with:
+
+- **HID**: ``ACPI0003`` (AC Adapter)
+- **Device Path**: ``\_SB.ADP0``
+- **Notification Values**:
+
+  - ``0x80``: Status change (connected/disconnected)
+
+ACPI Methods
+~~~~~~~~~~~~
+
+``_PSR`` (Power Source)
+  Returns the current AC adapter state (0 = offline, 1 = online).
+
+``_PCL`` (Power Consumer List)
+  Returns the list of devices powered by this adapter.
+
+``_PIF`` (Power Source Information)
+  Returns static information about the power source including model number,
+  serial number, and OEM information.
+
+I/O Interface
+-------------
+
+The device uses a single I/O port register:
+
+- **Port**: ``ioport`` property value (default 0x53c)
+- **Size**: 1 byte
+- **Access**: Read-only
+
+Register Layout
+~~~~~~~~~~~~~~~
+
+**PWRS** (offset 0x00, 1 byte)
+  Current AC adapter state:
+
+  - ``0x00``: AC adapter offline (unplugged)
+  - ``0x01``: AC adapter online (plugged in)
+
+QMP Commands
+------------
+
+When using QMP control mode (default), the following commands are available:
+
+``ac-adapter-set-state``
+  Set the AC adapter connection state.
+
+  * ``connected``: Whether the AC adapter is connected (boolean)
+
+  Example::
+
+    -> { "execute": "ac-adapter-set-state",
+         "arguments": { "connected": true }}
+    <- { "return": {} }
+
+``query-ac-adapter``
+  Query the current AC adapter state.
+
+  Example::
+
+    -> { "execute": "query-ac-adapter" }
+    <- { "return": { "connected": true }}
+
+Examples
+--------
+
+QMP control mode (default - recommended)::
+
+  # Start with QMP control
+  qemu-system-x86_64 -device acad -qmp tcp:localhost:4444,server,wait=off
+
+  # From another terminal, set AC adapter state via QMP:
+  echo '{"execute":"qmp_capabilities"}
+        {"execute":"ac-adapter-set-state",
+         "arguments":{"connected":true}}' | \
+  nc -N localhost 4444
+
+Sysfs mode (mirror host AC adapter)::
+
+  # Enable sysfs mode to mirror host AC adapter
+  qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true
+
+  # Custom probe interval (5 seconds)
+  qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true,probe_interval=5000
+
+  # Specific AC adapter path
+  qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true,sysfs_path=/sys/class/power_supply/ADP1
+
+Testing with fake AC adapter::
+
+  # Create fake AC adapter files for testing
+  mkdir -p /tmp/fake_ac
+  echo "Mains" > /tmp/fake_ac/type
+  echo "1" > /tmp/fake_ac/online          # 1 = connected, 0 = disconnected
+
+  # Use fake AC adapter in sysfs mode
+  qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true,sysfs_path=/tmp/fake_ac
+
+  # Update AC adapter state while VM is running (from another terminal)
+  echo "0" > /tmp/fake_ac/online          # Disconnect AC adapter
+  echo "1" > /tmp/fake_ac/online          # Reconnect AC adapter
+
+Combined with battery device::
+
+  # QMP mode (recommended)
+  qemu-system-x86_64 -device battery -device acad
+
+  # Sysfs mode (desktop virtualization)
+  qemu-system-x86_64 -device battery,use-qmp=false,enable-sysfs=true \
+                     -device acad,use-qmp=false,enable-sysfs=true
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index 616e8228cc..e144afcd90 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -22,6 +22,7 @@ guest hardware that is specific to QEMU.
    acpi_pci_hotplug
    acpi_nvdimm
    acpi_erst
+   acad
    battery
    sev-guest-firmware
    fw_cfg
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
index 64403378bd..9d28c3addf 100644
--- a/hw/acpi/Kconfig
+++ b/hw/acpi/Kconfig
@@ -69,6 +69,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.c b/hw/acpi/acad.c
new file mode 100644
index 0000000000..699198c194
--- /dev/null
+++ b/hw/acpi/acad.c
@@ -0,0 +1,447 @@
+/*
+ * QEMU emulated AC adapter device.
+ *
+ * Copyright (c) 2019 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 "hw/nvram/fw_cfg.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "hw/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
+
+#define SYSFS_PATH                "/sys/class/power_supply"
+#define AC_ADAPTER_TYPE           "Mains"
+#define MAX_ALLOWED_TYPE_LENGTH   16
+
+enum {
+    AC_ADAPTER_OFFLINE = 0,
+    AC_ADAPTER_ONLINE = 1,
+};
+
+typedef struct ACADState {
+    ISADevice dev;
+    MemoryRegion io;
+    uint16_t ioport;
+    uint8_t state;
+    bool use_qmp_control;
+    bool qmp_connected;
+    bool enable_sysfs;
+
+    QEMUTimer *probe_state_timer;
+    uint64_t probe_state_interval;
+
+    char *acad_path;
+} ACADState;
+
+static const char *online_file = "online";
+static const char *type_file = "type";
+
+static inline bool acad_file_accessible(char *path, const char *file)
+{
+    char full_path[PATH_MAX];
+    int path_len;
+
+    path_len = snprintf(full_path, PATH_MAX, "%s/%s", path, file);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        return false;
+    }
+
+    if (access(full_path, R_OK) == 0) {
+        return true;
+    }
+    return false;
+}
+
+static void acad_get_state(ACADState *s)
+{
+    char file_path[PATH_MAX];
+    int path_len;
+    uint8_t val;
+    FILE *ff;
+
+    path_len = snprintf(file_path, PATH_MAX, "%s/%s", s->acad_path,
+                        online_file);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        warn_report("Could not read the AC adapter state.");
+        return;
+    }
+
+    ff = fopen(file_path, "r");
+    if (ff == NULL) {
+        warn_report("Could not read the AC adapter state.");
+        return;
+    }
+
+    if (!fscanf(ff, "%hhu", &val)) {
+        warn_report("AC adapter state unreadable.");
+    } else {
+        switch (val) {
+        case AC_ADAPTER_OFFLINE:
+        case AC_ADAPTER_ONLINE:
+            s->state = val;
+            break;
+        default:
+            warn_report("AC adapter state undetermined.");
+        }
+    }
+    fclose(ff);
+}
+
+static void acad_get_dynamic_status(ACADState *s)
+{
+    if (s->use_qmp_control) {
+        s->state = s->qmp_connected ? AC_ADAPTER_ONLINE : AC_ADAPTER_OFFLINE;
+    } else if (s->enable_sysfs) {
+        acad_get_state(s);
+    } else {
+        s->state = AC_ADAPTER_OFFLINE;
+    }
+
+    trace_acad_get_dynamic_status(s->state);
+}
+
+static void acad_probe_state(void *opaque)
+{
+    ACADState *s = opaque;
+
+    uint8_t state_before = s->state;
+
+    acad_get_dynamic_status(s);
+
+    if (state_before != s->state) {
+        Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
+        acpi_send_event(DEVICE(obj), ACPI_AC_ADAPTER_CHANGE_STATUS);
+    }
+    timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+              s->probe_state_interval);
+}
+
+static void acad_probe_state_timer_init(ACADState *s)
+{
+    if (s->enable_sysfs && s->probe_state_interval > 0) {
+        s->probe_state_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+                                            acad_probe_state, s);
+        timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+                  s->probe_state_interval);
+    }
+}
+
+static bool acad_verify_sysfs(ACADState *s, char *path)
+{
+    FILE *ff;
+    char type_path[PATH_MAX];
+    int path_len;
+    char val[MAX_ALLOWED_TYPE_LENGTH];
+
+    path_len = snprintf(type_path, PATH_MAX, "%s/%s", path, type_file);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        return false;
+    }
+
+    ff = fopen(type_path, "r");
+    if (ff == NULL) {
+        return false;
+    }
+
+    if (fgets(val, MAX_ALLOWED_TYPE_LENGTH, ff) == NULL) {
+        fclose(ff);
+        return false;
+    } else {
+        val[strcspn(val, "\n")] = 0;
+        if (strncmp(val, AC_ADAPTER_TYPE, MAX_ALLOWED_TYPE_LENGTH)) {
+            fclose(ff);
+            return false;
+        }
+    }
+    fclose(ff);
+
+    return acad_file_accessible(path, online_file);
+}
+
+static bool get_acad_path(DeviceState *dev)
+{
+    ACADState *s = AC_ADAPTER_DEVICE(dev);
+    DIR *dir;
+    struct dirent *ent;
+    char bp[PATH_MAX];
+    int path_len;
+
+    if (s->acad_path) {
+        return acad_verify_sysfs(s, s->acad_path);
+    }
+
+    dir = opendir(SYSFS_PATH);
+    if (dir == NULL) {
+        return false;
+    }
+
+    ent = readdir(dir);
+    while (ent != NULL) {
+        if (ent->d_name[0] != '.') {
+            path_len = snprintf(bp, PATH_MAX, "%s/%s", SYSFS_PATH,
+                                ent->d_name);
+            if (path_len < 0 || path_len >= PATH_MAX) {
+                return false;
+            }
+            if (acad_verify_sysfs(s, bp)) {
+                qdev_prop_set_string(dev, AC_ADAPTER_PATH_PROP, bp);
+                closedir(dir);
+                return true;
+            }
+        }
+        ent = readdir(dir);
+    }
+    closedir(dir);
+
+    return false;
+}
+
+static void acad_realize(DeviceState *dev, Error **errp)
+{
+    ISADevice *d = ISA_DEVICE(dev);
+    ACADState *s = AC_ADAPTER_DEVICE(dev);
+    FWCfgState *fw_cfg = fw_cfg_find();
+    uint16_t *acad_port;
+    char err_details[32] = {};
+
+    trace_acad_realize();
+
+    if (s->use_qmp_control && s->enable_sysfs) {
+        error_setg(errp, "Cannot enable both QMP control and sysfs mode");
+        return;
+    }
+
+    if (s->enable_sysfs) {
+        if (!s->acad_path) {
+            strcpy(err_details, " Try using 'sysfs_path='");
+        }
+
+        if (!get_acad_path(dev)) {
+            error_setg(errp, "AC adapter sysfs path not found or unreadable.%s",
+                       err_details);
+            return;
+        }
+    }
+
+    isa_register_ioport(d, &s->io, s->ioport);
+
+    acad_probe_state_timer_init(s);
+
+    if (!fw_cfg) {
+        return;
+    }
+
+    acad_port = g_malloc(sizeof(*acad_port));
+    *acad_port = cpu_to_le16(s->ioport);
+    fw_cfg_add_file(fw_cfg, "etc/acad-port", acad_port,
+                    sizeof(*acad_port));
+}
+
+static const Property acad_device_properties[] = {
+    DEFINE_PROP_UINT16(AC_ADAPTER_IOPORT_PROP, ACADState, ioport, 0x53c),
+    DEFINE_PROP_BOOL("use-qmp", ACADState, use_qmp_control, true),
+    DEFINE_PROP_BOOL("enable-sysfs", ACADState, enable_sysfs, false),
+    DEFINE_PROP_UINT64(AC_ADAPTER_PROBE_STATE_INTERVAL, ACADState,
+                       probe_state_interval, 2000),
+    DEFINE_PROP_STRING(AC_ADAPTER_PATH_PROP, ACADState, acad_path),
+};
+
+static const VMStateDescription acad_vmstate = {
+    .name = "acad",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT16(ioport, ACADState),
+        VMSTATE_UINT64(probe_state_interval, 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._E0A", 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;
+    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:
+        warn_report("AC adapter: guest read unknown value.");
+        trace_acad_ioport_read_unknown();
+        return 0;
+    }
+}
+
+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(void)
+{
+    Object *o = object_resolve_path_type("", TYPE_AC_ADAPTER, NULL);
+    if (!o) {
+        return NULL;
+    }
+    return AC_ADAPTER_DEVICE(o);
+}
+
+void qmp_ac_adapter_set_state(bool connected, Error **errp)
+{
+    ACADState *s = find_acad_device();
+
+    if (!s) {
+        error_setg(errp, "No AC adapter device found");
+        return;
+    }
+
+    s->qmp_connected = connected;
+
+    Object *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();
+    AcAdapterInfo *ret;
+
+    if (!s) {
+        error_setg(errp, "No AC adapter device found");
+        return NULL;
+    }
+
+    ret = g_new0(AcAdapterInfo, 1);
+
+    if (s->use_qmp_control) {
+        ret->connected = s->qmp_connected;
+    } else {
+        acad_get_dynamic_status(s);
+        ret->connected = (s->state == AC_ADAPTER_ONLINE);
+    }
+
+    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 10379a7b2c..2b24951f28 100644
--- a/hw/acpi/meson.build
+++ b/hw/acpi/meson.build
@@ -32,6 +32,7 @@ if have_tpm
   acpi_ss.add(files('tpm.c'))
 endif
 acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c'))
+acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c'))
 system_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c', 'acpi_interface.c'))
 system_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_false: files('pci-bridge-stub.c'))
 system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss)
diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events
index dd3e815482..68ac6e9701 100644
--- a/hw/acpi/trace-events
+++ b/hw/acpi/trace-events
@@ -92,3 +92,8 @@ acpi_nvdimm_invalid_revision(uint32_t revision) "Revision 0x%" PRIx32 " is not s
 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
 battery_ioport_read_unknown(void) "Battery read unknown"
+
+# acad.c
+acad_realize(void) "AC adapter device realize entry"
+acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8
+acad_ioport_read_unknown(void) "AC adapter read unknown"
diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig
index 2c878fd112..baab382a2e 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..a4e7149488
--- /dev/null
+++ b/include/hw/acpi/acad.h
@@ -0,0 +1,27 @@
+/*
+ * QEMU emulated AC adapter device.
+ *
+ * Copyright (c) 2019 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_PATH_PROP             "sysfs_path"
+#define AC_ADAPTER_PROBE_STATE_INTERVAL  "probe_interval"
+
+#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 3064ef6734..588fbbd05f 100644
--- a/include/hw/acpi/acpi_dev_interface.h
+++ b/include/hw/acpi/acpi_dev_interface.h
@@ -14,6 +14,7 @@ typedef enum {
     ACPI_VMGENID_CHANGE_STATUS = 32,
     ACPI_POWER_DOWN_STATUS = 64,
     ACPI_BATTERY_CHANGE_STATUS = 128,
+    ACPI_AC_ADAPTER_CHANGE_STATUS = 1024,
 } AcpiEventStatusBits;
 
 #define TYPE_ACPI_DEVICE_IF "acpi-device-interface"
diff --git a/qapi/acpi.json b/qapi/acpi.json
index d1ad663bfd..52e151f0e6 100644
--- a/qapi/acpi.json
+++ b/qapi/acpi.json
@@ -215,3 +215,52 @@
 ##
 { '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: 10.2
+#
+# .. 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: 10.2
+##
+{ 'struct': 'AcAdapterInfo',
+  'data': { 'connected': 'bool' } }
+
+##
+# @query-ac-adapter:
+#
+# Query the current state of the emulated AC adapter device
+#
+# Returns: AC adapter connection state
+#
+# Since: 10.2
+#
+# .. qmp-example::
+#
+#     -> { "execute": "query-ac-adapter" }
+#     <- { "return": { "connected": true } }
+##
+{ 'command': 'query-ac-adapter',
+  'returns': 'AcAdapterInfo' }
-- 
2.50.1



  parent reply	other threads:[~2025-08-21 17:48 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-21 17:45 [PATCH v2 0/4] Introduce a battery, AC adapter, and lid button Leonid Bloch
2025-08-21 17:45 ` [PATCH v2 1/4] hw/acpi: Increase the number of possible ACPI interrupts Leonid Bloch
2025-08-26 12:47   ` Igor Mammedov
2025-08-21 17:45 ` [PATCH v2 2/4] hw/acpi: Introduce the QEMU Battery Leonid Bloch
2025-08-21 17:45 ` Leonid Bloch [this message]
2025-08-21 17:45 ` [PATCH v2 4/4] hw/acpi: Introduce the QEMU lid button Leonid Bloch
2025-08-21 18:03 ` [PATCH v2 0/4] Introduce a battery, AC adapter, and " 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=20250821174554.40607-4-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.