From nobody Sat Nov 15 03:13:40 2025 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=1755798506; cv=none; d=zohomail.com; s=zohoarc; b=dKbbsFAvgPHkdSszLgheNC4OHYPVu7TJWmYuF6/S9TUnR7W+t3MBtqkYuNwwI0yG39j4v3AhRyylLlsNTSiNSf7VrsSMW6qw3qiSWwAM0ESsb1WMNoodM8I3MIRdBVc98//ptaj3NcuX07yoZmX7iTTcgIvSyOm1bXMugQZP/tg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1755798506; 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=ou2WNmGRU4pI7jg2sZGzfLbvp3JIANekT3jVlYK4v6w=; b=Z/W3L1XIme+dogjeBQwhJwnrmTiic9d+iMilmyJBP4vSsC9BnMjgQj+lboPwBoLJ5QvADSs77XACBoDoKWgA66W5tQJ0RRFenJvA1hHXEEBZfaGj4pSDaxlLVVwci9aVtVfb7YHEU7xorBxs3oIWx7SUUIHLzzFo0Z0tX9zxk/k= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1755798506339840.3214835602248; Thu, 21 Aug 2025 10:48:26 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1up9NG-0007Er-Gw; Thu, 21 Aug 2025 13:46:54 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1up9NF-0007E3-59 for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:46:53 -0400 Received: from mail-wr1-x435.google.com ([2a00:1450:4864:20::435]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1up9ND-0003me-C3 for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:46:52 -0400 Received: by mail-wr1-x435.google.com with SMTP id ffacd0b85a97d-3b9edf504e6so862380f8f.3 for ; Thu, 21 Aug 2025 10:46:50 -0700 (PDT) Received: from localhost.localdomain (46-116-237-160.bb.netvision.net.il. [46.116.237.160]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3c074879f5fsm12485360f8f.4.2025.08.21.10.46.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 21 Aug 2025 10:46:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755798409; x=1756403209; 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=ou2WNmGRU4pI7jg2sZGzfLbvp3JIANekT3jVlYK4v6w=; b=Ob8daiaU6DugtVC9eAK0nk3jFNA3CI9bsl+s2YFwvzH8uWq2mS8nBqq9XCRftgYsC6 jGYPCyiGr0D6gCcLarqWUxai1ArC4M8tyYFZKYgIk6akrg1lbv27M+hlNw+S/i95uqer bPS38SPTS46sY6cv/MMIgZfgZbAOl/XhytD0jTs5TsKOv/sNYrhkMq/eUuHYGx/Byt74 Ou3IWJbx2Yv4x+ci53TxXMZExhQ19i77MA1ZfGXBAEibqr6qowURbTLFSNqva46BSifY a2/8QBoB16KovJ/J/WgUCFI1tKxb9o2E1JI1v/zcYVPL9GCYRFj8o6lBIOYxp/h6YgCF adww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755798409; x=1756403209; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ou2WNmGRU4pI7jg2sZGzfLbvp3JIANekT3jVlYK4v6w=; b=btBIOCcNZgfdCA5Y5tdfIsBLTcB0QEB301VgRzwKXQxNhriOKXmR8UkoHo2YZELdc+ 1m+GD6gfyQhxbyFGE69CAcaIWoJImIRJ/erOCwD65XYgG9RfdT94xWid+n0WDZaDb0+t ugdj7ouSNwRvlYBiSIfmR6xMSAMPZEKX/xGgRjUL9e2LYDyKoQor2QiVzrIlkNbt7rCi DDacUVYYOEmZv+47dKOUAqsdIoAChqNSpnrHCvm/+yWilT6wAoJziNTuLQbxSx4z4dtr /T0Tp2S7Go3yFNA6WLetivQkqQJ7SCYZrletDtvcl8jDKpbtGAgny82w/VYks57mMBAR BVNw== X-Forwarded-Encrypted: i=1; AJvYcCUVBx8ou2enexNBESQo58WfNJsV5VC9/W43gC515c8nYTAX2bMJhF0v0uv6kY1yilmcxKYA1feFJ8DN@nongnu.org X-Gm-Message-State: AOJu0YxjA9KHDqTDrj1q5SIbptAfyGwJou+M2JTZa/HVAaZ5/Pk3WgVQ VA4PmF022Y86GShU66Ktapqq5zW2spn+y1yGVQZ5VzU/iQHw3YmRoEq0 X-Gm-Gg: ASbGnctFgcS9f5vNp84zVvYLYJ8h9PiKD/HK3zh9nVOSO0m13LEHJkhBY1VF2iQLxVQ uiod9T7HVHLAH5CPtdksZdbKzYUx4WQOSw/uuZmzF2lq/1c0SyKcXOzjxEOJb7jFtoIIyk4luSg vo9Loz1zOizdBAlR7aRx0fC1S7EVtd7kAFjEJ2HTS7KUKtu8XmfSVPplCR/gWRgAV7aeZiDYNy6 a3LBI6oCPpcQqndEykuE4PrQn46ySHRtdWAFQ0/Hx557+MhzjkfZJa69/gzmDdZ8y2y1z9iDqpK 8xujt4y4Ma+t7psouH2sT727tZoNnbdTW11kLNv4XzE4+QD/fnBDSbkLbf9pLXcgxA+ldbQ3leZ I5I0CZkfMFmOrFjsQ5tj8kC6F99vez3YXFoTvZfAZYo7o6qwu/PaUMIvzHgw9Mky+6PaVUnFyCi E= X-Google-Smtp-Source: AGHT+IHXwSonZhHY2rvnSb8PNEHQI7YTbATyMM6KEMsCPaQHeQ9ZMf90jSVz0H0vL+XvaLb90dH1VQ== X-Received: by 2002:a5d:6381:0:b0:3b8:f8e6:867b with SMTP id ffacd0b85a97d-3c496f92c82mr2195541f8f.31.1755798409309; Thu, 21 Aug 2025 10:46:49 -0700 (PDT) From: Leonid Bloch To: "Michael S . Tsirkin" , Igor Mammedov , Ani Sinha , Paolo Bonzini , Richard Henderson , Eduardo Habkost , Eric Blake , Markus Armbruster , Marcel Apfelbaum , Dmitry Fleytman Cc: Leonid Bloch , qemu-devel@nongnu.org Subject: [PATCH v2 1/4] hw/acpi: Increase the number of possible ACPI interrupts Date: Thu, 21 Aug 2025 20:45:49 +0300 Message-ID: <20250821174554.40607-2-lb.workbox@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250821174554.40607-1-lb.workbox@gmail.com> References: <20250821174554.40607-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 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=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::435; envelope-from=lb.workbox@gmail.com; helo=mail-wr1-x435.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1755798507609116600 Content-Type: text/plain; charset="utf-8" Increase the number of possible ACPI interrupts from 8, to the maximum available: 64 by default. 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 58f8964e13..447ff07878 100644 --- a/hw/acpi/core.c +++ b/hw/acpi/core.c @@ -729,19 +729,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 (sizeof(ar->gpe.sts[0]) * CHAR_BIT); + } + acpi_update_sci(ar, irq); } =20 void acpi_update_sci(ACPIREGS *regs, qemu_irq irq) { int sci_level, pm1a_sts; + uint64_t gpe_sci =3D 0; + 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 (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 !=3D 0); =20 qemu_set_irq(irq, sci_level); =20 --=20 2.50.1 From nobody Sat Nov 15 03:13:40 2025 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=1755798507; cv=none; d=zohomail.com; s=zohoarc; b=h1A0fytaOAuDYsVMqKUXh4VENhrbmshHL24PImIXapD0MIIEI+fB9v61huN++hAnFBbF8r6a7C4roFO2DjJJMibkpBnvD/Oegw2wUj+Z7UwprncEtuhWPCjs7Z5W2HbBZVR6pPoOqxUXlvlyUlWuvVkWZjujefZhdNcAwP6CerE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1755798507; h=Content-Type: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=L6mZQ5BL7J4y51N0HkeH4ik4Mhv/x5ble9Fy7IdIKGI=; b=nZmvtCpc0eE/alUnq/BcaSLQiaEkwB1mgS3xLV0hWmy9/y+rEkUBWPYxj45ygIxG13hO1HbxnpEMvBQySuuUUHl5SHhO1+E3VDkyrzjsfi8th/XSpduCP5ov8wKk6+hsIDw0/SYj6fEITYGXvR+ZoaBACSymwMA7xpGjrRhQzXQ= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1755798507554910.3391179290161; Thu, 21 Aug 2025 10:48:27 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1up9NU-0007Jh-BG; Thu, 21 Aug 2025 13:47:12 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1up9NJ-0007Hy-Pv for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:46:58 -0400 Received: from mail-wr1-x431.google.com ([2a00:1450:4864:20::431]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1up9NF-0003mi-Em for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:46:57 -0400 Received: by mail-wr1-x431.google.com with SMTP id ffacd0b85a97d-3b9d41c1963so791080f8f.0 for ; Thu, 21 Aug 2025 10:46:53 -0700 (PDT) Received: from localhost.localdomain (46-116-237-160.bb.netvision.net.il. [46.116.237.160]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3c074879f5fsm12485360f8f.4.2025.08.21.10.46.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 21 Aug 2025 10:46:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755798411; x=1756403211; 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=L6mZQ5BL7J4y51N0HkeH4ik4Mhv/x5ble9Fy7IdIKGI=; b=QlO5/SR063PN0i0vjG7nTAv6kIei2VRb640Pl2jzF57+Q7OTzUV2vhTAXxjZt9xtCj 5XAf6LrcqnlL7LvqKtkznn4i62mWWto2GnxAvC4+cUdvhoctLVFzoo91e7DFq2xeZVdL IFFfCXt9HZVSETfNd6Dg5e3X1LANBLbEggHr4x/xUJl2AzmsQJAmT6Lw2qbSypwvq8kL 0/uMGdBMfVhhYI1Av5r8JCOJ21LS0FzpxT8Nj1ba8okiqQpFdhJPUt+oLcImNrWYdKi8 ByoqZsOQDNoz1eOMmpwGQo1/0Mew9TIENxgeggT9Dy9lGrhRRyny915fLbd6GJiuu086 j5GA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755798411; x=1756403211; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=L6mZQ5BL7J4y51N0HkeH4ik4Mhv/x5ble9Fy7IdIKGI=; b=Fnc/gd5e+/qo8ni4BZzGk1Cgrg/uDqCa6UYfPwetoVjOQee7pmqPCfUDBM2MC5/i+L GPHIeOD7cI+7FVA8qZw+qBRAnwMh0NzgZ/b6oX+JdjkClywWXPKKUMi5GVdOI30PctGM LAG2G56IH2DzlxtcfbFBhrspJRFoyKXt6EIIdO6z7YFVOktQd7ghiAAA8tpqkHHUXyJF 2/Ut/rosN1latV8wsneVjYdwzMhR5ZqrgDfte8GmoihvqQ/r6Omwj5O0eHDYrjuu6oL6 /r9r+pUKBBLB7EXOqb8OPOswcnMZ9eQd5UV1RJQ5nLVE1vNj0kVwfn375rhXjrqgzFIj EwCg== X-Forwarded-Encrypted: i=1; AJvYcCWZZPT09MQeCmqqNiLPBoDMRaavQpo+n6nSbmOr7Izawit2q0kqBxyUh/w7TrJn8dzrZKMf8AbJnRfZ@nongnu.org X-Gm-Message-State: AOJu0YzwS8pl2gR+NRTerqcHSXmV5FBR8G4zdjJ28BNAWwpqqXDN6vfL /ZBi5v3pPu4W34p/XRlINS4ewje93iF3dlV6QWN2Wnc1lNs32FlmX2XV68VqAXlt X-Gm-Gg: ASbGnctjnZs9Nvp/PUAqXi8RgIPq6BZe0xXCOyL7vYCU1irOuRsDoHijjM/QT7pdDBd DCY0c4yBUuT7fKxCzy1AO41sQRZHXgY7syQZIDOd5Cb6tp2wxa6ogLG/z8oEHtYuU7mL7K5gj1O bNCGyz57COr7VQpYsVUqtbNG19mIjL9En5uf/vIqBq1HCriPEUlGhdrl/QifvO51+SBvTBfDanK 6m3KwS7OlRGXgJllXx3hIUrBu9f5vUcM3aBm2JP57wdXc8H49FgxmtNpT/N3wqt1IEyQFxfq+ke NazDmJJqZoBc8EqYsms9LBzIc30D/NYA1kkzd/pTprDE8kuFkntI2o8KGSphS9w15ZwPPiAI+pd JAjXcgHbb7vX0Y6P6IBBHE/wGsv6o5TNmISSrvCFcmls8u5UCsgtYOyGbPH3X7A0bBJDSPUCdcN g= X-Google-Smtp-Source: AGHT+IGeGvRZvRdIBMr+gU9Ve+BW7Vq8/k7I6SWKh3yM126ZHlejSus0uWDYNblWC/+7oE7KlhJTMQ== X-Received: by 2002:a5d:5d8a:0:b0:3b9:13e4:99b7 with SMTP id ffacd0b85a97d-3c495d47a7emr2668941f8f.29.1755798411163; Thu, 21 Aug 2025 10:46:51 -0700 (PDT) From: Leonid Bloch To: "Michael S . Tsirkin" , Igor Mammedov , Ani Sinha , Paolo Bonzini , Richard Henderson , Eduardo Habkost , Eric Blake , Markus Armbruster , Marcel Apfelbaum , Dmitry Fleytman Cc: Leonid Bloch , qemu-devel@nongnu.org Subject: [PATCH v2 2/4] hw/acpi: Introduce the QEMU Battery Date: Thu, 21 Aug 2025 20:45:50 +0300 Message-ID: <20250821174554.40607-3-lb.workbox@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250821174554.40607-1-lb.workbox@gmail.com> References: <20250821174554.40607-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" 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=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::431; envelope-from=lb.workbox@gmail.com; helo=mail-wr1-x431.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1755798509220124100 The battery device communicates battery state to the guest via ACPI. It supports two modes of operation: 1. QMP control mode (default): Battery state is controlled programmatically via QMP commands, making the device deterministic and migration-safe. 2. Host mirroring mode (optional): The device reflects the host's battery state from sysfs. Probing occurs on guest ACPI requests and at timed intervals (default 2 seconds, configurable via 'probe_interval' property in milliseconds). State changes trigger ACPI notifications to the guest. Properties: - 'use-qmp': Enable QMP control mode (default: true) - 'enable-sysfs': Enable host battery mirroring (default: false) - 'probe_interval': Probe interval in ms for sysfs mode (default: 2000) - 'sysfs_path': Override default sysfs path /sys/class/power_supply/ The device implements the ACPI_DEV_AML_IF interface to generate its own AML code, placing the 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 allows testing without host battery access and provides a stable interface for virtualization management systems. Signed-off-by: Leonid Bloch Signed-off-by: Marcel Apfelbaum --- MAINTAINERS | 6 + docs/specs/battery.rst | 225 ++++++++ docs/specs/index.rst | 1 + hw/acpi/Kconfig | 4 + hw/acpi/battery.c | 735 +++++++++++++++++++++++++++ hw/acpi/meson.build | 1 + hw/acpi/trace-events | 5 + hw/i386/Kconfig | 1 + hw/i386/acpi-build.c | 1 + include/hw/acpi/acpi_dev_interface.h | 1 + include/hw/acpi/battery.h | 33 ++ qapi/acpi.json | 73 +++ 12 files changed, 1086 insertions(+) create mode 100644 docs/specs/battery.rst create mode 100644 hw/acpi/battery.c create mode 100644 include/hw/acpi/battery.h diff --git a/MAINTAINERS b/MAINTAINERS index a07086ed76..51af9b7366 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2921,6 +2921,12 @@ F: hw/vmapple/* F: include/hw/vmapple/* F: docs/system/arm/vmapple.rst =20 +Battery +M: Leonid Bloch +S: Maintained +F: hw/acpi/battery.* +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..e426b91ea5 --- /dev/null +++ b/docs/specs/battery.rst @@ -0,0 +1,225 @@ +.. 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. It sup= ports +two operating modes: + +1. **QMP Control Mode** (default): Battery state is controlled via QMP com= mands, + providing deterministic control for testing and migration safety. +2. **Sysfs Mode**: Battery state mirrors the host's physical battery, usef= ul + for desktop virtualization where the guest should see the host's batter= y. + +Configuration +------------- + +The battery device is created as an ISA device using ``-device battery``. + +Operating Modes +~~~~~~~~~~~~~~~ + +**QMP Control Mode** (``use-qmp=3Dtrue``, default) + Battery state is controlled via QMP commands. This mode is recommended f= or: + + * Production environments requiring migration support + * Testing with predictable battery states + * Environments without host battery access + * Security-sensitive deployments + +**Sysfs Mode** (``enable-sysfs=3Dtrue``) + Battery mirrors the host's physical battery. This mode is useful for: + + * Desktop virtualization on laptops + * Development and testing with real battery behavior + + Note: Sysfs mode reads host files and runs timers, which may impact + security and migration. Use with caution in production. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x530) + I/O port base address for the battery device registers. + +``use-qmp`` (default: true) + Enable QMP control mode. When true, battery state is controlled via + QMP commands. Cannot be used together with ``enable-sysfs=3Dtrue``. + +``enable-sysfs`` (default: false) + Enable sysfs mode to mirror the host's battery. Cannot be used together + with ``use-qmp=3Dtrue``. + +``probe_interval`` (default: 2000) + Time interval between periodic probes in milliseconds (sysfs mode only). + A zero value disables the periodic probes, and makes the battery state + updates occur on guest requests only. + +``sysfs_path`` (default: auto-detected) + Path to the host's battery sysfs directory (sysfs mode only). If not + specified, the device will automatically detect the battery from + ``/sys/class/power_supply/``. This property allows overriding the default + path if: + + * The sysfs path differs from the standard location + * A different battery needs to be probed + * A "fake" host battery is to be provided for testing + +Host Battery Detection +---------------------- + +The host's battery information is taken from the sysfs battery data, +located in:: + + /sys/class/power_supply/[device of type "Battery"] + +The device automatically scans for the first available battery device +with type "Battery" in the power_supply directory. + +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) + + * 1 =3D Discharging + * 2 =3D Charging + +* **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. +Unknown values are represented as 0xFFFFFFFF. + +ACPI Notifications +------------------ + +The battery device generates ACPI notifications through GPE events: + +* **GPE._E07**: Device Check (0x01) - Battery insertion/removal +* **GPE._E08**: Status Change (0x80) - Battery state change +* **GPE._E09**: Information Change (0x81) - Battery information update + +QMP Commands +------------ + +When using QMP control mode (default), the following commands are availabl= e: + +``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 +-------- + +QMP control mode (default - recommended):: + + # Start with QMP control + 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 + +Sysfs mode (mirror host battery):: + + # Enable sysfs mode to mirror host battery + qemu-system-x86_64 -device battery,use-qmp=3Dfalse,enable-sysfs=3Dtrue + + # Custom probe interval (5 seconds) + qemu-system-x86_64 -device battery,use-qmp=3Dfalse,enable-sysfs=3Dtrue,p= robe_interval=3D5000 + + # Specific battery path + qemu-system-x86_64 -device battery,use-qmp=3Dfalse,enable-sysfs=3Dtrue,s= ysfs_path=3D/sys/class/power_supply/BAT1 + +Testing with fake battery:: + + # Create fake battery files for testing + mkdir -p /tmp/fake_battery + echo "Battery" > /tmp/fake_battery/type + echo "Discharging" > /tmp/fake_battery/status + echo "50" > /tmp/fake_battery/capacity + echo "1500000" > /tmp/fake_battery/energy_now # Current energy in =CE= =BCWh + echo "3000000" > /tmp/fake_battery/energy_full # Full capacity in =CE= =BCWh + echo "500000" > /tmp/fake_battery/power_now # Current power in =CE= =BCW + + # Use fake battery in sysfs mode + qemu-system-x86_64 -device battery,use-qmp=3Dfalse,enable-sysfs=3Dtrue,s= ysfs_path=3D/tmp/fake_battery + + # Update battery state while VM is running (from another terminal) + # Change to 75% charging: + echo "Charging" > /tmp/fake_battery/status + echo "75" > /tmp/fake_battery/capacity + echo "2250000" > /tmp/fake_battery/energy_now # 75% of 3000000 + echo "500000" > /tmp/fake_battery/power_now # Charging rate (500 mW) + + # Change to 25% discharging: + echo "Discharging" > /tmp/fake_battery/status + echo "25" > /tmp/fake_battery/capacity + echo "750000" > /tmp/fake_battery/energy_now # 25% of 3000000 + echo "300000" > /tmp/fake_battery/power_now # Discharge rate (300 m= W) diff --git a/docs/specs/index.rst b/docs/specs/index.rst index f19d73c9f6..616e8228cc 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 diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index 1d4e9f0845..64403378bd 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -83,3 +83,7 @@ config ACPI_ERST config ACPI_CXL bool depends on ACPI + +config BATTERY + bool + depends on ACPI diff --git a/hw/acpi/battery.c b/hw/acpi/battery.c new file mode 100644 index 0000000000..5ab3ab7472 --- /dev/null +++ b/hw/acpi/battery.c @@ -0,0 +1,735 @@ +/* + * QEMU emulated battery device. + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "trace.h" +#include "hw/isa/isa.h" +#include "hw/acpi/acpi.h" +#include "hw/nvram/fw_cfg.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/acpi/acpi_aml_interface.h" +#include "qapi/qapi-commands-acpi.h" + +#include "hw/acpi/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 SYSFS_PATH "/sys/class/power_supply" +#define BATTERY_TYPE "Battery" + +#define MAX_ALLOWED_STATE_LENGTH 32 /* For convinience when comparing */ + +#define NORMALIZE_BY_FULL(val, full) \ + ((full =3D=3D 0) ? BATTERY_VAL_UNKNOWN \ + : (uint32_t)(val * BATTERY_FULL_CAP / full)) + +typedef union bat_metric { + uint32_t val; + uint8_t acc[4]; +} bat_metric; + +typedef struct BatteryState { + ISADevice dev; + MemoryRegion io; + uint16_t ioport; + bat_metric state; + bat_metric rate; + bat_metric charge; + uint32_t charge_full; + int units; /* 0 - mWh, 1 - mAh */ + bool use_qmp_control; + bool qmp_present; + bool qmp_charging; + bool qmp_discharging; + int qmp_charge_percent; + int qmp_rate; + bool enable_sysfs; + + QEMUTimer *probe_state_timer; + uint64_t probe_state_interval; + + char *bat_path; +} BatteryState; + +/* Access addresses */ +enum acc_addr { + bsta_addr0, bsta_addr1, bsta_addr2, bsta_addr3, + brte_addr0, brte_addr1, brte_addr2, brte_addr3, + bcrg_addr0, bcrg_addr1, bcrg_addr2, bcrg_addr3 +}; + +/* Files used when the units are: mWh mAh */ +static const char *full_file[] =3D { "energy_full", "charge_full" }; +static const char *now_file[] =3D { "energy_now", "charge_now" }; +static const char *rate_file[] =3D { "power_now", "current_now" }; + +static const char *stat_file =3D "status"; +static const char *type_file =3D "type"; + +static const char *discharging_states[] =3D { "Discharging", "Not charging= " }; +static const char *charging_states[] =3D { "Charging", "Full", "Unknown" }; + +static inline bool battery_file_accessible(char *path, const char *file) +{ + char full_path[PATH_MAX]; + int path_len; + + path_len =3D snprintf(full_path, PATH_MAX, "%s/%s", path, file); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + if (access(full_path, R_OK) =3D=3D 0) { + return true; + } + return false; +} + +static inline int battery_select_file(char *path, const char **file) +{ + if (battery_file_accessible(path, file[0])) { + return 0; + } else if (battery_file_accessible(path, file[1])) { + return 1; + } else { + return -1; + } +} + +static void battery_get_full_charge(BatteryState *s, Error **errp) +{ + char file_path[PATH_MAX]; + int path_len; + uint32_t val; + FILE *ff; + + path_len =3D snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path, + full_file[s->units]); + if (path_len < 0 || path_len >=3D PATH_MAX) { + error_setg(errp, "Full capacity file path is inaccessible."); + return; + } + + ff =3D fopen(file_path, "r"); + if (ff =3D=3D NULL) { + error_setg_errno(errp, errno, "Could not read the full charge file= ."); + return; + } + + if (fscanf(ff, "%u", &val) !=3D 1) { + error_setg(errp, "Full capacity undetermined."); + return; + } else { + s->charge_full =3D val; + } + fclose(ff); +} + +static inline bool battery_is_discharging(char *val) +{ + static const int discharging_len =3D ARRAY_SIZE(discharging_states); + int i; + + for (i =3D 0; i < discharging_len; i++) { + if (!strncmp(val, discharging_states[i], MAX_ALLOWED_STATE_LENGTH)= ) { + return true; + } + } + return false; +} + +static inline bool battery_is_charging(char *val) +{ + static const int charging_len =3D ARRAY_SIZE(charging_states); + int i; + + for (i =3D 0; i < charging_len; i++) { + if (!strncmp(val, charging_states[i], MAX_ALLOWED_STATE_LENGTH)) { + return true; + } + } + return false; +} + +static void battery_get_state(BatteryState *s) +{ + char file_path[PATH_MAX]; + int path_len; + char val[MAX_ALLOWED_STATE_LENGTH]; + FILE *ff; + + path_len =3D snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path, stat_= file); + if (path_len < 0 || path_len >=3D PATH_MAX) { + warn_report("Could not read the battery state."); + return; + } + + ff =3D fopen(file_path, "r"); + if (ff =3D=3D NULL) { + warn_report("Could not read the battery state."); + return; + } + + if (fgets(val, MAX_ALLOWED_STATE_LENGTH, ff) =3D=3D NULL) { + warn_report("Battery state unreadable."); + } else { + val[strcspn(val, "\n")] =3D 0; + if (battery_is_discharging(val)) { + s->state.val =3D BATTERY_DISCHARGING; + } else if (battery_is_charging(val)) { + s->state.val =3D BATTERY_CHARGING; + } else { + s->state.val =3D 0; + warn_report("Battery state undetermined."); + } + } + fclose(ff); +} + +static void battery_get_rate(BatteryState *s) +{ + char file_path[PATH_MAX]; + int path_len; + uint64_t val; + FILE *ff; + + path_len =3D snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path, + rate_file[s->units]); + if (path_len < 0 || path_len >=3D PATH_MAX) { + warn_report("Could not read the battery rate."); + s->rate.val =3D BATTERY_VAL_UNKNOWN; + return; + } + + ff =3D fopen(file_path, "r"); + if (ff =3D=3D NULL) { + warn_report("Could not read the battery rate."); + s->rate.val =3D BATTERY_VAL_UNKNOWN; + return; + } + + if (fscanf(ff, "%lu", &val) !=3D 1) { + warn_report("Battery rate undetermined."); + s->rate.val =3D BATTERY_VAL_UNKNOWN; + } else { + s->rate.val =3D NORMALIZE_BY_FULL(val, s->charge_full); + } + fclose(ff); +} + +static void battery_get_charge(BatteryState *s) +{ + char file_path[PATH_MAX]; + int path_len; + uint64_t val; + FILE *ff; + + path_len =3D snprintf(file_path, PATH_MAX, "%s/%s", s->bat_path, + now_file[s->units]); + if (path_len < 0 || path_len >=3D PATH_MAX) { + warn_report("Could not read the battery charge."); + s->charge.val =3D BATTERY_VAL_UNKNOWN; + return; + } + + ff =3D fopen(file_path, "r"); + if (ff =3D=3D NULL) { + warn_report("Could not read the battery charge."); + s->charge.val =3D BATTERY_VAL_UNKNOWN; + return; + } + + if (fscanf(ff, "%lu", &val) !=3D 1) { + warn_report("Battery charge undetermined."); + s->charge.val =3D BATTERY_VAL_UNKNOWN; + } else { + s->charge.val =3D NORMALIZE_BY_FULL(val, s->charge_full); + } + fclose(ff); +} + +static void battery_get_dynamic_status(BatteryState *s) +{ + if (s->use_qmp_control) { + s->state.val =3D 0; + if (s->qmp_present) { + if (s->qmp_charging) { + s->state.val |=3D BATTERY_CHARGING; + } + if (s->qmp_discharging) { + s->state.val |=3D BATTERY_DISCHARGING; + } + } + s->rate.val =3D s->qmp_rate; + s->charge.val =3D (s->qmp_charge_percent * BATTERY_FULL_CAP) / 100; + } else if (s->enable_sysfs) { + battery_get_state(s); + battery_get_rate(s); + battery_get_charge(s); + } else { + s->state.val =3D 0; + s->rate.val =3D 0; + s->charge.val =3D 0; + } + + trace_battery_get_dynamic_status(s->state.val, s->rate.val, s->charge.= val); +} + +static void battery_probe_state(void *opaque) +{ + BatteryState *s =3D opaque; + + uint32_t state_before =3D s->state.val; + uint32_t rate_before =3D s->rate.val; + uint32_t charge_before =3D s->charge.val; + + battery_get_dynamic_status(s); + + if (state_before !=3D s->state.val || rate_before !=3D s->rate.val || + charge_before !=3D s->charge.val) { + Object *obj =3D object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, = NULL); + switch (charge_before) { + case 0: + break; /* Avoid marking initiation as an update */ + default: + acpi_send_event(DEVICE(obj), ACPI_BATTERY_CHANGE_STATUS); + } + } + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->probe_state_interval); +} + +static void battery_probe_state_timer_init(BatteryState *s) +{ + if (s->enable_sysfs && s->probe_state_interval > 0) { + s->probe_state_timer =3D timer_new_ms(QEMU_CLOCK_VIRTUAL, + battery_probe_state, s); + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTU= AL) + + s->probe_state_interval); + } +} + +static bool battery_verify_sysfs(BatteryState *s, char *path) +{ + int units; + FILE *ff; + char type_path[PATH_MAX]; + int path_len; + char val[MAX_ALLOWED_STATE_LENGTH]; + + path_len =3D snprintf(type_path, PATH_MAX, "%s/%s", path, type_file); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + ff =3D fopen(type_path, "r"); + if (ff =3D=3D NULL) { + return false; + } + + if (fgets(val, MAX_ALLOWED_STATE_LENGTH, ff) =3D=3D NULL) { + fclose(ff); + return false; + } else { + val[strcspn(val, "\n")] =3D 0; + if (strncmp(val, BATTERY_TYPE, MAX_ALLOWED_STATE_LENGTH)) { + fclose(ff); + return false; + } + } + fclose(ff); + + units =3D battery_select_file(path, full_file); + + if (units < 0) { + return false; + } else { + s->units =3D units; + } + + return (battery_file_accessible(path, now_file[s->units]) + & battery_file_accessible(path, rate_file[s->units]) + & battery_file_accessible(path, stat_file)); +} + +static bool get_battery_path(DeviceState *dev) +{ + BatteryState *s =3D BATTERY_DEVICE(dev); + DIR *dir; + struct dirent *ent; + char bp[PATH_MAX]; + int path_len; + + if (s->bat_path) { + return battery_verify_sysfs(s, s->bat_path); + } + + dir =3D opendir(SYSFS_PATH); + if (dir =3D=3D NULL) { + return false; + } + + ent =3D readdir(dir); + while (ent !=3D NULL) { + if (ent->d_name[0] !=3D '.') { + path_len =3D snprintf(bp, PATH_MAX, "%s/%s", SYSFS_PATH, + ent->d_name); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + if (battery_verify_sysfs(s, bp)) { + qdev_prop_set_string(dev, BATTERY_PATH_PROP, bp); + closedir(dir); + return true; + } + } + ent =3D readdir(dir); + } + closedir(dir); + + return false; +} + +static void battery_realize(DeviceState *dev, Error **errp) +{ + ISADevice *d =3D ISA_DEVICE(dev); + BatteryState *s =3D BATTERY_DEVICE(dev); + FWCfgState *fw_cfg =3D fw_cfg_find(); + uint16_t *battery_port; + char err_details[0x20] =3D {}; + + trace_battery_realize(); + + if (s->use_qmp_control && s->enable_sysfs) { + error_setg(errp, "Cannot enable both QMP control and sysfs mode"); + return; + } + + /* Initialize QMP state to sensible defaults when in QMP mode */ + if (s->use_qmp_control) { + 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 */ + } + + if (s->enable_sysfs) { + if (!s->bat_path) { + strcpy(err_details, " Try using 'sysfs_path=3D'"); + } + + if (!get_battery_path(dev)) { + error_setg(errp, "Battery sysfs path not found or unreadable.%= s", + err_details); + return; + } + battery_get_full_charge(s, errp); + } else { + s->charge_full =3D BATTERY_FULL_CAP; + } + + isa_register_ioport(d, &s->io, s->ioport); + + battery_probe_state_timer_init(s); + + if (!fw_cfg) { + return; + } + + battery_port =3D g_malloc(sizeof(*battery_port)); + *battery_port =3D cpu_to_le16(s->ioport); + fw_cfg_add_file(fw_cfg, "etc/battery-port", battery_port, + sizeof(*battery_port)); +} + +static const Property battery_device_properties[] =3D { + DEFINE_PROP_UINT16(BATTERY_IOPORT_PROP, BatteryState, ioport, 0x530), + DEFINE_PROP_BOOL("use-qmp", BatteryState, use_qmp_control, true), + DEFINE_PROP_BOOL("enable-sysfs", BatteryState, enable_sysfs, false), + DEFINE_PROP_UINT64(BATTERY_PROBE_STATE_INTERVAL, BatteryState, + probe_state_interval, 2000), + DEFINE_PROP_STRING(BATTERY_PATH_PROP, BatteryState, bat_path), +}; + +static const VMStateDescription battery_vmstate =3D { + .name =3D "battery", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_UINT16(ioport, BatteryState), + VMSTATE_UINT64(probe_state_interval, 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"))); + + method =3D aml_method("_STA", 0, AML_NOTSERIALIZED); + aml_append(method, aml_return(aml_int(0x1F))); + aml_append(dev, method); + + aml_append(dev, aml_operation_region("DBST", AML_SYSTEM_IO, + aml_int(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("_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_VAL_UNKNOWN)); + /* Design Capacity of Warning */ + aml_append(pkg, aml_int(BATTERY_CAPACITY_OF_WARNING)); + /* Design Capacity of Low */ + aml_append(pkg, aml_int(BATTERY_CAPACITY_OF_LOW)); + /* Battery Capacity Granularity 1 */ + aml_append(pkg, aml_int(BATTERY_CAPACITY_GRANULARITY)); + /* Battery Capacity Granularity 2 */ + aml_append(pkg, aml_int(BATTERY_CAPACITY_GRANULARITY)); + /* Model Number */ + aml_append(pkg, aml_string("QBAT001")); /* Model Number */ + /* Serial Number */ + aml_append(pkg, aml_string("SN00000")); /* Serial Number */ + /* Battery Type */ + aml_append(pkg, aml_string("Virtual")); /* Battery Type */ + /* OEM Information */ + aml_append(pkg, aml_string("QEMU")); /* OEM Information */ + aml_append(method, aml_return(pkg)); + aml_append(dev, method); + + pkg =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_VAL_UNKNOWN)); + aml_append(dev, aml_name_decl("DBPR", pkg)); + + method =3D aml_method("_BST", 0, AML_NOTSERIALIZED); + aml_append(method, aml_store(aml_name("BSTA"), bat_state)); + aml_append(method, aml_store(aml_name("BRTE"), bat_rate)); + aml_append(method, aml_store(aml_name("BCRG"), bat_charge)); + aml_append(method, aml_store(bat_state, + aml_index(aml_name("DBPR"), aml_int(0)))); + aml_append(method, aml_store(bat_rate, + aml_index(aml_name("DBPR"), aml_int(1)))); + aml_append(method, aml_store(bat_charge, + aml_index(aml_name("DBPR"), aml_int(2)))); + aml_append(method, aml_return(aml_name("DBPR"))); + aml_append(dev, method); + + aml_append(sb_scope, dev); + aml_append(scope, sb_scope); + + /* Device Check */ + method =3D aml_method("\\_GPE._E07", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.BAT0"), aml_int(0x01))); + aml_append(scope, method); + + /* 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); + + /* Information Change */ + method =3D aml_method("\\_GPE._E09", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.BAT0"), aml_int(0x81))); + 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; + 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_addr0: + return s->state.acc[0]; + case bsta_addr1: + return s->state.acc[1]; + case bsta_addr2: + return s->state.acc[2]; + case bsta_addr3: + return s->state.acc[3]; + case brte_addr0: + return s->rate.acc[0]; + case brte_addr1: + return s->rate.acc[1]; + case brte_addr2: + return s->rate.acc[2]; + case brte_addr3: + return s->rate.acc[3]; + case bcrg_addr0: + return s->charge.acc[0]; + case bcrg_addr1: + return s->charge.acc[1]; + case bcrg_addr2: + return s->charge.acc[2]; + case bcrg_addr3: + return s->charge.acc[3]; + default: + warn_report("Battery: guest read unknown value."); + trace_battery_ioport_read_unknown(); + return 0; + } +} + +static const MemoryRegionOps battery_ops =3D { + .read =3D battery_ioport_read, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 1, + }, +}; + +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(void) +{ + Object *o =3D object_resolve_path_type("", TYPE_BATTERY, NULL); + if (!o) { + return NULL; + } + return BATTERY_DEVICE(o); +} + +void qmp_battery_set_state(BatteryInfo *state, Error **errp) +{ + BatteryState *s =3D find_battery_device(); + + if (!s) { + error_setg(errp, "No battery device found"); + 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; + } + + Object *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(); + BatteryInfo *ret; + + if (!s) { + error_setg(errp, "No battery device found"); + return NULL; + } + + ret =3D g_new0(BatteryInfo, 1); + + if (s->use_qmp_control) { + 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; + } else { + battery_get_dynamic_status(s); + ret->present =3D true; + ret->charging =3D !!(s->state.val & BATTERY_CHARGING); + ret->discharging =3D !!(s->state.val & BATTERY_DISCHARGING); + ret->charge_percent =3D (s->charge.val * 100) / BATTERY_FULL_CAP; + ret->has_rate =3D true; + ret->rate =3D s->rate.val; + } + + 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 73f02b9691..10379a7b2c 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -31,6 +31,7 @@ acpi_ss.add(when: 'CONFIG_PC', if_false: files('acpi-x86-= stub.c')) if have_tpm acpi_ss.add(files('tpm.c')) endif +acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c')) system_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-bui= ld-stub.c', 'ghes-stub.c', 'acpi_interface.c')) system_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_false: files('pci-bridge-= stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events index edc93e703c..dd3e815482 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -87,3 +87,8 @@ 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 +battery_ioport_read_unknown(void) "Battery read unknown" diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 3a0e2b8ebb..2c878fd112 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/hw/i386/acpi-build.c b/hw/i386/acpi-build.c index 423c4959fe..790b16e582 100644 --- a/hw/i386/acpi-build.c +++ b/hw/i386/acpi-build.c @@ -1248,6 +1248,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, =20 aml_append(sb_scope, dev); } + aml_append(dsdt, sb_scope); =20 if (pm->pcihp_bridge_en || pm->pcihp_root_en) { diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_de= v_interface.h index 68d9d15f50..3064ef6734 100644 --- a/include/hw/acpi/acpi_dev_interface.h +++ b/include/hw/acpi/acpi_dev_interface.h @@ -13,6 +13,7 @@ typedef enum { ACPI_NVDIMM_HOTPLUG_STATUS =3D 16, ACPI_VMGENID_CHANGE_STATUS =3D 32, ACPI_POWER_DOWN_STATUS =3D 64, + ACPI_BATTERY_CHANGE_STATUS =3D 128, } 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..5c5e83abfa --- /dev/null +++ b/include/hw/acpi/battery.h @@ -0,0 +1,33 @@ +/* + * QEMU emulated battery device. + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_ACPI_BATTERY_H +#define HW_ACPI_BATTERY_H + +#define TYPE_BATTERY "battery" +#define BATTERY_IOPORT_PROP "ioport" +#define BATTERY_PATH_PROP "sysfs_path" +#define BATTERY_PROBE_STATE_INTERVAL "probe_interval" + +#define BATTERY_FULL_CAP 10000 /* mWh */ + +#define BATTERY_CAPACITY_OF_WARNING (BATTERY_FULL_CAP / 10) /* 10% */ +#define BATTERY_CAPACITY_OF_LOW (BATTERY_FULL_CAP / 25) /* 4% */ +#define BATTERY_CAPACITY_GRANULARITY (BATTERY_FULL_CAP / 100) /* 1% */ + +#define BATTERY_VAL_UNKNOWN 0xFFFFFFFF + +#define BATTERY_LEN 0x0C + +#endif diff --git a/qapi/acpi.json b/qapi/acpi.json index 906b3687a5..d1ad663bfd 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -142,3 +142,76 @@ ## { '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: 10.2 +## +{ '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: 10.2 +# +# .. 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: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "query-battery" } +# <- { "return": { "present": true, +# "charging": true, +# "discharging": false, +# "charge-percent": 85 } } +## +{ 'command': 'query-battery', + 'returns': 'BatteryInfo' } --=20 2.50.1 From nobody Sat Nov 15 03:13:40 2025 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=1755798507; cv=none; d=zohomail.com; s=zohoarc; b=j9+MKTfodHNIkdrPW9VwwNsJ7thWdjehmr7Oj+iRpmkC+klaXwkUemh+2rq3EpF+Kd2GrByev1UPOttIfGmbu1E20LU2TwKUkCzP0AbvoPWR8K82beWUxKgR/XQIob+HhYYsFzbsL91XYL0oSf1M8MlLJhwbC3HGsEyQU9CH/KU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1755798507; 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=tRzJOfeYEGRJJECW+6UDAw2spa4P0CZCvt+937FRHC8=; b=fvx0sCBEW34zEmHMxAsSCOMHvo+Pz9Mj1BLx7YLJW/BacWcXZNbgnaq06yqN04e7gL0OFp7KObvdUv+bfSCP8VneXyoIeDuqCVo0F/FyEsmlF2SFVkW3JsdUhIGQ6kZFL0aEKoYws9FSGzR0g053nbWy9/ZlyXciOvOSGbCh2ME= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 175579850753081.80739630601931; Thu, 21 Aug 2025 10:48:27 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1up9Nd-0007MI-Kt; Thu, 21 Aug 2025 13:47:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1up9NM-0007Iv-1w for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:47:00 -0400 Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1up9NH-0003n1-4g for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:46:59 -0400 Received: by mail-wm1-x32f.google.com with SMTP id 5b1f17b1804b1-45a1b0cd668so6768425e9.3 for ; Thu, 21 Aug 2025 10:46:54 -0700 (PDT) Received: from localhost.localdomain (46-116-237-160.bb.netvision.net.il. [46.116.237.160]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3c074879f5fsm12485360f8f.4.2025.08.21.10.46.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 21 Aug 2025 10:46:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755798413; x=1756403213; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=tRzJOfeYEGRJJECW+6UDAw2spa4P0CZCvt+937FRHC8=; b=ZgwbiK4s0gU0mPME3kCwILrYdLu+7ClPjn/qdmE//AN92/qOTdgny4HqQrYb+wkdvT n8u3F1HdfxEelf7i7qEFVGTA8QDvuylwiCVfOhVYOCUc2WMjb4HFisaiQOYkb63o5O5B qMHFaA0CZQqnBTiTMDQoW3YpLAlCE8rqHW1AjWksAclxnicF7Xty7308aaGXKAIurEzR byz+XDQ4jeRn7GhU3AgqOJ5n1CifTIdBirUJhQOuMpkVX2bfDREJdgGWGVIWfN17EMyE CCIB/nyDK405Lau429k5kpUceQxtslYSIWBWEnGscEnBG16NiaspI+dmHf5d1QaVoqiZ uAXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755798413; x=1756403213; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=tRzJOfeYEGRJJECW+6UDAw2spa4P0CZCvt+937FRHC8=; b=GIB5JVPsfG0oJnNnwMfKh7S2g3fyGNY5aj6k3kEvNe94TLLE6FgSDh+7vuxKXte7Pd fEfooEsp+W+0aBRAa3sQtcde6jo4j4KCmaZFgISQMDGkeu8Z6eFTTnWBV338KroHkP+M /7gYraQvdq4TKqUXX302/BWYpTR/WJ4M2WpvC4y92sd+r7czfkN6FIFHoDhXvLNYxzqI u6cSK72QKZgtTTPDzpHLPfrnvmVub7HW6k0opS9lAfMdTC7ROnQn2niRu0vrKE7G1zwt D5MlSHkx+6D5KwI3c96x1kKpYihQ9xtmr6eaMN/SqzT6jFhSIH2b9PFtbkEMaBhABf2U NdYw== X-Forwarded-Encrypted: i=1; AJvYcCXBLR1Udwpxbl/cMDljhflEJ/n+t5PIzWK3gRPHEaXwMF1RxB7J0uFKkikm60Z0QM2I41hQhPXRIr04@nongnu.org X-Gm-Message-State: AOJu0YxxgwV5D83P1jwhIvQLGjhUzGvK/o6uXCYYvQtJyABhsIaWN6Sc xoMInTRNWdNl+kdhX7xEKZpb9HyDbWpBuDSvNSBT6p3JU3AMr17vN52WG33Xp0OC X-Gm-Gg: ASbGncvlCqP3fhti9sM71camZuBxBL01uqzOPq99EItOFjDb2+i56HTy+MPebvCyJmU HP4MFX3190d8S6iRXuqPnouPnuGK0KMR8aDmHJnP+QBGx6PQncRy9efXNeKJr8R1EuBMnIAmjeO qO2E4F3By23k5YiVj0pdN0aXJVIuiB58fA6n6xXSxcnyPXo1HAOST6iVO6Za1Z3eVrbmJsbboOY AeIo/7ZqMV4brz97EIrhy67R3ameqQhui62kGeuL2u8JN/h5cQpSPF6Y7wVf9M8IborKymTdKJ/ tkMZFIw2gTKk1zFBuWUvIwQ2PH6iBdtkqvBnzNa1Ovd2FP8yXyFdEHPPlhKe6xXYfAz0GXsTtXu pFo49sqn0+/yZxIRFaAdgPkjNTb/Y9l5wHry9VIXtxZKLB9EWbPeHm06brjxzd8OHcAp99+3O/e w= X-Google-Smtp-Source: AGHT+IHQI0RlsjYyVuP+x3nX2pDd6W8xPyJlsbQvDPAkyKELzHAKD0IOyMRfvVaayHEUrA/ilMHWqA== X-Received: by 2002:a05:600c:3b27:b0:458:a7fa:211d with SMTP id 5b1f17b1804b1-45b4d855455mr30216855e9.29.1755798413025; Thu, 21 Aug 2025 10:46:53 -0700 (PDT) From: Leonid Bloch To: "Michael S . Tsirkin" , Igor Mammedov , Ani Sinha , Paolo Bonzini , Richard Henderson , Eduardo Habkost , Eric Blake , Markus Armbruster , Marcel Apfelbaum , Dmitry Fleytman Cc: Leonid Bloch , qemu-devel@nongnu.org Subject: [PATCH v2 3/4] hw/acpi: Introduce the QEMU AC adapter Date: Thu, 21 Aug 2025 20:45:51 +0300 Message-ID: <20250821174554.40607-4-lb.workbox@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250821174554.40607-1-lb.workbox@gmail.com> References: <20250821174554.40607-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 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=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32f; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x32f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1755798509948116600 Content-Type: text/plain; charset="utf-8" The AC adapter device communicates AC power state to the guest via ACPI. It supports two modes of operation: 1. QMP control mode (default): AC adapter state is controlled programmatically via QMP commands, ensuring deterministic behavior. 2. Host mirroring mode (optional): The device reflects the host's AC adapter state from sysfs. Probing occurs on guest ACPI requests and at timed intervals. State changes trigger ACPI notifications. Properties: - 'use-qmp': Enable QMP control mode (default: true) - 'enable-sysfs': Enable host AC adapter mirroring (default: false) - 'probe_interval': Probe interval in ms for sysfs mode (default: 2000) - 'sysfs_path': Override default sysfs path /sys/class/power_supply/ The device implements the ACPI_DEV_AML_IF interface to generate its own AML code, placing the ADP0 device directly under \_SB scope. QMP commands: - ac-adapter-set-state: Set AC adapter connection state - query-ac-adapter: Query current AC adapter state Signed-off-by: Leonid Bloch --- MAINTAINERS | 6 + docs/specs/acad.rst | 195 ++++++++++++ docs/specs/index.rst | 1 + hw/acpi/Kconfig | 4 + hw/acpi/acad.c | 447 +++++++++++++++++++++++++++ hw/acpi/meson.build | 1 + hw/acpi/trace-events | 5 + hw/i386/Kconfig | 1 + include/hw/acpi/acad.h | 27 ++ include/hw/acpi/acpi_dev_interface.h | 1 + qapi/acpi.json | 49 +++ 11 files changed, 737 insertions(+) create mode 100644 docs/specs/acad.rst create mode 100644 hw/acpi/acad.c create mode 100644 include/hw/acpi/acad.h diff --git a/MAINTAINERS b/MAINTAINERS index 51af9b7366..612efcb686 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2927,6 +2927,12 @@ S: Maintained F: hw/acpi/battery.* F: docs/specs/battery.rst =20 +AC Adapter +M: Leonid Bloch +S: Maintained +F: hw/acpi/acad.* +F: docs/specs/acad.rst + Subsystems ---------- Overall Audio backends diff --git a/docs/specs/acad.rst b/docs/specs/acad.rst new file mode 100644 index 0000000000..73d5501b8f --- /dev/null +++ b/docs/specs/acad.rst @@ -0,0 +1,195 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=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. It +supports two operating modes: + +1. **QMP Control Mode** (default): AC adapter state is controlled via QMP + commands, providing deterministic control for testing and migration saf= ety. +2. **Sysfs Mode**: AC adapter state mirrors the host's physical AC adapter, + useful for desktop virtualization where the guest should see the host's + power state. + +Configuration +------------- + +The AC adapter device is created as an ISA device using ``-device acad``. + +Operating Modes +~~~~~~~~~~~~~~~ + +**QMP Control Mode** (``use-qmp=3Dtrue``, default) + AC adapter state is controlled via QMP commands. This mode is recommende= d for: + + * Production environments requiring migration support + * Testing with predictable power states + * Environments without host AC adapter access + * Security-sensitive deployments + +**Sysfs Mode** (``enable-sysfs=3Dtrue``) + AC adapter mirrors the host's physical AC adapter. This mode is useful f= or: + + * Desktop virtualization on laptops + * Development and testing with real AC adapter behavior + + Note: Sysfs mode reads host files and runs timers, which may impact + security and migration. Use with caution in production. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x53c) + I/O port base address for the AC adapter device register. + +``use-qmp`` (default: true) + Enable QMP control mode. When true, AC adapter state is controlled via + QMP commands. Cannot be used together with ``enable-sysfs=3Dtrue``. + +``enable-sysfs`` (default: false) + Enable sysfs mode to mirror the host's AC adapter. Cannot be used togeth= er + with ``use-qmp=3Dtrue``. + +``probe_interval`` (default: 2000) + Time interval between periodic probes in milliseconds (sysfs mode only). + A zero value disables the periodic probes, and makes the AC adapter state + updates occur on guest requests only. + +``sysfs_path`` (default: auto-detected) + Path to the host's AC adapter sysfs directory (sysfs mode only). By defa= ult, + the device auto-detects the first AC adapter of type "Mains" in + ``/sys/class/power_supply/``. Use this property to specify a different + AC adapter, or to provide a custom path for testing purposes. + +Host AC Adapter Detection +------------------------- + +The host's AC adapter information is taken from the sysfs AC adapter +data, located in:: + + /sys/class/power_supply/[device of type "Mains"] + +The device automatically scans for the first AC adapter with: + +- A ``type`` file containing "Mains" +- An ``online`` file that can be read + +If the sysfs path differs, a different AC adapter needs to be probed, +or even if a "fake" host AC adapter is to be provided, the ``sysfs_path`` +property allows overriding the default detection. + +ACPI Interface +-------------- + +The AC adapter device is exposed to the guest as an ACPI device with: + +- **HID**: ``ACPI0003`` (AC Adapter) +- **Device Path**: ``\_SB.ADP0`` +- **Notification Values**: + + - ``0x80``: Status change (connected/disconnected) + +ACPI Methods +~~~~~~~~~~~~ + +``_PSR`` (Power Source) + Returns the current AC adapter state (0 =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 +------------ + +When using QMP control mode (default), the following commands are availabl= e: + +``ac-adapter-set-state`` + Set the AC adapter connection state. + + * ``connected``: Whether the AC adapter is connected (boolean) + + Example:: + + -> { "execute": "ac-adapter-set-state", + "arguments": { "connected": true }} + <- { "return": {} } + +``query-ac-adapter`` + Query the current AC adapter state. + + Example:: + + -> { "execute": "query-ac-adapter" } + <- { "return": { "connected": true }} + +Examples +-------- + +QMP control mode (default - recommended):: + + # Start with QMP control + qemu-system-x86_64 -device acad -qmp tcp:localhost:4444,server,wait=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 + +Sysfs mode (mirror host AC adapter):: + + # Enable sysfs mode to mirror host AC adapter + qemu-system-x86_64 -device acad,use-qmp=3Dfalse,enable-sysfs=3Dtrue + + # Custom probe interval (5 seconds) + qemu-system-x86_64 -device acad,use-qmp=3Dfalse,enable-sysfs=3Dtrue,prob= e_interval=3D5000 + + # Specific AC adapter path + qemu-system-x86_64 -device acad,use-qmp=3Dfalse,enable-sysfs=3Dtrue,sysf= s_path=3D/sys/class/power_supply/ADP1 + +Testing with fake AC adapter:: + + # Create fake AC adapter files for testing + mkdir -p /tmp/fake_ac + echo "Mains" > /tmp/fake_ac/type + echo "1" > /tmp/fake_ac/online # 1 =3D connected, 0 =3D disconn= ected + + # Use fake AC adapter in sysfs mode + qemu-system-x86_64 -device acad,use-qmp=3Dfalse,enable-sysfs=3Dtrue,sysf= s_path=3D/tmp/fake_ac + + # Update AC adapter state while VM is running (from another terminal) + echo "0" > /tmp/fake_ac/online # Disconnect AC adapter + echo "1" > /tmp/fake_ac/online # Reconnect AC adapter + +Combined with battery device:: + + # QMP mode (recommended) + qemu-system-x86_64 -device battery -device acad + + # Sysfs mode (desktop virtualization) + qemu-system-x86_64 -device battery,use-qmp=3Dfalse,enable-sysfs=3Dtrue \ + -device acad,use-qmp=3Dfalse,enable-sysfs=3Dtrue diff --git a/docs/specs/index.rst b/docs/specs/index.rst index 616e8228cc..e144afcd90 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -22,6 +22,7 @@ guest hardware that is specific to QEMU. acpi_pci_hotplug acpi_nvdimm acpi_erst + acad battery sev-guest-firmware fw_cfg diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index 64403378bd..9d28c3addf 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -69,6 +69,10 @@ config ACPI_VIOT bool depends on ACPI =20 +config AC_ADAPTER + bool + depends on ACPI + config ACPI_HW_REDUCED bool select ACPI diff --git a/hw/acpi/acad.c b/hw/acpi/acad.c new file mode 100644 index 0000000000..699198c194 --- /dev/null +++ b/hw/acpi/acad.c @@ -0,0 +1,447 @@ +/* + * QEMU emulated AC adapter device. + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "trace.h" +#include "hw/isa/isa.h" +#include "hw/acpi/acpi.h" +#include "hw/nvram/fw_cfg.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/acpi/acpi_aml_interface.h" +#include "qapi/qapi-commands-acpi.h" + +#include "hw/acpi/acad.h" + +#define AC_ADAPTER_DEVICE(obj) OBJECT_CHECK(ACADState, (obj), \ + TYPE_AC_ADAPTER) + +#define AC_STA_ADDR 0 + +#define SYSFS_PATH "/sys/class/power_supply" +#define AC_ADAPTER_TYPE "Mains" +#define MAX_ALLOWED_TYPE_LENGTH 16 + +enum { + AC_ADAPTER_OFFLINE =3D 0, + AC_ADAPTER_ONLINE =3D 1, +}; + +typedef struct ACADState { + ISADevice dev; + MemoryRegion io; + uint16_t ioport; + uint8_t state; + bool use_qmp_control; + bool qmp_connected; + bool enable_sysfs; + + QEMUTimer *probe_state_timer; + uint64_t probe_state_interval; + + char *acad_path; +} ACADState; + +static const char *online_file =3D "online"; +static const char *type_file =3D "type"; + +static inline bool acad_file_accessible(char *path, const char *file) +{ + char full_path[PATH_MAX]; + int path_len; + + path_len =3D snprintf(full_path, PATH_MAX, "%s/%s", path, file); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + + if (access(full_path, R_OK) =3D=3D 0) { + return true; + } + return false; +} + +static void acad_get_state(ACADState *s) +{ + char file_path[PATH_MAX]; + int path_len; + uint8_t val; + FILE *ff; + + path_len =3D snprintf(file_path, PATH_MAX, "%s/%s", s->acad_path, + online_file); + if (path_len < 0 || path_len >=3D PATH_MAX) { + warn_report("Could not read the AC adapter state."); + return; + } + + ff =3D fopen(file_path, "r"); + if (ff =3D=3D NULL) { + warn_report("Could not read the AC adapter state."); + return; + } + + if (!fscanf(ff, "%hhu", &val)) { + warn_report("AC adapter state unreadable."); + } else { + switch (val) { + case AC_ADAPTER_OFFLINE: + case AC_ADAPTER_ONLINE: + s->state =3D val; + break; + default: + warn_report("AC adapter state undetermined."); + } + } + fclose(ff); +} + +static void acad_get_dynamic_status(ACADState *s) +{ + if (s->use_qmp_control) { + s->state =3D s->qmp_connected ? AC_ADAPTER_ONLINE : AC_ADAPTER_OFF= LINE; + } else if (s->enable_sysfs) { + acad_get_state(s); + } else { + s->state =3D AC_ADAPTER_OFFLINE; + } + + trace_acad_get_dynamic_status(s->state); +} + +static void acad_probe_state(void *opaque) +{ + ACADState *s =3D opaque; + + uint8_t state_before =3D s->state; + + acad_get_dynamic_status(s); + + if (state_before !=3D s->state) { + Object *obj =3D object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, = NULL); + acpi_send_event(DEVICE(obj), ACPI_AC_ADAPTER_CHANGE_STATUS); + } + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->probe_state_interval); +} + +static void acad_probe_state_timer_init(ACADState *s) +{ + if (s->enable_sysfs && s->probe_state_interval > 0) { + s->probe_state_timer =3D timer_new_ms(QEMU_CLOCK_VIRTUAL, + acad_probe_state, s); + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTU= AL) + + s->probe_state_interval); + } +} + +static bool acad_verify_sysfs(ACADState *s, char *path) +{ + FILE *ff; + char type_path[PATH_MAX]; + int path_len; + char val[MAX_ALLOWED_TYPE_LENGTH]; + + path_len =3D snprintf(type_path, PATH_MAX, "%s/%s", path, type_file); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + + ff =3D fopen(type_path, "r"); + if (ff =3D=3D NULL) { + return false; + } + + if (fgets(val, MAX_ALLOWED_TYPE_LENGTH, ff) =3D=3D NULL) { + fclose(ff); + return false; + } else { + val[strcspn(val, "\n")] =3D 0; + if (strncmp(val, AC_ADAPTER_TYPE, MAX_ALLOWED_TYPE_LENGTH)) { + fclose(ff); + return false; + } + } + fclose(ff); + + return acad_file_accessible(path, online_file); +} + +static bool get_acad_path(DeviceState *dev) +{ + ACADState *s =3D AC_ADAPTER_DEVICE(dev); + DIR *dir; + struct dirent *ent; + char bp[PATH_MAX]; + int path_len; + + if (s->acad_path) { + return acad_verify_sysfs(s, s->acad_path); + } + + dir =3D opendir(SYSFS_PATH); + if (dir =3D=3D NULL) { + return false; + } + + ent =3D readdir(dir); + while (ent !=3D NULL) { + if (ent->d_name[0] !=3D '.') { + path_len =3D snprintf(bp, PATH_MAX, "%s/%s", SYSFS_PATH, + ent->d_name); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + if (acad_verify_sysfs(s, bp)) { + qdev_prop_set_string(dev, AC_ADAPTER_PATH_PROP, bp); + closedir(dir); + return true; + } + } + ent =3D readdir(dir); + } + closedir(dir); + + return false; +} + +static void acad_realize(DeviceState *dev, Error **errp) +{ + ISADevice *d =3D ISA_DEVICE(dev); + ACADState *s =3D AC_ADAPTER_DEVICE(dev); + FWCfgState *fw_cfg =3D fw_cfg_find(); + uint16_t *acad_port; + char err_details[32] =3D {}; + + trace_acad_realize(); + + if (s->use_qmp_control && s->enable_sysfs) { + error_setg(errp, "Cannot enable both QMP control and sysfs mode"); + return; + } + + if (s->enable_sysfs) { + if (!s->acad_path) { + strcpy(err_details, " Try using 'sysfs_path=3D'"); + } + + if (!get_acad_path(dev)) { + error_setg(errp, "AC adapter sysfs path not found or unreadabl= e.%s", + err_details); + return; + } + } + + isa_register_ioport(d, &s->io, s->ioport); + + acad_probe_state_timer_init(s); + + if (!fw_cfg) { + return; + } + + acad_port =3D g_malloc(sizeof(*acad_port)); + *acad_port =3D cpu_to_le16(s->ioport); + fw_cfg_add_file(fw_cfg, "etc/acad-port", acad_port, + sizeof(*acad_port)); +} + +static const Property acad_device_properties[] =3D { + DEFINE_PROP_UINT16(AC_ADAPTER_IOPORT_PROP, ACADState, ioport, 0x53c), + DEFINE_PROP_BOOL("use-qmp", ACADState, use_qmp_control, true), + DEFINE_PROP_BOOL("enable-sysfs", ACADState, enable_sysfs, false), + DEFINE_PROP_UINT64(AC_ADAPTER_PROBE_STATE_INTERVAL, ACADState, + probe_state_interval, 2000), + DEFINE_PROP_STRING(AC_ADAPTER_PATH_PROP, ACADState, acad_path), +}; + +static const VMStateDescription acad_vmstate =3D { + .name =3D "acad", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_UINT16(ioport, ACADState), + VMSTATE_UINT64(probe_state_interval, ACADState), + VMSTATE_END_OF_LIST() + } +}; + +static void build_acad_aml(AcpiDevAmlIf *adev, Aml *scope) +{ + Aml *dev, *field, *method, *pkg; + Aml *acad_state; + Aml *sb_scope; + ACADState *s =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._E0A", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.ADP0"), aml_int(0x80))); + aml_append(scope, method); +} + +static void acad_class_init(ObjectClass *class, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(class); + AcpiDevAmlIfClass *adevc =3D ACPI_DEV_AML_IF_CLASS(class); + + dc->realize =3D acad_realize; + 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: + warn_report("AC adapter: guest read unknown value."); + trace_acad_ioport_read_unknown(); + return 0; + } +} + +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(void) +{ + Object *o =3D object_resolve_path_type("", TYPE_AC_ADAPTER, NULL); + if (!o) { + return NULL; + } + return AC_ADAPTER_DEVICE(o); +} + +void qmp_ac_adapter_set_state(bool connected, Error **errp) +{ + ACADState *s =3D find_acad_device(); + + if (!s) { + error_setg(errp, "No AC adapter device found"); + return; + } + + s->qmp_connected =3D connected; + + Object *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(); + AcAdapterInfo *ret; + + if (!s) { + error_setg(errp, "No AC adapter device found"); + return NULL; + } + + ret =3D g_new0(AcAdapterInfo, 1); + + if (s->use_qmp_control) { + ret->connected =3D s->qmp_connected; + } else { + acad_get_dynamic_status(s); + ret->connected =3D (s->state =3D=3D AC_ADAPTER_ONLINE); + } + + return ret; +} + +static void acad_register_types(void) +{ + type_register_static(&acad_info); +} + +type_init(acad_register_types) diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build index 10379a7b2c..2b24951f28 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -32,6 +32,7 @@ if have_tpm acpi_ss.add(files('tpm.c')) endif acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c')) +acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c')) system_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-bui= ld-stub.c', 'ghes-stub.c', 'acpi_interface.c')) system_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_false: files('pci-bridge-= stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events index dd3e815482..68ac6e9701 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -92,3 +92,8 @@ acpi_nvdimm_invalid_revision(uint32_t revision) "Revision= 0x%" PRIx32 " is not s battery_realize(void) "Battery device realize entry" battery_get_dynamic_status(uint32_t state, uint32_t rate, uint32_t charge)= "Battery read state: 0x%"PRIx32", rate: %"PRIu32", charge: %"PRIu32 battery_ioport_read_unknown(void) "Battery read unknown" + +# acad.c +acad_realize(void) "AC adapter device realize entry" +acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8 +acad_ioport_read_unknown(void) "AC adapter read unknown" diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index 2c878fd112..baab382a2e 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -40,6 +40,7 @@ config PC imply NVDIMM imply FDC_ISA imply BATTERY + imply AC_ADAPTER select I8259 select I8254 select PCKBD diff --git a/include/hw/acpi/acad.h b/include/hw/acpi/acad.h new file mode 100644 index 0000000000..a4e7149488 --- /dev/null +++ b/include/hw/acpi/acad.h @@ -0,0 +1,27 @@ +/* + * QEMU emulated AC adapter device. + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_ACPI_AC_ADAPTER_H +#define HW_ACPI_AC_ADAPTER_H + +#define TYPE_AC_ADAPTER "acad" +#define AC_ADAPTER_IOPORT_PROP "ioport" +#define AC_ADAPTER_PATH_PROP "sysfs_path" +#define AC_ADAPTER_PROBE_STATE_INTERVAL "probe_interval" + +#define AC_ADAPTER_VAL_UNKNOWN 0xFFFFFFFF + +#define AC_ADAPTER_LEN 1 + +#endif diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_de= v_interface.h index 3064ef6734..588fbbd05f 100644 --- a/include/hw/acpi/acpi_dev_interface.h +++ b/include/hw/acpi/acpi_dev_interface.h @@ -14,6 +14,7 @@ typedef enum { ACPI_VMGENID_CHANGE_STATUS =3D 32, ACPI_POWER_DOWN_STATUS =3D 64, ACPI_BATTERY_CHANGE_STATUS =3D 128, + ACPI_AC_ADAPTER_CHANGE_STATUS =3D 1024, } AcpiEventStatusBits; =20 #define TYPE_ACPI_DEVICE_IF "acpi-device-interface" diff --git a/qapi/acpi.json b/qapi/acpi.json index d1ad663bfd..52e151f0e6 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -215,3 +215,52 @@ ## { 'command': 'query-battery', 'returns': 'BatteryInfo' } + +## +# @ac-adapter-set-state: +# +# Set the state of the emulated AC adapter device +# +# @connected: whether the AC adapter is connected +# + +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "ac-adapter-set-state", +# "arguments": { "connected": true } } +# <- { "return": {} } +## +{ 'command': 'ac-adapter-set-state', + 'data': { 'connected': 'bool' } } + +## +# @AcAdapterInfo: +# +# AC adapter state information +# +# @connected: whether the AC adapter is connected +# +# Since: 10.2 +## +{ 'struct': 'AcAdapterInfo', + 'data': { 'connected': 'bool' } } + +## +# @query-ac-adapter: +# +# Query the current state of the emulated AC adapter device +# +# Returns: AC adapter connection state +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "query-ac-adapter" } +# <- { "return": { "connected": true } } +## +{ 'command': 'query-ac-adapter', + 'returns': 'AcAdapterInfo' } --=20 2.50.1 From nobody Sat Nov 15 03:13:40 2025 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=1755798506; cv=none; d=zohomail.com; s=zohoarc; b=QMj5ZzwaiCyFuGWTr92KU/XJcJm/EQ1c2U8UucSO9Po6hG4zTt7TUIQNO9MqTuOBIwUY/t0cExHqYlO/hMvFIhV0IOOLtQQk1IQvh/ifA8J5vBsh8wHaEiZuvFVE/fSaH3Pifm0EGba+19FqaDZ4bIArHNt8xugt6mQW2bLbJGE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1755798506; 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=Qzuofo+VxQ2Vqu+msrfT+81t9nCiffS2fcQ5tvevD/U=; b=QdddxeG6SQnY+w1lJfbjQuG8VZwK6qGcbW3QsBnwe3vbDKa/kBHdVpdD0RCozuNxq7PFnmxmxiyZgTnjb0tTa5/gIfHpc+HNrKqpf9TCp3wzMtTaFIENJE5dnZTCmDYGFR7crHBPJrDj1Ov4eYbdTEUbyPkm2OBlHpasNf3DO/s= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 175579850688154.311104602054; Thu, 21 Aug 2025 10:48:26 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1up9Na-0007Kv-BW; Thu, 21 Aug 2025 13:47:14 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1up9NM-0007J0-Ax for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:47:00 -0400 Received: from mail-wm1-x332.google.com ([2a00:1450:4864:20::332]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1up9NI-0003n8-QE for qemu-devel@nongnu.org; Thu, 21 Aug 2025 13:47:00 -0400 Received: by mail-wm1-x332.google.com with SMTP id 5b1f17b1804b1-45a1b00a65fso6898895e9.0 for ; Thu, 21 Aug 2025 10:46:56 -0700 (PDT) Received: from localhost.localdomain (46-116-237-160.bb.netvision.net.il. [46.116.237.160]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3c074879f5fsm12485360f8f.4.2025.08.21.10.46.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 21 Aug 2025 10:46:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755798415; x=1756403215; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Qzuofo+VxQ2Vqu+msrfT+81t9nCiffS2fcQ5tvevD/U=; b=AV6Czbu1CJchkOV1px/NGhRBQBeDeC4cs07MipnrbDI35vALwzOkMllNkPj5sgXibG q18mHpRN5o7S+MpO/pm0zEhHAcS8H35VUKrsemPh3hUS3wYOL5e7x7gyU1rUq2LtYQke kR9/l3WC6AylKShYdI7q/CfpDXGEkmrljyNjcLO900PqZyKYK2LMAmKwLZOyrj89hFEf 9w6LDM1A7AZBXkJjvF2g3ae3PIwOoqEN+cJpSuXT6lCzbwMsbnBTd/Pugi+/uRsL0TCq LsMeVUaiB0LI/0fQtIxnv845Lsfdl5jdf2V7+jMxYxb/SLKcNVPTS2cMJW+Lj/AYJVgT cipA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755798415; x=1756403215; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Qzuofo+VxQ2Vqu+msrfT+81t9nCiffS2fcQ5tvevD/U=; b=cs1May89oUxzzZXWo7OI0/B0p8VYaTcYTDaXtc/SANqGEy6joyaSD91us0FTSRLHjo l2AYxgrt0dAICLuuSt2H+0trqeIi7bc12r3KEMkfjJ2T8qXrY85b1x6nsMsAZ9X26VbV jK6AGtlMm8JW5Zlu9LO0g2MTOPD5GlpoMNB2Z2HQhkOx/rSvbEJFt0B8BH8wrHaHuKaB /8XVwJvmqC00UPe2veA94gXRF5zmvHbqVCreRr9BWJCMsE7YJ2BaNyRwDhW1Y3cce9+Y SMOVHiVajU44CMDLCYCm4sPz8GSUCnHDd0/8PESlXcUHS8itwJfwo5L39i0hlYWaVm+t SPYQ== X-Forwarded-Encrypted: i=1; AJvYcCVGKM3zN2bt93zNJt6Dx5n+sbImYN7rnFGVOje+c/CylHwgz8FyFTBjfv9uDpdA12bh/NdrPADCc9UP@nongnu.org X-Gm-Message-State: AOJu0Yzg20eWqZuRXr7KG4nr/8asskofOfK3lsDBWsbE4KQdkud7oa1Y LhhiWtMKJRRfIXHd3d07ZmmjXCsDKFP+vV4ngpRcWfPWsFo6w3CHnKOW X-Gm-Gg: ASbGnctht8cznya+TmWKy3Z0nDkYonykO+5/TbDlbHIxdi3zdIY//L8867YKG5sYbyo e3wsveD32kqYi3MLHOuywkgXB6zaNPhmXg0LYUEHw0EsmIwl8vwitbPyUiWcFG9SuQZkGjETOtD XWmkQy4NSCUOKQG2Wfk6vwKHqjBHClFyNAKlpB2Mlh+QYqR+wHdeGG0WHvIrpK7CI9lQk3TG0dt 4lcXRZdkC9D3EOiuEFalXx4CQ9ZutZ76f6Rlmz+MSKIxKRiGoouGw7SNta+a034IPCFkS6zYAOD rrAdxJnm2yzayAzILcV/IPnt7vl3Q8InCpFdNS2GryNwSqVMnKVskdAAJpXmhZ6qw0MUELWgaBH b0Tr+a/OdMT498S+dsNZRjldV6RaNIsDi7UtFY4cr8774Rhz098f8TvrEGc6Mwp4aogPKuPX0oj 4= X-Google-Smtp-Source: AGHT+IHfPwzxbJjFmQRLhCeQF/d11s0oqhhpQx0NeQMCoNc+iqI3Zqe/CfynoQnutdRc0FskaQrrgA== X-Received: by 2002:a05:600c:1c1b:b0:456:f00:4b5d with SMTP id 5b1f17b1804b1-45b4d8309f3mr27502805e9.22.1755798414864; Thu, 21 Aug 2025 10:46:54 -0700 (PDT) From: Leonid Bloch To: "Michael S . Tsirkin" , Igor Mammedov , Ani Sinha , Paolo Bonzini , Richard Henderson , Eduardo Habkost , Eric Blake , Markus Armbruster , Marcel Apfelbaum , Dmitry Fleytman Cc: Leonid Bloch , qemu-devel@nongnu.org Subject: [PATCH v2 4/4] hw/acpi: Introduce the QEMU lid button Date: Thu, 21 Aug 2025 20:45:52 +0300 Message-ID: <20250821174554.40607-5-lb.workbox@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250821174554.40607-1-lb.workbox@gmail.com> References: <20250821174554.40607-1-lb.workbox@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 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=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::332; envelope-from=lb.workbox@gmail.com; helo=mail-wm1-x332.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1755798507923116600 Content-Type: text/plain; charset="utf-8" The lid button device communicates laptop lid state to the guest via ACPI. It supports two modes of operation: 1. QMP control mode (default): Lid state is controlled programmatically via QMP commands for consistent behavior across environments. 2. Host mirroring mode (optional): The device reflects the host's lid button state from procfs (/proc/acpi/button/lid/*/state). State changes trigger ACPI notifications to the guest. Properties: - 'use-qmp': Enable QMP control mode (default: true) - 'enable-procfs': Enable host lid button mirroring (default: false) - 'probe_interval': Probe interval in ms for procfs mode (default: 2000) - 'procfs_path': Override default procfs path /proc/acpi/button The device implements the ACPI_DEV_AML_IF interface to generate its own AML code, placing the LID0 device directly under \_SB scope. QMP commands: - lid-button-set-state: Set lid open/closed state - query-lid-button: Query current lid state Signed-off-by: Leonid Bloch --- MAINTAINERS | 6 + docs/specs/button.rst | 189 ++++++++++++ docs/specs/index.rst | 1 + hw/acpi/Kconfig | 4 + hw/acpi/button.c | 438 +++++++++++++++++++++++++++ hw/acpi/meson.build | 1 + hw/acpi/trace-events | 5 + hw/i386/Kconfig | 1 + include/hw/acpi/acpi_dev_interface.h | 1 + include/hw/acpi/button.h | 25 ++ qapi/acpi.json | 49 +++ 11 files changed, 720 insertions(+) create mode 100644 docs/specs/button.rst create mode 100644 hw/acpi/button.c create mode 100644 include/hw/acpi/button.h diff --git a/MAINTAINERS b/MAINTAINERS index 612efcb686..4d54a2ffb5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2933,6 +2933,12 @@ S: Maintained F: hw/acpi/acad.* F: docs/specs/acad.rst =20 +Button +M: Leonid Bloch +S: Maintained +F: hw/acpi/button.* +F: docs/specs/button.rst + Subsystems ---------- Overall Audio backends diff --git a/docs/specs/button.rst b/docs/specs/button.rst new file mode 100644 index 0000000000..10a940b9f5 --- /dev/null +++ b/docs/specs/button.rst @@ -0,0 +1,189 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=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. +It supports two operating modes: + +1. **QMP Control Mode** (default): Lid state is controlled via QMP command= s, + providing deterministic control for testing and migration safety. +2. **Procfs Mode**: Lid state mirrors the host's physical lid button, usef= ul + for desktop virtualization where the guest should see the host's lid st= ate. + +Configuration +------------- + +The lid button device is created as an ISA device using ``-device button``. + +Operating Modes +~~~~~~~~~~~~~~~ + +**QMP Control Mode** (``use-qmp=3Dtrue``, default) + Lid state is controlled via QMP commands. This mode is recommended for: + + * Production environments requiring migration support + * Testing with predictable lid states + * Environments without host lid button access + * Security-sensitive deployments + +**Procfs Mode** (``enable-procfs=3Dtrue``) + Lid mirrors the host's physical lid button. This mode is useful for: + + * Desktop virtualization on laptops + * Development and testing with real lid button behavior + + Note: Procfs mode reads host files and runs timers, which may impact + security and migration. Use with caution in production. + +Properties +~~~~~~~~~~ + +``ioport`` (default: 0x53d) + I/O port base address for the lid button device register. + +``use-qmp`` (default: true) + Enable QMP control mode. When true, lid state is controlled via + QMP commands. Cannot be used together with ``enable-procfs=3Dtrue``. + +``enable-procfs`` (default: false) + Enable procfs mode to mirror the host's lid button. Cannot be used toget= her + with ``use-qmp=3Dtrue``. + +``probe_interval`` (default: 2000) + Time interval between periodic probes in milliseconds (procfs mode only). + The minimum allowed value is 10ms to prevent excessive polling. + +``procfs_path`` (default: /proc/acpi/button) + Path to the host's lid button procfs directory (procfs mode only). The d= evice + will automatically scan this directory to find the lid state file. Use t= his + property to specify a different path or to provide a custom location for + testing purposes. + +Host Lid Button Detection +------------------------- + +The host's lid button information is taken from:: + + /proc/acpi/button/lid/*/state + +This file is expected to be formatted as: + +- ``state: open`` (if the lid is open) +- ``state: closed`` (if the lid is closed) + +These formats are based on the Linux 'button' driver. + +The device automatically scans the ``/proc/acpi/button/lid/`` directory +for subdirectories containing a readable ``state`` file. If the procfs path +differs, a different lid button needs to be probed, or even if a "fake" ho= st +lid button is to be provided, the ``procfs_path`` property allows overridi= ng +the default detection. + +ACPI Interface +-------------- + +The lid button device is exposed to the guest as an ACPI device with: + +- **HID**: ``PNP0C0D`` (Lid Device) +- **Device Path**: ``\_SB.LID0`` +- **Notification Values**: + + - ``0x80``: Status change (lid opened/closed) + +ACPI Methods +~~~~~~~~~~~~ + +``_LID`` (Lid Status) + Returns the current lid state (0 =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 +------------ + +When using QMP control mode (default), the following commands are availabl= e: + +``lid-button-set-state`` + Set the lid button state. + + * ``open``: Whether the lid is open (boolean) + + Example:: + + -> { "execute": "lid-button-set-state", + "arguments": { "open": true }} + <- { "return": {} } + +``query-lid-button`` + Query the current lid button state. + + Example:: + + -> { "execute": "query-lid-button" } + <- { "return": { "open": true }} + +Examples +-------- + +QMP control mode (default - recommended):: + + # Start with QMP control + qemu-system-x86_64 -device button -qmp tcp:localhost:4444,server,wait=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 + +Procfs mode (mirror host lid button):: + + # Enable procfs mode to mirror host lid button + qemu-system-x86_64 -device button,use-qmp=3Dfalse,enable-procfs=3Dtrue + + # Custom probe interval (5 seconds) + qemu-system-x86_64 -device button,use-qmp=3Dfalse,enable-procfs=3Dtrue,p= robe_interval=3D5000 + + # Custom procfs path + qemu-system-x86_64 -device button,use-qmp=3Dfalse,enable-procfs=3Dtrue,p= rocfs_path=3D/custom/path + +Testing with fake lid button:: + + # Create fake lid button files for testing + mkdir -p /tmp/fake_lid/lid/LID0 + echo "state: open" > /tmp/fake_lid/lid/LID0/state # Format: "sta= te: open" or "state: closed" + + # Use fake lid button in procfs mode + qemu-system-x86_64 -device button,use-qmp=3Dfalse,enable-procfs=3Dtrue,p= rocfs_path=3D/tmp/fake_lid + + # Update lid state while VM is running (from another terminal) + echo "state: closed" > /tmp/fake_lid/lid/LID0/state # Close lid + echo "state: open" > /tmp/fake_lid/lid/LID0/state # Open lid + +Combined with other laptop devices:: + + # QMP mode (recommended) + qemu-system-x86_64 -device battery -device acad -device button + + # Procfs/sysfs mode (desktop virtualization) + qemu-system-x86_64 -device battery,use-qmp=3Dfalse,enable-sysfs=3Dtrue \ + -device acad,use-qmp=3Dfalse,enable-sysfs=3Dtrue \ + -device button,use-qmp=3Dfalse,enable-procfs=3Dtrue diff --git a/docs/specs/index.rst b/docs/specs/index.rst index e144afcd90..e1c9b91b7b 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -24,6 +24,7 @@ guest hardware that is specific to QEMU. acpi_erst acad battery + button sev-guest-firmware fw_cfg fsi diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig index 9d28c3addf..6600685855 100644 --- a/hw/acpi/Kconfig +++ b/hw/acpi/Kconfig @@ -73,6 +73,10 @@ config AC_ADAPTER bool depends on ACPI =20 +config BUTTON + bool + depends on ACPI + config ACPI_HW_REDUCED bool select ACPI diff --git a/hw/acpi/button.c b/hw/acpi/button.c new file mode 100644 index 0000000000..dfe86af713 --- /dev/null +++ b/hw/acpi/button.c @@ -0,0 +1,438 @@ +/* + * QEMU emulated lid button device + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "trace.h" +#include "hw/isa/isa.h" +#include "hw/acpi/acpi.h" +#include "hw/nvram/fw_cfg.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/acpi/acpi_aml_interface.h" +#include "qapi/qapi-commands-acpi.h" + +#include "hw/acpi/button.h" + +#define BUTTON_DEVICE(obj) OBJECT_CHECK(BUTTONState, (obj), \ + TYPE_BUTTON) + +#define BUTTON_STA_ADDR 0 + +#define PROCFS_PATH "/proc/acpi/button" +#define LID_DIR "lid" +#define LID_STATE_FILE "state" +#define MIN_BUTTON_PROBE_INTERVAL 10 /* ms */ +#define MAX_ALLOWED_LINE_LENGTH 32 /* For convenience when comparing */ + +enum { + LID_CLOSED =3D 0, + LID_OPEN =3D 1, +}; + +static const char *lid_state[] =3D { "closed", "open" }; + +typedef struct BUTTONState { + ISADevice dev; + MemoryRegion io; + uint16_t ioport; + uint8_t lid_state; + bool use_qmp_control; + bool qmp_lid_open; + bool enable_procfs; + + QEMUTimer *probe_state_timer; + uint64_t probe_state_interval; + + char *button_path; + char lid_dir[MAX_ALLOWED_LINE_LENGTH]; +} BUTTONState; + +static inline bool button_file_accessible(char *path, const char *dir, + char *subdir, const char *file) +{ + char full_path[PATH_MAX]; + int path_len; + + path_len =3D snprintf(full_path, PATH_MAX, "%s/%s/%s/%s", path, dir, s= ubdir, + file); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + + if (access(full_path, R_OK) =3D=3D 0) { + return true; + } + return false; +} + +static void button_get_lid_state(BUTTONState *s) +{ + char file_path[PATH_MAX]; + int path_len; + char line[MAX_ALLOWED_LINE_LENGTH]; + FILE *ff; + + path_len =3D snprintf(file_path, PATH_MAX, "%s/%s/%s/%s", s->button_pa= th, + LID_DIR, s->lid_dir, LID_STATE_FILE); + if (path_len < 0 || path_len >=3D PATH_MAX) { + warn_report("Could not read the lid state."); + return; + } + + ff =3D fopen(file_path, "r"); + if (ff =3D=3D NULL) { + warn_report("Could not read the lid state."); + return; + } + + if (fgets(line, MAX_ALLOWED_LINE_LENGTH, ff) =3D=3D NULL) { + warn_report("Lid state unreadable."); + } else { + if (strstr(line, lid_state[LID_OPEN]) !=3D NULL) { + s->lid_state =3D LID_OPEN; + } else if (strstr(line, lid_state[LID_CLOSED]) !=3D NULL) { + s->lid_state =3D LID_CLOSED; + } else { + warn_report("Lid state undetermined."); + } + } + + fclose(ff); +} + +static void button_get_dynamic_status(BUTTONState *s) +{ + trace_button_get_dynamic_status(); + + if (s->use_qmp_control) { + s->lid_state =3D s->qmp_lid_open ? LID_OPEN : LID_CLOSED; + } else if (s->enable_procfs) { + button_get_lid_state(s); + } else { + s->lid_state =3D LID_CLOSED; + } +} + +static void button_probe_state(void *opaque) +{ + BUTTONState *s =3D opaque; + + uint8_t lid_state_before =3D s->lid_state; + + button_get_dynamic_status(s); + + if (lid_state_before !=3D s->lid_state) { + Object *obj =3D object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, = NULL); + acpi_send_event(DEVICE(obj), ACPI_BUTTON_CHANGE_STATUS); + } + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->probe_state_interval); +} + +static void button_probe_state_timer_init(BUTTONState *s) +{ + if (s->enable_procfs && s->probe_state_interval > 0) { + s->probe_state_timer =3D timer_new_ms(QEMU_CLOCK_VIRTUAL, + button_probe_state, s); + timer_mod(s->probe_state_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTU= AL) + + s->probe_state_interval); + } +} + +static inline bool button_verify_lid_procfs(char *path, char *lid_subdir) +{ + return button_file_accessible(path, LID_DIR, lid_subdir, LID_STATE_FIL= E); +} + +static bool button_get_lid_dir(BUTTONState *s, char *path) +{ + DIR *dir; + char lid_path[PATH_MAX]; + int path_len; + struct dirent *ent; + + path_len =3D snprintf(lid_path, PATH_MAX, "%s/%s", path, LID_DIR); + if (path_len < 0 || path_len >=3D PATH_MAX) { + return false; + } + + dir =3D opendir(lid_path); + if (dir =3D=3D NULL) { + return false; + } + + ent =3D readdir(dir); + while (ent !=3D NULL) { + if (ent->d_name[0] !=3D '.') { + if (button_verify_lid_procfs(path, ent->d_name)) { + path_len =3D snprintf(s->lid_dir, strlen(ent->d_name) + 1,= "%s", + ent->d_name); + if (path_len < 0 || path_len > strlen(ent->d_name)) { + return false; + } + closedir(dir); + return true; + } + } + ent =3D readdir(dir); + } + closedir(dir); + return false; +} + +static bool get_button_path(DeviceState *dev) +{ + BUTTONState *s =3D BUTTON_DEVICE(dev); + char procfs_path[PATH_MAX]; + int path_len; + + if (s->button_path) { + path_len =3D snprintf(procfs_path, strlen(s->button_path) + 1, "%s= ", + s->button_path); + if (path_len < 0 || path_len > strlen(s->button_path)) { + return false; + } + } else { + path_len =3D snprintf(procfs_path, sizeof(PROCFS_PATH), "%s", + PROCFS_PATH); + if (path_len < 0 || path_len >=3D sizeof(PROCFS_PATH)) { + return false; + } + } + + if (button_get_lid_dir(s, procfs_path)) { + qdev_prop_set_string(dev, BUTTON_PATH_PROP, procfs_path); + return true; + } + + return false; +} + +static void button_realize(DeviceState *dev, Error **errp) +{ + ISADevice *d =3D ISA_DEVICE(dev); + BUTTONState *s =3D BUTTON_DEVICE(dev); + FWCfgState *fw_cfg =3D fw_cfg_find(); + uint16_t *button_port; + char err_details[32] =3D {}; + + trace_button_realize(); + + if (s->use_qmp_control && s->enable_procfs) { + error_setg(errp, "Cannot enable both QMP control and procfs mode"); + return; + } + + /* Initialize lid to open by default when in QMP mode */ + if (s->use_qmp_control) { + s->qmp_lid_open =3D true; + } + + if (s->probe_state_interval < MIN_BUTTON_PROBE_INTERVAL) { + error_setg(errp, "'probe_state_interval' must be greater than %d m= s", + MIN_BUTTON_PROBE_INTERVAL); + return; + } + + if (s->enable_procfs) { + if (!s->button_path) { + strcpy(err_details, " Try using 'procfs_path=3D'"); + } + + if (!get_button_path(dev)) { + error_setg(errp, "Button procfs path not found or unreadable.%= s", + err_details); + return; + } + } + + isa_register_ioport(d, &s->io, s->ioport); + + button_probe_state_timer_init(s); + + if (!fw_cfg) { + return; + } + + button_port =3D g_malloc(sizeof(*button_port)); + *button_port =3D cpu_to_le16(s->ioport); + fw_cfg_add_file(fw_cfg, "etc/button-port", button_port, + sizeof(*button_port)); +} + +static const Property button_device_properties[] =3D { + DEFINE_PROP_UINT16(BUTTON_IOPORT_PROP, BUTTONState, ioport, 0x53d), + DEFINE_PROP_BOOL("use-qmp", BUTTONState, use_qmp_control, true), + DEFINE_PROP_BOOL("enable-procfs", BUTTONState, enable_procfs, false), + DEFINE_PROP_UINT64(BUTTON_PROBE_STATE_INTERVAL, BUTTONState, + probe_state_interval, 2000), + DEFINE_PROP_STRING(BUTTON_PATH_PROP, BUTTONState, button_path), +}; + +static const VMStateDescription button_vmstate =3D { + .name =3D "button", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_UINT16(ioport, BUTTONState), + VMSTATE_UINT64(probe_state_interval, BUTTONState), + VMSTATE_END_OF_LIST() + } +}; + +static void build_button_aml(AcpiDevAmlIf *adev, Aml *scope) +{ + Aml *dev, *field, *method; + Aml *button_state; + Aml *sb_scope; + BUTTONState *s =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._E0B", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.LID0"), aml_int(0x80))); + aml_append(scope, method); +} + +static void button_class_init(ObjectClass *class, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(class); + AcpiDevAmlIfClass *adevc =3D ACPI_DEV_AML_IF_CLASS(class); + + dc->realize =3D button_realize; + 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: + warn_report("Button: guest read unknown value."); + trace_button_ioport_read_unknown(); + return 0; + } +} + +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(void) +{ + Object *o =3D object_resolve_path_type("", TYPE_BUTTON, NULL); + if (!o) { + return NULL; + } + return BUTTON_DEVICE(o); +} + +void qmp_lid_button_set_state(bool open, Error **errp) +{ + BUTTONState *s =3D find_button_device(); + + if (!s) { + error_setg(errp, "No lid button device found"); + return; + } + + s->qmp_lid_open =3D open; + + Object *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(); + LidButtonInfo *ret; + + if (!s) { + error_setg(errp, "No lid button device found"); + return NULL; + } + + ret =3D g_new0(LidButtonInfo, 1); + + if (s->use_qmp_control) { + ret->open =3D s->qmp_lid_open; + } else { + button_get_dynamic_status(s); + ret->open =3D (s->lid_state =3D=3D LID_OPEN); + } + + return ret; +} + +static void button_register_types(void) +{ + type_register_static(&button_info); +} + +type_init(button_register_types) diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build index 2b24951f28..94993ecb9a 100644 --- a/hw/acpi/meson.build +++ b/hw/acpi/meson.build @@ -33,6 +33,7 @@ if have_tpm endif acpi_ss.add(when: 'CONFIG_BATTERY', if_true: files('battery.c')) acpi_ss.add(when: 'CONFIG_AC_ADAPTER', if_true: files('acad.c')) +acpi_ss.add(when: 'CONFIG_BUTTON', if_true: files('button.c')) system_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-bui= ld-stub.c', 'ghes-stub.c', 'acpi_interface.c')) system_ss.add(when: 'CONFIG_ACPI_PCI_BRIDGE', if_false: files('pci-bridge-= stub.c')) system_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss) diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events index 68ac6e9701..816f027bb3 100644 --- a/hw/acpi/trace-events +++ b/hw/acpi/trace-events @@ -97,3 +97,8 @@ battery_ioport_read_unknown(void) "Battery read unknown" acad_realize(void) "AC adapter device realize entry" acad_get_dynamic_status(uint8_t state) "AC adapter read state: %"PRIu8 acad_ioport_read_unknown(void) "AC adapter read unknown" + +# button.c +button_realize(void) "Button device realize entry" +button_get_dynamic_status(void) "Button read dynamic status entry" +button_ioport_read_unknown(void) "Button read unknown" diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index baab382a2e..1d2d809028 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -41,6 +41,7 @@ config PC imply FDC_ISA imply BATTERY imply AC_ADAPTER + imply BUTTON select I8259 select I8254 select PCKBD diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_de= v_interface.h index 588fbbd05f..4f5bcc15da 100644 --- a/include/hw/acpi/acpi_dev_interface.h +++ b/include/hw/acpi/acpi_dev_interface.h @@ -15,6 +15,7 @@ typedef enum { ACPI_POWER_DOWN_STATUS =3D 64, ACPI_BATTERY_CHANGE_STATUS =3D 128, ACPI_AC_ADAPTER_CHANGE_STATUS =3D 1024, + ACPI_BUTTON_CHANGE_STATUS =3D 2048, } 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..fa5f7001b2 --- /dev/null +++ b/include/hw/acpi/button.h @@ -0,0 +1,25 @@ +/* + * QEMU emulated button device. + * + * Copyright (c) 2019 Janus Technologies, Inc. (http://janustech.com) + * + * Authors: + * Leonid Bloch + * Marcel Apfelbaum + * Dmitry Fleytman + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_ACPI_BUTTON_H +#define HW_ACPI_BUTTON_H + +#define TYPE_BUTTON "button" +#define BUTTON_IOPORT_PROP "ioport" +#define BUTTON_PATH_PROP "procfs_path" +#define BUTTON_PROBE_STATE_INTERVAL "probe_interval" + +#define BUTTON_LEN 1 + +#endif diff --git a/qapi/acpi.json b/qapi/acpi.json index 52e151f0e6..bcbddbfbd5 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -264,3 +264,52 @@ ## { 'command': 'query-ac-adapter', 'returns': 'AcAdapterInfo' } + +## +# @lid-button-set-state: +# +# Set the state of the emulated laptop lid button device +# +# @open: whether the lid is open +# + +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "lid-button-set-state", +# "arguments": { "open": true } } +# <- { "return": {} } +## +{ 'command': 'lid-button-set-state', + 'data': { 'open': 'bool' } } + +## +# @LidButtonInfo: +# +# Lid button state information +# +# @open: whether the lid is open +# +# Since: 10.2 +## +{ 'struct': 'LidButtonInfo', + 'data': { 'open': 'bool' } } + +## +# @query-lid-button: +# +# Query the current state of the emulated laptop lid button device +# +# Returns: lid button state +# +# Since: 10.2 +# +# .. qmp-example:: +# +# -> { "execute": "query-lid-button" } +# <- { "return": { "open": true } } +## +{ 'command': 'query-lid-button', + 'returns': 'LidButtonInfo' } --=20 2.50.1