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>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Richard Henderson <richard.henderson@linaro.org>,
	Eduardo Habkost <ehabkost@redhat.com>,
	Marcel Apfelbaum <marcel.apfelbaum@gmail.com>
Cc: Leonid Bloch <lb.workbox@gmail.com>, qemu-devel@nongnu.org
Subject: [PATCH 4/4] hw/acpi: Introduce the QEMU lid button
Date: Wed, 20 Jan 2021 22:55:01 +0200	[thread overview]
Message-ID: <20210120205501.33918-5-lb.workbox@gmail.com> (raw)
In-Reply-To: <20210120205501.33918-1-lb.workbox@gmail.com>

The button device communicates the host's "lid button" state to the guest.
The probing of the host's lid button state occurs on timed intervals.
If a change of the host's lid button state is detected (open/closed)
an ACPI notification is sent to the guest, so it will be able to act
accordingly.

The time interval between the periodic probes is 2 s by default.
A property 'probe_interval' allows to modify this value. The value
is provided in milliseconds.

The host's lid button information is taken from:

/proc/acpi/button/lid/*/state

And this file is expected to be formatted as:
'state:      open' (if the lid is open)
or:
'state:      closed' (if the lid is closed)

These are based on the Linux 'button' driver.

If the above procfs path differs, or even if a "fake" host lid button
is to be provided, a 'procfs_path' property allows to override the
default path.

Usage example (default values):

'-device button'

Is the same as:

'-device button,procfs_path=/proc/acpi/button,probe_interval=2000'

Signed-off-by: Leonid Bloch <lb.workbox@gmail.com>
---
 MAINTAINERS                          |   5 +
 docs/specs/button.txt                |  35 +++
 hw/acpi/Kconfig                      |   4 +
 hw/acpi/button.c                     | 327 +++++++++++++++++++++++++++
 hw/acpi/meson.build                  |   1 +
 hw/acpi/trace-events                 |   5 +
 hw/i386/Kconfig                      |   1 +
 hw/i386/acpi-build.c                 |  29 +++
 include/hw/acpi/acpi_dev_interface.h |   1 +
 include/hw/acpi/button.h             |  35 +++
 10 files changed, 443 insertions(+)
 create mode 100644 docs/specs/button.txt
 create mode 100644 hw/acpi/button.c
 create mode 100644 include/hw/acpi/button.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 1a3fc1dd56..72bead7023 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2151,6 +2151,11 @@ M: Leonid Bloch <lb.workbox@gmail.com>
 S: Maintained
 F: hw/acpi/acad.*
 
+Button
+M: Leonid Bloch <lb.workbox@gmail.com>
+S: Maintained
+F: hw/acpi/button.*
+
 Subsystems
 ----------
 Audio
diff --git a/docs/specs/button.txt b/docs/specs/button.txt
new file mode 100644
index 0000000000..48e9f3388d
--- /dev/null
+++ b/docs/specs/button.txt
@@ -0,0 +1,35 @@
+BUTTON DEVICE
+=================
+
+The button device communicates the host's "lid button" state to the guest.
+The probing of the host's lid button state occurs on timed intervals.
+If a change of the host's lid button state is detected (open/closed)
+an ACPI notification is sent to the guest, so it will be able to act
+accordingly.
+
+The time interval between the periodic probes is 2 s by default.
+A property 'probe_interval' allows to modify this value. The value
+is provided in milliseconds.
+
+The host's lid button information is taken from:
+
+/proc/acpi/button/lid/*/state
+
+And this file is expected to be formatted as:
+'state:      open' (if the lid is open)
+or:
+'state:      closed' (if the lid is closed)
+
+These are based on the Linux 'button' driver.
+
+If the above procfs path differs, or even if a "fake" host lid button
+is to be provided, a 'procfs_path' property allows to override the
+default path.
+
+Usage example (default values):
+
+'-device button'
+
+Is the same as:
+
+'-device button,procfs_path=/proc/acpi/button,probe_interval=2000'
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
index d35331fbf7..0e78560f0f 100644
--- a/hw/acpi/Kconfig
+++ b/hw/acpi/Kconfig
@@ -49,4 +49,8 @@ config AC_ADAPTER
     bool
     depends on ACPI
 
+config BUTTON
+    bool
+    depends on ACPI
+
 config ACPI_HW_REDUCED
