All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] Introduce a battery, AC adapter, and lid button
@ 2021-01-20 20:54 Leonid Bloch
  2021-01-20 20:54 ` [PATCH 1/4] hw/acpi: Increase the number of possible ACPI interrupts Leonid Bloch
                   ` (4 more replies)
  0 siblings, 5 replies; 9+ messages in thread
From: Leonid Bloch @ 2021-01-20 20:54 UTC (permalink / raw)
  To: Michael S . Tsirkin, Igor Mammedov, Paolo Bonzini,
	Richard Henderson, Eduardo Habkost, Marcel Apfelbaum
  Cc: Leonid Bloch, qemu-devel

This series introduces the following ACPI devices:

* Battery
* AC adapter
* Laptop lid button

When running QEMU on a laptop, these paravirtualized devices reflect the
state of these physical devices onto the guest. This functionality is
relevant not only for laptops, but also for any other device which has e.g.
a battery. This even allows to insert a ``fake'' battery to the
guest, in a form of a file which emulates the behavior of the actual
battery in sysfs. A possible use case for such a ``fake'' battery can be
limiting the budget of VM usage to a subscriber, in a naturally-visible way.
But of course, the main purpose here is addressing the desktop users.

This series was tested with Windows and (desktop) Linux guests, on which
indeed the battery icon appears in the corresponding state (full,
charging, discharging, time remaining to empty, etc.) and the AC adapter
plugging/unplugging behaves as expected. So is the laptop lid button.

For the ease of review, these commits are also available here:
https://github.com/blochl/qemu/pull/1/commits


Thanks,
Leonid.

Leonid Bloch (4):
  hw/acpi: Increase the number of possible ACPI interrupts
  hw/acpi: Introduce the QEMU Battery
  hw/acpi: Introduce the QEMU AC adapter
  hw/acpi: Introduce the QEMU lid button

 MAINTAINERS                          |  15 +
 docs/specs/acad.txt                  |  24 ++
 docs/specs/battery.txt               |  23 ++
 docs/specs/button.txt                |  35 ++
 hw/acpi/Kconfig                      |  12 +
 hw/acpi/acad.c                       | 318 +++++++++++++++++
 hw/acpi/battery.c                    | 512 +++++++++++++++++++++++++++
 hw/acpi/button.c                     | 327 +++++++++++++++++
 hw/acpi/core.c                       |  17 +-
 hw/acpi/meson.build                  |   3 +
 hw/acpi/trace-events                 |  15 +
 hw/i386/Kconfig                      |   3 +
 hw/i386/acpi-build.c                 | 178 ++++++++++
 include/hw/acpi/acad.h               |  37 ++
 include/hw/acpi/acpi_dev_interface.h |   3 +
 include/hw/acpi/battery.h            |  43 +++
 include/hw/acpi/button.h             |  35 ++
 17 files changed, 1598 insertions(+), 2 deletions(-)
 create mode 100644 docs/specs/acad.txt
 create mode 100644 docs/specs/battery.txt
 create mode 100644 docs/specs/button.txt
 create mode 100644 hw/acpi/acad.c
 create mode 100644 hw/acpi/battery.c
 create mode 100644 hw/acpi/button.c
 create mode 100644 include/hw/acpi/acad.h
 create mode 100644 include/hw/acpi/battery.h
 create mode 100644 include/hw/acpi/button.h

-- 
2.30.0



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/4] hw/acpi: Increase the number of possible ACPI interrupts
  2021-01-20 20:54 [PATCH 0/4] Introduce a battery, AC adapter, and lid button Leonid Bloch
@ 2021-01-20 20:54 ` Leonid Bloch
  2021-01-20 20:54 ` [PATCH 2/4] hw/acpi: Introduce the QEMU Battery Leonid Bloch
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 9+ messages in thread
From: Leonid Bloch @ 2021-01-20 20:54 UTC (permalink / raw)
  To: Michael S . Tsirkin, Igor Mammedov, Paolo Bonzini,
	Richard Henderson, Eduardo Habkost, Marcel Apfelbaum
  Cc: Leonid Bloch, qemu-devel

Increase the number of possible ACPI interrupts from 8, to the maximum
available: 64 by default.

Signed-off-by: Leonid Bloch <lb.workbox@gmail.com>
---
 hw/acpi/core.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/hw/acpi/core.c b/hw/acpi/core.c
index 7170bff657..71ba7c17b8 100644
--- a/hw/acpi/core.c
+++ b/hw/acpi/core.c
@@ -706,19 +706,32 @@ uint32_t acpi_gpe_ioport_readb(ACPIREGS *ar, uint32_t addr)
 void acpi_send_gpe_event(ACPIREGS *ar, qemu_irq irq,
                          AcpiEventStatusBits status)
 {
-    ar->gpe.sts[0] |= status;
+    int i;
+
+    AcpiEventStatusBits st = status;
+    for (i = 0; i < ar->gpe.len / 2; i++) {
+        ar->gpe.sts[i] |= st;
+        st >>= (sizeof(ar->gpe.sts[0]) * CHAR_BIT);
+    }
+
     acpi_update_sci(ar, irq);
 }
 
 void acpi_update_sci(ACPIREGS *regs, qemu_irq irq)
 {
     int sci_level, pm1a_sts;
+    uint64_t gpe_sci = 0;
+    int i;
 
     pm1a_sts = acpi_pm1_evt_get_sts(regs);
 
+    for (i = 0; i < regs->gpe.len / 2; i++) {
+        gpe_sci |= (regs->gpe.sts[i] & regs->gpe.en[i]);
+    }
+
     sci_level = ((pm1a_sts &
                   regs->pm1.evt.en & ACPI_BITMASK_PM1_COMMON_ENABLED) != 0) ||
-                ((regs->gpe.sts[0] & regs->gpe.en[0]) != 0);
+                (gpe_sci != 0);
 
     qemu_set_irq(irq, sci_level);
 
-- 
2.30.0



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/4] hw/acpi: Introduce the QEMU Battery
  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 ` Leonid Bloch
  2021-01-20 20:55 ` [PATCH 3/4] hw/acpi: Introduce the QEMU AC adapter Leonid Bloch
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 9+ messages in thread
From: Leonid Bloch @ 2021-01-20 20:54 UTC (permalink / raw)
  To: Michael S . Tsirkin, Igor Mammedov, Paolo Bonzini,
	Richard Henderson, Eduardo Habkost, Marcel Apfelbaum
  Cc: Leonid Bloch, qemu-devel

The battery device communicates the host's battery state to the guest.
The probing of the host's battery state occurs on guest ACPI requests,
as well as on timed intervals. If a change of the host's battery state is
detected on one of the timed probes (charging/discharging, rate, charge)
an ACPI notification is sent to the guest, so it will be able to update
its battery status accordingly.

