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 v4 7/8] hw/acpi: Introduce the QEMU lid button
Date: Tue, 26 May 2026 07:29:26 +0300	[thread overview]
Message-ID: <20260526042928.9203-8-lb.workbox@gmail.com> (raw)
In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com>

The lid button device communicates laptop lid state to the guest via ACPI.
Lid state is controlled programmatically via QMP commands for consistent
behavior across environments.

The device implements the ACPI_DEV_AML_IF interface to generate its
own AML code, placing the LID0 device directly under \_SB scope.

QMP commands:
- lid-button-set-state: Set lid open/closed state
- query-lid-button: Query current lid state

Signed-off-by: Leonid Bloch <lb.workbox@gmail.com>
---
 MAINTAINERS                          |   2 +
 hw/acpi/Kconfig                      |   4 +
 hw/acpi/button-stub.c                |  20 +++
 hw/acpi/button.c                     | 227 +++++++++++++++++++++++++++
 hw/acpi/meson.build                  |   2 +
 hw/acpi/trace-events                 |   4 +
 hw/i386/Kconfig                      |   1 +
 include/hw/acpi/acpi_dev_interface.h |   1 +
 include/hw/acpi/button.h             |  23 +++
 qapi/acpi.json                       |  47 ++++++
 10 files changed, 331 insertions(+)
 create mode 100644 hw/acpi/button-stub.c
 create mode 100644 hw/acpi/button.c
 create mode 100644 include/hw/acpi/button.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 42179aba95..1f8f3e247e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3054,6 +3054,8 @@ Button
 M: Leonid Bloch <lb.workbox@gmail.com>
 S: Maintained
 F: docs/specs/button.rst
+F: hw/acpi/button*
+F: include/hw/acpi/button.h
 
 Subsystems
 ----------
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
index 889ace2dfa..0d5f885095 100644
--- a/hw/acpi/Kconfig
+++ b/hw/acpi/Kconfig
@@ -78,6 +78,10 @@ config AC_ADAPTER
     bool
     depends on ACPI
 
+config BUTTON
+    bool
+    depends on ACPI
+
 config ACPI_HW_REDUCED
     bool
     select ACPI
