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 60617CA0EFC 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 1up9Na-0007Kv-BW; Thu, 21 Aug 2025 13:47:14 -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-0007J0-Ax for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:47:00 -0400 Received: from mail-wm1-x332.google.com ([2a00:1450:4864:20::332]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1up9NI-0003n8-QE for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:47:00 -0400 Received: by mail-wm1-x332.google.com with SMTP id 5b1f17b1804b1-45a1b00a65fso6898895e9.0 for ; Thu, 21 Aug 2025 10:46:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755798415; x=1756403215; 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=Qzuofo+VxQ2Vqu+msrfT+81t9nCiffS2fcQ5tvevD/U=; b=AV6Czbu1CJchkOV1px/NGhRBQBeDeC4cs07MipnrbDI35vALwzOkMllNkPj5sgXibG q18mHpRN5o7S+MpO/pm0zEhHAcS8H35VUKrsemPh3hUS3wYOL5e7x7gyU1rUq2LtYQke kR9/l3WC6AylKShYdI7q/CfpDXGEkmrljyNjcLO900PqZyKYK2LMAmKwLZOyrj89hFEf 9w6LDM1A7AZBXkJjvF2g3ae3PIwOoqEN+cJpSuXT6lCzbwMsbnBTd/Pugi+/uRsL0TCq LsMeVUaiB0LI/0fQtIxnv845Lsfdl5jdf2V7+jMxYxb/SLKcNVPTS2cMJW+Lj/AYJVgT cipA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755798415; x=1756403215; 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=Qzuofo+VxQ2Vqu+msrfT+81t9nCiffS2fcQ5tvevD/U=; b=cs1May89oUxzzZXWo7OI0/B0p8VYaTcYTDaXtc/SANqGEy6joyaSD91us0FTSRLHjo l2AYxgrt0dAICLuuSt2H+0trqeIi7bc12r3KEMkfjJ2T8qXrY85b1x6nsMsAZ9X26VbV jK6AGtlMm8JW5Zlu9LO0g2MTOPD5GlpoMNB2Z2HQhkOx/rSvbEJFt0B8BH8wrHaHuKaB /8XVwJvmqC00UPe2veA94gXRF5zmvHbqVCreRr9BWJCMsE7YJ2BaNyRwDhW1Y3cce9+Y SMOVHiVajU44CMDLCYCm4sPz8GSUCnHDd0/8PESlXcUHS8itwJfwo5L39i0hlYWaVm+t SPYQ== X-Forwarded-Encrypted: i=1; AJvYcCVGKM3zN2bt93zNJt6Dx5n+sbImYN7rnFGVOje+c/CylHwgz8FyFTBjfv9uDpdA12bh/NdrPADCc9UP@nongnu.org X-Gm-Message-State: AOJu0Yzg20eWqZuRXr7KG4nr/8asskofOfK3lsDBWsbE4KQdkud7oa1Y LhhiWtMKJRRfIXHd3d07ZmmjXCsDKFP+vV4ngpRcWfPWsFo6w3CHnKOW X-Gm-Gg: ASbGnctht8cznya+TmWKy3Z0nDkYonykO+5/TbDlbHIxdi3zdIY//L8867YKG5sYbyo e3wsveD32kqYi3MLHOuywkgXB6zaNPhmXg0LYUEHw0EsmIwl8vwitbPyUiWcFG9SuQZkGjETOtD XWmkQy4NSCUOKQG2Wfk6vwKHqjBHClFyNAKlpB2Mlh+QYqR+wHdeGG0WHvIrpK7CI9lQk3TG0dt 4lcXRZdkC9D3EOiuEFalXx4CQ9ZutZ76f6Rlmz+MSKIxKRiGoouGw7SNta+a034IPCFkS6zYAOD rrAdxJnm2yzayAzILcV/IPnt7vl3Q8InCpFdNS2GryNwSqVMnKVskdAAJpXmhZ6qw0MUELWgaBH b0Tr+a/OdMT498S+dsNZRjldV6RaNIsDi7UtFY4cr8774Rhz098f8TvrEGc6Mwp4aogPKuPX0oj 4= X-Google-Smtp-Source: AGHT+IHfPwzxbJjFmQRLhCeQF/d11s0oqhhpQx0NeQMCoNc+iqI3Zqe/CfynoQnutdRc0FskaQrrgA== X-Received: by 2002:a05:600c:1c1b:b0:456:f00:4b5d with SMTP id 5b1f17b1804b1-45b4d8309f3mr27502805e9.22.1755798414864; Thu, 21 Aug 2025 10:46:54 -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.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 21 Aug 2025 10:46:54 -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 4/4] hw/acpi: Introduce the QEMU lid button Date: Thu, 21 Aug 2025 20:45:52 +0300 Message-ID: <20250821174554.40607-5-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::332; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x332.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 lid button device communicates laptop lid state to the guest via ACPI. It supports two modes of operation: 1. QMP control mode (default): Lid state is controlled programmatically via QMP commands for consistent behavior across environments. 2. Host mirroring mode (optional): The device reflects the host's lid button state from procfs (/proc/acpi/button/lid/*/state). State changes trigger ACPI notifications to the guest. Properties: - 'use-qmp': Enable QMP control mode (default: true) - 'enable-procfs': Enable host lid button mirroring (default: false) - 'probe_interval': Probe interval in ms for procfs mode (default: 2000) - 'procfs_path': Override default procfs path /proc/acpi/button The device implements the ACPI_DEV_AML_IF interface to generate its own AML code, placing the LID0 device directly under \_SB scope. QMP commands: - lid-button-set-state: Set lid open/closed state - query-lid-button: Query current lid state Signed-off-by: Leonid Bloch --- MAINTAINERS | 6 + docs/specs/button.rst | 189 ++++++++++++ docs/specs/index.rst | 1 + hw/acpi/Kconfig | 4 + hw/acpi/button.c | 438 +++++++++++++++++++++++++++ hw/acpi/meson.build | 1 + hw/acpi/trace-events | 5 + hw/i386/Kconfig | 1 + include/hw/acpi/acpi_dev_interface.h | 1 + include/hw/acpi/button.h | 25 ++ qapi/acpi.json | 49 +++ 11 files changed, 720 insertions(+) create mode 100644 docs/specs/button.rst create mode 100644 hw/acpi/button.c create mode 100644 include/hw/acpi/button.h diff --git a/MAINTAINERS b/MAINTAINERS index 612efcb686..4d54a2ffb5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2933,6 +2933,12 @@ S: Maintained F: hw/acpi/acad.* F: docs/specs/acad.rst +Button +M: Leonid Bloch +S: Maintained +F: hw/acpi/button.* +F: docs/specs/button.rst + Subsystems ---------- Overall Audio backends diff --git a/docs/specs/button.rst b/docs/specs/button.rst new file mode 100644 index 0000000000..10a940b9f5 --- /dev/null +++ b/docs/specs/button.rst @@ -0,0 +1,189 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +======================== +Laptop Lid Button Device +======================== + +The button device provides laptop lid button state information to the guest. +It supports two operating modes: + +1. **QMP Control Mode** (default): Lid state is controlled via QMP commands, + providing deterministic control for testing and migration safety. +2. **Procfs Mode**: Lid state mirrors the host's physical lid button, useful + for desktop virtualization where the guest should see the host's lid state. + +Configuration +------------- + +The lid button device is created as an ISA device using ``-device button``. + +Operating Modes +~~~~~~~~~~~~~~~ + +**QMP Control Mode** (``use-qmp=true``, default) + Lid state is controlled via QMP commands. This mode is recommended for: + + * Production environments requiring migration support + * Testing with predictable lid states + * Environments without host lid button access + * Security-sensitive deployments + +**Procfs Mode** (``enable-procfs=true``) + Lid mirrors the host's physical lid button. This mode is useful for: + + * Desktop virtualization on laptops + * Development and testing with real lid button behavior + + Note: Procfs mode reads host files and runs timers, which may impact + security and migration. Use with caution in production. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x53d) + I/O port base address for the lid button device register. + +``use-qmp`` (default: true) + Enable QMP control mode. When true, lid state is controlled via + QMP commands. Cannot be used together with ``enable-procfs=true``. + +``enable-procfs`` (default: false) + Enable procfs mode to mirror the host's lid button. Cannot be used together + with ``use-qmp=true``. + +``probe_interval`` (default: 2000) + Time interval between periodic probes in milliseconds (procfs mode only). + The minimum allowed value is 10ms to prevent excessive polling. + +``procfs_path`` (default: /proc/acpi/button) + Path to the host's lid button procfs directory (procfs mode only). The device + will automatically scan this directory to find the lid state file. Use this + property to specify a different path or to provide a custom location for + testing purposes. + +Host Lid Button Detection +------------------------- + +The host's lid button information is taken from:: + + /proc/acpi/button/lid/*/state + +This file is expected to be formatted as: + +- ``state: open`` (if the lid is open) +- ``state: closed`` (if the lid is closed) + +These formats are based on the Linux 'button' driver. + +The device automatically scans the ``/proc/acpi/button/lid/`` directory +for subdirectories containing a readable ``state`` file. If the procfs path +differs, a different lid button needs to be probed, or even if a "fake" host +lid button is to be provided, the ``procfs_path`` property allows overriding +the default detection. + +ACPI Interface +-------------- + +The lid button device is exposed to the guest as an ACPI device with: + +- **HID**: ``PNP0C0D`` (Lid Device) +- **Device Path**: ``\_SB.LID0`` +- **Notification Values**: + + - ``0x80``: Status change (lid opened/closed) + +ACPI Methods +~~~~~~~~~~~~ + +``_LID`` (Lid Status) + Returns the current lid state (0 = closed, 1 = open). + +I/O Interface +------------- + +The device uses a single I/O port register: + +- **Port**: ``ioport`` property value (default 0x53d) +- **Size**: 1 byte +- **Access**: Read-only + +Register Layout +~~~~~~~~~~~~~~~ + +**LIDS** (offset 0x00, 1 byte) + Current lid state: + + - ``0x00``: Lid closed + - ``0x01``: Lid open + +QMP Commands +------------ + +When using QMP control mode (default), the following commands are available: + +``lid-button-set-state`` + Set the lid button state. + + * ``open``: Whether the lid is open (boolean) + + Example:: + + -> { "execute": "lid-button-set-state", + "arguments": { "open": true }} + <- { "return": {} } + +``query-lid-button`` + Query the current lid button state. + + Example:: + + -> { "execute": "query-lid-button" } + <- { "return": { "open": true }} + +Examples +-------- + +QMP control mode (default - recommended):: + + # Start with QMP control + qemu-system-x86_64 -device button -qmp tcp:localhost:4444,server,wait=off + + # From another terminal, set lid state via QMP: + echo '{"execute":"qmp_capabilities"} + {"execute":"lid-button-set-state", + "arguments":{"open":false}}' | \ + nc -N localhost 4444 + +Procfs mode (mirror host lid button):: + + # Enable procfs mode to mirror host lid button + qemu-system-x86_64 -device button,use-qmp=false,enable-procfs=true + + # Custom probe interval (5 seconds) + qemu-system-x86_64 -device button,use-qmp=false,enable-procfs=true,probe_interval=5000 + + # Custom procfs path + qemu-system-x86_64 -device button,use-qmp=false,enable-procfs=true,procfs_path=/custom/path + +Testing with fake lid button:: + + # Create fake lid button files for testing + mkdir -p /tmp/fake_lid/lid/LID0 + echo "state: open" > /tmp/fake_lid/lid/LID0/state # Format: "state: open" or "state: closed" + + # Use fake lid button in procfs mode + qemu-system-x86_64 -device button,use-qmp=false,enable-procfs=true,procfs_path=/tmp/fake_lid + + # Update lid state while VM is running (from another terminal) + echo "state: closed" > /tmp/fake_lid/lid/LID0/state # Close lid + echo "state: open" > /tmp/fake_lid/lid/LID0/state # Open lid + +Combined with other laptop devices:: + + # QMP mode (recommended) + qemu-system-x86_64 -device battery -device acad -device button + + # Procfs/sysfs mode (desktop virtualization) + qemu-system-x86_64 -device battery,use-qmp=false,enable-sysfs=true \ + -device acad,use-qmp=false,enable-sysfs=true \ + -device button,use-qmp=false,enable-procfs=true diff --git a/docs/specs/index.rst b/docs/specs/index.rst index e144afcd90..e1c9b91b7b 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -24,6 +24,7 @@ guest hardware that is specific to QEMU. acpi_erst acad battery + button sev-guest-firmware fw_cfg fsi diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index 9d28c3addf..6600685855 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -73,6 +73,10 @@ config AC_ADAPTER bool depends on ACPI +config BUTTON + bool + depends on ACPI + config ACPI_HW_REDUCED bool select ACPI diff --git a/hw/acpi/button.c b/hw/acpi/button.c new file mode 100644 index 0000000000..dfe86af713 --- /dev/null +++ b/hw/acpi/button.c @@ -0,0 +1,438 @@ +/* + * QEMU emulated lid button 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/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; + bool use_qmp_control; + bool qmp_lid_open; + bool enable_procfs; + + 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(); + + if (s->use_qmp_control) { + s->lid_state = s->qmp_lid_open ? LID_OPEN : LID_CLOSED; + } else if (s->enable_procfs) { + button_get_lid_state(s); + } else { + s->lid_state = LID_CLOSED; + } +} + +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->enable_procfs && 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->use_qmp_control && s->enable_procfs) { + error_setg(errp, "Cannot enable both QMP control and procfs mode"); + return; + } + + /* Initialize lid to open by default when in QMP mode */ + if (s->use_qmp_control) { + s->qmp_lid_open = true; + } + + 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->enable_procfs) { + 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 const Property button_device_properties[] = { + DEFINE_PROP_UINT16(BUTTON_IOPORT_PROP, BUTTONState, ioport, 0x53d), + DEFINE_PROP_BOOL("use-qmp", BUTTONState, use_qmp_control, true), + DEFINE_PROP_BOOL("enable-procfs", BUTTONState, enable_procfs, false), + DEFINE_PROP_UINT64(BUTTON_PROBE_STATE_INTERVAL, BUTTONState, + probe_state_interval, 2000), + DEFINE_PROP_STRING(BUTTON_PATH_PROP, BUTTONState, button_path), +}; + +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 build_button_aml(AcpiDevAmlIf *adev, Aml *scope) +{ + Aml *dev, *field, *method; + Aml *button_state; + Aml *sb_scope; + BUTTONState *s = BUTTON_DEVICE(adev); + + button_state = aml_local(0); + + sb_scope = aml_scope("\\_SB"); + dev = aml_device("LID0"); + aml_append(dev, aml_name_decl("_HID", aml_string("PNP0C0D"))); + + aml_append(dev, aml_operation_region("LSTA", AML_SYSTEM_IO, + aml_int(s->ioport), + BUTTON_LEN)); + field = aml_field("LSTA", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); + aml_append(field, aml_named_field("LIDS", 8)); + aml_append(dev, field); + + method = aml_method("_LID", 0, AML_NOTSERIALIZED); + aml_append(method, aml_store(aml_name("LIDS"), button_state)); + aml_append(method, aml_return(button_state)); + aml_append(dev, method); + + aml_append(sb_scope, dev); + aml_append(scope, sb_scope); + + /* Status Change */ + method = aml_method("\\_GPE._E0B", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.LID0"), aml_int(0x80))); + aml_append(scope, method); +} + +static void button_class_init(ObjectClass *class, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(class); + AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(class); + + dc->realize = button_realize; + device_class_set_props(dc, button_device_properties); + dc->vmsd = &button_vmstate; + adevc->build_dev_aml = build_button_aml; +} + +static uint64_t button_ioport_read(void *opaque, hwaddr addr, unsigned size) +{ + BUTTONState *s = opaque; + + button_get_dynamic_status(s); + + switch (addr) { + case BUTTON_STA_ADDR: + return s->lid_state; + default: + 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, + .interfaces = (InterfaceInfo[]) { + { TYPE_ACPI_DEV_AML_IF }, + { }, + }, +}; + +static BUTTONState *find_button_device(void) +{ + Object *o = object_resolve_path_type("", TYPE_BUTTON, NULL); + if (!o) { + return NULL; + } + return BUTTON_DEVICE(o); +} + +void qmp_lid_button_set_state(bool open, Error **errp) +{ + BUTTONState *s = find_button_device(); + + if (!s) { + error_setg(errp, "No lid button device found"); + return; + } + + s->qmp_lid_open = open; + + Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL); + if (obj) { + acpi_send_event(DEVICE(obj), ACPI_BUTTON_CHANGE_STATUS); + } +} + +LidButtonInfo *qmp_query_lid_button(Error **errp) +{ + BUTTONState *s = find_button_device(); + LidButtonInfo *ret; + + if (!s) { + error_setg(errp, "No lid button device found"); + return NULL; + } + + ret = g_new0(LidButtonInfo, 1); + + if (s->use_qmp_control) { + ret->open = s->qmp_lid_open; + } else { + button_get_dynamic_status(s); + ret->open = (s->lid_state == LID_OPEN); + } + + return ret; +} + +static void button_register_types(void) +{ + type_register_static(&button_info); +} + +type_init(button_register_types) diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build index 2b24951f28..94993ecb9a 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -33,6 +33,7 @@ if have_tpm endif 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')) 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 68ac6e9701..816f027bb3 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -97,3 +97,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 baab382a2e..1d2d809028 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -41,6 +41,7 @@ config PC imply FDC_ISA imply BATTERY imply AC_ADAPTER + imply BUTTON select I8259 select I8254 select PCKBD diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h index 588fbbd05f..4f5bcc15da 100644 --- a/include/hw/acpi/acpi_dev_interface.h +++ b/include/hw/acpi/acpi_dev_interface.h @@ -15,6 +15,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..fa5f7001b2 --- /dev/null +++ b/include/hw/acpi/button.h @@ -0,0 +1,25 @@ +/* + * QEMU emulated button 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_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 + +#endif diff --git a/qapi/acpi.json b/qapi/acpi.json index 52e151f0e6..bcbddbfbd5 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -264,3 +264,52 @@ ## { 'command': 'query-ac-adapter', 'returns': 'AcAdapterInfo' } + +## +# @lid-button-set-state: +# +# Set the state of the emulated laptop lid button device +# +# @open: whether the lid is open +# + +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "lid-button-set-state", +# "arguments": { "open": true } } +# <- { "return": {} } +## +{ 'command': 'lid-button-set-state', + 'data': { 'open': 'bool' } } + +## +# @LidButtonInfo: +# +# Lid button state information +# +# @open: whether the lid is open +# +# Since: 10.2 +## +{ 'struct': 'LidButtonInfo', + 'data': { 'open': 'bool' } } + +## +# @query-lid-button: +# +# Query the current state of the emulated laptop lid button device +# +# Returns: lid button state +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "query-lid-button" } +# <- { "return": { "open": true } } +## +{ 'command': 'query-lid-button', + 'returns': 'LidButtonInfo' } -- 2.50.1