The time interval between periodic probes is 2 seconds by default.
A property 'probe_interval' allows to modify this value. The value should
be provided in milliseconds. A zero value disables the periodic probes,
and makes the battery state updates occur on guest requests only.

The host's battery information is taken from the sysfs battery data,
located in:

/sys/class/power_supply/[device of type "Battery"]

If the sysfs path differs, a different battery needs to be probed, or
even if a "fake" host battery is to be provided, a 'sysfs_path' property
allows to override the default one.

Signed-off-by: Leonid Bloch <lb.workbox@gmail.com>
Signed-off-by: Marcel Apfelbaum <marcel.apfelbaum@gmail.com>
---
 MAINTAINERS                          |   5 +
 docs/specs/battery.txt               |  23 ++
 hw/acpi/Kconfig                      |   4 +
 hw/acpi/battery.c                    | 512 +++++++++++++++++++++++++++
 hw/acpi/meson.build                  |   1 +
 hw/acpi/trace-events                 |   5 +
 hw/i386/Kconfig                      |   1 +
 hw/i386/acpi-build.c                 |  97 +++++
 include/hw/acpi/acpi_dev_interface.h |   1 +
 include/hw/acpi/battery.h            |  43 +++
 10 files changed, 692 insertions(+)
 create mode 100644 docs/specs/battery.txt
 create mode 100644 hw/acpi/battery.c
 create mode 100644 include/hw/acpi/battery.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 3216387521..33eea28c22 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2141,6 +2141,11 @@ F: net/can/*
 F: hw/net/can/*
 F: include/net/can_*.h
 
+Battery
+M: Leonid Bloch <lb.workbox@gmail.com>
+S: Maintained
+F: hw/acpi/battery.*
+
 Subsystems
 ----------
 Audio
diff --git a/docs/specs/battery.txt b/docs/specs/battery.txt
new file mode 100644
index 0000000000..e90324ac03
--- /dev/null
+++ b/docs/specs/battery.txt
@@ -0,0 +1,23 @@
+BATTERY DEVICE
+==============
+
+The battery device communicates the host's battery state to the guest.
+The probing of the host's battery state occurs on guest ACPI requests,
+as well as on timed intervals. If a change of the host's battery state is
+detected on one of the timed probes (charging/discharging, rate, charge)
+an ACPI notification is sent to the guest, so it will be able to
+update its battery status accordingly.
+
+The time interval between periodic probes is 2 s by default. A property
+'probe_interval' allows to modify this value. The value should be
+provided in milliseconds. A zero value disables the periodic probes,
+and makes the battery state updates occur on guest requests only.
+
+The host's battery information is taken from the sysfs battery data,
+located in:
+
+/sys/class/power_supply/[device of type "Battery"]
+
+If the sysfs path differs, a different battery needs to be probed,
+or even if a "fake" host battery is to be provided, a 'sysfs_path'
+property allows to override the default one.
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
index 1932f66af8..6b4c41037a 100644
--- a/hw/acpi/Kconfig
+++ b/hw/acpi/Kconfig
@@ -41,4 +41,8 @@ config ACPI_VMGENID
     default y
     depends on PC
 
+config BATTERY
+    bool
+    depends on ACPI
+
 config ACPI_HW_REDUCED
diff --git a/hw/acpi/battery.c b/hw/acpi/battery.c
new file mode 100644
index 0000000000..afd82594b1
--- /dev/null
+++ b/hw/acpi/battery.c
@@ -0,0 +1,512 @@
+/*
+ * QEMU emulated battery 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/battery.h"
+
+#define BATTERY_DEVICE(obj) OBJECT_CHECK(BatteryState, (obj), TYPE_BATTERY)
+
+#define BATTERY_DISCHARGING  1
+#define BATTERY_CHARGING     2
+
+#define SYSFS_PATH       "/sys/class/power_supply"
+#define BATTERY_TYPE     "Battery"
+
+#define MAX_ALLOWED_STATE_LENGTH  32  /* For convinience when comparing */
+
+#define NORMALIZE_BY_FULL(val, full) \
+    ((full == 0) ? BATTERY_VAL_UNKNOWN \
+     : (uint32_t)(val * BATTERY_FULL_CAP / full))
+
+typedef union bat_metric {
+    uint32_t val;
+    uint8_t acc[4];
+} bat_metric;
+
+typedef struct BatteryState {
+    ISADevice dev;
+    MemoryRegion io;
+    uint16_t ioport;
+    bat_metric state;
+    bat_metric rate;
+    bat_metric charge;
+    uint32_t charge_full;
+    int units;  /* 0 - mWh, 1 - mAh */
+
+    QEMUTimer *probe_state_timer;
+    uint64_t probe_state_interval;
+
+    char *bat_path;
+} BatteryState;
+
+/* Access addresses */
+enum acc_addr {
+    bsta_addr0, bsta_addr1, bsta_addr2, bsta_addr3,
+    brte_addr0, brte_addr1, brte_addr2, brte_addr3,
+    bcrg_addr0, bcrg_addr1, bcrg_addr2, bcrg_addr3
+};
+
+/* Files used when the units are:      mWh             mAh      */
+static const char *full_file[] = { "energy_full", "charge_full" };
+static const char *now_file[]  = { "energy_now",  "charge_now"  };
+static const char *rate_file[] = { "power_now",   "current_now" };
+
+static const char *stat_file = "status";
+static const char *type_file = "type";
+
+static const char *discharging_states[] = { "Discharging", "Not charging" };
+static const char *charging_states[] = { "Charging", "Full", "Unknown" };
+
+static inline bool battery_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 inline int battery_select_file(char *path, const char **file)
+{
+    if (battery_file_accessible(path, file[0])) {
+        return 0;
+    } else if (battery_file_accessible(path, file[1])) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+static void battery_get_full_charge(BatteryState *s, Error **errp)
+{
+    char file_path[PATH_MAX];
+    int path_len;
+    uint32_t val;
+    FILE *ff;
+
+    path_len = snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path,
+                        full_file[s->units]);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        error_setg(errp, "Full capacity file path is inaccessible.");
+        return;
+    }
+
+    ff = fopen(file_path, "r");
+    if (ff == NULL) {
+        error_setg_errno(errp, errno, "Could not read the full charge file.");
+        return;
+    }
+
+    if (fscanf(ff, "%u", &val) != 1) {
+        error_setg(errp, "Full capacity undetermined.");
+        return;
+    } else {
+        s->charge_full = val;
+    }
+    fclose(ff);
+}
+
+static inline bool battery_is_discharging(char *val)
+{
+    static const int discharging_len = ARRAY_SIZE(discharging_states);
+    int i;
+
+    for (i = 0; i < discharging_len; i++) {
+        if (!strncmp(val, discharging_states[i], MAX_ALLOWED_STATE_LENGTH)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static inline bool battery_is_charging(char *val)
+{
+    static const int charging_len = ARRAY_SIZE(charging_states);
+    int i;
+
+    for (i = 0; i < charging_len; i++) {
+        if (!strncmp(val, charging_states[i], MAX_ALLOWED_STATE_LENGTH)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static void battery_get_state(BatteryState *s)
+{
+    char file_path[PATH_MAX];
+    int path_len;
+    char val[MAX_ALLOWED_STATE_LENGTH];
+    FILE *ff;
+
+    path_len = snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path, stat_file);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        warn_report("Could not read the battery state.");
+        return;
+    }
+
+    ff = fopen(file_path, "r");
+    if (ff == NULL) {
+        warn_report("Could not read the battery state.");
+        return;
+    }
+
+    if (fgets(val, MAX_ALLOWED_STATE_LENGTH, ff) == NULL) {
+        warn_report("Battery state unreadable.");
+    } else {
+        val[strcspn(val, "\n")] = 0;
+        if (battery_is_discharging(val)) {
+            s->state.val = BATTERY_DISCHARGING;
+        } else if (battery_is_charging(val)) {
+            s->state.val = BATTERY_CHARGING;
+        } else {
+            warn_report("Battery state undetermined.");
+        }
+    }
+    fclose(ff);
+}
+
+static void battery_get_rate(BatteryState *s)
+{
+    char file_path[PATH_MAX];
+    int path_len;
+    uint64_t val;
+    FILE *ff;
+
+    path_len = snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path,
+                        rate_file[s->units]);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        warn_report("Could not read the battery rate.");
+        s->rate.val = BATTERY_VAL_UNKNOWN;
+        return;
+    }
+
+    ff = fopen(file_path, "r");
+    if (ff == NULL) {
+        warn_report("Could not read the battery rate.");
+        s->rate.val = BATTERY_VAL_UNKNOWN;
+        return;
+    }
+
+    if (fscanf(ff, "%lu", &val) != 1) {
+        warn_report("Battery rate undetermined.");
+        s->rate.val = BATTERY_VAL_UNKNOWN;
+    } else {
+        s->rate.val = NORMALIZE_BY_FULL(val, s->charge_full);
+    }
+    fclose(ff);
+}
+
+static void battery_get_charge(BatteryState *s)
+{
+    char file_path[PATH_MAX];
+    int path_len;
+    uint64_t val;
+    FILE *ff;
+
+    path_len = snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path,
+                        now_file[s->units]);
+    if (path_len < 0 || path_len >= PATH_MAX) {
+        warn_report("Could not read the battery charge.");
+        s->charge.val = BATTERY_VAL_UNKNOWN;
+        return;
+    }
+
+    ff = fopen(file_path, "r");
+    if (ff == NULL) {
+        warn_report("Could not read the battery charge.");
+        s->charge.val = BATTERY_VAL_UNKNOWN;
+        return;
+    }
+
+    if (fscanf(ff, "%lu", &val) != 1) {
+        warn_report("Battery charge undetermined.");
+        s->charge.val = BATTERY_VAL_UNKNOWN;
+    } else {
+        s->charge.val = NORMALIZE_BY_FULL(val, s->charge_full);
+    }
+    fclose(ff);
+}
+
+static void battery_get_dynamic_status(BatteryState *s)
+{
+    battery_get_state(s);
+    battery_get_rate(s);
+    battery_get_charge(s);
+
+    trace_battery_get_dynamic_status(s->state.val, s->rate.val, s->charge.val);
+}
+
+static void battery_probe_state(void *opaque)
+{
+    BatteryState *s = opaque;
+
+    uint32_t state_before = s->state.val;
+    uint32_t rate_before = s->rate.val;
+    uint32_t charge_before = s->charge.val;
+
+    battery_get_dynamic_status(s);
+
+    if (state_before != s->state.val || rate_before != s->rate.val ||
+        charge_before != s->charge.val) {
+        Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
+        switch (charge_before) {
+        case 0:
+            break;  /* Avoid marking initiation as an update */
+        default:
+            acpi_send_event(DEVICE(obj), ACPI_BATTERY_CHANGE_STATUS);
+        }
+    }
+    timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+              s->probe_state_interval);
+}
+
+static void battery_probe_state_timer_init(BatteryState *s)
+{
+    if (s->probe_state_interval > 0) {
+        s->probe_state_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+                                            battery_probe_state, s);
+        timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+                  s->probe_state_interval);
+    }
+}
+
+static bool battery_verify_sysfs(BatteryState *s, char *path)
+{
+    int units;
+    FILE *ff;
+    char type_path[PATH_MAX];
+    int path_len;
+    char val[MAX_ALLOWED_STATE_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_STATE_LENGTH, ff) == NULL) {
+        fclose(ff);
+        return false;
+    } else {
+        val[strcspn(val, "\n")] = 0;
+        if (strncmp(val, BATTERY_TYPE, MAX_ALLOWED_STATE_LENGTH)) {
+            fclose(ff);
+            return false;
+        }
+    }
+    fclose(ff);
+
+    units = battery_select_file(path, full_file);
+
+    if (units < 0) {
+        return false;
+    } else {
+        s->units = units;
+    }
+
+    return (battery_file_accessible(path, now_file[s->units])
+            & battery_file_accessible(path, rate_file[s->units])
+            & battery_file_accessible(path, stat_file));
+}
+
+static bool get_battery_path(DeviceState *dev)
+{
+    BatteryState *s = BATTERY_DEVICE(dev);
+    DIR *dir;
+    struct dirent *ent;
+    char bp[PATH_MAX];
+    int path_len;
+
+    if (s->bat_path) {
+        return battery_verify_sysfs(s, s->bat_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 (battery_verify_sysfs(s, bp)) {
+                qdev_prop_set_string(dev, BATTERY_PATH_PROP, bp);
+                closedir(dir);
+                return true;
+            }
+        }
+        ent = readdir(dir);
+    }
+    closedir(dir);
+
+    return false;
+}
+
+static void battery_realize(DeviceState *dev, Error **errp)
+{
+    ISADevice *d = ISA_DEVICE(dev);
+    BatteryState *s = BATTERY_DEVICE(dev);
+    FWCfgState *fw_cfg = fw_cfg_find();
+    uint16_t *battery_port;
+    char err_details[0x20] = {};
+
+    trace_battery_realize();
+
+    if (!s->bat_path) {
+        strcpy(err_details, " Try using 'sysfs_path='");
+    }
+
+    if (!get_battery_path(dev)) {
+        error_setg(errp, "Battery sysfs path not found or unreadable.%s",
+                   err_details);
+        return;
+    }
+
+    battery_get_full_charge(s, errp);
+
+    isa_register_ioport(d, &s->io, s->ioport);
+
+    battery_probe_state_timer_init(s);
+
+    if (!fw_cfg) {
+        return;
+    }
+
+    battery_port = g_malloc(sizeof(*battery_port));
+    *battery_port = cpu_to_le16(s->ioport);
+    fw_cfg_add_file(fw_cfg, "etc/battery-port", battery_port,
+                    sizeof(*battery_port));
+}
+
+static Property battery_device_properties[] = {
+    DEFINE_PROP_UINT16(BATTERY_IOPORT_PROP, BatteryState, ioport, 0x530),
+    DEFINE_PROP_UINT64(BATTERY_PROBE_STATE_INTERVAL, BatteryState,
+                       probe_state_interval, 2000),
+    DEFINE_PROP_STRING(BATTERY_PATH_PROP, BatteryState, bat_path),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription battery_vmstate = {
+    .name = "battery",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT16(ioport, BatteryState),
+        VMSTATE_UINT64(probe_state_interval, BatteryState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void battery_class_init(ObjectClass *class, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+
+    dc->realize = battery_realize;
+    device_class_set_props(dc, battery_device_properties);
+    dc->vmsd = &battery_vmstate;
+}
+
+static uint64_t battery_ioport_read(void *opaque, hwaddr addr, unsigned size)
+{
+    BatteryState *s = opaque;
+
+    battery_get_dynamic_status(s);
+
+    switch (addr) {
+    case bsta_addr0:
+        return s->state.acc[0];
+    case bsta_addr1:
+        return s->state.acc[1];
+    case bsta_addr2:
+        return s->state.acc[2];
+    case bsta_addr3:
+        return s->state.acc[3];
+    case brte_addr0:
+        return s->rate.acc[0];
+    case brte_addr1:
+        return s->rate.acc[1];
+    case brte_addr2:
+        return s->rate.acc[2];
+    case brte_addr3:
+        return s->rate.acc[3];
+    case bcrg_addr0:
+        return s->charge.acc[0];
+    case bcrg_addr1:
+        return s->charge.acc[1];
+    case bcrg_addr2:
+        return s->charge.acc[2];
+    case bcrg_addr3:
+        return s->charge.acc[3];
+    default:
+        warn_report("Battery: guest read unknown value.");
+        trace_battery_ioport_read_unknown();
+        return 0;
+    }
+}
+
+static const MemoryRegionOps battery_ops = {
+    .read = battery_ioport_read,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 1,
+    },
+};
+
+static void battery_instance_init(Object *obj)
+{
+    BatteryState *s = BATTERY_DEVICE(obj);
+
+    memory_region_init_io(&s->io, obj, &battery_ops, s, "battery",
+                          BATTERY_LEN);
+}
+
+static const TypeInfo battery_info = {
+    .name          = TYPE_BATTERY,
+    .parent        = TYPE_ISA_DEVICE,
+    .instance_size = sizeof(BatteryState),
+    .class_init    = battery_class_init,
+    .instance_init = battery_instance_init,
+};
+
+static void battery_register_types(void)
+{
+    type_register_static(&battery_info);
+}
+
+type_init(battery_register_types)
diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build
index dd69577212..485eab33b6 100644
--- a/hw/acpi/meson.build
+++ b/hw/acpi/meson.build
@@ -19,6 +19,7 @@ acpi_ss.add(when: 'CONFIG_ACPI_X86_ICH', if_true: files('ich9.c', 'tco.c'))
 acpi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c'), if_false: files('ipmi-stub.c'))
 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'))
 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 f91ced477d..5d2c606496 100644
--- a/hw/acpi/trace-events
+++ b/hw/acpi/trace-events
@@ -53,3 +53,8 @@ piix4_gpe_writeb(uint64_t addr, unsigned width, uint64_t val) "addr: 0x%" PRIx64
 # tco.c
 tco_timer_reload(int ticks, int msec) "ticks=%d (%d ms)"
 tco_timer_expired(int timeouts_no, bool strap, bool no_reboot) "timeouts_no=%d no_reboot=%d/%d"
+
+# 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
+battery_ioport_read_unknown(void) "Battery read unknown"
diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig
index eea059ffef..b081be7a0f 100644
--- a/hw/i386/Kconfig
+++ b/hw/i386/Kconfig
@@ -23,6 +23,7 @@ config PC
     imply TPM_TIS_ISA
     imply VGA_PCI
     imply VIRTIO_VGA
+    imply BATTERY
     select FDC
     select I8259
     select I8254
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index f56d699c7f..ecd5380f82 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -35,6 +35,7 @@
 #include "hw/acpi/acpi-defs.h"
 #include "hw/acpi/acpi.h"
 #include "hw/acpi/cpu.h"
+#include "hw/acpi/battery.h"
 #include "hw/nvram/fw_cfg.h"
 #include "hw/acpi/bios-linker-loader.h"
 #include "hw/isa/isa.h"
@@ -112,6 +113,7 @@ typedef struct AcpiMiscInfo {
     const unsigned char *dsdt_code;
     unsigned dsdt_size;
     uint16_t pvpanic_port;
+    uint16_t battery_port;
     uint16_t applesmc_io_base;
 } AcpiMiscInfo;
 
@@ -277,6 +279,7 @@ static void acpi_get_misc_info(AcpiMiscInfo *info)
     info->has_hpet = hpet_find();
     info->tpm_version = tpm_get_version(tpm_find());
     info->pvpanic_port = pvpanic_port();
+    info->battery_port = battery_port();
     info->applesmc_io_base = applesmc_port();
 }
 
@@ -1631,6 +1634,100 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
         aml_append(sb_scope, dev);
     }
 
