@@ -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
new file mode 100644
@@ -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.
@@ -45,4 +45,8 @@ config BATTERY
bool
depends on ACPI
+config AC_ADAPTER
+ bool
+ depends on ACPI
+
config ACPI_HW_REDUCED
new file mode 100644
@@ -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)
@@ -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',
@@ -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"
@@ -24,6 +24,7 @@ config PC
imply VGA_PCI
imply VIRTIO_VGA
imply BATTERY
+ imply AC_ADAPTER
select FDC
select I8259
select I8254
@@ -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 */
new file mode 100644
@@ -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
@@ -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"
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