From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 90CF6CA0FE4 for ; Thu, 21 Aug 2025 17:48:26 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1up9Nd-0007MI-Kt; Thu, 21 Aug 2025 13:47:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1up9NM-0007Iv-1w for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:47:00 -0400 Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1up9NH-0003n1-4g for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:46:59 -0400 Received: by mail-wm1-x32f.google.com with SMTP id 5b1f17b1804b1-45a1b0cd668so6768425e9.3 for ; Thu, 21 Aug 2025 10:46:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755798413; x=1756403213; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=tRzJOfeYEGRJJECW+6UDAw2spa4P0CZCvt+937FRHC8=; b=ZgwbiK4s0gU0mPME3kCwILrYdLu+7ClPjn/qdmE//AN92/qOTdgny4HqQrYb+wkdvT n8u3F1HdfxEelf7i7qEFVGTA8QDvuylwiCVfOhVYOCUc2WMjb4HFisaiQOYkb63o5O5B qMHFaA0CZQqnBTiTMDQoW3YpLAlCE8rqHW1AjWksAclxnicF7Xty7308aaGXKAIurEzR byz+XDQ4jeRn7GhU3AgqOJ5n1CifTIdBirUJhQOuMpkVX2bfDREJdgGWGVIWfN17EMyE CCIB/nyDK405Lau429k5kpUceQxtslYSIWBWEnGscEnBG16NiaspI+dmHf5d1QaVoqiZ uAXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755798413; x=1756403213; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=tRzJOfeYEGRJJECW+6UDAw2spa4P0CZCvt+937FRHC8=; b=GIB5JVPsfG0oJnNnwMfKh7S2g3fyGNY5aj6k3kEvNe94TLLE6FgSDh+7vuxKXte7Pd fEfooEsp+W+0aBRAa3sQtcde6jo4j4KCmaZFgISQMDGkeu8Z6eFTTnWBV338KroHkP+M /7gYraQvdq4TKqUXX302/BWYpTR/WJ4M2WpvC4y92sd+r7czfkN6FIFHoDhXvLNYxzqI u6cSK72QKZgtTTPDzpHLPfrnvmVub7HW6k0opS9lAfMdTC7ROnQn2niRu0vrKE7G1zwt D5MlSHkx+6D5KwI3c96x1kKpYihQ9xtmr6eaMN/SqzT6jFhSIH2b9PFtbkEMaBhABf2U NdYw== X-Forwarded-Encrypted: i=1; AJvYcCXBLR1Udwpxbl/cMDljhflEJ/n+t5PIzWK3gRPHEaXwMF1RxB7J0uFKkikm60Z0QM2I41hQhPXRIr04@nongnu.org X-Gm-Message-State: AOJu0YxxgwV5D83P1jwhIvQLGjhUzGvK/o6uXCYYvQtJyABhsIaWN6Sc xoMInTRNWdNl+kdhX7xEKZpb9HyDbWpBuDSvNSBT6p3JU3AMr17vN52WG33Xp0OC X-Gm-Gg: ASbGncvlCqP3fhti9sM71camZuBxBL01uqzOPq99EItOFjDb2+i56HTy+MPebvCyJmU HP4MFX3190d8S6iRXuqPnouPnuGK0KMR8aDmHJnP+QBGx6PQncRy9efXNeKJr8R1EuBMnIAmjeO qO2E4F3By23k5YiVj0pdN0aXJVIuiB58fA6n6xXSxcnyPXo1HAOST6iVO6Za1Z3eVrbmJsbboOY AeIo/7ZqMV4brz97EIrhy67R3ameqQhui62kGeuL2u8JN/h5cQpSPF6Y7wVf9M8IborKymTdKJ/ tkMZFIw2gTKk1zFBuWUvIwQ2PH6iBdtkqvBnzNa1Ovd2FP8yXyFdEHPPlhKe6xXYfAz0GXsTtXu pFo49sqn0+/yZxIRFaAdgPkjNTb/Y9l5wHry9VIXtxZKLB9EWbPeHm06brjxzd8OHcAp99+3O/e w= X-Google-Smtp-Source: AGHT+IHQI0RlsjYyVuP+x3nX2pDd6W8xPyJlsbQvDPAkyKELzHAKD0IOyMRfvVaayHEUrA/ilMHWqA== X-Received: by 2002:a05:600c:3b27:b0:458:a7fa:211d with SMTP id 5b1f17b1804b1-45b4d855455mr30216855e9.29.1755798413025; Thu, 21 Aug 2025 10:46:53 -0700 (PDT) Received: from localhost.localdomain (46-116-237-160.bb.netvision.net.il. [46.116.237.160]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3c074879f5fsm12485360f8f.4.2025.08.21.10.46.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 21 Aug 2025 10:46:52 -0700 (PDT) From: Leonid Bloch To: "Michael S . Tsirkin" , Igor Mammedov , Ani Sinha , Paolo Bonzini , Richard Henderson , Eduardo Habkost , Eric Blake , Markus Armbruster , Marcel Apfelbaum , Dmitry Fleytman Cc: Leonid Bloch , qemu-devel@nongnu.org Subject: [PATCH v2 3/4] hw/acpi: Introduce the QEMU AC adapter Date: Thu, 21 Aug 2025 20:45:51 +0300 Message-ID: <20250821174554.40607-4-lb.workbox@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250821174554.40607-1-lb.workbox@gmail.com> References: <20250821174554.40607-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=2a00:1450:4864:20::32f; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org The AC adapter device communicates AC power state to the guest via ACPI. It supports two modes of operation: 1. QMP control mode (default): AC adapter state is controlled programmatically via QMP commands, ensuring deterministic behavior. 2. Host mirroring mode (optional): The device reflects the host's AC adapter state from sysfs. Probing occurs on guest ACPI requests and at timed intervals. State changes trigger ACPI notifications. Properties: - 'use-qmp': Enable QMP control mode (default: true) - 'enable-sysfs': Enable host AC adapter mirroring (default: false) - 'probe_interval': Probe interval in ms for sysfs mode (default: 2000) - 'sysfs_path': Override default sysfs path /sys/class/power_supply/ The device implements the ACPI_DEV_AML_IF interface to generate its own AML code, placing the ADP0 device directly under \_SB scope. QMP commands: - ac-adapter-set-state: Set AC adapter connection state - query-ac-adapter: Query current AC adapter state Signed-off-by: Leonid Bloch --- MAINTAINERS | 6 + docs/specs/acad.rst | 195 ++++++++++++ docs/specs/index.rst | 1 + hw/acpi/Kconfig | 4 + hw/acpi/acad.c | 447 +++++++++++++++++++++++++++ hw/acpi/meson.build | 1 + hw/acpi/trace-events | 5 + hw/i386/Kconfig | 1 + include/hw/acpi/acad.h | 27 ++ include/hw/acpi/acpi_dev_interface.h | 1 + qapi/acpi.json | 49 +++ 11 files changed, 737 insertions(+) create mode 100644 docs/specs/acad.rst create mode 100644 hw/acpi/acad.c create mode 100644 include/hw/acpi/acad.h diff --git a/MAINTAINERS b/MAINTAINERS index 51af9b7366..612efcb686 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2927,6 +2927,12 @@ S: Maintained F: hw/acpi/battery.* F: docs/specs/battery.rst +AC Adapter +M: Leonid Bloch +S: Maintained +F: hw/acpi/acad.* +F: docs/specs/acad.rst + Subsystems ---------- Overall Audio backends diff --git a/docs/specs/acad.rst b/docs/specs/acad.rst new file mode 100644 index 0000000000..73d5501b8f --- /dev/null +++ b/docs/specs/acad.rst @@ -0,0 +1,195 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +================= +AC Adapter Device +================= + +The AC adapter device provides AC power state information to the guest. It +supports two operating modes: + +1. **QMP Control Mode** (default): AC adapter state is controlled via QMP + commands, providing deterministic control for testing and migration safety. +2. **Sysfs Mode**: AC adapter state mirrors the host's physical AC adapter, + useful for desktop virtualization where the guest should see the host's + power state. + +Configuration +------------- + +The AC adapter device is created as an ISA device using ``-device acad``. + +Operating Modes +~~~~~~~~~~~~~~~ + +**QMP Control Mode** (``use-qmp=true``, default) + AC adapter state is controlled via QMP commands. This mode is recommended for: + + * Production environments requiring migration support + * Testing with predictable power states + * Environments without host AC adapter access + * Security-sensitive deployments + +**Sysfs Mode** (``enable-sysfs=true``) + AC adapter mirrors the host's physical AC adapter. This mode is useful for: + + * Desktop virtualization on laptops + * Development and testing with real AC adapter behavior + + Note: Sysfs mode reads host files and runs timers, which may impact + security and migration. Use with caution in production. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x53c) + I/O port base address for the AC adapter device register. + +``use-qmp`` (default: true) + Enable QMP control mode. When true, AC adapter state is controlled via + QMP commands. Cannot be used together with ``enable-sysfs=true``. + +``enable-sysfs`` (default: false) + Enable sysfs mode to mirror the host's AC adapter. Cannot be used together + with ``use-qmp=true``. + +``probe_interval`` (default: 2000) + Time interval between periodic probes in milliseconds (sysfs mode only). + A zero value disables the periodic probes, and makes the AC adapter state + updates occur on guest requests only. + +``sysfs_path`` (default: auto-detected) + Path to the host's AC adapter sysfs directory (sysfs mode only). By default, + the device auto-detects the first AC adapter of type "Mains" in + ``/sys/class/power_supply/``. Use this property to specify a different + AC adapter, or to provide a custom path for testing purposes. + +Host AC Adapter Detection +------------------------- + +The host's AC adapter information is taken from the sysfs AC adapter +data, located in:: + + /sys/class/power_supply/[device of type "Mains"] + +The device automatically scans for the first AC adapter with: + +- A ``type`` file containing "Mains" +- An ``online`` file that can be read + +If the sysfs path differs, a different AC adapter needs to be probed, +or even if a "fake" host AC adapter is to be provided, the ``sysfs_path`` +property allows overriding the default detection. + +ACPI Interface +-------------- + +The AC adapter device is exposed to the guest as an ACPI device with: + +- **HID**: ``ACPI0003`` (AC Adapter) +- **Device Path**: ``\_SB.ADP0`` +- **Notification Values**: + + - ``0x80``: Status change (connected/disconnected) + +ACPI Methods +~~~~~~~~~~~~ + +``_PSR`` (Power Source) + Returns the current AC adapter state (0 = offline, 1 = online). + +``_PCL`` (Power Consumer List) + Returns the list of devices powered by this adapter. + +``_PIF`` (Power Source Information) + Returns static information about the power source including model number, + serial number, and OEM information. + +I/O Interface +------------- + +The device uses a single I/O port register: + +- **Port**: ``ioport`` property value (default 0x53c) +- **Size**: 1 byte +- **Access**: Read-only + +Register Layout +~~~~~~~~~~~~~~~ + +**PWRS** (offset 0x00, 1 byte) + Current AC adapter state: + + - ``0x00``: AC adapter offline (unplugged) + - ``0x01``: AC adapter online (plugged in) + +QMP Commands +------------ + +When using QMP control mode (default), the following commands are available: + +``ac-adapter-set-state`` + Set the AC adapter connection state. + + * ``connected``: Whether the AC adapter is connected (boolean) + + Example:: + + -> { "execute": "ac-adapter-set-state", + "arguments": { "connected": true }} + <- { "return": {} } + +``query-ac-adapter`` + Query the current AC adapter state. + + Example:: + + -> { "execute": "query-ac-adapter" } + <- { "return": { "connected": true }} + +Examples +-------- + +QMP control mode (default - recommended):: + + # Start with QMP control + qemu-system-x86_64 -device acad -qmp tcp:localhost:4444,server,wait=off + + # From another terminal, set AC adapter state via QMP: + echo '{"execute":"qmp_capabilities"} + {"execute":"ac-adapter-set-state", + "arguments":{"connected":true}}' | \ + nc -N localhost 4444 + +Sysfs mode (mirror host AC adapter):: + + # Enable sysfs mode to mirror host AC adapter + qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true + + # Custom probe interval (5 seconds) + qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true,probe_interval=5000 + + # Specific AC adapter path + qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true,sysfs_path=/sys/class/power_supply/ADP1 + +Testing with fake AC adapter:: + + # Create fake AC adapter files for testing + mkdir -p /tmp/fake_ac + echo "Mains" > /tmp/fake_ac/type + echo "1" > /tmp/fake_ac/online # 1 = connected, 0 = disconnected + + # Use fake AC adapter in sysfs mode + qemu-system-x86_64 -device acad,use-qmp=false,enable-sysfs=true,sysfs_path=/tmp/fake_ac + + # Update AC adapter state while VM is running (from another terminal) + echo "0" > /tmp/fake_ac/online # Disconnect AC adapter + echo "1" > /tmp/fake_ac/online # Reconnect AC adapter + +Combined with battery device:: + + # QMP mode (recommended) + qemu-system-x86_64 -device battery -device acad + + # Sysfs mode (desktop virtualization) + qemu-system-x86_64 -device battery,use-qmp=false,enable-sysfs=true \ + -device acad,use-qmp=false,enable-sysfs=true diff --git a/docs/specs/index.rst b/docs/specs/index.rst index 616e8228cc..e144afcd90 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -22,6 +22,7 @@ guest hardware that is specific to QEMU. acpi_pci_hotplug acpi_nvdimm acpi_erst + acad battery sev-guest-firmware fw_cfg diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index 64403378bd..9d28c3addf 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -69,6 +69,10 @@ config ACPI_VIOT bool depends on ACPI +config AC_ADAPTER + bool + depends on ACPI + config ACPI_HW_REDUCED bool select ACPI diff --git a/hw/acpi/acad.c b/hw/acpi/acad.c new file mode 100644 index 0000000000..699198c194 --- /dev/null +++ b/hw/acpi/acad.c @@ -0,0 +1,447 @@ +/* + * QEMU emulated AC adapter device. + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "trace.h" +#include "hw/isa/isa.h" +#include "hw/acpi/acpi.h" +#include "hw/nvram/fw_cfg.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/acpi/acpi_aml_interface.h" +#include "qapi/qapi-commands-acpi.h" + +#include "hw/acpi/acad.h" + +#define AC_ADAPTER_DEVICE(obj) OBJECT_CHECK(ACADState, (obj), \ + TYPE_AC_ADAPTER) + +#define AC_STA_ADDR 0 + +#define SYSFS_PATH "/sys/class/power_supply" +#define AC_ADAPTER_TYPE "Mains" +#define MAX_ALLOWED_TYPE_LENGTH 16 + +enum { + AC_ADAPTER_OFFLINE = 0, + AC_ADAPTER_ONLINE = 1, +}; + +typedef struct ACADState { + ISADevice dev; + MemoryRegion io; + uint16_t ioport; + uint8_t state; + bool use_qmp_control; + bool qmp_connected; + bool enable_sysfs; + + QEMUTimer *probe_state_timer; + uint64_t probe_state_interval; + + char *acad_path; +} ACADState; + +static const char *online_file = "online"; +static const char *type_file = "type"; + +static inline bool acad_file_accessible(char *path, const char *file) +{ + char full_path[PATH_MAX]; + int path_len; + + path_len = snprintf(full_path, PATH_MAX, "%s/%s", path, file); + if (path_len < 0 || path_len >= PATH_MAX) { + return false; + } + + if (access(full_path, R_OK) == 0) { + return true; + } + return false; +} + +static void acad_get_state(ACADState *s) +{ + char file_path[PATH_MAX]; + int path_len; + uint8_t val; + FILE *ff; + + path_len = snprintf(file_path, PATH_MAX, "%s/%s", s->acad_path, + online_file); + if (path_len < 0 || path_len >= PATH_MAX) { + warn_report("Could not read the AC adapter state."); + return; + } + + ff = fopen(file_path, "r"); + if (ff == NULL) { + warn_report("Could not read the AC adapter state."); + return; + } + + if (!fscanf(ff, "%hhu", &val)) { + warn_report("AC adapter state unreadable."); + } else { + switch (val) { + case AC_ADAPTER_OFFLINE: + case AC_ADAPTER_ONLINE: + s->state = val; + break; + default: + warn_report("AC adapter state undetermined."); + } + } + fclose(ff); +} + +static void acad_get_dynamic_status(ACADState *s) +{ + if (s->use_qmp_control) { + s->state = s->qmp_connected ? AC_ADAPTER_ONLINE : AC_ADAPTER_OFFLINE; + } else if (s->enable_sysfs) { + acad_get_state(s); + } else { + s->state = AC_ADAPTER_OFFLINE; + } + + trace_acad_get_dynamic_status(s->state); +} + +static void acad_probe_state(void *opaque) +{ + ACADState *s = opaque; + + uint8_t state_before = s->state; + + acad_get_dynamic_status(s); + + if (state_before != s->state) { + Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL); + acpi_send_event(DEVICE(obj), ACPI_AC_ADAPTER_CHANGE_STATUS); + } + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->probe_state_interval); +} + +static void acad_probe_state_timer_init(ACADState *s) +{ + if (s->enable_sysfs && s->probe_state_interval > 0) { + s->probe_state_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, + acad_probe_state, s); + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->probe_state_interval); + } +} + +static bool acad_verify_sysfs(ACADState *s, char *path) +{ + FILE *ff; + char type_path[PATH_MAX]; + int path_len; + char val[MAX_ALLOWED_TYPE_LENGTH]; + + path_len = snprintf(type_path, PATH_MAX, "%s/%s", path, type_file); + if (path_len < 0 || path_len >= PATH_MAX) { + return false; + } + + ff = fopen(type_path, "r"); + if (ff == NULL) { + return false; + } + + if (fgets(val, MAX_ALLOWED_TYPE_LENGTH, ff) == NULL) { + fclose(ff); + return false; + } else { + val[strcspn(val, "\n")] = 0; + if (strncmp(val, AC_ADAPTER_TYPE, MAX_ALLOWED_TYPE_LENGTH)) { + fclose(ff); + return false; + } + } + fclose(ff); + + return acad_file_accessible(path, online_file); +} + +static bool get_acad_path(DeviceState *dev) +{ + ACADState *s = AC_ADAPTER_DEVICE(dev); + DIR *dir; + struct dirent *ent; + char bp[PATH_MAX]; + int path_len; + + if (s->acad_path) { + return acad_verify_sysfs(s, s->acad_path); + } + + dir = opendir(SYSFS_PATH); + if (dir == NULL) { + return false; + } + + ent = readdir(dir); + while (ent != NULL) { + if (ent->d_name[0] != '.') { + path_len = snprintf(bp, PATH_MAX, "%s/%s", SYSFS_PATH, + ent->d_name); + if (path_len < 0 || path_len >= PATH_MAX) { + return false; + } + if (acad_verify_sysfs(s, bp)) { + qdev_prop_set_string(dev, AC_ADAPTER_PATH_PROP, bp); + closedir(dir); + return true; + } + } + ent = readdir(dir); + } + closedir(dir); + + return false; +} + +static void acad_realize(DeviceState *dev, Error **errp) +{ + ISADevice *d = ISA_DEVICE(dev); + ACADState *s = AC_ADAPTER_DEVICE(dev); + FWCfgState *fw_cfg = fw_cfg_find(); + uint16_t *acad_port; + char err_details[32] = {}; + + trace_acad_realize(); + + if (s->use_qmp_control && s->enable_sysfs) { + error_setg(errp, "Cannot enable both QMP control and sysfs mode"); + return; + } + + if (s->enable_sysfs) { + if (!s->acad_path) { + strcpy(err_details, " Try using 'sysfs_path='"); + } + + if (!get_acad_path(dev)) { + error_setg(errp, "AC adapter sysfs path not found or unreadable.%s", + err_details); + return; + } + } + + isa_register_ioport(d, &s->io, s->ioport); + + acad_probe_state_timer_init(s); + + if (!fw_cfg) { + return; + } + + acad_port = g_malloc(sizeof(*acad_port)); + *acad_port = cpu_to_le16(s->ioport); + fw_cfg_add_file(fw_cfg, "etc/acad-port", acad_port, + sizeof(*acad_port)); +} + +static const Property acad_device_properties[] = { + DEFINE_PROP_UINT16(AC_ADAPTER_IOPORT_PROP, ACADState, ioport, 0x53c), + DEFINE_PROP_BOOL("use-qmp", ACADState, use_qmp_control, true), + DEFINE_PROP_BOOL("enable-sysfs", ACADState, enable_sysfs, false), + DEFINE_PROP_UINT64(AC_ADAPTER_PROBE_STATE_INTERVAL, ACADState, + probe_state_interval, 2000), + DEFINE_PROP_STRING(AC_ADAPTER_PATH_PROP, ACADState, acad_path), +}; + +static const VMStateDescription acad_vmstate = { + .name = "acad", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(ioport, ACADState), + VMSTATE_UINT64(probe_state_interval, ACADState), + VMSTATE_END_OF_LIST() + } +}; + +static void build_acad_aml(AcpiDevAmlIf *adev, Aml *scope) +{ + Aml *dev, *field, *method, *pkg; + Aml *acad_state; + Aml *sb_scope; + ACADState *s = AC_ADAPTER_DEVICE(adev); + + acad_state = aml_local(0); + + sb_scope = aml_scope("\\_SB"); + dev = aml_device("ADP0"); + aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0003"))); + + aml_append(dev, aml_operation_region("ACST", AML_SYSTEM_IO, + aml_int(s->ioport), + AC_ADAPTER_LEN)); + field = aml_field("ACST", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); + aml_append(field, aml_named_field("PWRS", 8)); + aml_append(dev, field); + + method = aml_method("_PSR", 0, AML_NOTSERIALIZED); + aml_append(method, aml_store(aml_name("PWRS"), acad_state)); + aml_append(method, aml_return(acad_state)); + aml_append(dev, method); + + method = aml_method("_PCL", 0, AML_NOTSERIALIZED); + pkg = aml_package(1); + aml_append(pkg, aml_name("_SB")); + aml_append(method, aml_return(pkg)); + aml_append(dev, method); + + method = aml_method("_PIF", 0, AML_NOTSERIALIZED); + pkg = aml_package(6); + /* Power Source State */ + aml_append(pkg, aml_int(0)); /* Non-redundant, non-shared */ + /* Maximum Output Power */ + aml_append(pkg, aml_int(AC_ADAPTER_VAL_UNKNOWN)); + /* Maximum Input Power */ + aml_append(pkg, aml_int(AC_ADAPTER_VAL_UNKNOWN)); + /* Model Number */ + aml_append(pkg, aml_string("QADP001")); + /* Serial Number */ + aml_append(pkg, aml_string("SN00000")); + /* OEM Information */ + aml_append(pkg, aml_string("QEMU")); + aml_append(method, aml_return(pkg)); + aml_append(dev, method); + + aml_append(sb_scope, dev); + aml_append(scope, sb_scope); + + /* Status Change */ + method = aml_method("\\_GPE._E0A", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.ADP0"), aml_int(0x80))); + aml_append(scope, method); +} + +static void acad_class_init(ObjectClass *class, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(class); + AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(class); + + dc->realize = acad_realize; + device_class_set_props(dc, acad_device_properties); + dc->vmsd = &acad_vmstate; + adevc->build_dev_aml = build_acad_aml; +} + +static uint64_t acad_ioport_read(void *opaque, hwaddr addr, unsigned size) +{ + ACADState *s = opaque; + + acad_get_dynamic_status(s); + + switch (addr) { + case AC_STA_ADDR: + return s->state; + default: + warn_report("AC adapter: guest read unknown value."); + trace_acad_ioport_read_unknown(); + return 0; + } +} + +static const MemoryRegionOps acad_ops = { + .read = acad_ioport_read, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void acad_instance_init(Object *obj) +{ + ACADState *s = AC_ADAPTER_DEVICE(obj); + + memory_region_init_io(&s->io, obj, &acad_ops, s, "acad", + AC_ADAPTER_LEN); +} + +static const TypeInfo acad_info = { + .name = TYPE_AC_ADAPTER, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ACADState), + .class_init = acad_class_init, + .instance_init = acad_instance_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_ACPI_DEV_AML_IF }, + { }, + }, +}; + +static ACADState *find_acad_device(void) +{ + Object *o = object_resolve_path_type("", TYPE_AC_ADAPTER, NULL); + if (!o) { + return NULL; + } + return AC_ADAPTER_DEVICE(o); +} + +void qmp_ac_adapter_set_state(bool connected, Error **errp) +{ + ACADState *s = find_acad_device(); + + if (!s) { + error_setg(errp, "No AC adapter device found"); + return; + } + + s->qmp_connected = connected; + + Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL); + if (obj) { + acpi_send_event(DEVICE(obj), ACPI_AC_ADAPTER_CHANGE_STATUS); + } +} + +AcAdapterInfo *qmp_query_ac_adapter(Error **errp) +{ + ACADState *s = find_acad_device(); + AcAdapterInfo *ret; + + if (!s) { + error_setg(errp, "No AC adapter device found"); + return NULL; + } + + ret = g_new0(AcAdapterInfo, 1); + + if (s->use_qmp_control) { + ret->connected = s->qmp_connected; + } else { + acad_get_dynamic_status(s); + ret->connected = (s->state == AC_ADAPTER_ONLINE); + } + + return ret; +} + +static void acad_register_types(void) +{ + type_register_static(&acad_info); +} + +type_init(acad_register_types) diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build index 10379a7b2c..2b24951f28 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -32,6 +32,7 @@ if have_tpm acpi_ss.add(files('tpm.c')) endif acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c')) +acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c')) system_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c', 'acpi_interface.c')) system_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_false: files('pci-bridge-stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events index dd3e815482..68ac6e9701 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -92,3 +92,8 @@ acpi_nvdimm_invalid_revision(uint32_t revision) "Revision 0x%" PRIx32 " is not s battery_realize(void) "Battery device realize entry" battery_get_dynamic_status(uint32_t state, uint32_t rate, uint32_t charge) "Battery read state: 0x%"PRIx32", rate: %"PRIu32", charge: %"PRIu32 battery_ioport_read_unknown(void) "Battery read unknown" + +# acad.c +acad_realize(void) "AC adapter device realize entry" +acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8 +acad_ioport_read_unknown(void) "AC adapter read unknown" diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 2c878fd112..baab382a2e 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -40,6 +40,7 @@ config PC imply NVDIMM imply FDC_ISA imply BATTERY + imply AC_ADAPTER select I8259 select I8254 select PCKBD diff --git a/include/hw/acpi/acad.h b/include/hw/acpi/acad.h new file mode 100644 index 0000000000..a4e7149488 --- /dev/null +++ b/include/hw/acpi/acad.h @@ -0,0 +1,27 @@ +/* + * QEMU emulated AC adapter device. + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_ACPI_AC_ADAPTER_H +#define HW_ACPI_AC_ADAPTER_H + +#define TYPE_AC_ADAPTER "acad" +#define AC_ADAPTER_IOPORT_PROP "ioport" +#define AC_ADAPTER_PATH_PROP "sysfs_path" +#define AC_ADAPTER_PROBE_STATE_INTERVAL "probe_interval" + +#define AC_ADAPTER_VAL_UNKNOWN 0xFFFFFFFF + +#define AC_ADAPTER_LEN 1 + +#endif diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h index 3064ef6734..588fbbd05f 100644 --- a/include/hw/acpi/acpi_dev_interface.h +++ b/include/hw/acpi/acpi_dev_interface.h @@ -14,6 +14,7 @@ typedef enum { ACPI_VMGENID_CHANGE_STATUS = 32, ACPI_POWER_DOWN_STATUS = 64, ACPI_BATTERY_CHANGE_STATUS = 128, + ACPI_AC_ADAPTER_CHANGE_STATUS = 1024, } AcpiEventStatusBits; #define TYPE_ACPI_DEVICE_IF "acpi-device-interface" diff --git a/qapi/acpi.json b/qapi/acpi.json index d1ad663bfd..52e151f0e6 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -215,3 +215,52 @@ ## { 'command': 'query-battery', 'returns': 'BatteryInfo' } + +## +# @ac-adapter-set-state: +# +# Set the state of the emulated AC adapter device +# +# @connected: whether the AC adapter is connected +# + +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "ac-adapter-set-state", +# "arguments": { "connected": true } } +# <- { "return": {} } +## +{ 'command': 'ac-adapter-set-state', + 'data': { 'connected': 'bool' } } + +## +# @AcAdapterInfo: +# +# AC adapter state information +# +# @connected: whether the AC adapter is connected +# +# Since: 10.2 +## +{ 'struct': 'AcAdapterInfo', + 'data': { 'connected': 'bool' } } + +## +# @query-ac-adapter: +# +# Query the current state of the emulated AC adapter device +# +# Returns: AC adapter connection state +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "query-ac-adapter" } +# <- { "return": { "connected": true } } +## +{ 'command': 'query-ac-adapter', + 'returns': 'AcAdapterInfo' } -- 2.50.1