+    if (misc->battery_port) {
+        Aml *bat_state  = aml_local(0);
+        Aml *bat_rate   = aml_local(1);
+        Aml *bat_charge = aml_local(2);
+
+        dev = aml_device("BAT0");
+        aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C0A")));
+
+        method = aml_method("_STA", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_return(aml_int(0x1F)));
+        aml_append(dev, method);
+
+        aml_append(dev, aml_operation_region("DBST", AML_SYSTEM_IO,
+                                             aml_int(misc->battery_port),
+                                             BATTERY_LEN));
+        field = aml_field("DBST", AML_DWORD_ACC, AML_NOLOCK, AML_PRESERVE);
+        aml_append(field, aml_named_field("BSTA", 32));
+        aml_append(field, aml_named_field("BRTE", 32));
+        aml_append(field, aml_named_field("BCRG", 32));
+        aml_append(dev, field);
+
+        method = aml_method("_BIF", 0, AML_NOTSERIALIZED);
+        pkg = aml_package(13);
+        /* Power Unit */
+        aml_append(pkg, aml_int(0));             /* mW */
+        /* Design Capacity */
+        aml_append(pkg, aml_int(BATTERY_FULL_CAP));
+        /* Last Full Charge Capacity */
+        aml_append(pkg, aml_int(BATTERY_FULL_CAP));
+        /* Battery Technology */
+        aml_append(pkg, aml_int(1));             /* Secondary */
+        /* Design Voltage */
+        aml_append(pkg, aml_int(BATTERY_VAL_UNKNOWN));
+        /* Design Capacity of Warning */
+        aml_append(pkg, aml_int(BATTERY_CAPACITY_OF_WARNING));
+        /* Design Capacity of Low */
+        aml_append(pkg, aml_int(BATTERY_CAPACITY_OF_LOW));
+        /* Battery Capacity Granularity 1 */
+        aml_append(pkg, aml_int(BATTERY_CAPACITY_GRANULARITY));
+        /* Battery Capacity Granularity 2 */
+        aml_append(pkg, aml_int(BATTERY_CAPACITY_GRANULARITY));
+        /* Model Number */
+        aml_append(pkg, aml_string("QBAT001"));  /* Model Number */
+        /* Serial Number */
+        aml_append(pkg, aml_string("SN00000"));  /* Serial Number */
+        /* Battery Type */
+        aml_append(pkg, aml_string("Virtual"));  /* Battery Type */
+        /* OEM Information */
+        aml_append(pkg, aml_string("QEMU"));     /* OEM Information */
+        aml_append(method, aml_return(pkg));
+        aml_append(dev, method);
+
+        pkg = aml_package(4);
+        /* Battery State */
+        aml_append(pkg, aml_int(0));
+        /* Battery Present Rate */
+        aml_append(pkg, aml_int(BATTERY_VAL_UNKNOWN));
+        /* Battery Remaining Capacity */
+        aml_append(pkg, aml_int(BATTERY_VAL_UNKNOWN));
+        /* Battery Present Voltage */
+        aml_append(pkg, aml_int(BATTERY_VAL_UNKNOWN));
+        aml_append(dev, aml_name_decl("DBPR", pkg));
+
+        method = aml_method("_BST", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_store(aml_name("BSTA"), bat_state));
+        aml_append(method, aml_store(aml_name("BRTE"), bat_rate));
+        aml_append(method, aml_store(aml_name("BCRG"), bat_charge));
+        aml_append(method, aml_store(bat_state,
+                                     aml_index(aml_name("DBPR"), aml_int(0))));
+        aml_append(method, aml_store(bat_rate,
+                                     aml_index(aml_name("DBPR"), aml_int(1))));
+        aml_append(method, aml_store(bat_charge,
+                                     aml_index(aml_name("DBPR"), aml_int(2))));
+        aml_append(method, aml_return(aml_name("DBPR")));
+        aml_append(dev, method);
+
+        aml_append(sb_scope, dev);
+
+        /* Device Check */
+        method = aml_method("\\_GPE._E07", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_notify(aml_name("\\_SB.BAT0"), aml_int(0x01)));
+        aml_append(dsdt, method);
+
+        /* Status Change */
+        method = aml_method("\\_GPE._E08", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_notify(aml_name("\\_SB.BAT0"), aml_int(0x80)));
+        aml_append(dsdt, method);
+
+        /* Information Change */
+        method = aml_method("\\_GPE._E09", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_notify(aml_name("\\_SB.BAT0"), aml_int(0x81)));
+        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 769ff55c7e..3d98d5636a 100644
--- a/include/hw/acpi/acpi_dev_interface.h
+++ b/include/hw/acpi/acpi_dev_interface.h
@@ -14,6 +14,7 @@ typedef enum {
     ACPI_NVDIMM_HOTPLUG_STATUS = 16,
     ACPI_VMGENID_CHANGE_STATUS = 32,
     ACPI_POWER_DOWN_STATUS = 64,
+    ACPI_BATTERY_CHANGE_STATUS = 128,
 } AcpiEventStatusBits;
 
 #define TYPE_ACPI_DEVICE_IF "acpi-device-interface"
diff --git a/include/hw/acpi/battery.h b/include/hw/acpi/battery.h
new file mode 100644
index 0000000000..6224a97d9c
--- /dev/null
+++ b/include/hw/acpi/battery.h
@@ -0,0 +1,43 @@
+/*
+ * QEMU emulated battery 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_BATTERY_H
+#define HW_ACPI_BATTERY_H
+
+#define TYPE_BATTERY                  "battery"
+#define BATTERY_IOPORT_PROP           "ioport"
+#define BATTERY_PATH_PROP             "sysfs_path"
+#define BATTERY_PROBE_STATE_INTERVAL  "probe_interval"
+
+#define BATTERY_FULL_CAP     10000  /* mWh */
+
+#define BATTERY_CAPACITY_OF_WARNING   (BATTERY_FULL_CAP /  10)  /* 10% */
+#define BATTERY_CAPACITY_OF_LOW       (BATTERY_FULL_CAP /  25)  /* 4%  */
+#define BATTERY_CAPACITY_GRANULARITY  (BATTERY_FULL_CAP / 100)  /* 1%  */
+
+#define BATTERY_VAL_UNKNOWN  0xFFFFFFFF
+
+#define BATTERY_LEN          0x0C
+
+static inline uint16_t battery_port(void)
+{
+    Object *o = object_resolve_path_type("", TYPE_BATTERY, NULL);
+    if (!o) {
+        return 0;
+    }
+    return object_property_get_uint(o, BATTERY_IOPORT_PROP, NULL);
+}
+
+#endif
-- 
2.30.0



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/4] hw/acpi: Introduce the QEMU AC adapter
  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 ` Leonid Bloch
  2021-01-20 20:55 ` [PATCH 4/4] hw/acpi: Introduce the QEMU lid button Leonid Bloch
  2021-01-20 21:52 ` [PATCH 0/4] Introduce a battery, AC adapter, and " Philippe Mathieu-Daudé
  4 siblings, 0 replies; 9+ messages in thread
From: Leonid Bloch @ 2021-01-20 20:55 UTC (permalink / raw)
  To: Michael S . Tsirkin, Igor Mammedov, Paolo Bonzini,
	Richard Henderson, Eduardo Habkost, Marcel Apfelbaum
  Cc: Leonid Bloch, qemu-devel

The AC adapter device communicates the host's AC adapter state to the
guest. The probing of the host's AC adapter state occurs on guest ACPI
requests, as well as on timed intervals. If a change of the host's AC
adapter state is detected on one of the timed probes (connected/disconnected)
an ACPI notification is sent to the guest, so it will be able to
update its AC adapter status accordingly.

The time interval between the periodic probes is 2 seconds by default.
A property 'probe_interval' allows to modify this value. The value
should be provided in milliseconds. A zero value disables the periodic
probes, and makes the AC adapter state updates occur on guest requests
only.

The host's AC adapter information is taken from the sysfs AC adapter
data, located in:

/sys/class/power_supply/[device of type "Mains"]

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, a 'sysfs_path'
property allows to override the default one.

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

diff --git a/MAINTAINERS b/MAINTAINERS
index 33eea28c22..1a3fc1dd56 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2146,6 +2146,11 @@ M: Leonid Bloch <lb.workbox@gmail.com>
 S: Maintained
 F: hw/acpi/battery.*
 
+AC Adapter
+M: Leonid Bloch <lb.workbox@gmail.com>
+S: Maintained
+F: hw/acpi/acad.*
+
 Subsystems
 ----------
 Audio
diff --git a/docs/specs/acad.txt b/docs/specs/acad.txt
new file mode 100644
index 0000000000..0d563a7b50
--- /dev/null
+++ b/docs/specs/acad.txt
@@ -0,0 +1,24 @@
+AC ADAPTER DEVICE
+=================
+
+The AC adapter device communicates the host's AC adapter state to the
+guest. The probing of the host's AC adapter state occurs on guest ACPI
+requests, as well as on timed intervals. If a change of the host's AC
+adapter state is detected on one of the timed probes (connected/disconnected)
+an ACPI notification is sent to the guest, so it will be able to
+update its AC adapter status accordingly.
+
+The time interval between the periodic probes is 2 s by default.
+A property 'probe_interval' allows to modify this value. The value
+should be provided in milliseconds. A zero value disables the periodic
+probes, and makes the AC adapter state updates occur on guest requests
+only.
+
+The host's AC adapter information is taken from the sysfs AC adapter
+data, located in:
+
+/sys/class/power_supply/[device of type "Mains"]
+
+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, a 'sysfs_path'
+property allows to override the default one.
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
index 6b4c41037a..d35331fbf7 100644
--- a/hw/acpi/Kconfig
+++ b/hw/acpi/Kconfig
@@ -45,4 +45,8 @@ config BATTERY
     bool
     depends on ACPI
 
+config AC_ADAPTER
+    bool
+    depends on ACPI
+
 config ACPI_HW_REDUCED
diff --git a/hw/acpi/acad.c b/hw/acpi/acad.c
new file mode 100644
index 0000000000..279452e95f
--- /dev/null
+++ b/hw/acpi/acad.c
@@ -0,0 +1,318 @@
+/*
+ * 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>
+ *
+ * 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/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;
+
+    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)
+{
+    acad_get_state(s);
+
+    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->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->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 Property acad_device_properties[] = {
+    DEFINE_PROP_UINT16(AC_ADAPTER_IOPORT_PROP, ACADState, ioport, 0x53c),
+    DEFINE_PROP_UINT64(AC_ADAPTER_PROBE_STATE_INTERVAL, ACADState,
+                       probe_state_interval, 2000),
+    DEFINE_PROP_STRING(AC_ADAPTER_PATH_PROP, ACADState, acad_path),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+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 acad_class_init(ObjectClass *class, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+
+    dc->realize = acad_realize;
+    device_class_set_props(dc, acad_device_properties);
+    dc->vmsd = &acad_vmstate;
+}
+
+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,
+};
+
+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 485eab33b6..2ae756c6ef 100644
--- a/hw/acpi/meson.build
+++ b/hw/acpi/meson.build
@@ -20,6 +20,7 @@ acpi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c'), if_false: files('ipmi
 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'))
 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 5d2c606496..496a282cdc 100644
--- a/hw/acpi/trace-events
+++ b/hw/acpi/trace-events
@@ -58,3 +58,8 @@ tco_timer_expired(int timeouts_no, bool strap, bool no_reboot) "timeouts_no=%d n
 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 b081be7a0f..483b3ccec8 100644
--- a/hw/i386/Kconfig
+++ b/hw/i386/Kconfig
@@ -24,6 +24,7 @@ config PC
     imply VGA_PCI
     imply VIRTIO_VGA
     imply BATTERY
+    imply AC_ADAPTER
     select FDC
     select I8259
     select I8254
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index ecd5380f82..96dbeed22f 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -36,6 +36,7 @@
 #include "hw/acpi/acpi.h"
 #include "hw/acpi/cpu.h"
 #include "hw/acpi/battery.h"
+#include "hw/acpi/acad.h"
 #include "hw/nvram/fw_cfg.h"
 #include "hw/acpi/bios-linker-loader.h"
 #include "hw/isa/isa.h"
@@ -114,6 +115,7 @@ typedef struct AcpiMiscInfo {
     unsigned dsdt_size;
     uint16_t pvpanic_port;
     uint16_t battery_port;
+    uint16_t acad_port;
     uint16_t applesmc_io_base;
 } AcpiMiscInfo;
 
@@ -280,6 +282,7 @@ static void acpi_get_misc_info(AcpiMiscInfo *info)
     info->tpm_version = tpm_get_version(tpm_find());
     info->pvpanic_port = pvpanic_port();
     info->battery_port = battery_port();
+    info->acad_port = acad_port();
     info->applesmc_io_base = applesmc_port();
 }
 
@@ -1728,6 +1731,55 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
         aml_append(dsdt, method);
     }
 
+    if (misc->acad_port) {
+        Aml *acad_state  = aml_local(0);
+
+        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(misc->acad_port),
+                                             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);
+
+        /* Status Change */
+        method = aml_method("\\_GPE._E0A", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_notify(aml_name("\\_SB.ADP0"), 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/acad.h b/include/hw/acpi/acad.h
new file mode 100644
index 0000000000..be61c57064
--- /dev/null
+++ b/include/hw/acpi/acad.h
@@ -0,0 +1,37 @@
+/*
+ * 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>
+ *
+ * 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_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
+
+static inline uint16_t acad_port(void)
+{
+    Object *o = object_resolve_path_type("", TYPE_AC_ADAPTER, NULL);
+    if (!o) {
+        return 0;
+    }
+    return object_property_get_uint(o, AC_ADAPTER_IOPORT_PROP, NULL);
+}
+
+#endif
diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h
index 3d98d5636a..b577a4db07 100644
--- a/include/hw/acpi/acpi_dev_interface.h
+++ b/include/hw/acpi/acpi_dev_interface.h
@@ -15,6 +15,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"
-- 
2.30.0



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 4/4] hw/acpi: Introduce the QEMU lid button
  2021-01-20 20:54 [PATCH 0/4] Introduce a battery, AC adapter, and lid button Leonid Bloch
                   ` (2 preceding siblings ...)
  2021-01-20 20:55 ` [PATCH 3/4] hw/acpi: Introduce the QEMU AC adapter Leonid Bloch
@ 2021-01-20 20:55 ` Leonid Bloch
  2021-01-20 21:52 ` [PATCH 0/4] Introduce a battery, AC adapter, and " Philippe Mathieu-Daudé
  4 siblings, 0 replies; 9+ messages in thread
From: Leonid Bloch @ 2021-01-20 20:55 UTC (permalink / raw)
  To: Michael S . Tsirkin, Igor Mammedov, Paolo Bonzini,
	Richard Henderson, Eduardo Habkost, Marcel Apfelbaum
  Cc: Leonid Bloch, qemu-devel

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



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/4] Introduce a battery, AC adapter, and lid button
  2021-01-20 20:54 [PATCH 0/4] Introduce a battery, AC adapter, and lid button Leonid Bloch
                   ` (3 preceding siblings ...)
  2021-01-20 20:55 ` [PATCH 4/4] hw/acpi: Introduce the QEMU lid button Leonid Bloch
@ 2021-01-20 21:52 ` Philippe Mathieu-Daudé
  2021-01-21  5:38   ` Leonid Bloch
  4 siblings, 1 reply; 9+ messages in thread
From: Philippe Mathieu-Daudé @ 2021-01-20 21:52 UTC (permalink / raw)
  To: Leonid Bloch, Michael S . Tsirkin, Igor Mammedov, Paolo Bonzini,
	Richard Henderson, Eduardo Habkost, Marcel Apfelbaum
  Cc: qemu-devel

Hi Leonid, Marcel,

On 1/20/21 9:54 PM, Leonid Bloch wrote:
> This series introduces the following ACPI devices:
> 
> * Battery
> * AC adapter
> * Laptop lid button
> 
> When running QEMU on a laptop, these paravirtualized devices reflect the
> state of these physical devices onto the guest. This functionality is
> relevant not only for laptops, but also for any other device which has e.g.
> a battery. This even allows to insert a ``fake'' battery to the
> guest, in a form of a file which emulates the behavior of the actual
> battery in sysfs. A possible use case for such a ``fake'' battery can be
> limiting the budget of VM usage to a subscriber, in a naturally-visible way.

Your series looks good. Now for this feature to be even more useful for
the community, it would be better to

1/ Have a generic (kind of abstract QDev) battery model.
   Your model would be the ISA implementation. But we could add LPC,
   SPI or I2C implementations for example.

2/ Make it a model backend accepting various kind of frontends:
   - host Linux sysfs mirroring is a particular frontend implementation
   - mirroring on Windows would be another
   - any connection (TCP) to battery simulator (Octave, ...)

Meanwhile 2/ is not available, it would be useful to have QMP commands
to set the battery charge and state (also max capacity).

Ditto QMP command to set the LID/AC adapter state.

> But of course, the main purpose here is addressing the desktop users.
> 
> This series was tested with Windows and (desktop) Linux guests, on which
> indeed the battery icon appears in the corresponding state (full,
> charging, discharging, time remaining to empty, etc.) and the AC adapter
> plugging/unplugging behaves as expected. So is the laptop lid button.
[...]

In patch #2 you comment 'if a "fake" host battery is to be provided,
a 'sysfs_path' property allows to override the default one.'.

Eventually you'd provide a such fake file as example, ideally used
by a QTest.

Another question. If the battery is disconnected, is there an event
propagated to the guest?

Thanks for contributing these patches :)

Phil.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/4] Introduce a battery, AC adapter, and lid button
  2021-01-20 21:52 ` [PATCH 0/4] Introduce a battery, AC adapter, and " Philippe Mathieu-Daudé
