From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769832; cv=none; d=zohomail.com; s=zohoarc; b=mLGpjNMgLlzOWgRjoAy4z/CEVaJ6UO1OVNX1pYvzeNmvRFxEA8tzaln3iVPvAILNlaq8hz0LYurAdbFot6uxPVjUD8AZDzopNtTRIAFueKKhgvv2msM3T8lh1imZClBrTfLnftSprn3jNYuaZ/zqNwSkMqX8S35obG2b29a8obo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769832; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=XmJyzF++EuXN5ZLIdNyei6W6W9VYeSiHX7DolYmw/4E=; b=XdK+tquNVJVyRzK078SnyzWL+wsOzZ1+tIeRl1EOQEgB/g/zdiAdeNczu4/xZx4N35pB6WO//qONEBibNhCVC9hHoUNAV7qMk2Ya16ZFpeNfTBuEgMENLXX6OOblO+s3mW06HwJbjOaqDdvUwSiMzlXN8l3L0Hl3i0TcWFZI1KU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769832684957.9856993467216; Mon, 25 May 2026 21:30:32 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjQd-0007Pf-US; Tue, 26 May 2026 00:30:07 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQc-0007Oq-Kp for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:06 -0400 Received: from mail-wm1-x32a.google.com ([2a00:1450:4864:20::32a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQa-0007So-VF for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:06 -0400 Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-48984d29fe3so105946485e9.0 for ; Mon, 25 May 2026 21:30:04 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769803; x=1780374603; 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=XmJyzF++EuXN5ZLIdNyei6W6W9VYeSiHX7DolYmw/4E=; b=GDJ7w57J6amIoTYax/kO1dw2W5q/3jiJ0RlkbbmVyHdLIWGmNTSUXd9VsjAmTxKgCd x39O7rlI3aOWYTrgfh+ILQiaBnM8S3KT6DyaudRsO11RUfARFXKK91BM1LI0VwlU1yVI sP/YjGjjDhTs+k7rotwvfkkETg8WB2dePzG0TtRfXbTUy+EDrEdaQ0X2xww/ZxuR61kF sBzfmYW1tZ1jYP3JfGeSUhSYDiQ0zVeU8ZEWGD2BGid/SiB4m8sRK9ywuVnq4BlQSZ8n AkORUEyT/O9m3Vf/wJDekofKCF+tp/OFNP16B+UQAbgUfYf4iXSwiPI6hwZ4e5bpmyOQ n6fA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769803; x=1780374603; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=XmJyzF++EuXN5ZLIdNyei6W6W9VYeSiHX7DolYmw/4E=; b=ILuXaiI9I7VPyx7beVpdzFcCDtPOAJx2iqyjbrt5aTJjQZUKFDTzzp8XZylHcNfoun eBVWgWNjocAIbwA1vJNu5TOTfoCYiRao63r3Y0du3eQ/wbLyejvk4hqHgAyNxwGtTJ+N sdW8vEnApfjDxyWr5SEeANRJPH0YOzMvzyXg6mVsGQjL0LSo9MaxUvCYxPZ8Da3wsFgE Rn9lYWAS7KUvdfUXOjVikuCsLyHpk92rveTViNIF0lMple4eZJp0qz7+j1++HV+qsnXU tJNO5saSHjw1oDww7Yz7qwq3L3eLgwIbMostp/P7fy8pSMZ+06hmXby2vTtYdk6so3vu gb4w== X-Forwarded-Encrypted: i=1; AFNElJ9N+e2ejd3vrmnKNEuNdPZPXs990iL/rW2XX8kIR2yUrvl4pMmW02s8bjABbJvG8XmiVY21mEsA58bN@nongnu.org X-Gm-Message-State: AOJu0YwUC1jTUdoiYp3PWBUHAEWPKomwMYr+gO2HF+k8eLFUmWvbZdlB 3N7+gkmAUbACqN9X+EQFXGQ64EQdT60/pHAAng/Ey2o+Dmfg26NQxe/A X-Gm-Gg: Acq92OFZCx4WmSrXN+83F2Gpm3sMscFxOlLVD69L/ZdLyeV/m0Z3l+mdIEnt7VlikwH MpYg2h6ih4JzrrqamjK9Sy3RHIrKDirUDeplW2jcRYxNWOJiK4+Sdytb36+DOJ0teTEljMWoIU6 Cp2p8rw0xjjufdBL8Yk0OcCdHJbivgfJqAEpY55nqnlnw0KEjW2Fp2bSfMDldYCUoJF77IMe5yi CbfGhwZ/hlta9PX7dw1hXnZbMPymR7WcBSaQv8dWFEpdKXWsa/JbCvRGnjE41o4EYg0x7xYoYNZ +K3XYYqVXy0R72stimQGQUN64jrEbVwMcoEOqV+oE/s5XmTec1ES3MnCRHwPn6KPtVQ/5c0jnNv xkMAnECn9ASeP4A8R1z7VTxRJ/It/OSV1OTMwNntg3WYVZ2dMDogCRL9vlJW6P188c0yTL0siOE UoVB+V19gWpbUq+W5brY4fHDc0A97Iz+aSBuZRimx6COisM1kllJEPRVnpSSC47IkB8GOS+9F97 4prqmQYYXrm9NKTHZZWk+GN X-Received: by 2002:a05:600c:3b97:b0:490:52fb:12dd with SMTP id 5b1f17b1804b1-49052fb14fdmr230836135e9.10.1779769803206; Mon, 25 May 2026 21:30:03 -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 v4 1/8] hw/acpi: Support extended GPE handling for additional ACPI devices Date: Tue, 26 May 2026 07:29:20 +0300 Message-ID: <20260526042928.9203-2-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32a; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32a.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769835125154100 Content-Type: text/plain; charset="utf-8" This patch extends the GPE (General Purpose Event) handling to support the maximum number of interrupts available based on the machine's GPE register length, rather than being limited to the first 8 bits. This change is needed to support additional ACPI devices that will be introduced in subsequent patches (Battery, AC adapter, and button devices). These new devices require GPE event bits beyond the first 8, which were previously not being properly handled by the event sending and SCI (System Control Interrupt) update mechanisms. The actual number of available GPE interrupts varies by machine type: - PIIX4: GPE_LEN =3D 4 (32 bits total across status and enable registers) - ICH9: ICH9_PMIO_GPE0_LEN =3D 16 (128 bits total) The patch modifies: - acpi_send_gpe_event(): Now properly propagates status bits across all available GPE registers based on the machine's gpe.len value - acpi_update_sci(): Checks all GPE registers for pending interrupts, not just the first byte Note: A future enhancement could refactor the GPE handling to use the bitmap API from bitops.h instead of the current manual bit manipulation, which would provide a cleaner interface for these operations. Signed-off-by: Leonid Bloch --- 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 a6a62a742d..ad7abe568d 100644 --- a/hw/acpi/core.c +++ b/hw/acpi/core.c @@ -733,19 +733,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] |=3D status; + int i; + AcpiEventStatusBits st =3D status; + + for (i =3D 0; i < ar->gpe.len / 2; i++) { + ar->gpe.sts[i] |=3D st; + st >>=3D TYPE_WIDTH(ar->gpe.sts[0]); + } + acpi_update_sci(ar, irq); } =20 void acpi_update_sci(ACPIREGS *regs, qemu_irq irq) { int sci_level, pm1a_sts; + bool gpe_sci =3D false; + int i; =20 pm1a_sts =3D acpi_pm1_evt_get_sts(regs); =20 + for (i =3D 0; i < regs->gpe.len / 2; i++) { + gpe_sci =3D gpe_sci || !!(regs->gpe.sts[i] & regs->gpe.en[i]); + } + sci_level =3D ((pm1a_sts & regs->pm1.evt.en & ACPI_BITMASK_PM1_COMMON_ENABLED) !=3D= 0) || - ((regs->gpe.sts[0] & regs->gpe.en[0]) !=3D 0); + gpe_sci; =20 qemu_set_irq(irq, sci_level); =20 --=20 2.54.0 From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769829; cv=none; d=zohomail.com; s=zohoarc; b=kkCkmwHP03HqRT1WXX+8D6SUQ2pcyMwpf9qXHMLl9t9PyAOFtiwwjU6XgvsH6mReZZFOR5GhR3tJW0EYZnpFEoPQsDocDiElQKryieZj9gn1UClBCuxsKF5o4wxF0vbcPjWL7dVtXACSAHu+lV1IombtvXUMRGFvDdsMqGPp+mg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769829; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=j/7L4SVLqfKNU7CK9jkc52FIKfdVSn65sUnvc+m80r8=; b=CWdGqbii4GiD/xl/qG2h1ZVQM5Qr2zTwhkYvZBBt75pFAwt7brtEQrb0u+AfBuAKW+PFCBxXSkvbn30Ip2l/ez4Sf/HZo3eKu9Be0/41yvtzlp0f6hIJQDGtIAwLVgx1IY3c4B2ZsOHVaSrQ6mzsvlQcy/Bz5CQKbLrLpqQRfDQ= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769829009598.7909317420825; Mon, 25 May 2026 21:30:29 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjQh-0007Qa-Fk; Tue, 26 May 2026 00:30:11 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQf-0007QH-OT for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:09 -0400 Received: from mail-wm1-x32c.google.com ([2a00:1450:4864:20::32c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQd-0007ZS-LG for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:09 -0400 Received: by mail-wm1-x32c.google.com with SMTP id 5b1f17b1804b1-4904c1ce4c1so36301875e9.3 for ; Mon, 25 May 2026 21:30:07 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769806; x=1780374606; 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=j/7L4SVLqfKNU7CK9jkc52FIKfdVSn65sUnvc+m80r8=; b=LwC6kEF4XmLbBq/Mnbr64pG+pTBAYhjYAeUH18sTjlPKZgRYQv2cXmWO0MaBdZRIY6 AgvVkws97qW8aGKR5DQVe3nTnmNVvo2fZv+XtjvimEbxvpwppZ3/rn1Zo+XkoJYSTELH 1PaRgQksoUXPQUW3iPDMcW5ukjaaO02+mdJBG/59wYUe0iOameodf42Lk/EOs2PcvmBC Lsbu4uZFJ6kGY9sribTNo8pMcyddJfSLcmwdPRj5HfklT0AQA5F5svQze/HFOdlyDwKS n7dZ/hUWsSi1Um8DA6Vz3du6ve3dmQi9erFcFCBBfYPBjJemPD+o3LOj484X9v5JCgyp n5Ww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769806; x=1780374606; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=j/7L4SVLqfKNU7CK9jkc52FIKfdVSn65sUnvc+m80r8=; b=EigkQunyqrElCy6oYdKVVzGGQT2NT1v78FHgDDkLpRT10T/pZ1omPCFdfUQTQPqGOR UIs41qQRAA1hE6x/3R0UaDJUXcZekum8hK+5YRCW/NKGN3CmP7iXfH57+j5MBo4tMZRQ YLfdYO3SJXtHBuMtwAHl9AdVqPxs6LoPa9yGKh4kn5dlYQc1jVXNvTyl7p0B1pDBMETz xjbwopFUCC/JRExc0pBuqlYtQSCc5ZGCO4kC9FKYFiikkSPAp48Db1NGdXFwA8SnhWK8 +bqX2wN4c2ETop0cHYxaFZS7OMLXbkwPKfJrUzFRcrI37HotmHgbfG2D0WmF+vUBOxQo 0C7g== X-Forwarded-Encrypted: i=1; AFNElJ9xRdANH9dcEMApq42cPe15LRDkz6XdkM3tyJUsrnFnjAy3jjne1AJd2hp8Fb31WrGFZNm0QKWW+IGu@nongnu.org X-Gm-Message-State: AOJu0Yy2mMxSaOiKdFfQ3hk5591VeZd4UVIrd7F13e6ELK291RNOzm9q ZBCfKM+DmplT8FymfJp3FpUuu1dwgGzZJ2hCgXu+OOgRJVL35tZLJrn3 X-Gm-Gg: Acq92OFWSAMPzovg6+yinzW0yDtF5BurFtCqAK7tdcbtWBCPFkMBlkm6Zj2MWbmHCo5 5drkBJ00ciPiX1KjG5MfuM/4B4HW+a5d0ckAnEXnx/gunqmIN1DxiINQbAdaEltAUWv0zwHdMys MrZD7XOcpAgaI1W6g66iGW9oy8uY+ZCEgHEHxPNTTuTpCR86cSZbFHQt/36AsSjfPNSTCuOoyaW L3wieZfZ0rw/ehMxw+ljivnhXHmuNQ4UOkiifY/vUKp3YODaPX19LnxODwD2eXbAxM0VZU/3CNC cLldibinWDq8l6RPtnm5chzTxO+6SXEjjyqF0kuURySCPwyUK/YT+cCKdFJf+VlCN4HvFiOtDiI 0PnfgoUlfyJt8yJZQnpBDQzKtXW74QXEfQ/khO+dkaPg8Im0sW8Y1agxYh1Q1HTlKrUu6vmax2T 9OMn5/IcrRYu6KzoeqZaTSEzccEYuIlyBA9iajNETN6qmxYGhISD1Ibsct4bNvF5Nr79VaRrCQj LFIZMbaGrd5HGQNwTyo6mOC X-Received: by 2002:a05:600c:154d:b0:490:44eb:c1e4 with SMTP id 5b1f17b1804b1-49044ebc273mr258509015e9.26.1779769805650; Mon, 25 May 2026 21:30:05 -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 v4 2/8] docs/specs: Introduce the QEMU Battery documentation Date: Tue, 26 May 2026 07:29:21 +0300 Message-ID: <20260526042928.9203-3-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32c; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32c.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769831339154100 Content-Type: text/plain; charset="utf-8" Signed-off-by: Leonid Bloch --- MAINTAINERS | 5 ++ docs/specs/battery.rst | 154 +++++++++++++++++++++++++++++++++++++++++ docs/specs/index.rst | 1 + 3 files changed, 160 insertions(+) create mode 100644 docs/specs/battery.rst diff --git a/MAINTAINERS b/MAINTAINERS index cd5c4831e2..e356f46a58 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3036,6 +3036,11 @@ F: hw/nitro/ F: include/hw/nitro/ F: docs/system/nitro.rst =20 +Battery +M: Leonid Bloch +S: Maintained +F: docs/specs/battery.rst + Subsystems ---------- Overall Audio backends diff --git a/docs/specs/battery.rst b/docs/specs/battery.rst new file mode 100644 index 0000000000..2ce2b90da4 --- /dev/null +++ b/docs/specs/battery.rst @@ -0,0 +1,154 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Battery Device +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The battery device provides battery state information to the guest via ACP= I. +Battery state is controlled via QMP commands, providing deterministic cont= rol +for testing and migration safety. + +Configuration +------------- + +The battery device is created as an ISA device using ``-device battery``. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x530) + I/O port base address for the battery device registers. + +ACPI Interface +-------------- + +The battery device exposes itself as an ACPI battery device with: + +* **_HID**: ``PNP0C0A`` (Control Method Battery) +* **Device path**: ``\_SB.BAT0`` + +The device implements standard ACPI battery methods: + +``_STA`` (Status) + Returns the battery presence status. + +``_BIF`` (Battery Information) + Returns static battery information including design capacity, + technology, and model information. + +``_BST`` (Battery Status) + Returns dynamic battery status including current state + (charging/discharging), present rate, and remaining capacity. + +I/O Interface +------------- + +The battery device exposes 12 bytes of I/O space at the configured +I/O port address with the following layout: + +* **Bytes 0-3**: Battery state (DWORD) + + Bits 0-3 hold the ACPI ``_BST`` state (1 =3D discharging, + 2 =3D charging); bit 4 carries the battery presence flag exposed by + ``_STA`` (set when the battery is present). + +* **Bytes 4-7**: Battery rate (DWORD) + + Current charge/discharge rate normalized to design capacity. + +* **Bytes 8-11**: Battery charge (DWORD) + + Current battery charge level normalized to design capacity. + +All values are normalized where the design capacity equals 10000 units. + +ACPI Notifications +------------------ + +The battery device generates ACPI notifications through GPE events: + +* **GPE._E08**: Status Change (0x80) - Battery state change + +QMP Commands +------------ + +The following QMP commands control the battery state: + +``battery-set-state`` + Set the battery state. Parameters: + + * ``present``: Whether the battery is present (boolean) + * ``charging``: Whether the battery is charging (boolean) + * ``discharging``: Whether the battery is discharging (boolean) + * ``charge-percent``: Battery charge percentage 0-100 (integer) + * ``rate``: Charge/discharge rate in mW (optional integer) + + Example:: + + -> { "execute": "battery-set-state", + "arguments": { "state": { + "present": true, + "charging": true, + "discharging": false, + "charge-percent": 85, + "rate": 500 + }}} + <- { "return": {} } + +``query-battery`` + Query the current battery state. Returns the same fields as above. + + Example:: + + -> { "execute": "query-battery" } + <- { "return": { + "present": true, + "charging": true, + "discharging": false, + "charge-percent": 85, + "rate": 500, + "design-capacity": 10000 + }} + +Examples +-------- + +Basic usage:: + + # Start VM with battery device + qemu-system-x86_64 -device battery -qmp tcp:localhost:4444,server,wait= =3Doff + + # From another terminal, set battery state via QMP: + echo '{"execute":"qmp_capabilities"} + {"execute":"battery-set-state", + "arguments":{"state":{"present":true,"charging":false, + "discharging":true,"charge-percent":42, + "rate":500}}}' | \ + nc -N localhost 4444 + +Simulate battery discharge:: + + # Start with 100% charged battery + echo '{"execute":"battery-set-state", + "arguments":{"state":{"present":true,"charging":false, + "discharging":true,"charge-percent":100, + "rate":1000}}}' | nc -N localhost 4444 + + # Later, update to 50% charged + echo '{"execute":"battery-set-state", + "arguments":{"state":{"present":true,"charging":false, + "discharging":true,"charge-percent":50, + "rate":1000}}}' | nc -N localhost 4444 + +Simulate charging:: + + # Start with 25% battery, begin charging + echo '{"execute":"battery-set-state", + "arguments":{"state":{"present":true,"charging":true, + "discharging":false,"charge-percent":25, + "rate":500}}}' | nc -N localhost 4444 + +Combined with other laptop devices:: + + # Create a complete laptop environment + qemu-system-x86_64 -device battery -device acad -device button diff --git a/docs/specs/index.rst b/docs/specs/index.rst index b7909a108a..3442b76da6 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 + battery sev-guest-firmware fw_cfg fsi --=20 2.54.0 From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769854; cv=none; d=zohomail.com; s=zohoarc; b=YZ00LUk2fzLF6jc7184NlHzsvSbbsIseMoQ+zT+Cp1uKsFjAvtaxvKtdhPtAh2xDBS4gCkG1T7s4edzL1OzUL7glZKBZJxungTMKbcq8OFx0l7esG7e3d4t1H/pCVoHVkamBqbxPgTRiIB2citHY0oY/PgKauVWzTOZdjNgrc9k= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769854; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=RRYWGOL09YQ6Ynjjtdpealy3l/NaHMqGBRplsHaGGZQ=; b=a9z6VLKp09W4zD/tTDOkv+O57YILtsoQNP83a6bHjR2rATRmjF7nq5ZDVIRRlRhFhpedplHX8VzrJDWnr61868XsmGuZ46kT3oUmarDLFIrmcr1DbV6ehI+cMVMNX6xp60imi1yQtJ3l72OZBoCZ4J6GRver330uMJtgiXXCDWk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769854246727.5250633516217; Mon, 25 May 2026 21:30:54 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjQl-0007TK-Hb; Tue, 26 May 2026 00:30:15 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQj-0007SA-GS for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:13 -0400 Received: from mail-wm1-x32b.google.com ([2a00:1450:4864:20::32b]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQg-0007aD-88 for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:13 -0400 Received: by mail-wm1-x32b.google.com with SMTP id 5b1f17b1804b1-4891e86fabeso1351885e9.1 for ; Mon, 25 May 2026 21:30:09 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769808; x=1780374608; 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=RRYWGOL09YQ6Ynjjtdpealy3l/NaHMqGBRplsHaGGZQ=; b=DeJ541EbiUe4Ho0VtPpgMFrnWyBjZp80ZAUfwOWp2MfMLoLy6089+YiNIiguh2ZHkd HIjnes8xaHYYF85TClvYnunPszuSybUiH9zZX2yUUkd/RAr+9H6r8iNA/c9pnzjecPL4 CdS5ss6PiFUbBHQhnyw6wkah6iVHZtopS7tYCvOgWtg+Ik8536MXggdMHgL0Hah4+3+3 VGl6U2boy/wqXe7+PoBvyn8wcpp1nR5jkYKKb8oQD42aXAp8HzqdrEFnYk/QiS5taT4H fwyNgzM14aCpqG9lANjImbSsTKvl9jMtSbB3wTbo5etiEre4a/n56RBjpFDhkAuNSKiQ caOg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769808; x=1780374608; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=RRYWGOL09YQ6Ynjjtdpealy3l/NaHMqGBRplsHaGGZQ=; b=UTDhtesIzkfmeVwmQtU5CznmRi5KL7TXI4mHbyyxZcYUWN2iA32USkw0DzBpu4u5Po fQ4eJ6D9e9JsueHL//q6Gep7o3ZKgfgymW1vjrjpgdfcqXagW7AqehULDTYBc5ihET4N 37ynq24JNAzlVcSOwRlSszRn1TL5eE0YoJi++fpJwgaWAOjqSkFbvJFhdrmTiYObEvpP tyKtldZ6jhxy5L6ShM+diaT336KhFRUP9m/KfKkTfpqp/aWAdpG59ekD3b/y/gr0c5eD 1yg6n1D5rp4QA3OtCI9Zs420J1ID02yN18yrWQ79HXn0oZ9XvAiDBbubdrUBSi8msixC a2IQ== X-Forwarded-Encrypted: i=1; AFNElJ837DHhiIWRpYRHeghjvl9f4oB9EoyVwS3KQR4E7QLmHbdKtTnarBYbeazobe4GAD/7L8GiSWS3jW7B@nongnu.org X-Gm-Message-State: AOJu0YyU1HnH5DAKw4TBRfVGLcipD6Ns1N2RubRk5+NIzp9myYkPubKE hf/PO1CnGiZ4wfv8wapdp+b5HXLnXok1+9P6NU+CwpwCawebQYDRZ/IS X-Gm-Gg: Acq92OEHlJbw7XCk2ui2b8LUNatWsgaBw2lvRkF+T3D1NJ1xdfDNaEPvqIycKDa9xwY fls9tlYpyvLMzqTw0EpBJegW5nPjzx6q5e679cHb5Y7slMJ9fe/ilLTX0j/m+R+rgiruHED2h3G 8wvEBay62sGHwp5dpUWQWtayJZdeDkc9Cu5MWNBkTDRObAElKdGMgk1hEMG4AqNVgc7osXbj5uX nKTq0H4n6Z9/TUYD7xFnmH7c4zOdYIl1FQ8LvjPwlwzZrSo+EhrCBnlqy/CnBbXgtVUWXiShDi+ QkA38OVSzACgA7UlSzAgg/dcCPCsKpA8UjwruvkCjHEMePacH2fRQnGxFO3vuxsDPIRFmxAjhLC KfnaywbLdWdOK+jQ+nMpoPDGoN48HpxEKicUYXF87zlcyI74G7xZGdy+p42J+YzJ2sBXXR5Xb9v krrytCKoIRZ4t99X40xn/p6p17O9OCQdobQ+F40CYGoLjxar2hGE1MipsjBwN82W04nDUwGie5M H8qJWDC8tk33w== X-Received: by 2002:a05:600c:4f0f:b0:490:50e4:7224 with SMTP id 5b1f17b1804b1-49050e474d4mr245343565e9.3.1779769808176; Mon, 25 May 2026 21:30:08 -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 v4 3/8] hw/acpi: Introduce the QEMU Battery Date: Tue, 26 May 2026 07:29:22 +0300 Message-ID: <20260526042928.9203-4-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32b; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32b.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769857628154100 Content-Type: text/plain; charset="utf-8" The battery device communicates battery state to the guest via ACPI. Battery state is controlled programmatically via QMP commands, making the device deterministic and migration-safe. Properties: - 'ioport': I/O port base address (default: 0x530) The device implements the ACPI_DEV_AML_IF interface to generate its own AML code, placing the BAT0 device directly under \_SB scope as per ACPI specification. QMP commands: - battery-set-state: Set battery state (present, charging, capacity, rate) - query-battery: Query current battery state This provides a stable interface for virtualization management systems. Signed-off-by: Leonid Bloch Signed-off-by: Marcel Apfelbaum --- MAINTAINERS | 2 + hw/acpi/Kconfig | 4 + hw/acpi/battery-stub.c | 20 ++ hw/acpi/battery.c | 364 +++++++++++++++++++++++++++ hw/acpi/meson.build | 2 + hw/acpi/trace-events | 4 + hw/i386/Kconfig | 1 + include/hw/acpi/acpi_dev_interface.h | 1 + include/hw/acpi/battery.h | 32 +++ qapi/acpi.json | 71 ++++++ 10 files changed, 501 insertions(+) create mode 100644 hw/acpi/battery-stub.c create mode 100644 hw/acpi/battery.c create mode 100644 include/hw/acpi/battery.h diff --git a/MAINTAINERS b/MAINTAINERS index e356f46a58..ce50329f48 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3040,6 +3040,8 @@ Battery M: Leonid Bloch S: Maintained F: docs/specs/battery.rst +F: hw/acpi/battery* +F: include/hw/acpi/battery.h =20 Subsystems ---------- diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index daabbe6cd1..6b2c46d37a 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -88,3 +88,7 @@ config ACPI_ERST config ACPI_CXL bool depends on ACPI + +config BATTERY + bool + depends on ACPI diff --git a/hw/acpi/battery-stub.c b/hw/acpi/battery-stub.c new file mode 100644 index 0000000000..d2f13b51c1 --- /dev/null +++ b/hw/acpi/battery-stub.c @@ -0,0 +1,20 @@ +/* + * QEMU emulated battery device - QMP stubs. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-acpi.h" + +void qmp_battery_set_state(BatteryInfo *state, Error **errp) +{ + error_setg(errp, "No battery device found"); +} + +BatteryInfo *qmp_query_battery(Error **errp) +{ + error_setg(errp, "No battery device found"); + return NULL; +} diff --git a/hw/acpi/battery.c b/hw/acpi/battery.c new file mode 100644 index 0000000000..35b81ad486 --- /dev/null +++ b/hw/acpi/battery.c @@ -0,0 +1,364 @@ +/* + * QEMU emulated battery device. + * + * Copyright (c) 2019-2026 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 "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/acpi/acpi_aml_interface.h" +#include "qapi/qapi-commands-acpi.h" + +#include "hw/acpi/battery.h" + +#define BATTERY_DEVICE(obj) OBJECT_CHECK(BatteryState, (obj), TYPE_BATTERY) + +#define BATTERY_DISCHARGING 0x01 /* ACPI _BST bit 0 */ +#define BATTERY_CHARGING 0x02 /* ACPI _BST bit 1 */ +#define BATTERY_CRITICAL 0x04 /* ACPI _BST bit 2 */ +#define BATTERY_PRESENT 0x10 /* ACPI _STA bit 4 */ + +typedef struct BatteryState { + ISADevice dev; + MemoryRegion io; + uint16_t ioport; + uint32_t state; + uint32_t rate; + uint32_t charge; + bool qmp_present; + bool qmp_charging; + bool qmp_discharging; + int32_t qmp_charge_percent; + int32_t qmp_rate; +} BatteryState; + +enum { + BSTA_ADDR =3D 0, + BRTE_ADDR =3D 4, + BCRG_ADDR =3D 8, +}; + +static void battery_get_dynamic_status(BatteryState *s) +{ + s->state =3D 0; + if (s->qmp_present) { + s->state |=3D BATTERY_PRESENT; + if (s->qmp_charging) { + s->state |=3D BATTERY_CHARGING; + } + if (s->qmp_discharging) { + s->state |=3D BATTERY_DISCHARGING; + } + } + s->rate =3D s->qmp_rate; + s->charge =3D (s->qmp_charge_percent * BATTERY_FULL_CAP) / 100; + + trace_battery_get_dynamic_status(s->state, s->rate, s->charge); +} + +static void battery_realize(DeviceState *dev, Error **errp) +{ + ISADevice *d =3D ISA_DEVICE(dev); + BatteryState *s =3D BATTERY_DEVICE(dev); + bool ambiguous; + + trace_battery_realize(); + + object_resolve_path_type("", TYPE_BATTERY, &ambiguous); + if (ambiguous) { + error_setg(errp, "at most one %s device is permitted", TYPE_BATTER= Y); + return; + } + + /* Initialize QMP state to sensible defaults */ + s->qmp_present =3D true; + s->qmp_charging =3D false; + s->qmp_discharging =3D true; + s->qmp_charge_percent =3D 50; + s->qmp_rate =3D 1000; /* 1000 mW discharge rate */ + + isa_register_ioport(d, &s->io, s->ioport); +} + +static const Property battery_device_properties[] =3D { + DEFINE_PROP_UINT16(BATTERY_IOPORT_PROP, BatteryState, ioport, 0x530), +}; + +static const VMStateDescription battery_vmstate =3D { + .name =3D "battery", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_BOOL(qmp_present, BatteryState), + VMSTATE_BOOL(qmp_charging, BatteryState), + VMSTATE_BOOL(qmp_discharging, BatteryState), + VMSTATE_INT32(qmp_charge_percent, BatteryState), + VMSTATE_INT32(qmp_rate, BatteryState), + VMSTATE_END_OF_LIST() + } +}; + +static void build_battery_aml(AcpiDevAmlIf *adev, Aml *scope) +{ + Aml *dev, *field, *method, *pkg; + Aml *bat_state, *bat_rate, *bat_charge; + Aml *sb_scope; + BatteryState *s =3D BATTERY_DEVICE(adev); + + bat_state =3D aml_local(0); + bat_rate =3D aml_local(1); + bat_charge =3D aml_local(2); + + sb_scope =3D aml_scope("\\_SB"); + dev =3D aml_device("BAT0"); + aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C0A"))); + + aml_append(dev, aml_operation_region("DBST", AML_SYSTEM_IO, + aml_int(s->ioport), + BATTERY_LEN)); + field =3D 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 =3D aml_method("_STA", 0, AML_NOTSERIALIZED); + aml_append(method, aml_return(aml_or(aml_int(0x0F), + aml_and(aml_name("BSTA"), + aml_int(0x10), NULL), + NULL))); + aml_append(dev, method); + + method =3D aml_method("_BIF", 0, AML_NOTSERIALIZED); + pkg =3D 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_DESIGN_VOLTAGE)); + /* 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 =3D 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_DESIGN_VOLTAGE)); + aml_append(dev, aml_name_decl("DBPR", pkg)); + + method =3D aml_method("_BST", 0, AML_NOTSERIALIZED); + aml_append(method, aml_store(aml_and(aml_name("BSTA"), aml_int(0x0F), + NULL), + 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); + aml_append(scope, sb_scope); + + /* Status Change */ + method =3D aml_method("\\_GPE._E08", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.BAT0"), aml_int(0x80))); + aml_append(scope, method); +} + +static void battery_class_init(ObjectClass *class, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(class); + AcpiDevAmlIfClass *adevc =3D ACPI_DEV_AML_IF_CLASS(class); + + dc->realize =3D battery_realize; + dc->hotpluggable =3D false; + device_class_set_props(dc, battery_device_properties); + dc->vmsd =3D &battery_vmstate; + adevc->build_dev_aml =3D build_battery_aml; +} + +static uint64_t battery_ioport_read(void *opaque, hwaddr addr, unsigned si= ze) +{ + BatteryState *s =3D opaque; + + battery_get_dynamic_status(s); + + switch (addr) { + case BSTA_ADDR: + return s->state; + case BRTE_ADDR: + return s->rate; + case BCRG_ADDR: + return s->charge; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps battery_ops =3D { + .read =3D battery_ioport_read, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, + .impl =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void battery_instance_init(Object *obj) +{ + BatteryState *s =3D BATTERY_DEVICE(obj); + + memory_region_init_io(&s->io, obj, &battery_ops, s, "battery", + BATTERY_LEN); +} + +static const TypeInfo battery_info =3D { + .name =3D TYPE_BATTERY, + .parent =3D TYPE_ISA_DEVICE, + .instance_size =3D sizeof(BatteryState), + .class_init =3D battery_class_init, + .instance_init =3D battery_instance_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_ACPI_DEV_AML_IF }, + { }, + }, +}; + +static BatteryState *find_battery_device(Error **errp) +{ + bool ambiguous; + Object *o =3D object_resolve_path_type("", TYPE_BATTERY, &ambiguous); + + if (!o) { + error_setg(errp, "No battery device found"); + return NULL; + } + if (ambiguous) { + error_setg(errp, "More than one battery device present"); + return NULL; + } + return BATTERY_DEVICE(o); +} + +void qmp_battery_set_state(BatteryInfo *state, Error **errp) +{ + BatteryState *s =3D find_battery_device(errp); + Object *obj; + + if (!s) { + return; + } + + if (state->charging && state->discharging) { + error_setg(errp, + "'charging' and 'discharging' are mutually exclusive"); + return; + } + if (!state->present && (state->charging || state->discharging)) { + error_setg(errp, + "'charging'/'discharging' require 'present' to be true"= ); + return; + } + if (state->charge_percent < 0 || state->charge_percent > 100) { + error_setg(errp, "'charge-percent' must be in the range 0..100"); + return; + } + if (state->has_rate && (state->rate < 0 || state->rate > INT32_MAX)) { + error_setg(errp, "'rate' must be in the range 0..0x%" PRIX32, + (uint32_t)INT32_MAX); + return; + } + + s->qmp_present =3D state->present; + s->qmp_charging =3D state->charging; + s->qmp_discharging =3D state->discharging; + s->qmp_charge_percent =3D state->charge_percent; + + if (state->has_rate) { + s->qmp_rate =3D state->rate; + } + + obj =3D object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL); + if (obj) { + acpi_send_event(DEVICE(obj), ACPI_BATTERY_CHANGE_STATUS); + } +} + +BatteryInfo *qmp_query_battery(Error **errp) +{ + BatteryState *s =3D find_battery_device(errp); + BatteryInfo *ret; + + if (!s) { + return NULL; + } + + ret =3D g_new0(BatteryInfo, 1); + + ret->present =3D s->qmp_present; + ret->charging =3D s->qmp_charging; + ret->discharging =3D s->qmp_discharging; + ret->charge_percent =3D s->qmp_charge_percent; + ret->has_rate =3D true; + ret->rate =3D s->qmp_rate; + + ret->has_remaining_capacity =3D false; + ret->has_design_capacity =3D true; + ret->design_capacity =3D BATTERY_FULL_CAP; + + return ret; +} + +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 1c5251909b..e6bc78274e 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -35,6 +35,8 @@ if have_tpm endif stub_ss.add(files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c')) stub_ss.add(files('pci-bridge-stub.c')) +acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c')) +stub_ss.add(files('battery-stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) system_ss.add(when: 'CONFIG_GHES_CPER', if_true: files('ghes_cper.c')) stub_ss.add(files('ghes_cper_stub.c')) diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events index edc93e703c..8a6ab91a13 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -87,3 +87,7 @@ acpi_nvdimm_read_io_port(void) "Alert: we never read _DSM= IO Port" acpi_nvdimm_dsm_mem_addr(uint64_t dsm_mem_addr) "dsm memory address 0x%" P= RIx64 acpi_nvdimm_dsm_info(uint32_t revision, uint32_t handle, uint32_t function= ) "Revision 0x%" PRIx32 " Handle 0x%" PRIx32 " Function 0x%" PRIx32 acpi_nvdimm_invalid_revision(uint32_t revision) "Revision 0x%" PRIx32 " is= not supported, expect 0x1" + +# 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 diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 12473acaa7..94004ffeb2 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -39,6 +39,7 @@ config PC imply VIRTIO_VGA imply NVDIMM imply FDC_ISA + imply BATTERY select I8259 select I8254 select PCKBD diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_de= v_interface.h index 65debb90a8..a6f9022c0b 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 =3D 32, ACPI_POWER_DOWN_STATUS =3D 64, ACPI_GENERIC_ERROR =3D 128, + ACPI_BATTERY_CHANGE_STATUS =3D 256, } AcpiEventStatusBits; =20 #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..eaff760db9 --- /dev/null +++ b/include/hw/acpi/battery.h @@ -0,0 +1,32 @@ +/* + * QEMU emulated battery device. + * + * Copyright (c) 2019-2026 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_ACPI_BATTERY_H +#define HW_ACPI_BATTERY_H + +#define TYPE_BATTERY "battery" +#define BATTERY_IOPORT_PROP "ioport" + +#define BATTERY_FULL_CAP 10000 /* mWh */ +#define BATTERY_DESIGN_VOLTAGE 12000 /* mV */ + +#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 + +#endif diff --git a/qapi/acpi.json b/qapi/acpi.json index 906b3687a5..4711a05614 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -142,3 +142,74 @@ ## { 'event': 'ACPI_DEVICE_OST', 'data': { 'info': 'ACPIOSTInfo' } } + +## +# @BatteryInfo: +# +# Battery state information +# +# @present: whether the battery is present +# +# @charging: whether the battery is charging +# +# @discharging: whether the battery is discharging +# +# @charge-percent: battery charge percentage (0-100) +# +# @rate: charge/discharge rate in mW (optional) +# +# @remaining-capacity: remaining capacity in mWh (optional) +# +# @design-capacity: design capacity in mWh (optional) +# +# Since: 11.1 +## +{ 'struct': 'BatteryInfo', + 'data': { 'present': 'bool', + 'charging': 'bool', + 'discharging': 'bool', + 'charge-percent': 'int', + '*rate': 'int', + '*remaining-capacity': 'int', + '*design-capacity': 'int' } } + +## +# @battery-set-state: +# +# Set the state of the emulated battery device +# +# @state: new battery state +# +# Since: 11.1 +# +# .. qmp-example:: +# +# -> { "execute": "battery-set-state", +# "arguments": { "state": { "present": true, +# "charging": true, +# "discharging": false, +# "charge-percent": 85 } } } +# <- { "return": {} } +## +{ 'command': 'battery-set-state', + 'data': { 'state': 'BatteryInfo' } } + +## +# @query-battery: +# +# Query the current state of the emulated battery device +# +# Returns: current battery state +# +# Since: 11.1 +# +# .. qmp-example:: +# +# -> { "execute": "query-battery" } +# <- { "return": { "present": true, +# "charging": true, +# "discharging": false, +# "charge-percent": 85 } } +## +{ 'command': 'query-battery', + 'returns': 'BatteryInfo' } --=20 2.54.0 From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769854; cv=none; d=zohomail.com; s=zohoarc; b=BYpCpEhBgojHUqZGYDGIJXoFjLOyQ0e7pJ9wZa5WbSEVOXutZNv4MCyvD1a4mYVRjhX0SPvWwAT1SbcwfcUvuU1t0cmn9bVA3GLqwPWHCRO0R0Vcuqx5zBfSvY2bA80NCe9WlwrSFryUAjcRBI0NgghS82jSBAzpfiKjKKJePfE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769854; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=VoRWV4KuXE1NBOqs848LK5rIJtwxJka5Ant/9lNcJsw=; b=UFjEBH6EcwQrNmqfRbOU/loddHNZRDNHPND+DDUISefMxWCyDVJC4iSXeSJDZfWZKq3nlKMOlis30ydK9znaKPhSJ04TRN7j80zGRE0HKQqIyOzQhGO8KaGKtdFZdp4r152nq+0O0voVKSDyinUyFOeXGdnLq72EltKCtSgo4U8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769854552110.45858950998786; Mon, 25 May 2026 21:30:54 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjQl-0007T8-1c; Tue, 26 May 2026 00:30:15 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQj-0007SB-E5 for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:13 -0400 Received: from mail-wm1-x32d.google.com ([2a00:1450:4864:20::32d]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQh-0007aT-Hj for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:13 -0400 Received: by mail-wm1-x32d.google.com with SMTP id 5b1f17b1804b1-490388fd0dbso44960015e9.0 for ; Mon, 25 May 2026 21:30:11 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769810; x=1780374610; 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=VoRWV4KuXE1NBOqs848LK5rIJtwxJka5Ant/9lNcJsw=; b=Mpox/IpZNlm7XMHSiZuGEgneHfG1CQZVE/GPffI5lOAq0/kACl4Jgz6xREvkYboKPw L7KjM8mrbqsrRcZr/FmRntQYLr9afV24z3JPAMZMyo/FCUP+ESqVi3pAmWL86P5Fi29u WoPmkjO1Mg2M/W+1ht/JhKyMRznmxFwRGTdOa+ApmEVnTM3BVPxjnvZbyE9MlOHUhJZ+ e7U5Jyyao40nxPwv8RqpWbQezcEPEuy3jTwIBGyHuM/lwJ+XhM02BFwld3FpQD0l/NPf BOrjSv7nb3TM3s39/FNdkFVXZZHLTXF1ihgwrOpBdZsNya8QfM1FS+b9uXufOd7e4ZD0 LzSQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769810; x=1780374610; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=VoRWV4KuXE1NBOqs848LK5rIJtwxJka5Ant/9lNcJsw=; b=tZd91A9hd7utcs2W25NFLWfUKaodXjoUYdNSPnNX4aIyp8/VBP4gFUZ35xC1NbA+0J AWardDrSe0XEUWHbgLgJ7zymk5X0x3pccSvGiByLNS/0ez9PUMpE4Z1X1aOpal09lpX9 lNkX/dkr4UeZq0Z+uz290l5r9b8Cvi88iPhPgRfMS+DGy9BGppPmDM6+SLjXrbgHMu3A lDGkuOB4F/9GowNMdYfsj2EkcLRmYN12p9x/z3wryPJbpr0DxV7OmxPB8CgBTwT5WOgJ 18y8/VNujliWxtr1djO5Y7dSzJuTEeiZ7rQ6hrf3z08X593+pIyNkH8CZc88uJ+L/x9O Nlaw== X-Forwarded-Encrypted: i=1; AFNElJ96Jvn6uC0qW3HNhtDCtfzD4fPk6r5SJJnVPF2PZfaSCBTsPQXgg0fKIKdbSIHtiZSZmzP8oTCU7rdc@nongnu.org X-Gm-Message-State: AOJu0Ywk56X0Dt89vga19nlFjSJldvKrZxaOOOtZL+pqGyAZhaRg5z0f Kgk/ndEHkK0OViuKv6wsfctm60MnLgIwhK2lPvLEql8OAZB6La2nTob5 X-Gm-Gg: Acq92OFzTzfRFe3REpYX6ETLeZsHkElfDCdS4cA8gzuA3gHbex7BGdIn2UEMByF4ecu /5UxKwwhq4mMnf9arfej7HBJ3khZvSkSMSH4orNAkTzyCoMgiiaKnV6fLe/iGTh+0hDyORyTpuS 8V218OBZzQc/UyWdnsYuTZzNHwZC+C+G2LKKOQw6sCUw/ykBGpE6og0nQTvkbOcHszifxcpg8HS MNgA3OIe/EcwkGKgqSPlSY/qx5UwOunMXuKBqmyxaOH2lBlKVCd8PZvqr//orGEiTM45mB1y36u Hk+/OdV2TFx9K+t3h+Ml+zCsPvA6IzlA8e5k1S7lEPgDHUhYu9giVhFuWq/Zo+aKbCzob1Gtfx7 kalKmAIof4/KdUzLZ9gwUBLGynRXsQUluPBLZm2f8Z71xSvmArzWXK/ineWbGqEtM69yhQ3E6oQ zm60YGr6t1YNUeVZLsYGNkhmmvgxLclaCITap6+NoOxPFtnLZhfFSj8U0l1jKBH9ew1DvsLAeVN Oaq5yNiRVbhBsCXEAyZ1NfG X-Received: by 2002:a05:600c:46cb:b0:48e:6db3:ff33 with SMTP id 5b1f17b1804b1-490426add7dmr276165165e9.14.1779769810005; Mon, 25 May 2026 21:30:10 -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 v4 4/8] docs/specs: Introduce the QEMU AC adapter documentation Date: Tue, 26 May 2026 07:29:23 +0300 Message-ID: <20260526042928.9203-5-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32d; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32d.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769857587154100 Content-Type: text/plain; charset="utf-8" Signed-off-by: Leonid Bloch --- MAINTAINERS | 5 ++ docs/specs/acad.rst | 126 +++++++++++++++++++++++++++++++++++++++++++ docs/specs/index.rst | 1 + 3 files changed, 132 insertions(+) create mode 100644 docs/specs/acad.rst diff --git a/MAINTAINERS b/MAINTAINERS index ce50329f48..47436ec878 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3043,6 +3043,11 @@ F: docs/specs/battery.rst F: hw/acpi/battery* F: include/hw/acpi/battery.h =20 +AC Adapter +M: Leonid Bloch +S: Maintained +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..af79b4b8d7 --- /dev/null +++ b/docs/specs/acad.rst @@ -0,0 +1,126 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +AC Adapter Device +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The AC adapter device provides AC power state information to the guest via= ACPI. +AC adapter state is controlled via QMP commands, providing deterministic c= ontrol +for testing and migration safety. + +Configuration +------------- + +The AC adapter device is created as an ISA device using ``-device acad``. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x53c) + I/O port base address for the AC adapter device register. + +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 =3D offline, 1 =3D 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 +------------ + +The following QMP commands control the AC adapter state: + +``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 +-------- + +Basic usage:: + + # Start VM with AC adapter + qemu-system-x86_64 -device acad -qmp tcp:localhost:4444,server,wait=3Doff + + # 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 + +Simulate unplugging AC adapter:: + + # Start with AC adapter connected + echo '{"execute":"ac-adapter-set-state", + "arguments":{"connected":true}}' | nc -N localhost 4444 + + # Later, disconnect AC adapter + echo '{"execute":"ac-adapter-set-state", + "arguments":{"connected":false}}' | nc -N localhost 4444 + + # Reconnect AC adapter + echo '{"execute":"ac-adapter-set-state", + "arguments":{"connected":true}}' | nc -N localhost 4444 + +Combined with battery device:: + + # Create a complete laptop power environment + qemu-system-x86_64 -device battery -device acad + + # Simulate unplugging AC while on battery + echo '{"execute":"battery-set-state", + "arguments":{"state":{"present":true,"charging":false, + "discharging":true,"charge-percent":75}}} + {"execute":"ac-adapter-set-state", + "arguments":{"connected":false}}' | nc -N localhost 4444 diff --git a/docs/specs/index.rst b/docs/specs/index.rst index 3442b76da6..a19c75384c 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 --=20 2.54.0 From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769829; cv=none; d=zohomail.com; s=zohoarc; b=b0PyW+0DzKo/lMIuKgG7aCzfZJKOV7mJivT+XBBJR2VqDKKDD4sbjsdWM7ogCDuM5bWU3ic4f4mKihDtJP4pbsE8rzewemYCi7DCjL4f4jgcBFq4Virruk5UBLYTvqe2Up/UmCpgAafakbYRhp/K0h8xqz9pG3UT6rjJbTTiQig= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769829; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=GKB0CL/PndMACk8dNljjuEebI2FwjYhCCYhB05ovDtE=; b=fZv6Idz1ZS3FyGCWL4sawTcBmBGPYd/loIFsU1WaYZ/eAzg02IToxjXJFzYm4M/o8wfGotOQvlYFEH4FryDtZ5wyd9nd0WxdEoBrkXT/oe/P3XwnDZ7PV/DWOCKXnOmyugB6rc7eKkbVL5nmmdT72kUW5alGL/h/1RQMCyOOPKA= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769829114609.9571238492935; Mon, 25 May 2026 21:30:29 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjQo-0007Ut-J3; Tue, 26 May 2026 00:30:18 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQm-0007UO-VX for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:16 -0400 Received: from mail-wm1-x331.google.com ([2a00:1450:4864:20::331]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQk-0007au-70 for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:16 -0400 Received: by mail-wm1-x331.google.com with SMTP id 5b1f17b1804b1-48e8132c6d0so63400315e9.1 for ; Mon, 25 May 2026 21:30:13 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769813; x=1780374613; 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=GKB0CL/PndMACk8dNljjuEebI2FwjYhCCYhB05ovDtE=; b=q7IYK9pM/oHMhRKsInnRlE/mzGdOvcL2KXYGQgxLHJdPnr4Pv5m0zESdFCe0RwZ8/U z8JpqNg1LNXCzcA+4/0knmaZD5tl8H9gpM3zPHtyRZm34Qb4Gnkx74D1jjhb/+itcfAE ulHHyGyrol6yXXHyhYYULCNhWKHfiqECNsnuAggsUB7kI9FO5fSo05B6fINkUkxwQ2vZ b85+q+M+TQ/9Lr5uFx7wVDzZOKPUDxU4vPTvEnH1xm3HtFBwqo6oeUp7QkXySDmNrl8d AT9pSBd0adEege7eh8+ry68yxyU9z8xLuiQIBIOxslFOeUAjaXR9G5Em4/cjEDN0J3lQ AeYw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769813; x=1780374613; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=GKB0CL/PndMACk8dNljjuEebI2FwjYhCCYhB05ovDtE=; b=iLxzHzkDYak7bcsVQ4YRX9jz+ZnJo1scEbv5toqhUPSc5Ho+fFaF2Ofeat5AG8DVSf u4dMKaOPNhCRDefL/EMkFlUtNKUA4RVOcp3AuCeDAP+t+lu9AWGl8oo54U3VjSbvbH7z /Jm7KCtxXjV8L9iQ2e+yAPW8/WZM+Fo9Qc3sXi0VlZR2POcwNQAWkc8ykfU1SZYCu5w7 PWZcSKvXVHedkpXWQhfn6mwZJYaagbKI5mFGo0/qZbw+HvyqOBI/XkZrAied23IvDyPQ HGqt5vcRDs+CTSiw3SVeSrbRsYhW9LJ5Wuhwy6g6BSlLut+/bHNjeHPcNOxvn6kz4Ck1 eHjw== X-Forwarded-Encrypted: i=1; AFNElJ8ROWT71an7p+oHQqc/z6TxnJO649cPF85qkI50tlzbUyg+AzMXj2hx8720+MUo+1+RNtzARPtTipMD@nongnu.org X-Gm-Message-State: AOJu0YyBTKTvCiAAxITkjyLJKUnq/3Sj7m+dAt45PAneDZ2hbAcqGuMT BKRTwx26kodE2WQcSv1dZrtecyaeaWJArSLgBUn1HtYFTxWjt0GZkPg/ X-Gm-Gg: Acq92OFVkZ8SdvX+lAdF8Bp/7EAMJsOuz1Lc4PdSxz1lqLVsJ6FOB7r3uLW0Bzbyglf XzU7hsNWQNSPbAk+QoU6klbA3wirl310HZNUR5UxHyGBl9k2xOs2u62MNGLo7l3hq3vUaLcDrBx Q8DkjS37SeV+5pe+ubUHXyRarZc1Ho1KXdz+txaVHfiRNUibLBurHHMzx2XFvfoLOul6TeVjxWF QJlfsLkHfqyPHwXptf/wNrGe3feFmHvzmIXlCtaiLJCAykoQlV36DQZbb5/ERzf5CKFhF2vJAiv jU03KMdn8gtPPy+BHSZ3Q+TLvPTBF3V56tyqnte4+iVSPRNZih3qE519zI/pZArIQt3So8uZ3V1 nex6TSdlBZ5rj+u/ZZspWlDW5bXSc4aUHHJagi6e55icyPOMnLRLKPO6dq5Q+fSn0AjJnHLTuDl FxRU7GQDlqXFga3op2DzrMvOnr43R2/nC4SXyN2a56VGQeXPTUM0c0LJDTruuKfAd9Yrx6a/h36 XU= X-Received: by 2002:a05:600c:3e0c:b0:488:ac01:72de with SMTP id 5b1f17b1804b1-49042489c30mr320297815e9.5.1779769812633; Mon, 25 May 2026 21:30:12 -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 v4 5/8] hw/acpi: Introduce the QEMU AC adapter Date: Tue, 26 May 2026 07:29:24 +0300 Message-ID: <20260526042928.9203-6-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::331; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x331.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769831381154100 Content-Type: text/plain; charset="utf-8" The AC adapter device communicates AC power state to the guest via ACPI. AC adapter state is controlled programmatically via QMP commands, ensuring deterministic behavior. Properties: - 'ioport': I/O port base address (default: 0x53c) 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 | 2 + hw/acpi/Kconfig | 4 + hw/acpi/acad-stub.c | 20 +++ hw/acpi/acad.c | 251 +++++++++++++++++++++++++++ hw/acpi/meson.build | 2 + hw/acpi/trace-events | 4 + hw/i386/Kconfig | 1 + include/hw/acpi/acad.h | 25 +++ include/hw/acpi/acpi_dev_interface.h | 1 + qapi/acpi.json | 47 +++++ 10 files changed, 357 insertions(+) create mode 100644 hw/acpi/acad-stub.c create mode 100644 hw/acpi/acad.c create mode 100644 include/hw/acpi/acad.h diff --git a/MAINTAINERS b/MAINTAINERS index 47436ec878..90941519e3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3047,6 +3047,8 @@ AC Adapter M: Leonid Bloch S: Maintained F: docs/specs/acad.rst +F: hw/acpi/acad* +F: include/hw/acpi/acad.h =20 Subsystems ---------- diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index 6b2c46d37a..889ace2dfa 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -74,6 +74,10 @@ config ACPI_VIOT bool depends on ACPI =20 +config AC_ADAPTER + bool + depends on ACPI + config ACPI_HW_REDUCED bool select ACPI diff --git a/hw/acpi/acad-stub.c b/hw/acpi/acad-stub.c new file mode 100644 index 0000000000..92093b7682 --- /dev/null +++ b/hw/acpi/acad-stub.c @@ -0,0 +1,20 @@ +/* + * QEMU emulated AC adapter device - QMP stubs. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-acpi.h" + +void qmp_ac_adapter_set_state(bool connected, Error **errp) +{ + error_setg(errp, "No AC adapter device found"); +} + +AcAdapterInfo *qmp_query_ac_adapter(Error **errp) +{ + error_setg(errp, "No AC adapter device found"); + return NULL; +} diff --git a/hw/acpi/acad.c b/hw/acpi/acad.c new file mode 100644 index 0000000000..11777fbb3c --- /dev/null +++ b/hw/acpi/acad.c @@ -0,0 +1,251 @@ +/* + * QEMU emulated AC adapter device. + * + * Copyright (c) 2019-2026 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 "qapi/error.h" +#include "hw/core/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 + +enum { + AC_ADAPTER_OFFLINE =3D 0, + AC_ADAPTER_ONLINE =3D 1, +}; + +typedef struct ACADState { + ISADevice dev; + MemoryRegion io; + uint16_t ioport; + uint8_t state; + bool qmp_connected; +} ACADState; + +static void acad_get_dynamic_status(ACADState *s) +{ + s->state =3D s->qmp_connected ? AC_ADAPTER_ONLINE : AC_ADAPTER_OFFLINE; + + trace_acad_get_dynamic_status(s->state); +} + +static void acad_realize(DeviceState *dev, Error **errp) +{ + ISADevice *d =3D ISA_DEVICE(dev); + ACADState *s =3D AC_ADAPTER_DEVICE(dev); + bool ambiguous; + + trace_acad_realize(); + + object_resolve_path_type("", TYPE_AC_ADAPTER, &ambiguous); + if (ambiguous) { + error_setg(errp, "at most one %s device is permitted", + TYPE_AC_ADAPTER); + return; + } + + /* Initialize to disconnected by default */ + s->qmp_connected =3D false; + + isa_register_ioport(d, &s->io, s->ioport); +} + +static const Property acad_device_properties[] =3D { + DEFINE_PROP_UINT16(AC_ADAPTER_IOPORT_PROP, ACADState, ioport, 0x53c), +}; + +static const VMStateDescription acad_vmstate =3D { + .name =3D "acad", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_BOOL(qmp_connected, 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 =3D AC_ADAPTER_DEVICE(adev); + + acad_state =3D aml_local(0); + + sb_scope =3D aml_scope("\\_SB"); + dev =3D 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 =3D aml_field("ACST", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); + aml_append(field, aml_named_field("PWRS", 8)); + aml_append(dev, field); + + method =3D 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 =3D aml_method("_PCL", 0, AML_NOTSERIALIZED); + pkg =3D aml_package(1); + aml_append(pkg, aml_name("_SB")); + aml_append(method, aml_return(pkg)); + aml_append(dev, method); + + method =3D aml_method("_PIF", 0, AML_NOTSERIALIZED); + pkg =3D 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 =3D aml_method("\\_GPE._E0B", 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 =3D DEVICE_CLASS(class); + AcpiDevAmlIfClass *adevc =3D ACPI_DEV_AML_IF_CLASS(class); + + dc->realize =3D acad_realize; + dc->hotpluggable =3D false; + device_class_set_props(dc, acad_device_properties); + dc->vmsd =3D &acad_vmstate; + adevc->build_dev_aml =3D build_acad_aml; +} + +static uint64_t acad_ioport_read(void *opaque, hwaddr addr, unsigned size) +{ + ACADState *s =3D opaque; + + acad_get_dynamic_status(s); + + switch (addr) { + case AC_STA_ADDR: + return s->state; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps acad_ops =3D { + .read =3D acad_ioport_read, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 1, + }, +}; + +static void acad_instance_init(Object *obj) +{ + ACADState *s =3D AC_ADAPTER_DEVICE(obj); + + memory_region_init_io(&s->io, obj, &acad_ops, s, "acad", + AC_ADAPTER_LEN); +} + +static const TypeInfo acad_info =3D { + .name =3D TYPE_AC_ADAPTER, + .parent =3D TYPE_ISA_DEVICE, + .instance_size =3D sizeof(ACADState), + .class_init =3D acad_class_init, + .instance_init =3D acad_instance_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_ACPI_DEV_AML_IF }, + { }, + }, +}; + +static ACADState *find_acad_device(Error **errp) +{ + bool ambiguous; + Object *o =3D object_resolve_path_type("", TYPE_AC_ADAPTER, &ambiguous= ); + + if (!o) { + error_setg(errp, "No AC adapter device found"); + return NULL; + } + if (ambiguous) { + error_setg(errp, "More than one AC adapter device present"); + return NULL; + } + return AC_ADAPTER_DEVICE(o); +} + +void qmp_ac_adapter_set_state(bool connected, Error **errp) +{ + ACADState *s =3D find_acad_device(errp); + Object *obj; + + if (!s) { + return; + } + + s->qmp_connected =3D connected; + + obj =3D 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 =3D find_acad_device(errp); + AcAdapterInfo *ret; + + if (!s) { + return NULL; + } + + ret =3D g_new0(AcAdapterInfo, 1); + ret->connected =3D s->qmp_connected; + + 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 e6bc78274e..731e9477e3 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -37,6 +37,8 @@ stub_ss.add(files('acpi-stub.c', 'aml-build-stub.c', 'ghe= s-stub.c')) stub_ss.add(files('pci-bridge-stub.c')) acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c')) stub_ss.add(files('battery-stub.c')) +acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c')) +stub_ss.add(files('acad-stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) system_ss.add(when: 'CONFIG_GHES_CPER', if_true: files('ghes_cper.c')) stub_ss.add(files('ghes_cper_stub.c')) diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events index 8a6ab91a13..67602000f3 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -91,3 +91,7 @@ acpi_nvdimm_invalid_revision(uint32_t revision) "Revision= 0x%" PRIx32 " is not s # 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 + +# acad.c +acad_realize(void) "AC adapter device realize entry" +acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8 diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 94004ffeb2..06f21cadb7 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..f163158f35 --- /dev/null +++ b/include/hw/acpi/acad.h @@ -0,0 +1,25 @@ +/* + * QEMU emulated AC adapter device. + * + * Copyright (c) 2019-2026 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_VAL_UNKNOWN 0xFFFFFFFF + +#define AC_ADAPTER_LEN 1 + +#endif diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_de= v_interface.h index a6f9022c0b..00566c56a7 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 =3D 64, ACPI_GENERIC_ERROR =3D 128, ACPI_BATTERY_CHANGE_STATUS =3D 256, + ACPI_AC_ADAPTER_CHANGE_STATUS =3D 2048, } AcpiEventStatusBits; =20 #define TYPE_ACPI_DEVICE_IF "acpi-device-interface" diff --git a/qapi/acpi.json b/qapi/acpi.json index 4711a05614..025b5d8eaa 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -213,3 +213,50 @@ ## { '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: 11.1 +# +# .. 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: 11.1 +## +{ 'struct': 'AcAdapterInfo', + 'data': { 'connected': 'bool' } } + +## +# @query-ac-adapter: +# +# Query the current state of the emulated AC adapter device +# +# Returns: AC adapter connection state +# +# Since: 11.1 +# +# .. qmp-example:: +# +# -> { "execute": "query-ac-adapter" } +# <- { "return": { "connected": true } } +## +{ 'command': 'query-ac-adapter', + 'returns': 'AcAdapterInfo' } --=20 2.54.0 From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769854; cv=none; d=zohomail.com; s=zohoarc; b=FCzmxL/99Btc6cp68kQjXvY656C1sWjj5bacFBj/g/5qYYgp422GnY6c4othmayM5aQaAboPQpf7gABmvlp7eQzSLeIJw8p1Ff8vSeXe7gFAPsDlLeXhVYR/By+XYUM8vh8OgB2CGGIHYos3H6QSdYT5c5IRNAr8JhIFnYp0OrY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769854; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=1GgotwpIiqgakcYFqmApeCWJNrj5+Tz6ljYoT6vEE4I=; b=DXeXSbc4G6mDhDbvlVsScyWBj3ZtaphngAGxbDZ7ihJF8eqtr+EclXgB1QutuqosOOtcMux/01m8Yxoiyg9OO61obsXMJCboBNDyP0Va8/itvpFBz6pE7fjSHiDxfTRok1jWkk1Jd5Zi0l5kOKjaQ2mr9sh/jo4hj2m4NrKWPBE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769854529423.60185220975904; Mon, 25 May 2026 21:30:54 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjQq-0007Xl-7O; Tue, 26 May 2026 00:30:20 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQo-0007VI-Pr for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:19 -0400 Received: from mail-wm1-x336.google.com ([2a00:1450:4864:20::336]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQm-0007bB-Ox for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:18 -0400 Received: by mail-wm1-x336.google.com with SMTP id 5b1f17b1804b1-48e8132c6d0so63400505e9.1 for ; Mon, 25 May 2026 21:30:16 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769815; x=1780374615; 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=1GgotwpIiqgakcYFqmApeCWJNrj5+Tz6ljYoT6vEE4I=; b=gZ3Ii3oZmMFlGIXfSz4k01E6con0NUuyBesmsMbu1MAk+59eG7SBUlDF+rx0IOkcsk k6gm1RQzgYTB3021MK/4ejlWlvYjkzMRTTJlK8QKWSQXSKBXs7s/00n2EgJQojNu+ufO 08aq8gG9tjrbkWzxD6VltbAe3FW4r4/pUTfsHy58GrDt6BdxtPoopWZhpITwrLJpvgTa /qwI+Qvnw4uEfAsz7GVn+xkve8dayuuegIVoLn/LdIt6jSULuYY6IjsmWFBuqPvPKc87 N5+rm8hy0qYEJOR/iGOKHZdm56k4ycxRojBQxEPj2gqByAvyCw6kPLDwrdn9cq9SZiYV kggg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769815; x=1780374615; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=1GgotwpIiqgakcYFqmApeCWJNrj5+Tz6ljYoT6vEE4I=; b=FlcWEsZSbyvLhW4VHQGch9wtOBqtleaNuPCIdAs90KszqLdKAKQEYtkuBHkzHcKT4k XHPnONI3/A4FA83PCP9xe9/NwSYsnM4ba1fOhPvv/YXshMWBStKGrhFg9U9/rAxy2Jfg SoQWgL7HHVnjhSxJkYE1sMGUOuPouHz0EZIei6waWzgttXKQBmk1qiGmh6Ri8bafti17 mC87cXlCpjKsTrr3ZpevNlLvSqV50ufZOzMLFQPSGdlhjD9T+Lr0K1Qw3yYmL4c76C6q gIJe//JLVnn9/qNNVVaWUrN9o9FnqEXs8vfeSoeAriwbN95mSqIL/ftnzNxWIuFasSyd 4Uqg== X-Forwarded-Encrypted: i=1; AFNElJ8oLV8XNHflxx/8mom59Ye1Q+HacUHs9OV6xRc2N3VGI4NTtENRNtDpSp4Z/RQheC4k2sPUM6nGpiyO@nongnu.org X-Gm-Message-State: AOJu0Yx/skjtKgWB5343wQe2mrNiaIOpriouO6kuALU6juYGmAJBJ0dB lRjSW7cH9P+42Gn8KRVeBy2BkUv6XDH1HNvyd32Ah3uFugr2ygHoZA2B X-Gm-Gg: Acq92OEarSOqxKt+ueUU5LXcljaFDO7bmywFjiAjWV+bKH7hp0j9JQFMJq47P/qATvS za1jE831HEUvGsb2hSAWbcObVhxwGYSjGsYWOi7ZmcikA3+X3vFQMX89Me3GnKe3QTAzNha4GAO pfO1TBF8MRtnSCmllUsesX+dpFJoVSbaIB71J8ZoDnvUDO56dfTNQ/5HC9c/QlWGH3UQ4K+qEjQ Dm8QTlB/OjRTGjXyS28jxIrcbojorLNxZOT7IbIMHZ7jAFaPpBJ4ygdfw8KcphJItCil6H2ue84 RFIBLnFfZN6UDMkru+EEzitew4tUk27wScCzotfoLlrrUatf24lsV8twDO7yiJEsm0XUf57sOzK 3chioHw61CgctAaqa4OMWasEyP4F4znfvs2F9ghUFnXsxaX3cWhmpKKj2yuB9yl6CsElms2Ykqy rcM8NpSHLjm/sxokH9mB/0o96JBt71tiKMPzhbVFJOj78S28YJ5/558xAEjctZ9kgrxAFu0JE4v vIlyBoTwMj5LA== X-Received: by 2002:a05:600c:c059:20b0:490:31e:d6fd with SMTP id 5b1f17b1804b1-490426d3319mr205667305e9.25.1779769815217; Mon, 25 May 2026 21:30:15 -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 v4 6/8] docs/specs: Introduce the QEMU lid button documentation Date: Tue, 26 May 2026 07:29:25 +0300 Message-ID: <20260526042928.9203-7-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::336; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x336.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769857579154100 Content-Type: text/plain; charset="utf-8" Signed-off-by: Leonid Bloch --- MAINTAINERS | 5 ++ docs/specs/button.rst | 131 ++++++++++++++++++++++++++++++++++++++++++ docs/specs/index.rst | 1 + 3 files changed, 137 insertions(+) create mode 100644 docs/specs/button.rst diff --git a/MAINTAINERS b/MAINTAINERS index 90941519e3..42179aba95 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3050,6 +3050,11 @@ F: docs/specs/acad.rst F: hw/acpi/acad* F: include/hw/acpi/acad.h =20 +Button +M: Leonid Bloch +S: Maintained +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..10e57a380e --- /dev/null +++ b/docs/specs/button.rst @@ -0,0 +1,131 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Laptop Lid Button Device +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The button device provides laptop lid button state information to the gues= t via +ACPI. Lid state is controlled via QMP commands, providing deterministic co= ntrol +for testing and migration safety. + +Configuration +------------- + +The lid button device is created as an ISA device using ``-device button``. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x53d) + I/O port base address for the lid button device register. + +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 =3D closed, 1 =3D 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 +------------ + +The following QMP commands control the lid button state: + +``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 +-------- + +Basic usage:: + + # Start VM with lid button + qemu-system-x86_64 -device button -qmp tcp:localhost:4444,server,wait=3D= 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 + +Simulate closing lid:: + + # Start with lid open (default) + # Close the lid + echo '{"execute":"lid-button-set-state", + "arguments":{"open":false}}' | nc -N localhost 4444 + + # Later, open the lid + echo '{"execute":"lid-button-set-state", + "arguments":{"open":true}}' | nc -N localhost 4444 + +Test suspend on lid close:: + + # Start VM with ACPI support + qemu-system-x86_64 -device button + + # Close lid - guest OS should detect this and may suspend + echo '{"execute":"lid-button-set-state", + "arguments":{"open":false}}' | nc -N localhost 4444 + + # Open lid - guest OS should wake or detect lid open + echo '{"execute":"lid-button-set-state", + "arguments":{"open":true}}' | nc -N localhost 4444 + +Combined with other laptop devices:: + + # Create a complete laptop environment + qemu-system-x86_64 -device battery -device acad -device button + + # Simulate closing lid while on battery power + echo '{"execute":"battery-set-state", + "arguments":{"state":{"present":true,"charging":false, + "discharging":true,"charge-percent":60}}} + {"execute":"ac-adapter-set-state", + "arguments":{"connected":false}} + {"execute":"lid-button-set-state", + "arguments":{"open":false}}' | nc -N localhost 4444 diff --git a/docs/specs/index.rst b/docs/specs/index.rst index a19c75384c..54d8f5f7c5 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 --=20 2.54.0 From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769834; cv=none; d=zohomail.com; s=zohoarc; b=LMrNmrHQNIhEYA+78axmid39N2jHHLAH8LllXejNiMbNEo/tt30wCGHjgcfcoF7MbAnyYiaohCaj0HkJiUHiOyjbGgjcxozKf2Q1ZfiWmND7uWGkAkA9a7P4syzHFtcqoTzk8H5Cg3H/DvLT38tJiAW16NZCdecP/TzZW7OO7qI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769834; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=qrFjlhVp73qG6tJ6h5b8qEZ/n8t0fpSSdJJKT8K1sIU=; b=f+ACDox8NEB2aaIlHGMJd12nP5+/mz1QOr7AtaNxdKcpWcmovPXhqieSRNqsTOagxTjR/hCS1oVymv17v3vfRkf7xaDO4khDQmFR6ZWyuy3Zrt55/r2m4lP36qFPRGtvr+HgaaYuj7HgsSH7otcplrDNm7ob0L/5wL5x20/Fimc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769834986503.3960604376987; Mon, 25 May 2026 21:30:34 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjR0-0007oA-QL; Tue, 26 May 2026 00:30:30 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQr-0007bz-Rr for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:22 -0400 Received: from mail-wm1-x32e.google.com ([2a00:1450:4864:20::32e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQo-0007bT-Ra for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:21 -0400 Received: by mail-wm1-x32e.google.com with SMTP id 5b1f17b1804b1-490388fd0dbso44961105e9.0 for ; Mon, 25 May 2026 21:30:18 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769817; x=1780374617; 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=qrFjlhVp73qG6tJ6h5b8qEZ/n8t0fpSSdJJKT8K1sIU=; b=ZtF+pWJx5e6KGaal7maN+hSG4SA8GawtJThJgGhv7oVI/qdfcPE30GXfKoEndmQqLy p/O36Ba0bJkf19SgpkrcC18FCo9nKkgn8FTnI1NkBPXpXLD5hBd9ygjuYvp/Fp8rUdHi Vdc1ypg7P80ooISBjCTYUhqs1BZBfpLTxIqDiXvuGEL/NJ9a44WG0nSfljBjR+KcBAnh phFWiEgu3RvXbkF50aHXDZQPeQqmIxdGXl9GykHFHoYwK0edosq0VZ9SkbSMNPfFRQda h4iTGLNZRZHVmu8AKoRUF7Ja7kigjCbsSI4ZRWYiGdutogD70hx3kp8Sa4nprazJ36Ck otjg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769817; x=1780374617; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=qrFjlhVp73qG6tJ6h5b8qEZ/n8t0fpSSdJJKT8K1sIU=; b=GYklqozR8LKVHhylI9RXI3DPP4KyKiV+sr442wHf4VN2FG6n+RO1dXETxACSZONUjn I/9g3Kg7HQAz4o47DBOEeOdyyd7RveHk3SVeuCJHi2thbkxZDIp/2UDVqKM0Etxe1SMz fgfiRHEE2bphDziLv1rQ5YdWEUgBmbk+3I13RHDgXdCH1HoH+icYLNKPH+mOsBaEK4Lv WMK+9cxMRidBHxWMCQratcc5fJruPMpB7Tyk0Sg0c3AWOiry4gkQ1tf1E2Y6PQ+96jIF FH57VY5mBzZeeJVJm5huuyLEOwdRvs5tmL6uKjHOISXChTeL739I6EhpdIX2uPNEuRHb GW2A== X-Forwarded-Encrypted: i=1; AFNElJ+sC8+ujYBKbZVUHq7zg7rffKHk7QB8/9NqogD6Zm4CBzMJG2T5wYilhGGX+Ik8YBZAz10CU0/kMZ4v@nongnu.org X-Gm-Message-State: AOJu0Yz2zi6IpOKjHQbTw3dSGGJ3LGp2li3wqVgj2JT9f55w2qU+Uv66 zlgzvFVlOp/hZi7bzwivqDkYFn39FRk8eWqrlKkFil4yxvBwiZ75nuhc X-Gm-Gg: Acq92OF6oXT2SgjT+34gPNi1/jWVo3bCBrFAhGgK/+hmGmHOkXsyqSYcJgtW++Mwk85 9oHIK4g6yzppoWP9hdlJ2w+rXwiU9aNl/p/Vb2sc/IqLd0A45H83l1SUo+50bcQ1nv3DW+euPIH 0Wn0hzU9Pt23gDNoLUadt6buIIOAefytVd0WzmKUJYjWKGY8C/5P2TrGyvFoWidzRPzYfWDzSCD pLOiIclXWOx3gN5MpdJh7b5zi8h+eQhrzHhbxcR7YiTd2skIDdNrv/Q43kZQeqqwQaRCnhOJY3t xgSzuyGGlixchJcFJDvEflg7gpY8pSzz3YPNff3xAXgh5izlascx0pUysYhXdyIg4skvBf9iCof vVnO/7AW9IfXTKgvpsVIkBxa4OhFZ0GPvOfxckSQSGhYuCtMkkKTx6lvUvfOUFh+h7qTjdGEwrj KZUU0rBGpijSN5WtcprKkG2CZSUXohcNhUt4Frmv3rAHldaHTAHnun/vJpkExWAE7/38sAMyLy+ cA= X-Received: by 2002:a05:600c:4f52:b0:490:4f91:5519 with SMTP id 5b1f17b1804b1-4904f91568dmr257113735e9.18.1779769817156; Mon, 25 May 2026 21:30:17 -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 v4 7/8] hw/acpi: Introduce the QEMU lid button Date: Tue, 26 May 2026 07:29:26 +0300 Message-ID: <20260526042928.9203-8-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32e; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32e.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769836407158500 Content-Type: text/plain; charset="utf-8" The lid button device communicates laptop lid state to the guest via ACPI. Lid state is controlled programmatically via QMP commands for consistent behavior across environments. 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 | 2 + hw/acpi/Kconfig | 4 + hw/acpi/button-stub.c | 20 +++ hw/acpi/button.c | 227 +++++++++++++++++++++++++++ hw/acpi/meson.build | 2 + hw/acpi/trace-events | 4 + hw/i386/Kconfig | 1 + include/hw/acpi/acpi_dev_interface.h | 1 + include/hw/acpi/button.h | 23 +++ qapi/acpi.json | 47 ++++++ 10 files changed, 331 insertions(+) create mode 100644 hw/acpi/button-stub.c create mode 100644 hw/acpi/button.c create mode 100644 include/hw/acpi/button.h diff --git a/MAINTAINERS b/MAINTAINERS index 42179aba95..1f8f3e247e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3054,6 +3054,8 @@ Button M: Leonid Bloch S: Maintained F: docs/specs/button.rst +F: hw/acpi/button* +F: include/hw/acpi/button.h =20 Subsystems ---------- diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index 889ace2dfa..0d5f885095 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -78,6 +78,10 @@ config AC_ADAPTER bool depends on ACPI =20 +config BUTTON + bool + depends on ACPI + config ACPI_HW_REDUCED bool select ACPI diff --git a/hw/acpi/button-stub.c b/hw/acpi/button-stub.c new file mode 100644 index 0000000000..0ae478055b --- /dev/null +++ b/hw/acpi/button-stub.c @@ -0,0 +1,20 @@ +/* + * QEMU emulated lid button device - QMP stubs. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-acpi.h" + +void qmp_lid_button_set_state(bool open, Error **errp) +{ + error_setg(errp, "No lid button device found"); +} + +LidButtonInfo *qmp_query_lid_button(Error **errp) +{ + error_setg(errp, "No lid button device found"); + return NULL; +} diff --git a/hw/acpi/button.c b/hw/acpi/button.c new file mode 100644 index 0000000000..126969a5e7 --- /dev/null +++ b/hw/acpi/button.c @@ -0,0 +1,227 @@ +/* + * QEMU emulated lid button device + * + * Copyright (c) 2019-2026 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 "qapi/error.h" +#include "hw/core/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 + +enum { + LID_CLOSED =3D 0, + LID_OPEN =3D 1, +}; + +typedef struct ButtonState { + ISADevice dev; + MemoryRegion io; + uint16_t ioport; + uint8_t lid_state; + bool qmp_lid_open; +} ButtonState; + +static void button_get_dynamic_status(ButtonState *s) +{ + trace_button_get_dynamic_status(); + + s->lid_state =3D s->qmp_lid_open ? LID_OPEN : LID_CLOSED; +} + +static void button_realize(DeviceState *dev, Error **errp) +{ + ISADevice *d =3D ISA_DEVICE(dev); + ButtonState *s =3D BUTTON_DEVICE(dev); + bool ambiguous; + + trace_button_realize(); + + object_resolve_path_type("", TYPE_BUTTON, &ambiguous); + if (ambiguous) { + error_setg(errp, "at most one %s device is permitted", TYPE_BUTTON= ); + return; + } + + /* Initialize lid to open by default */ + s->qmp_lid_open =3D true; + + isa_register_ioport(d, &s->io, s->ioport); +} + +static const Property button_device_properties[] =3D { + DEFINE_PROP_UINT16(BUTTON_IOPORT_PROP, ButtonState, ioport, 0x53d), +}; + +static const VMStateDescription button_vmstate =3D { + .name =3D "button", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_BOOL(qmp_lid_open, 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 =3D BUTTON_DEVICE(adev); + + button_state =3D aml_local(0); + + sb_scope =3D aml_scope("\\_SB"); + dev =3D 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 =3D aml_field("LSTA", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); + aml_append(field, aml_named_field("LIDS", 8)); + aml_append(dev, field); + + method =3D 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 =3D aml_method("\\_GPE._E0C", 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 =3D DEVICE_CLASS(class); + AcpiDevAmlIfClass *adevc =3D ACPI_DEV_AML_IF_CLASS(class); + + dc->realize =3D button_realize; + dc->hotpluggable =3D false; + device_class_set_props(dc, button_device_properties); + dc->vmsd =3D &button_vmstate; + adevc->build_dev_aml =3D build_button_aml; +} + +static uint64_t button_ioport_read(void *opaque, hwaddr addr, unsigned siz= e) +{ + ButtonState *s =3D opaque; + + button_get_dynamic_status(s); + + switch (addr) { + case BUTTON_STA_ADDR: + return s->lid_state; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps button_ops =3D { + .read =3D button_ioport_read, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 1, + }, +}; + +static void button_instance_init(Object *obj) +{ + ButtonState *s =3D BUTTON_DEVICE(obj); + + memory_region_init_io(&s->io, obj, &button_ops, s, "button", + BUTTON_LEN); +} + +static const TypeInfo button_info =3D { + .name =3D TYPE_BUTTON, + .parent =3D TYPE_ISA_DEVICE, + .instance_size =3D sizeof(ButtonState), + .class_init =3D button_class_init, + .instance_init =3D button_instance_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_ACPI_DEV_AML_IF }, + { }, + }, +}; + +static ButtonState *find_button_device(Error **errp) +{ + bool ambiguous; + Object *o =3D object_resolve_path_type("", TYPE_BUTTON, &ambiguous); + + if (!o) { + error_setg(errp, "No lid button device found"); + return NULL; + } + if (ambiguous) { + error_setg(errp, "More than one lid button device present"); + return NULL; + } + return BUTTON_DEVICE(o); +} + +void qmp_lid_button_set_state(bool open, Error **errp) +{ + ButtonState *s =3D find_button_device(errp); + Object *obj; + + if (!s) { + return; + } + + s->qmp_lid_open =3D open; + + obj =3D 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 =3D find_button_device(errp); + LidButtonInfo *ret; + + if (!s) { + return NULL; + } + + ret =3D g_new0(LidButtonInfo, 1); + ret->open =3D s->qmp_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 731e9477e3..ab0acd2521 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -39,6 +39,8 @@ acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('batte= ry.c')) stub_ss.add(files('battery-stub.c')) acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c')) stub_ss.add(files('acad-stub.c')) +acpi_ss.add(when: 'CONFIG_BUTTON', if_true: files('button.c')) +stub_ss.add(files('button-stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) system_ss.add(when: 'CONFIG_GHES_CPER', if_true: files('ghes_cper.c')) stub_ss.add(files('ghes_cper_stub.c')) diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events index 67602000f3..13728637ce 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -95,3 +95,7 @@ battery_get_dynamic_status(uint32_t state, uint32_t rate,= uint32_t charge) "Batt # acad.c acad_realize(void) "AC adapter device realize entry" acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8 + +# button.c +button_realize(void) "Button device realize entry" +button_get_dynamic_status(void) "Button read dynamic status entry" diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 06f21cadb7..35c65d3f37 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_de= v_interface.h index 00566c56a7..35005d7ad0 100644 --- a/include/hw/acpi/acpi_dev_interface.h +++ b/include/hw/acpi/acpi_dev_interface.h @@ -16,6 +16,7 @@ typedef enum { ACPI_GENERIC_ERROR =3D 128, ACPI_BATTERY_CHANGE_STATUS =3D 256, ACPI_AC_ADAPTER_CHANGE_STATUS =3D 2048, + ACPI_BUTTON_CHANGE_STATUS =3D 4096, } AcpiEventStatusBits; =20 #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..d0e2d33231 --- /dev/null +++ b/include/hw/acpi/button.h @@ -0,0 +1,23 @@ +/* + * QEMU emulated button device. + * + * Copyright (c) 2019-2026 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_LEN 1 + +#endif diff --git a/qapi/acpi.json b/qapi/acpi.json index 025b5d8eaa..e0534e3657 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -260,3 +260,50 @@ ## { '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: 11.1 +# +# .. 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: 11.1 +## +{ 'struct': 'LidButtonInfo', + 'data': { 'open': 'bool' } } + +## +# @query-lid-button: +# +# Query the current state of the emulated laptop lid button device +# +# Returns: lid button state +# +# Since: 11.1 +# +# .. qmp-example:: +# +# -> { "execute": "query-lid-button" } +# <- { "return": { "open": true } } +## +{ 'command': 'query-lid-button', + 'returns': 'LidButtonInfo' } --=20 2.54.0 From nobody Sat May 30 17:43:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1779769874; cv=none; d=zohomail.com; s=zohoarc; b=dHmH8WkBdZnkVHP8SpXwbeJuGn04PWifpv2302BGvNS6Qbd6dMuZgpIFOdVZSHId1kGAh0RjM+pzwS7wPwWlT7O5Ejp8KSKYr3hxE7NMvq3mDIW3AS1L5faJpTqfI18SFvnY1HHdAZOhaoZLsxr5GTO13QA/+AQr6S2qQOYNidc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1779769874; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=xeRRTnWnaEVO/UepoOFJKLi/LO7+Q/LxWMTun4QJ2wA=; b=bETc4kbdik8BRwDzS3wxzLKZQsU1LY88o6CpTqSZh9uRiIyIqFQAjIf8inZTPkllFSSftyOEayYZxPf+K7QAou+Zt+KarOidCWiVNAsjUzw3f051vxQUUpiPdmGMfVxC2J0Sk4WeRV07Bp/F/WG6H+ne2IhX2ARnUJ7pNGxbSnk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1779769874127265.5403474924351; Mon, 25 May 2026 21:31:14 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wRjR1-0007up-Cx; Tue, 26 May 2026 00:30:31 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wRjQt-0007eI-1y for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:23 -0400 Received: from mail-wm1-x32b.google.com ([2a00:1450:4864:20::32b]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wRjQq-0007bx-Mw for qemu-devel@nongnu.org; Tue, 26 May 2026 00:30:22 -0400 Received: by mail-wm1-x32b.google.com with SMTP id 5b1f17b1804b1-49050ff7cbdso24928115e9.2 for ; Mon, 25 May 2026 21:30:20 -0700 (PDT) Received: from localhost.localdomain (46-116-239-136.bb.netvision.net.il. [46.116.239.136]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49076a6abfasm6659625e9.12.2026.05.25.21.30.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 25 May 2026 21:30:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779769819; x=1780374619; 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=xeRRTnWnaEVO/UepoOFJKLi/LO7+Q/LxWMTun4QJ2wA=; b=gICVKZXwqdpgeJSEDi+hpGUIrJv7PbG/8BhYy9+QIu5O+GO721aNJWVZqB0kvuiavz efh9ixXtEwzQkvzAV21tRGZV5N/9CdcvU65FisLiygj0yyO5n7gF8mWhAZoklAGTFd7O 8T/2hhtx+G0xS4dME81xs7iVR2wf+KG18nYwpZztKGsIeIo1J4lNaisVSiYBRIR4Kefh bsbwulyYj4UitPQb9GEwre6ygz4mKj39o4KOn2pBKtR0M7a12PT1kzpst97it1kWrbrT wi37kfNa7v9Fxkmtd/tKbJXQTP0ECb0px66q/Tl7EPl2Zl4dDdf5iWDdJ0FGIc59acEl wHzg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779769819; x=1780374619; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=xeRRTnWnaEVO/UepoOFJKLi/LO7+Q/LxWMTun4QJ2wA=; b=bGVA0trHcm7qssH4rdt7iKLJ3P9HAZLZXGBBnmTiit8aYV/iHDf4wIvdtUAdvy+anA PPrUPreCJxBvKleJlKM5CAvgfnWA869HbaoemNjlp+k7Gcht+Pb2tI07ropujVDC7JLa GnxU2TwvE86ufG+TowjSLj0IYFQ8f7akVbpme2VoBdmyxRyxpur/+zWe/0dvVBvoXnfW 2H2Kt2VO2wjCYs8PZU9tYcgogLhhm6TY5HF1eaWzxYp2pQ0E0dAjTSaCXmY6zws8sz0O n2kHX+IhqwfkAyjSE993Y4TTgWB6PMMQQrwMLubETXavlh9IcZFOmZ3ZpOiPyIruVvD4 x1ig== X-Forwarded-Encrypted: i=1; AFNElJ/lF0rEsoOOeb5+VTJEH3voW3sVUp8VS/NEM9kflET6gpPJPqgpCs9COdZJf6k38oIrZ+Iw3SOaT7im@nongnu.org X-Gm-Message-State: AOJu0YxraYJRgHWrdAdTkZfMiGKa0Rt5wO7F7i9q4kSP5SFdUFpOrlha Odg2PVn2k7hrXN69fD83TZrtFxan/JApz0K1W23gQbDJgEmwnyiPTZcF X-Gm-Gg: Acq92OGhXZgUYJJ6edOw39lj+9n+fyLyWZMEXxJGtuJPE9sNWUPQS17Ji2uhqlQsXgp uNMSHOvBuFgj/dFJvVjMAAlaBzhJivzPkYFza7L9QGQpSlg5Qjr2+DdqsYJS9W37uDEOOpy0NKc FDmdsVKuHdPz0fxvRR1rq0FsHTwLIs21oHTlm0jZHt3KPJ41UVESnlbmLOKO3sdW2601aZ/u1T6 uoJBlCi0Kubit0qpfefNkoOuBkG/nUSD3jgS4Q/V1hxXbkCOYxa4AbbP5SH6jKlAUtZ7VoO37f8 Y2R35023xjqp6HSI57I841HVLfbIZvJvqxi7CY8Zgz7cqpAf0i2fH2Tqb8paHLRzFoPhqi+GeWg NG8yJhs1bW0eyu6qvhVSOKIHskpjfnOd0DrwEo45Q/mR+gSq2AEZRrYdp94yfvuNOESenhkRRuS yjFEPQx3zCnpWtdeq+cYgjftqWTgftGWUq1mAjMyAXTTQarM+i8lYHLDDrWLGUlZ3PifBW0yfTk Go= X-Received: by 2002:a05:600c:45ce:b0:490:59cc:998e with SMTP id 5b1f17b1804b1-49059cc99famr148876495e9.3.1779769818990; Mon, 25 May 2026 21:30:18 -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 v4 8/8] scripts: Add laptop-mirror reference script Date: Tue, 26 May 2026 07:29:27 +0300 Message-ID: <20260526042928.9203-9-lb.workbox@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260526042928.9203-1-lb.workbox@gmail.com> References: <20260526042928.9203-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32b; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32b.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: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1779769874413158500 Content-Type: text/plain; charset="utf-8" Add an in-tree Python reference implementation that reads the host's battery, AC adapter and lid state from sysfs/procfs and forwards changes to a running QEMU guest through the battery-set-state, ac-adapter-set-state and lid-button-set-state QMP commands. This script is intended as an example for management layers (such as libvirt) to follow when wiring host hardware to those devices, not as an end-user deployment tool. Signed-off-by: Leonid Bloch --- MAINTAINERS | 6 + docs/tools/index.rst | 1 + docs/tools/laptop-mirror.rst | 82 +++++++++++++ scripts/laptop-mirror.py | 219 +++++++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 docs/tools/laptop-mirror.rst create mode 100755 scripts/laptop-mirror.py diff --git a/MAINTAINERS b/MAINTAINERS index 1f8f3e247e..9616544d29 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3057,6 +3057,12 @@ F: docs/specs/button.rst F: hw/acpi/button* F: include/hw/acpi/button.h =20 +Laptop mirror +M: Leonid Bloch +S: Maintained +F: docs/tools/laptop-mirror.rst +F: scripts/laptop-mirror.py + Subsystems ---------- Overall Audio backends diff --git a/docs/tools/index.rst b/docs/tools/index.rst index 868c3c4d9d..0c8fa6010f 100644 --- a/docs/tools/index.rst +++ b/docs/tools/index.rst @@ -17,3 +17,4 @@ command line utilities and other standalone programs. qemu-trace-stap qemu-vmsr-helper qemu-vnc + laptop-mirror diff --git a/docs/tools/laptop-mirror.rst b/docs/tools/laptop-mirror.rst new file mode 100644 index 0000000000..62fc2f92c3 --- /dev/null +++ b/docs/tools/laptop-mirror.rst @@ -0,0 +1,82 @@ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +QEMU laptop mirror tool +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Synopsis +-------- + +**laptop-mirror.py** [*OPTIONS*] + +Description +----------- + +``laptop-mirror.py`` polls the host's battery, AC adapter and lid state +from sysfs/procfs and forwards every change to a running QEMU guest using +the ``battery-set-state``, ``ac-adapter-set-state`` and +``lid-button-set-state`` QMP commands. This script is a reference for how +a management layer (libvirt or similar) can wire host hardware to them, +and isn't meant for production use as-is. + +Options +------- + +.. program:: laptop-mirror.py + +.. option:: -s SOCKET, --socket SOCKET + + QMP socket: a Unix path or ``host:port``. Falls back to ``$QMP_SOCKET``. + +.. option:: -i SECONDS, --interval SECONDS + + Polling interval, in seconds. Default ``2.0``. + +.. option:: --battery, --no-battery + + Mirror the battery (default: on). + +.. option:: --ac-adapter, --no-ac-adapter + + Mirror the AC adapter (default: on). + +.. option:: --lid, --no-lid + + Mirror the lid button (default: on). A device that is enabled but not + present on the host is silently skipped. + +.. option:: -v, --verbose + + ``-v`` logs every state change; ``-vv`` adds debug output. + +Example +------- + +Start QEMU with the laptop devices and a QMP socket:: + + qemu-system-x86_64 \ + -device battery -device acad -device button \ + -qmp unix:/tmp/qmp.sock,server=3Don,wait=3Doff \ + ... + +Then mirror your host state:: + + export QMP_SOCKET=3D/tmp/qmp.sock + $builddir/run scripts/laptop-mirror.py -v + +The script depends on the in-tree ``qemu.qmp`` package; ``$builddir/run`` +puts it on ``PYTHONPATH``. + +Caveats +------- + +* QMP allows one client at a time. If ``qmp-shell``, libvirt or another + script is already connected, the mirror times out after ten seconds and + exits with an error. +* When QEMU runs as root, its Unix QMP socket is root-owned. Run the + mirror as root too, ``chmod`` the socket after QEMU is up, or expose + QMP over TCP. + +See also +-------- + +:doc:`/specs/battery`, :doc:`/specs/acad`, :doc:`/specs/button`, +:manpage:`qemu-qmp-ref(7)` diff --git a/scripts/laptop-mirror.py b/scripts/laptop-mirror.py new file mode 100755 index 0000000000..8db76e4ff9 --- /dev/null +++ b/scripts/laptop-mirror.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2025-2026 Leonid Bloch + +"""Reference: mirror host laptop power state into a QEMU guest via QMP. + +The C devices (battery/acad/button) are pure QMP-controlled; this script +shows how a management layer (libvirt etc.) might wire host sysfs/procfs +state to the QMP commands. See docs/tools/laptop-mirror.rst. +""" +from __future__ import annotations + +import argparse +import logging +import os +import signal +import socket +import sys +import time +from pathlib import Path +from typing import Any + +try: + from qemu.qmp import QMPError + from qemu.qmp.legacy import QEMUMonitorProtocol +except ModuleNotFoundError as exc: + print(f"Module '{exc.name}' not found.", file=3Dsys.stderr) + print(f"Try $builddir/run {' '.join(sys.argv)}", file=3Dsys.stderr) + sys.exit(1) + + +log =3D logging.getLogger("laptop-mirror") + +POWER_SUPPLY =3D Path("/sys/class/power_supply") +ACPI_BUTTON =3D Path("/proc/acpi/button") + + +def read_str(p: Path) -> str | None: + try: + return p.read_text().strip() + except OSError: + return None + + +def read_int(p: Path) -> int | None: + s =3D read_str(p) + try: + return int(s) if s is not None else None + except ValueError: + return None + + +def find_supply(kind: str) -> Path | None: + if not POWER_SUPPLY.is_dir(): + return None + for d in sorted(POWER_SUPPLY.iterdir()): + if read_str(d / "type") =3D=3D kind: + return d + return None + + +def find_lid() -> Path | None: + lid_dir =3D ACPI_BUTTON / "lid" + if not lid_dir.is_dir(): + return None + for sub in sorted(lid_dir.iterdir()): + if (state :=3D sub / "state").is_file(): + return state + return None + + +def battery_state(path: Path) -> dict[str, Any] | None: + status =3D read_str(path / "status") or "" + cap =3D read_int(path / "capacity") + if cap is None: + en, ef =3D read_int(path / "energy_now"), read_int(path / "energy_= full") + if en is None or not ef: + return None + cap =3D en * 100 // ef + + state: dict[str, Any] =3D { + "present": True, + "charging": status =3D=3D "Charging", + "discharging": status =3D=3D "Discharging", + "charge-percent": max(0, min(100, cap)), + } + pw =3D read_int(path / "power_now") + if pw is not None: + state["rate"] =3D abs(pw) // 1000 + return state + + +def ac_online(path: Path) -> bool | None: + v =3D read_int(path / "online") + return None if v is None else bool(v) + + +def lid_open(path: Path) -> bool | None: + s =3D read_str(path) + return None if s is None else "open" in s.lower() + + +def qmp_connect(address, timeout): + if isinstance(address, tuple): + sock =3D socket.create_connection(address, timeout=3Dtimeout) + else: + sock =3D socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect(address) + sock.settimeout(timeout) + if not sock.recv(1, socket.MSG_PEEK): + sock.close() + raise TimeoutError + sock.settimeout(None) + return sock + + +def parse_args() -> argparse.Namespace: + p =3D argparse.ArgumentParser( + description=3D"Mirror host laptop hardware state to a QEMU guest " + "via QMP.") + p.add_argument("-s", "--socket", default=3Dos.environ.get("QMP_SOCKET"= ), + help=3D"QMP socket: unix path or addr:port " + "(default: $QMP_SOCKET)") + p.add_argument("-i", "--interval", type=3Dfloat, default=3D2.0, + metavar=3D"SECONDS", + help=3D"polling interval in seconds (default: 2.0)") + p.add_argument("-v", "--verbose", action=3D"count", default=3D0, + help=3D"increase verbosity (-v info, -vv debug)") + p.add_argument("--battery", action=3Dargparse.BooleanOptionalAction, + default=3DTrue, help=3D"monitor the battery") + p.add_argument("--ac-adapter", action=3Dargparse.BooleanOptionalAction, + default=3DTrue, help=3D"monitor the AC adapter") + p.add_argument("--lid", action=3Dargparse.BooleanOptionalAction, + default=3DTrue, help=3D"monitor the lid button") + args =3D p.parse_args() + if not args.socket: + p.error("--socket is required (or set $QMP_SOCKET)") + if args.interval <=3D 0: + p.error("--interval must be positive") + if not (args.battery or args.ac_adapter or args.lid): + p.error("at least one device must be enabled") + return args + + +def main() -> int: + args =3D parse_args() + levels =3D [logging.WARNING, logging.INFO, logging.DEBUG] + logging.basicConfig(level=3Dlevels[min(args.verbose, 2)], + format=3D"%(message)s", stream=3Dsys.stderr) + logging.getLogger("qemu.qmp").setLevel(logging.CRITICAL) + + bat =3D find_supply("Battery") if args.battery else None + ac =3D find_supply("Mains") if args.ac_adapter else None + lid =3D find_lid() if args.lid else None + if not (bat or ac or lid): + log.error("No host laptop devices found to mirror") + return 1 + for name, path in (("battery", bat), ("ac-adapter", ac), ("lid", lid)): + if path is not None: + log.info("Mirroring %s from %s", name, path) + + try: + sock =3D qmp_connect(QEMUMonitorProtocol.parse_address(args.socket= ), 10) + except TimeoutError: + log.error("Timed out negotiating QMP with %s. Is another QMP " + "client (e.g. qmp-shell) holding the socket?", args.sock= et) + return 1 + except OSError as exc: + log.error("Could not connect to %s: %s", args.socket, exc) + return 1 + + qmp =3D QEMUMonitorProtocol(sock) + try: + qmp.connect() + except QMPError as exc: + log.error("QMP error: %s", exc) + return 1 + + prev: dict[str, dict[str, Any]] =3D {} + + def push(command: str, payload: dict[str, Any]) -> None: + if prev.get(command) =3D=3D payload: + return + try: + qmp.cmd(command, **payload) + except QMPError as exc: + log.warning("%s failed: %s", command, exc) + return + prev[command] =3D payload + log.info("%s -> %s", command, payload) + + running =3D True + + def stop(_signum, _frame): + nonlocal running + running =3D False + + signal.signal(signal.SIGINT, stop) + signal.signal(signal.SIGTERM, stop) + + try: + while running: + if bat and (s :=3D battery_state(bat)) is not None: + push("battery-set-state", {"state": s}) + if ac and (c :=3D ac_online(ac)) is not None: + push("ac-adapter-set-state", {"connected": c}) + if lid and (o :=3D lid_open(lid)) is not None: + push("lid-button-set-state", {"open": o}) + time.sleep(args.interval) + return 0 + finally: + qmp.close() + + +if __name__ =3D=3D "__main__": + sys.exit(main()) --=20 2.54.0