diff --git a/hw/acpi/button.c b/hw/acpi/button.c
new file mode 100644
index 0000000000..edca46ce6d
--- /dev/null
+++ b/hw/acpi/button.c
@@ -0,0 +1,327 @@
+/*
+ * QEMU emulated lid button 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>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory for details.
+ *
+ */
+
+#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/button.h"
+
+#define BUTTON_DEVICE(obj) OBJECT_CHECK(BUTTONState, (obj), \
+                                        TYPE_BUTTON)
+
+#define BUTTON_STA_ADDR            0
+
+#define PROCFS_PATH                "/proc/acpi/button"
+#define LID_DIR                    "lid"
+#define LID_STATE_FILE             "state"
+#define MIN_BUTTON_PROBE_INTERVAL  10  /* ms */
+#define MAX_ALLOWED_LINE_LENGTH    32  /* For convenience when comparing */
+
+enum {
+    LID_CLOSED = 0,
+    LID_OPEN = 1,
+};
+
+static const char *lid_state[] = { "closed", "open" };
+
+typedef struct BUTTONState {
+    ISADevice dev;
+    MemoryRegion io;
+    uint16_t ioport;
+    uint8_t lid_state;
+
+    QEMUTimer *probe_state_timer;
+    uint64_t probe_state_interval;
+
+    char *button_path;
+    char lid_dir[MAX_ALLOWED_LINE_LENGTH];
+} BUTTONState;
+
+static inline bool button_file_accessible(char *path, const char *dir,
+                                          char *subdir, const char *file)
+{
+    char full_path[PATH_MAX];
+    int path_len;
+
+    path_len = snprintf(full_path, PATH_MAX, "%s/%s/%s/%s", path, dir, subdir,
+                        file);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        return false;
+    }
+
+    if (access(full_path, R_OK) == 0) {
+        return true;
+    }
+    return false;
+}
+
+static void button_get_lid_state(BUTTONState *s)
+{
+    char file_path[PATH_MAX];
+    int path_len;
+    char line[MAX_ALLOWED_LINE_LENGTH];
+    FILE *ff;
+
+    path_len = snprintf(file_path, PATH_MAX, "%s/%s/%s/%s", s->button_path,
+                        LID_DIR, s->lid_dir, LID_STATE_FILE);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        warn_report("Could not read the lid state.");
+        return;
+    }
+
+    ff = fopen(file_path, "r");
+    if (ff == NULL) {
+        warn_report("Could not read the lid state.");
+        return;
+    }
+
+    if (fgets(line, MAX_ALLOWED_LINE_LENGTH, ff) == NULL) {
+        warn_report("Lid state unreadable.");
+    } else {
+        if (strstr(line, lid_state[LID_OPEN]) != NULL) {
+            s->lid_state = LID_OPEN;
+        } else if (strstr(line, lid_state[LID_CLOSED]) != NULL) {
+            s->lid_state = LID_CLOSED;
+        } else {
+            warn_report("Lid state undetermined.");
+        }
+    }
+
+    fclose(ff);
+}
+
+static void button_get_dynamic_status(BUTTONState *s)
+{
+    trace_button_get_dynamic_status();
+
+    button_get_lid_state(s);
+}
+
+static void button_probe_state(void *opaque)
+{
+    BUTTONState *s = opaque;
+
+    uint8_t lid_state_before = s->lid_state;
+
+    button_get_dynamic_status(s);
+
+    if (lid_state_before != s->lid_state) {
+        Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
+        acpi_send_event(DEVICE(obj), ACPI_BUTTON_CHANGE_STATUS);
+    }
+    timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+              s->probe_state_interval);
+}
+
+static void button_probe_state_timer_init(BUTTONState *s)
+{
+    if (s->probe_state_interval > 0) {
+        s->probe_state_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+                                            button_probe_state, s);
+        timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+                  s->probe_state_interval);
+    }
+}
+
+static inline bool button_verify_lid_procfs(char *path, char *lid_subdir)
+{
+    return button_file_accessible(path, LID_DIR, lid_subdir, LID_STATE_FILE);
+}
+
+static bool button_get_lid_dir(BUTTONState *s, char *path)
+{
+    DIR *dir;
+    char lid_path[PATH_MAX];
+    int path_len;
+    struct dirent *ent;
+
+    path_len = snprintf(lid_path, PATH_MAX, "%s/%s", path, LID_DIR);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        return false;
+    }
+
+    dir = opendir(lid_path);
+    if (dir == NULL) {
+        return false;
+    }
+
+    ent = readdir(dir);
+    while (ent != NULL) {
+        if (ent->d_name[0] != '.') {
+            if (button_verify_lid_procfs(path, ent->d_name)) {
+                path_len = snprintf(s->lid_dir, strlen(ent->d_name) + 1, "%s",
+                                    ent->d_name);
+                if (path_len < 0 || path_len > strlen(ent->d_name)) {
+                    return false;
+                }
+                closedir(dir);
+                return true;
+            }
+        }
+        ent = readdir(dir);
+    }
+    closedir(dir);
+    return false;
+}
+
+static bool get_button_path(DeviceState *dev)
+{
+    BUTTONState *s = BUTTON_DEVICE(dev);
+    char procfs_path[PATH_MAX];
+    int path_len;
+
+    if (s->button_path) {
+        path_len = snprintf(procfs_path, strlen(s->button_path) + 1, "%s",
+                            s->button_path);
+        if (path_len < 0 || path_len > strlen(s->button_path)) {
+            return false;
+        }
+    } else {
+        path_len = snprintf(procfs_path, sizeof(PROCFS_PATH), "%s",
+                            PROCFS_PATH);
+        if (path_len < 0 || path_len >= sizeof(PROCFS_PATH)) {
+            return false;
+        }
+    }
+
+    if (button_get_lid_dir(s, procfs_path)) {
+        qdev_prop_set_string(dev, BUTTON_PATH_PROP, procfs_path);
+        return true;
+    }
+
+    return false;
+}
+
+static void button_realize(DeviceState *dev, Error **errp)
+{
+    ISADevice *d = ISA_DEVICE(dev);
+    BUTTONState *s = BUTTON_DEVICE(dev);
+    FWCfgState *fw_cfg = fw_cfg_find();
+    uint16_t *button_port;
+    char err_details[32] = {};
+
+    trace_button_realize();
+
+    if (s->probe_state_interval < MIN_BUTTON_PROBE_INTERVAL) {
+        error_setg(errp, "'probe_state_interval' must be greater than %d ms",
+                   MIN_BUTTON_PROBE_INTERVAL);
+        return;
+    }
+
+    if (!s->button_path) {
+        strcpy(err_details, " Try using 'procfs_path='");
+    }
+
+    if (!get_button_path(dev)) {
+        error_setg(errp, "Button procfs path not found or unreadable.%s",
+                   err_details);
+        return;
+    }
+
+    isa_register_ioport(d, &s->io, s->ioport);
+
+    button_probe_state_timer_init(s);
+
+    if (!fw_cfg) {
+        return;
+    }
+
+    button_port = g_malloc(sizeof(*button_port));
+    *button_port = cpu_to_le16(s->ioport);
+    fw_cfg_add_file(fw_cfg, "etc/button-port", button_port,
+                    sizeof(*button_port));
+}
+
+static Property button_device_properties[] = {
+    DEFINE_PROP_UINT16(BUTTON_IOPORT_PROP, BUTTONState, ioport, 0x53d),
+    DEFINE_PROP_UINT64(BUTTON_PROBE_STATE_INTERVAL, BUTTONState,
+                       probe_state_interval, 2000),
+    DEFINE_PROP_STRING(BUTTON_PATH_PROP, BUTTONState, button_path),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription button_vmstate = {
+    .name = "button",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT16(ioport, BUTTONState),
+        VMSTATE_UINT64(probe_state_interval, BUTTONState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void button_class_init(ObjectClass *class, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+
+    dc->realize = button_realize;
+    device_class_set_props(dc, button_device_properties);
+    dc->vmsd = &button_vmstate;
+}
+
+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:
+        warn_report("Button: guest read unknown value.");
+        trace_button_ioport_read_unknown();
+        return 0;
+    }
+}
+
+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,
+};
+
+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 2ae756c6ef..0f66766dd3 100644
--- a/hw/acpi/meson.build
+++ b/hw/acpi/meson.build
@@ -21,6 +21,7 @@ acpi_ss.add(when: 'CONFIG_PC', if_false: files('acpi-x86-stub.c'))
 acpi_ss.add(when: 'CONFIG_TPM', if_true: files('tpm.c'))
 acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c'))
 acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c'))