@ 2021-01-21  5:38   ` Leonid Bloch
  2021-01-26 14:39     ` Michael S. Tsirkin
  0 siblings, 1 reply; 9+ messages in thread
From: Leonid Bloch @ 2021-01-21  5:38 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé
  Cc: Eduardo Habkost, Michael S . Tsirkin, Richard Henderson,
	qemu-devel, Paolo Bonzini, Igor Mammedov

Hi Phil,

Thanks for your feedback! Please see below.

On Wed, Jan 20, 2021 at 11:52 PM Philippe Mathieu-Daudé <f4bug@amsat.org> wrote:
>
> Hi Leonid, Marcel,
>
> On 1/20/21 9:54 PM, Leonid Bloch wrote:
> > This series introduces the following ACPI devices:
> >
> > * Battery
> > * AC adapter
> > * Laptop lid button
> >
> > When running QEMU on a laptop, these paravirtualized devices reflect the
> > state of these physical devices onto the guest. This functionality is
> > relevant not only for laptops, but also for any other device which has e.g.
> > a battery. This even allows to insert a ``fake'' battery to the
> > guest, in a form of a file which emulates the behavior of the actual
> > battery in sysfs. A possible use case for such a ``fake'' battery can be
> > limiting the budget of VM usage to a subscriber, in a naturally-visible way.
>
> Your series looks good. Now for this feature to be even more useful for
> the community, it would be better to
>
> 1/ Have a generic (kind of abstract QDev) battery model.
>    Your model would be the ISA implementation. But we could add LPC,
>    SPI or I2C implementations for example.