diff --git a/hw/acpi/button-stub.c b/hw/acpi/button-stub.c
new file mode 100644
index 0000000000..0ae478055b
--- /dev/null
+++ b/hw/acpi/button-stub.c
@@ -0,0 +1,20 @@
+/*
+ * QEMU emulated lid button 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_lid_button_set_state(bool open, Error **errp)
+{
+    error_setg(errp, "No lid button device found");
+}
+
+LidButtonInfo *qmp_query_lid_button(Error **errp)
+{
+    error_setg(errp, "No lid button device found");
+    return NULL;
+}
diff --git a/hw/acpi/button.c b/hw/acpi/button.c
new file mode 100644
index 0000000000..126969a5e7
--- /dev/null
+++ b/hw/acpi/button.c
@@ -0,0 +1,227 @@
+/*
+ * QEMU emulated lid button 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/button.h"
+
+#define BUTTON_DEVICE(obj) OBJECT_CHECK(ButtonState, (obj), \
+                                        TYPE_BUTTON)
+
+#define BUTTON_STA_ADDR            0
+
+enum {
+    LID_CLOSED = 0,
+    LID_OPEN = 1,
+};
+
+typedef struct ButtonState {
+    ISADevice dev;
+    MemoryRegion io;
+    uint16_t ioport;
+    uint8_t lid_state;
+    bool qmp_lid_open;
+} ButtonState;
+
+static void button_get_dynamic_status(ButtonState *s)
+{
+    trace_button_get_dynamic_status();
+
+    s->lid_state = s->qmp_lid_open ? LID_OPEN : LID_CLOSED;
+}
+
+static void button_realize(DeviceState *dev, Error **errp)
+{
+    ISADevice *d = ISA_DEVICE(dev);
+    ButtonState *s = BUTTON_DEVICE(dev);
+    bool ambiguous;
+
+    trace_button_realize();
+
+    object_resolve_path_type("", TYPE_BUTTON, &ambiguous);
+    if (ambiguous) {
+        error_setg(errp, "at most one %s device is permitted", TYPE_BUTTON);
+        return;
+    }
+
+    /* Initialize lid to open by default */
+    s->qmp_lid_open = true;
+
+    isa_register_ioport(d, &s->io, s->ioport);
+}
+
+static const Property button_device_properties[] = {
+    DEFINE_PROP_UINT16(BUTTON_IOPORT_PROP, ButtonState, ioport, 0x53d),
+};
+
+static const VMStateDescription button_vmstate = {
+    .name = "button",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_BOOL(qmp_lid_open, ButtonState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void build_button_aml(AcpiDevAmlIf *adev, Aml *scope)
+{
+    Aml *dev, *field, *method;
+    Aml *button_state;
+    Aml *sb_scope;
+    ButtonState *s = BUTTON_DEVICE(adev);
+
+    button_state = aml_local(0);
+
+    sb_scope = aml_scope("\\_SB");
+    dev = aml_device("LID0");
+    aml_append(dev, aml_name_decl("_HID", aml_string("PNP0C0D")));
+
+    aml_append(dev, aml_operation_region("LSTA", AML_SYSTEM_IO,
+                                         aml_int(s->ioport),
+                                         BUTTON_LEN));
+    field = aml_field("LSTA", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
+    aml_append(field, aml_named_field("LIDS", 8));
+    aml_append(dev, field);
+
+    method = aml_method("_LID", 0, AML_NOTSERIALIZED);
+    aml_append(method, aml_store(aml_name("LIDS"), button_state));
+    aml_append(method, aml_return(button_state));
+    aml_append(dev, method);
+
+    aml_append(sb_scope, dev);
+    aml_append(scope, sb_scope);
+
+    /* Status Change */
+    method = aml_method("\\_GPE._E0C", 0, AML_NOTSERIALIZED);
+    aml_append(method, aml_notify(aml_name("\\_SB.LID0"), aml_int(0x80)));
+    aml_append(scope, method);
+}
+
+static void button_class_init(ObjectClass *class, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+    AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(class);
+
+    dc->realize = button_realize;
+    dc->hotpluggable = false;
+    device_class_set_props(dc, button_device_properties);
+    dc->vmsd = &button_vmstate;
+    adevc->build_dev_aml = build_button_aml;
+}
+
+static uint64_t button_ioport_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ButtonState *s = opaque;
+
+    button_get_dynamic_status(s);
+
+    switch (addr) {
+    case BUTTON_STA_ADDR:
+        return s->lid_state;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static const MemoryRegionOps button_ops = {
+    .read = button_ioport_read,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 1,
+    },
+};
+
+static void button_instance_init(Object *obj)
+{
+    ButtonState *s = BUTTON_DEVICE(obj);
+
+    memory_region_init_io(&s->io, obj, &button_ops, s, "button",
+                          BUTTON_LEN);
+}
+
+static const TypeInfo button_info = {
+    .name          = TYPE_BUTTON,
+    .parent        = TYPE_ISA_DEVICE,
+    .instance_size = sizeof(ButtonState),
+    .class_init    = button_class_init,
+    .instance_init = button_instance_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_ACPI_DEV_AML_IF },
+        { },
+    },
+};
+
+static ButtonState *find_button_device(Error **errp)
+{
+    bool ambiguous;
+    Object *o = object_resolve_path_type("", TYPE_BUTTON, &ambiguous);
+
+    if (!o) {
+        error_setg(errp, "No lid button device found");
+        return NULL;
+    }
+    if (ambiguous) {
+        error_setg(errp, "More than one lid button device present");
+        return NULL;
+    }
+    return BUTTON_DEVICE(o);
+}
+
+void qmp_lid_button_set_state(bool open, Error **errp)
+{
+    ButtonState *s = find_button_device(errp);
+    Object *obj;
+
+    if (!s) {
+        return;
+    }
+
+    s->qmp_lid_open = open;
+
+    obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
+    if (obj) {
+        acpi_send_event(DEVICE(obj), ACPI_BUTTON_CHANGE_STATUS);
+    }
+}
+
+LidButtonInfo *qmp_query_lid_button(Error **errp)
+{
+    ButtonState *s = find_button_device(errp);
+    LidButtonInfo *ret;
+
+    if (!s) {
+        return NULL;
+    }
+
+    ret = g_new0(LidButtonInfo, 1);
+    ret->open = s->qmp_lid_open;
+
+    return ret;
+}
+
+static void button_register_types(void)
+{
+    type_register_static(&button_info);
+}
+
+type_init(button_register_types)
diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build
index 731e9477e3..ab0acd2521 100644
--- a/hw/acpi/meson.build
+++ b/hw/acpi/meson.build
@@ -39,6 +39,8 @@ 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'))
+acpi_ss.add(when: 'CONFIG_BUTTON', if_true: files('button.c'))
+stub_ss.add(files('button-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 67602000f3..13728637ce 100644
--- a/hw/acpi/trace-events
+++ b/hw/acpi/trace-events
@@ -95,3 +95,7 @@ battery_get_dynamic_status(uint32_t state, uint32_t rate, uint32_t charge) "Batt
 # acad.c
 acad_realize(void) "AC adapter device realize entry"
 acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8
+
+# button.c
+button_realize(void) "Button device realize entry"
+button_get_dynamic_status(void) "Button read dynamic status entry"
diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig
index 06f21cadb7..35c65d3f37 100644
--- a/hw/i386/Kconfig
+++ b/hw/i386/Kconfig
@@ -41,6 +41,7 @@ config PC
     imply FDC_ISA
     imply BATTERY
     imply AC_ADAPTER
+    imply BUTTON
     select I8259
     select I8254
     select PCKBD
diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h
index 00566c56a7..35005d7ad0 100644
--- a/include/hw/acpi/acpi_dev_interface.h
+++ b/include/hw/acpi/acpi_dev_interface.h
@@ -16,6 +16,7 @@ typedef enum {
     ACPI_GENERIC_ERROR = 128,
     ACPI_BATTERY_CHANGE_STATUS = 256,
     ACPI_AC_ADAPTER_CHANGE_STATUS = 2048,
+    ACPI_BUTTON_CHANGE_STATUS = 4096,
 } AcpiEventStatusBits;
 
 #define TYPE_ACPI_DEVICE_IF "acpi-device-interface"
diff --git a/include/hw/acpi/button.h b/include/hw/acpi/button.h
new file mode 100644
index 0000000000..d0e2d33231
--- /dev/null
+++ b/include/hw/acpi/button.h
@@ -0,0 +1,23 @@
+/*
+ * QEMU emulated button 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_BUTTON_H
+#define HW_ACPI_BUTTON_H
+
+#define TYPE_BUTTON                  "button"
+#define BUTTON_IOPORT_PROP           "ioport"
+
+#define BUTTON_LEN                   1
+
+#endif
diff --git a/qapi/acpi.json b/qapi/acpi.json
index 025b5d8eaa..e0534e3657 100644
--- a/qapi/acpi.json
+++ b/qapi/acpi.json
@@ -260,3 +260,50 @@
 ##
 { 'command': 'query-ac-adapter',
   'returns': 'AcAdapterInfo' }
+
+##
+# @lid-button-set-state:
+#
+# Set the state of the emulated laptop lid button device
+#
+# @open: whether the lid is open
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+#     -> { "execute": "lid-button-set-state",
+#          "arguments": { "open": true } }
+#     <- { "return": {} }
+##
+{ 'command': 'lid-button-set-state',
+  'data': { 'open': 'bool' } }
+
+##
+# @LidButtonInfo:
+#
+# Lid button state information
+#
+# @open: whether the lid is open
+#
+# Since: 11.1
+##
+{ 'struct': 'LidButtonInfo',
+  'data': { 'open': 'bool' } }
+
+##
+# @query-lid-button:
+#
+# Query the current state of the emulated laptop lid button device
+#
+# Returns: lid button state
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+#     -> { "execute": "query-lid-button" }
+#     <- { "return": { "open": true } }
+##
+{ 'command': 'query-lid-button',
+  'returns': 'LidButtonInfo' }
-- 
2.54.0



  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 ` [PATCH v4 5/8] hw/acpi: Introduce the QEMU AC adapter Leonid Bloch
2026-06-02  5:51   ` 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 ` Leonid Bloch [this message]
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-8-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.