+acpi_ss.add(when: 'CONFIG_BUTTON', if_true: files('button.c'))
 softmmu_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-build-stub.c'))
 softmmu_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss)
 softmmu_ss.add(when: 'CONFIG_ALL', if_true: files('acpi-stub.c', 'aml-build-stub.c',
diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events
index 496a282cdc..7554f0c8b6 100644
--- a/hw/acpi/trace-events
+++ b/hw/acpi/trace-events
@@ -63,3 +63,8 @@ battery_ioport_read_unknown(void) "Battery read unknown"
 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"
+
+# button.c
+button_realize(void) "Button device realize entry"
+button_get_dynamic_status(void) "Button read dynamic status entry"
+button_ioport_read_unknown(void) "Button read unknown"
diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig
index 483b3ccec8..2a09e6f742 100644
--- a/hw/i386/Kconfig
+++ b/hw/i386/Kconfig
@@ -25,6 +25,7 @@ config PC
     imply VIRTIO_VGA
     imply BATTERY
     imply AC_ADAPTER
+    imply BUTTON
     select FDC
     select I8259
     select I8254
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index 96dbeed22f..ee5537a1b4 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -37,6 +37,7 @@
 #include "hw/acpi/cpu.h"
 #include "hw/acpi/battery.h"
 #include "hw/acpi/acad.h"
+#include "hw/acpi/button.h"
 #include "hw/nvram/fw_cfg.h"
 #include "hw/acpi/bios-linker-loader.h"
 #include "hw/isa/isa.h"
@@ -116,6 +117,7 @@ typedef struct AcpiMiscInfo {
     uint16_t pvpanic_port;
     uint16_t battery_port;
     uint16_t acad_port;
+    uint16_t button_port;
     uint16_t applesmc_io_base;
 } AcpiMiscInfo;
 
@@ -283,6 +285,7 @@ static void acpi_get_misc_info(AcpiMiscInfo *info)
     info->pvpanic_port = pvpanic_port();
     info->battery_port = battery_port();
     info->acad_port = acad_port();
+    info->button_port = button_port();
     info->applesmc_io_base = applesmc_port();
 }
 
@@ -1780,6 +1783,32 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
         aml_append(dsdt, method);
     }
 