It definitely feels that it needs to be more generic, and I thought
how to make it so, but so far it is what I came up with. I'll think
some more, but any ideas are welcome, cause nowadays I'm doing this in
my free time only.

> 2/ Make it a model backend accepting various kind of frontends:
>    - host Linux sysfs mirroring is a particular frontend implementation
>    - mirroring on Windows would be another
>    - any connection (TCP) to battery simulator (Octave, ...)

Well, it does accept an arbitrary file to represent a battery, so this
covers the battery simulator, does it? As for Windows - indeed, it
would be nice to have.

> Meanwhile 2/ is not available, it would be useful to have QMP commands
> to set the battery charge and state (also max capacity).

But the battery state is determined by the physical battery, or by an
externally provided file. Do you mean introducing another source for
battery information which will be controlled by QMP commands?
As for the max capacity, as with an actual battery, the "QEMU battery"
has it set "by the manufacturer". It is not passed through from the
host, for simplicity sake, and only the percentage is passed. How will
it help if we allow to set the max capacity? It's something pretty
much transparent to the user. (But if there is a use case, of course
it can be done.)

> Ditto QMP command to set the LID/AC adapter state.
>
> > But of course, the main purpose here is addressing the desktop users.
> >
> > This series was tested with Windows and (desktop) Linux guests, on which
> > indeed the battery icon appears in the corresponding state (full,
> > charging, discharging, time remaining to empty, etc.) and the AC adapter
> > plugging/unplugging behaves as expected. So is the laptop lid button.
> [...]
>
> In patch #2 you comment 'if a "fake" host battery is to be provided,
> a 'sysfs_path' property allows to override the default one.'.
>
> Eventually you'd provide a such fake file as example, ideally used
> by a QTest.

Sure! I will - it's definitely a good idea.

> Another question. If the battery is disconnected, is there an event
> propagated to the guest?

No. I definitely need to add! Thanks!

> Thanks for contributing these patches :)

Thank you!
Leonid.

> Phil.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/4] Introduce a battery, AC adapter, and lid button
  2021-01-21  5:38   ` Leonid Bloch
@ 2021-01-26 14:39     ` Michael S. Tsirkin
  2021-01-28  6:02       ` Leonid Bloch
  0 siblings, 1 reply; 9+ messages in thread
From: Michael S. Tsirkin @ 2021-01-26 14:39 UTC (permalink / raw)
  To: Leonid Bloch
  Cc: Eduardo Habkost, Richard Henderson, Philippe Mathieu-Daudé,
	qemu-devel, Paolo Bonzini, Igor Mammedov

On Thu, Jan 21, 2021 at 07:38:46AM +0200, Leonid Bloch wrote:
> Hi Phil,
> 
> Thanks for your feedback! Please see below.
> 
> On Wed, Jan 20, 2021 at 11:52 PM Philippe Mathieu-Daudé <f4bug@amsat.org> wrote:
> >
> > Hi Leonid, Marcel,
> >
> > On 1/20/21 9:54 PM, Leonid Bloch wrote:
> > > This series introduces the following ACPI devices:
> > >
> > > * Battery
> > > * AC adapter
> > > * Laptop lid button
> > >
> > > When running QEMU on a laptop, these paravirtualized devices reflect the
> > > state of these physical devices onto the guest. This functionality is
> > > relevant not only for laptops, but also for any other device which has e.g.
> > > a battery. This even allows to insert a ``fake'' battery to the
> > > guest, in a form of a file which emulates the behavior of the actual
> > > battery in sysfs. A possible use case for such a ``fake'' battery can be
> > > limiting the budget of VM usage to a subscriber, in a naturally-visible way.
> >
> > Your series looks good. Now for this feature to be even more useful for
> > the community, it would be better to
> >
> > 1/ Have a generic (kind of abstract QDev) battery model.
> >    Your model would be the ISA implementation. But we could add LPC,
> >    SPI or I2C implementations for example.
> 
> It definitely feels that it needs to be more generic, and I thought
> how to make it so, but so far it is what I came up with. I'll think
> some more, but any ideas are welcome, cause nowadays I'm doing this in
> my free time only.
> 
> > 2/ Make it a model backend accepting various kind of frontends:
> >    - host Linux sysfs mirroring is a particular frontend implementation
> >    - mirroring on Windows would be another
> >    - any connection (TCP) to battery simulator (Octave, ...)
> 
> Well, it does accept an arbitrary file to represent a battery, so this
> covers the battery simulator, does it? As for Windows - indeed, it
> would be nice to have.

Poking at sysfs from QEMU poses a bunch of issues, for example,
security, migration, etc. Running timers on the host is also not nice
since it causes exits from VM ...

So I agree, as a starting point let's just let user
control the battery level through QMP.



> > Meanwhile 2/ is not available, it would be useful to have QMP commands
> > to set the battery charge and state (also max capacity).
> 
> But the battery state is determined by the physical battery, or by an
> externally provided file. Do you mean introducing another source for
> battery information which will be controlled by QMP commands?
> As for the max capacity, as with an actual battery, the "QEMU battery"
> has it set "by the manufacturer". It is not passed through from the
> host, for simplicity sake, and only the percentage is passed. How will
> it help if we allow to set the max capacity? It's something pretty
> much transparent to the user. (But if there is a use case, of course
> it can be done.)
> 
> > Ditto QMP command to set the LID/AC adapter state.
> >
> > > But of course, the main purpose here is addressing the desktop users.
> > >
> > > This series was tested with Windows and (desktop) Linux guests, on which
> > > indeed the battery icon appears in the corresponding state (full,
> > > charging, discharging, time remaining to empty, etc.) and the AC adapter
> > > plugging/unplugging behaves as expected. So is the laptop lid button.
> > [...]
> >
> > In patch #2 you comment 'if a "fake" host battery is to be provided,
> > a 'sysfs_path' property allows to override the default one.'.
> >
> > Eventually you'd provide a such fake file as example, ideally used
> > by a QTest.
> 
> Sure! I will - it's definitely a good idea.
> 
> > Another question. If the battery is disconnected, is there an event
> > propagated to the guest?
> 
> No. I definitely need to add! Thanks!
> 
> > Thanks for contributing these patches :)
> 
> Thank you!
> Leonid.
> 
> > Phil.



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/4] Introduce a battery, AC adapter, and lid button
  2021-01-26 14:39     ` Michael S. Tsirkin
@ 2021-01-28  6:02       ` Leonid Bloch
  0 siblings, 0 replies; 9+ messages in thread
From: Leonid Bloch @ 2021-01-28  6:02 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: Eduardo Habkost, Richard Henderson, Philippe Mathieu-Daudé,
	qemu-devel, Paolo Bonzini, Igor Mammedov

On Tue, Jan 26, 2021 at 4:40 PM Michael S. Tsirkin <mst@redhat.com> wrote:
>
> Poking at sysfs from QEMU poses a bunch of issues, for example,
> security, migration, etc. Running timers on the host is also not nice
> since it causes exits from VM ...
>
> So I agree, as a starting point let's just let user
> control the battery level through QMP.
>

Thanks for the review, Michael. Yep, I fully agree - indeed looking at
the host's battery is better to be left for the user, and QEMU should
just set what the guest sees. Will modify accordingly, and send a v2.

Cheers,
Leonid.


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2021-01-28  6:04 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH 4/4] hw/acpi: Introduce the QEMU lid button Leonid Bloch
2021-01-20 21:52 ` [PATCH 0/4] Introduce a battery, AC adapter, and " 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

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.