+    if (misc->button_port) {
+        Aml *button_state  = aml_local(0);
+
+        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(misc->button_port),
+                                             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);
+
+        /* Status Change */
+        method = aml_method("\\_GPE._E0B", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_notify(aml_name("\\_SB.LID0"), aml_int(0x80)));
+        aml_append(dsdt, method);
+    }
+
     aml_append(dsdt, sb_scope);
 
     /* copy AML table into ACPI tables blob and patch header there */
diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h
index b577a4db07..5ba37a42d2 100644
--- a/include/hw/acpi/acpi_dev_interface.h
+++ b/include/hw/acpi/acpi_dev_interface.h
@@ -16,6 +16,7 @@ typedef enum {
     ACPI_POWER_DOWN_STATUS = 64,
     ACPI_BATTERY_CHANGE_STATUS = 128,
     ACPI_AC_ADAPTER_CHANGE_STATUS = 1024,
+    ACPI_BUTTON_CHANGE_STATUS = 2048,
 } 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..6da17d7cee
--- /dev/null
+++ b/include/hw/acpi/button.h
@@ -0,0 +1,35 @@
+/*
+ * QEMU emulated button 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>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory for details.
+ *
+ */
+
+#ifndef HW_ACPI_BUTTON_H
+#define HW_ACPI_BUTTON_H
+
+#define TYPE_BUTTON                  "button"
+#define BUTTON_IOPORT_PROP           "ioport"
+#define BUTTON_PATH_PROP             "procfs_path"
+#define BUTTON_PROBE_STATE_INTERVAL  "probe_interval"
+
+#define BUTTON_LEN                   1
+
+static inline uint16_t button_port(void)
+{
+    Object *o = object_resolve_path_type("", TYPE_BUTTON, NULL);
+    if (!o) {
+        return 0;
+    }
+    return object_property_get_uint(o, BUTTON_IOPORT_PROP, NULL);
+}
+
+#endif
-- 
2.30.0



  parent reply	other threads:[~2021-01-20 20:57 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-01-20 20:54 [PATCH 0/4] Introduce a battery, AC adapter, and lid button Leonid Bloch
2021-01-20 20:54 ` [PATCH 1/4] hw/acpi: Increase the number of possible ACPI interrupts Leonid Bloch
2021-01-20 20:54 ` [PATCH 2/4] hw/acpi: Introduce the QEMU Battery Leonid Bloch
2021-01-20 20:55 ` [PATCH 3/4] hw/acpi: Introduce the QEMU AC adapter Leonid Bloch
2021-01-20 20:55 ` Leonid Bloch [this message]
2021-01-20 21:52 ` [PATCH 0/4] Introduce a battery, AC adapter, and lid button Philippe Mathieu-Daudé
2021-01-21  5:38   ` Leonid Bloch
2021-01-26 14:39     ` Michael S. Tsirkin
2021-01-28  6:02       ` 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=20210120205501.33918-5-lb.workbox@gmail.com \
    --to=lb.workbox@gmail.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.