From nobody Fri Apr  4 13:35:12 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=quarantine dis=none)  header.from=kernel.org
ARC-Seal: i=1; a=rsa-sha256; t=1742388849; cv=none;
	d=zohomail.com; s=zohoarc;
	b=aDta4x8uFwwznSqD+Wt6Cs8A+gMwVuIVwknw6qunVWlreHGadwuQi3dYvGa63z20n7/CaLWlWO+rvB4+6jd7dzzRGgSvonLxrCJ5ZCIKEyoG+oMAdS9j4QblR6vv85l3CmfLR4KuWqaSs+XHOPMQsSLBDBx8xITr8lgAtG4SYGg=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1742388849;
 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:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id;
	bh=vHzYjWhZ79fYrijwtuLOl4fk33vNs7A9LL9/qd/JJcQ=;
	b=ZT/oKpuYUyTkiTJIreUlsij5FNwR0esQHsBGJpirhpaOvznuHi4j4nmS6DrzDmqJjs5hhcTvfr2mSxCEebk5GMELhtFSp+qlWoFvNHzPiBnBo5l0Cct+zqI4B1a/q0SNTMt5n43g34zExOifAGf4zPYvzumDHgK6HXblth4F3wY=
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=<devnull+n.shubin.yadro.com@kernel.org> (p=quarantine
 dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 1742388849463940.0596907096137;
 Wed, 19 Mar 2025 05:54:09 -0700 (PDT)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1tusrg-0000RY-Cl; Wed, 19 Mar 2025 08:49:44 -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 <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJT-0006Fy-K8; Wed, 19 Mar 2025 03:58:07 -0400
Received: from tor.source.kernel.org ([2600:3c04:e001:324:0:1991:8:25])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJQ-0003dg-Cp; Wed, 19 Mar 2025 03:58:07 -0400
Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])
 by tor.source.kernel.org (Postfix) with ESMTP id 9A09561146;
 Wed, 19 Mar 2025 07:57:53 +0000 (UTC)
Received: by smtp.kernel.org (Postfix) with ESMTPS id 773F8C4CEEA;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org
 (localhost.localdomain [127.0.0.1])
 by smtp.lore.kernel.org (Postfix) with ESMTP id 5A718C35FFF;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;
 s=k20201202; t=1742371074;
 bh=mE2MaBr2QtIINHy+w5jhliGc0m4u+0PDxW2SPqeU2j4=;
 h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;
 b=oRFa/G3t3tcD6yuqUbS0yEv9KX3oSsd2Jr5IFd/ilSqjmvis0qWHHC0ycb1gRGHPW
 61o7UUqgJT2OkhhBwhW543+AYnDwzqkH61jcWPSZVoILzpyj9D9kRR/MyccrIX0YWY
 PIM7c0S8ZI/ywYtaYB8X1UirqcZrnPHRiFqlq6HJ/BgP2K5Db2yV318bh4s+SSKB4p
 yh5ySBQtoo90ugPtBVw7yiF0HGrcr/JqPaxRa6aZOH/r3EQASFNro++Keaa2YL829M
 0Vv+kbmdqgIApBm+aBG+EfdagvziFjiIaVWBLTtIvQC0ggC0U6t9AhaWhWOD+G8puZ
 WTyxOrWNG8D5g==
From: Nikita Shubin via B4 Relay <devnull+n.shubin.yadro.com@kernel.org>
Date: Wed, 19 Mar 2025 10:57:51 +0300
Subject: [PATCH PoC 1/7] QAPI: gpio JSON
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Message-Id: <20250319-gpiodev-v1-1-76da4e5800a1@yadro.com>
References: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
In-Reply-To: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
To: qemu-devel@nongnu.org
Cc: Linus Walleij <linus.walleij@linaro.org>,
 Bartosz Golaszewski <brgl@bgdev.pl>,
 "Enrico Weigelt, metux IT consult" <info@metux.net>,
 Viresh Kumar <vireshk@kernel.org>, Eric Blake <eblake@redhat.com>,
 Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>,
 Paolo Bonzini <pbonzini@redhat.com>,
 =?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,
 =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>,
 =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,
 Eduardo Habkost <eduardo@habkost.net>,
 =?utf-8?q?C=C3=A9dric_Le_Goater?= <clg@kaod.org>,
 Peter Maydell <peter.maydell@linaro.org>,
 Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>,
 Jamin Lin <jamin_lin@aspeedtech.com>,
 Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>,
 qemu-arm@nongnu.org, Nikita Shubin <nikita.shubin@maquefel.me>,
 Nikita Shubin <n.shubin@yadro.com>, Nikita Shubin <nshubin@yadro.com>
X-Mailer: b4 0.14.2
X-Developer-Signature: v=1; a=ed25519-sha256; t=1742371072; l=2476;
 i=n.shubin@yadro.com; s=20230718; h=from:subject:message-id;
 bh=yL5seaoNiRVlQ6Vi3YlxRJ5NFJn0Q9alBbQrrB7t0Sw=;
 b=YB9LbqclbLblyvFQXTTPw/d9jF1oOaso31QxGYcWBQPr1gC5v09w3qjV7m9K+tTJgMICur3vS
 HLPawi1W6dXBIfx7TEeM9Pftlkni22KryLNBv10MIdhlql2w9loRq7J
X-Developer-Key: i=n.shubin@yadro.com; a=ed25519;
 pk=vqf5YIUJ7BJv3EJFaNNxWZgGuMgDH6rwufTLflwU9ac=
X-Endpoint-Received: by B4 Relay for n.shubin@yadro.com/20230718 with
 auth_id=161
X-Original-From: Nikita Shubin <n.shubin@yadro.com>
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=2600:3c04:e001:324:0:1991:8:25;
 envelope-from=devnull+n.shubin.yadro.com@kernel.org;
 helo=tor.source.kernel.org
X-Spam_score_int: -23
X-Spam_score: -2.4
X-Spam_bar: --
X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.332,
 DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Mailman-Approved-At: Wed, 19 Mar 2025 08:49:30 -0400
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Reply-To: n.shubin@yadro.com
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @kernel.org)
X-ZM-MESSAGEID: 1742388851139019100

From: Nikita Shubin <nshubin@yadro.com>

Signed-off-by: Nikita Shubin <nshubin@yadro.com>
---
 qapi/gpio.json        | 68 +++++++++++++++++++++++++++++++++++++++++++++++=
++++
 qapi/meson.build      |  1 +
 qapi/qapi-schema.json |  1 +
 3 files changed, 70 insertions(+)

diff --git a/qapi/gpio.json b/qapi/gpio.json
new file mode 100644
index 0000000000000000000000000000000000000000..1c2b7af36813ff52cbb3a44e64a=
2e5a5d8658d62
--- /dev/null
+++ b/qapi/gpio.json
@@ -0,0 +1,68 @@
+# -*- Mode: Python -*-
+# vim: filetype=3Dpython
+#
+
+##
+# =3D Gpio devices
+##
+
+##
+# @GpiodevInfo:
+#
+# Information about a gpio device.
+#
+# @label: the label of the gpio device
+#
+# Since: 9.2
+##
+{ 'struct': 'GpiodevInfo',
+  'data': { 'label': 'str' } }
+
+##
+# @GpiodevBackendKind:
+#
+# @chardev: chardevs
+#
+# Since: 9.2
+##
+{ 'enum': 'GpiodevBackendKind',
+  'data': [ 'chardev' ] }
+
+##
+# @GpiodevChardev:
+#
+# Configuration info for chardev gpiodevs.
+#
+# @chardev: chardev id
+#
+# @size: buffer size, default is 65536
+#
+# Since: 9.2
+##
+  { 'struct': 'GpiodevChardev',
+    'data': { 'chardev': 'str',
+              '*size': 'int' } }
+
+##
+# @GpiodevChardevWrapper:
+#
+# @data: Configuration info for chardev gpiodevs
+#
+# Since: 9.2
+##
+{ 'struct': 'GpiodevChardevWrapper',
+  'data': { 'data': 'GpiodevChardev' } }
+
+##
+# @GpiodevBackend:
+#
+# Configuration info for the new chardev backend.
+#
+# @type: backend type
+#
+# Since: 9.2
+##
+{ 'union': 'GpiodevBackend',
+  'base': { 'type': 'GpiodevBackendKind' },
+  'discriminator': 'type',
+  'data': { 'chardev': 'GpiodevChardevWrapper' } }
\ No newline at end of file
diff --git a/qapi/meson.build b/qapi/meson.build
index eadde4db307f0c54a7a1f2471a063c78346810cc..13d255adb8e6d823356fe0ecae0=
b585688320f9d 100644
--- a/qapi/meson.build
+++ b/qapi/meson.build
@@ -35,6 +35,7 @@ qapi_all_modules =3D [
   'dump',
   'ebpf',
   'error',
+  'gpio',
   'introspect',
   'job',
   'machine-common',
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 2877aff73d0caff9d4ed681de0690c24a268cecd..03cfa1ed4a0438d00a6151d5616=
7ec929467f15d 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -52,6 +52,7 @@
 { 'include': 'block-export.json' }
 { 'include': 'char.json' }
 { 'include': 'dump.json' }
+{ 'include': 'gpio.json' }
 { 'include': 'net.json' }
 { 'include': 'ebpf.json' }
 { 'include': 'rocker.json' }

--=20
2.45.2
From nobody Fri Apr  4 13:35:12 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=quarantine dis=none)  header.from=kernel.org
ARC-Seal: i=1; a=rsa-sha256; t=1742388688; cv=none;
	d=zohomail.com; s=zohoarc;
	b=I+f2Jv299P7zWbhcwSbcIZgrMQF4TsdiN16vOJhx0hEBcXXr/R5Kbphixm/VpeiE/f1IRtum25TjNA48EEOdpLOhqB9/SNHKlQc1bUnlIG0sqKg3fKHotDdQQgkKRwePf+MZ4K6NzFTCXA5O75vVNWGdUG2OF2FoGk3iN4qfRAk=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1742388688;
 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:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id;
	bh=aYUktCS7lLiw6X7T8HY7Q6bGEbG5AJVP0FRmnHOrfYM=;
	b=J2xkUqgylm9Bl9sFcVvbwHwTzfJFmK7Vrn+1sAVYbzU0ZtYXOO47wAHWqViqnqLuTGTNbV90GmXbBp6G6Yumh+3rQTaTXK0s8QFvUjOftiZz1SKUIlSVPBBDhV6vlXhChTTImoluKo1aqNqTPFBtXupv7ZzHFioOBntUbSxbr0A=
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=<devnull+n.shubin.yadro.com@kernel.org> (p=quarantine
 dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 1742388688437384.6190330629155;
 Wed, 19 Mar 2025 05:51:28 -0700 (PDT)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1tusri-0000W2-B8; Wed, 19 Mar 2025 08:49:46 -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 <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJV-0006Gy-KY; Wed, 19 Mar 2025 03:58:09 -0400
Received: from tor.source.kernel.org ([2600:3c04:e001:324:0:1991:8:25])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJQ-0003dh-Ec; Wed, 19 Mar 2025 03:58:09 -0400
Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])
 by tor.source.kernel.org (Postfix) with ESMTP id 9B44961148;
 Wed, 19 Mar 2025 07:57:53 +0000 (UTC)
Received: by smtp.kernel.org (Postfix) with ESMTPS id 88247C4CEEE;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org
 (localhost.localdomain [127.0.0.1])
 by smtp.lore.kernel.org (Postfix) with ESMTP id 78E50C36001;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;
 s=k20201202; t=1742371074;
 bh=CFy2hbVKVpBX9vi/6sVNNO0HlEuppHseI/Bd+uQ1hos=;
 h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;
 b=RMWW8ctO11WjS6+u4m0V19i43Fqox7nE7M/+aNJINdA4tDYNg5thsVZ40CcMS7hmk
 sF5yVAf0c5jfrSEvCm3Ur32c7hefOzzEol6Dt9a3I6d26NSXUjNQQfl4Ua+Ou6vfqJ
 dNzmAti7k4CeJ1np0pX0nJwW9fJUP3mFv2hqJ8K91XlI7JHe4e0Ir729feO1J2hT8C
 KwHQcz4S9Ky31XH3UVlmCC/mH6WAsYshTv2VhRsPDGpNgIQGxnDGxeu7zFsiL93m1R
 bHt1ZUv8AwykypkhdP8KCBMZq/OD8i6NSXXdJuSmpG7HlVPruefv/3wnZBEvmmFuEj
 zSCZtr3hciCig==
From: Nikita Shubin via B4 Relay <devnull+n.shubin.yadro.com@kernel.org>
Date: Wed, 19 Mar 2025 10:57:52 +0300
Subject: [PATCH PoC 2/7] Add gpiodev dummy
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Message-Id: <20250319-gpiodev-v1-2-76da4e5800a1@yadro.com>
References: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
In-Reply-To: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
To: qemu-devel@nongnu.org
Cc: Linus Walleij <linus.walleij@linaro.org>,
 Bartosz Golaszewski <brgl@bgdev.pl>,
 "Enrico Weigelt, metux IT consult" <info@metux.net>,
 Viresh Kumar <vireshk@kernel.org>, Eric Blake <eblake@redhat.com>,
 Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>,
 Paolo Bonzini <pbonzini@redhat.com>,
 =?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,
 =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>,
 =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,
 Eduardo Habkost <eduardo@habkost.net>,
 =?utf-8?q?C=C3=A9dric_Le_Goater?= <clg@kaod.org>,
 Peter Maydell <peter.maydell@linaro.org>,
 Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>,
 Jamin Lin <jamin_lin@aspeedtech.com>,
 Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>,
 qemu-arm@nongnu.org, Nikita Shubin <nikita.shubin@maquefel.me>,
 Nikita Shubin <n.shubin@yadro.com>
X-Mailer: b4 0.14.2
X-Developer-Signature: v=1; a=ed25519-sha256; t=1742371072; l=9267;
 i=n.shubin@yadro.com; s=20230718; h=from:subject:message-id;
 bh=Us2ZTcDmCXB4hKwaTjATMDmdCnIaWHQ1GrXvw/CeVjQ=;
 b=2ucNJK+XU0gUIvebTFEg5dVj3MC/m+ixeBQuSYyHQSlCEpqaEhBYw3AL9s/htU+n+V205kVRu
 3a/ng7seFe4D1lBsmOjGAWECawPAtfr7YF1xk7WORUNqxRe2N5XQiST
X-Developer-Key: i=n.shubin@yadro.com; a=ed25519;
 pk=vqf5YIUJ7BJv3EJFaNNxWZgGuMgDH6rwufTLflwU9ac=
X-Endpoint-Received: by B4 Relay for n.shubin@yadro.com/20230718 with
 auth_id=161
X-Original-From: Nikita Shubin <n.shubin@yadro.com>
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=2600:3c04:e001:324:0:1991:8:25;
 envelope-from=devnull+n.shubin.yadro.com@kernel.org;
 helo=tor.source.kernel.org
X-Spam_score_int: -23
X-Spam_score: -2.4
X-Spam_bar: --
X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.332,
 DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Mailman-Approved-At: Wed, 19 Mar 2025 08:49:31 -0400
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Reply-To: n.shubin@yadro.com
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @kernel.org)
X-ZM-MESSAGEID: 1742388689971019000

From: Nikita Shubin <n.shubin@yadro.com>

Add gpiodev stub with single help option.

Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
---
 gpiodev/gpio.c         | 145 +++++++++++++++++++++++++++++++++++++++++++++=
++++
 gpiodev/meson.build    |   5 ++
 include/gpiodev/gpio.h |  34 ++++++++++++
 meson.build            |  11 +++-
 qemu-options.hx        |   9 +++
 system/vl.c            |  25 +++++++++
 6 files changed, 228 insertions(+), 1 deletion(-)

diff --git a/gpiodev/gpio.c b/gpiodev/gpio.c
new file mode 100644
index 0000000000000000000000000000000000000000..0f84fb6c502bd6d0a5f808bc299=
fabd4144c2909
--- /dev/null
+++ b/gpiodev/gpio.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * QEMU GPIO device.
+ *
+ * Author: 2025 Nikita Shubin <n.shubin@yadro.com>
+ *
+ */
+#include "qemu/osdep.h"
+
+#include "qapi/error.h"
+#include "qemu/config-file.h"
+#include "qemu/option.h"
+#include "qemu/qemu-print.h"
+#include "qemu/help_option.h"
+
+#include "gpiodev/gpio.h"
+
+static Object *get_gpiodevs_root(void)
+{
+    return object_get_container("gpiodevs");
+}
+
+static const TypeInfo gpiodev_types_info[] =3D {
+    {
+        .name =3D TYPE_GPIODEV,
+        .parent =3D TYPE_OBJECT,
+        .instance_size =3D sizeof(Gpiodev),
+        .abstract =3D true,
+    },
+};
+
+DEFINE_TYPES(gpiodev_types_info);
+
+static Gpiodev *gpiodev_new(const char *id,
+                            GMainContext *gcontext,
+                            Error **errp)
+{
+    Object *obj;
+    Gpiodev *gpio =3D NULL;
+
+    assert(id);
+
+    obj =3D object_new(TYPE_GPIODEV);
+    gpio =3D GPIODEV(obj);
+    gpio->gcontext =3D gcontext;
+
+    return gpio;
+}
+
+static Gpiodev *qemu_gpiodev_new(const char *id,
+                                 GMainContext *gcontext,
+                                 Error **errp)
+{
+    Gpiodev *gpio;
+
+    gpio =3D gpiodev_new(id, gcontext, errp);
+    if (!gpio) {
+        return NULL;
+    }
+
+    if (!object_property_try_add_child(get_gpiodevs_root(), id, OBJECT(gpi=
o),
+                                       errp)) {
+        object_unref(OBJECT(gpio));
+        return NULL;
+    }
+
+    object_unref(OBJECT(gpio));
+
+    return gpio;
+}
+
+typedef struct GpiodevClassFE {
+    void (*fn)(const char *name, void *opaque);
+    void *opaque;
+} GpiodevClassFE;
+
+static void
+gpiodev_class_foreach(ObjectClass *klass, void *opaque)
+{
+    GpiodevClassFE *fe =3D opaque;
+
+    assert(g_str_has_prefix(object_class_get_name(klass), "gpiodev-"));
+    fe->fn(object_class_get_name(klass) + 8, fe->opaque);
+}
+
+static void
+gpiodev_name_foreach(void (*fn)(const char *name, void *opaque),
+                     void *opaque)
+{
+    GpiodevClassFE fe =3D { .fn =3D fn, .opaque =3D opaque };
+
+    object_class_foreach(gpiodev_class_foreach, TYPE_GPIODEV, false, &fe);
+}
+
+static void
+help_string_append(const char *name, void *opaque)
+{
+    GString *str =3D opaque;
+
+    g_string_append_printf(str, "\n  %s", name);
+}
+
+Gpiodev *qemu_gpiodev_add(QemuOpts *opts, GMainContext *context,
+                          Error **errp)
+{
+    const char *id =3D qemu_opts_id(opts);
+    const char *name =3D qemu_opt_get(opts, "backend");
+
+    if (name && is_help_option(name)) {
+        GString *str =3D g_string_new("");
+
+        gpiodev_name_foreach(help_string_append, str);
+
+        qemu_printf("Available chardev backend types: %s\n", str->str);
+        g_string_free(str, true);
+        return NULL;
+    }
+
+    if (id =3D=3D NULL) {
+        error_setg(errp, "gpiodev: no id specified");
+        return NULL;
+    }
+
+    return qemu_gpiodev_new(id, context, errp);
+}
+
+static QemuOptsList qemu_gpiodev_opts =3D {
+    .name =3D "gpiodev",
+    .implied_opt_name =3D "backend",
+    .head =3D QTAILQ_HEAD_INITIALIZER(qemu_gpiodev_opts.head),
+    .desc =3D {
+        {
+            .name =3D "backend",
+            .type =3D QEMU_OPT_STRING,
+        },
+        { /* end of list */ }
+    },
+};
+
+static void gpiodev_register_config(void)
+{
+    qemu_add_opts(&qemu_gpiodev_opts);
+}
+
+opts_init(gpiodev_register_config);
diff --git a/gpiodev/meson.build b/gpiodev/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..05aeb266bce8d905753d90d917b=
5f8b2d265a0cf
--- /dev/null
+++ b/gpiodev/meson.build
@@ -0,0 +1,5 @@
+gpiodev_ss.add(files(
+  'gpio.c',
+))
+
+gpiodev_ss =3D gpiodev_ss.apply({})
diff --git a/include/gpiodev/gpio.h b/include/gpiodev/gpio.h
new file mode 100644
index 0000000000000000000000000000000000000000..aca0a6090bb5264e1646fa5faca=
ce2d8f7cc47fd
--- /dev/null
+++ b/include/gpiodev/gpio.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * QEMU GPIO device.
+ *
+ * Author: 2025 Nikita Shubin <n.shubin@yadro.com>
+ *
+ */
+#ifndef QEMU_GPIO_H
+#define QEMU_GPIO_H
+
+#include "qom/object.h"
+
+/* gpio back-end device */
+typedef struct GpioBackend GpioBackend;
+
+struct Gpiodev {
+    Object parent_obj;
+
+    GpioBackend *be;
+
+    GMainContext *gcontext;
+};
+
+struct GpiodevClass {
+    ObjectClass parent_class;
+};
+
+#define TYPE_GPIODEV "gpiodev"
+OBJECT_DECLARE_TYPE(Gpiodev, GpiodevClass, GPIODEV)
+
+Gpiodev *qemu_gpiodev_add(QemuOpts *opts, GMainContext *context,
+                          Error **errp);
+
+#endif /* QEMU_GPIO_H */
diff --git a/meson.build b/meson.build
index 8b9fda4d95e634e6faa8db5604e5e0ddc4b1eb9e..9daac5783bcf459a3ee6ff1e999=
582bf4e9a47cc 100644
--- a/meson.build
+++ b/meson.build
@@ -3646,6 +3646,7 @@ block_ss =3D ss.source_set()
 chardev_ss =3D ss.source_set()
 common_ss =3D ss.source_set()
 crypto_ss =3D ss.source_set()
+gpiodev_ss =3D ss.source_set()
 hwcore_ss =3D ss.source_set()
 io_ss =3D ss.source_set()
 qmp_ss =3D ss.source_set()
@@ -3735,6 +3736,7 @@ subdir('io')
 subdir('chardev')
 subdir('fsdev')
 subdir('dump')
+subdir('gpiodev')
=20
 if have_block
   block_ss.add(files(
@@ -4023,11 +4025,18 @@ libhwcore =3D static_library('hwcore', sources: hwc=
ore_ss.sources() + genh,
 hwcore =3D declare_dependency(objects: libhwcore.extract_all_objects(recur=
sive: false))
 common_ss.add(hwcore)
=20
+libgpiodev =3D static_library('gpiodev', gpiodev_ss.sources() + genh,
+                            dependencies: gpiodev_ss.dependencies(),
+                            build_by_default: false)
+
+gpiodev =3D declare_dependency(objects: libgpiodev.extract_all_objects(rec=
ursive: false),
+                             dependencies: gpiodev_ss.dependencies())
+
 ###########
 # Targets #
 ###########
=20
-system_ss.add(authz, blockdev, chardev, crypto, io, qmp)
+system_ss.add(authz, blockdev, chardev, crypto, gpiodev, io, qmp)
 common_ss.add(qom, qemuutil)
=20
 common_ss.add_all(when: 'CONFIG_SYSTEM_ONLY', if_true: [system_ss])
diff --git a/qemu-options.hx b/qemu-options.hx
index dc694a99a30a7d1ef1567005f59ece88d13f3455..97d293c7d3276a5835c40bb9b26=
e5f0c8c2a2d28 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4081,6 +4081,15 @@ ERST
=20
 DEFHEADING()
=20
+DEFHEADING(GPIO device options:)
+
+DEF("gpiodev", HAS_ARG, QEMU_OPTION_gpiodev,
+    "-gpiodev help\n"
+    , QEMU_ARCH_ALL
+)
+
+DEFHEADING()
+
 #ifdef CONFIG_TPM
 DEFHEADING(TPM device options:)
=20
diff --git a/system/vl.c b/system/vl.c
index 04f78466c412a2f9e38c0b187c45d09d8df873ce..67e266a93d0d53c2a7cbd49f883=
1ae85702c8b42 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -73,6 +73,7 @@
 #include "gdbstub/enums.h"
 #include "qemu/timer.h"
 #include "chardev/char.h"
+#include "gpiodev/gpio.h"
 #include "qemu/bitmap.h"
 #include "qemu/log.h"
 #include "system/blockdev.h"
@@ -1230,6 +1231,20 @@ static int chardev_init_func(void *opaque, QemuOpts =
*opts, Error **errp)
     return 0;
 }
=20
+static int gpiodev_init_func(void *opaque, QemuOpts *opts, Error **errp)
+{
+    Error *local_err =3D NULL;
+
+    if (!qemu_gpiodev_add(opts, NULL, &local_err)) {
+        if (local_err) {
+            error_propagate(errp, local_err);
+            return -1;
+        }
+        exit(0);
+    }
+    return 0;
+}
+
 #ifdef CONFIG_VIRTFS
 static int fsdev_init_func(void *opaque, QemuOpts *opts, Error **errp)
 {
@@ -2053,6 +2068,9 @@ static void qemu_create_early_backends(void)
     qemu_opts_foreach(qemu_find_opts("chardev"),
                       chardev_init_func, NULL, &error_fatal);
=20
+    qemu_opts_foreach(qemu_find_opts("gpiodev"),
+                      gpiodev_init_func, NULL, &error_fatal);
+
 #ifdef CONFIG_VIRTFS
     qemu_opts_foreach(qemu_find_opts("fsdev"),
                       fsdev_init_func, NULL, &error_fatal);
@@ -3241,6 +3259,13 @@ void qemu_init(int argc, char **argv)
                     exit(1);
                 }
                 break;
+            case QEMU_OPTION_gpiodev:
+                opts =3D qemu_opts_parse_noisily(qemu_find_opts("gpiodev"),
+                                               optarg, true);
+                if (!opts) {
+                    exit(1);
+                }
+                break;
             case QEMU_OPTION_fsdev:
                 olist =3D qemu_find_opts("fsdev");
                 if (!olist) {

--=20
2.45.2
From nobody Fri Apr  4 13:35:12 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=quarantine dis=none)  header.from=kernel.org
ARC-Seal: i=1; a=rsa-sha256; t=1742388831; cv=none;
	d=zohomail.com; s=zohoarc;
	b=GKiailVkMIUcRuGiM0lB3RCobkurlL50Q05u4yec+boWpeqfmVUV6PWZbsOTx3mAuozx3MMOi6Xk+oxW4NgZherG+JfDSj6lwLNaqOa41tKSVhoyRMnRn/aF13h1URj3n5AXn5giz5GWKFushuHHXi8tPn4/3bHPjc0xOcuor2s=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1742388831;
 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:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id;
	bh=dFLBLgY6tETb0f3xaNRFTbZZ/o0eEGqjfbNOV4N/B7k=;
	b=URHWq1twBi1p338LZohKPTYjhTQF5UQZkepByLvWOfXRSwYJFuAvQLUvbgug3K0yi6XA6yr09s2uj2Y0ms83t0BI9l1KZpmfllnYYygcpzczyJZvvN8z8jGM/Cwv4XeWC9DYiusbCvrQiHMdHFzR+nIjQaAY/GyABy0/JQ/2sS0=
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=<devnull+n.shubin.yadro.com@kernel.org> (p=quarantine
 dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 1742388831390234.06940748481895;
 Wed, 19 Mar 2025 05:53:51 -0700 (PDT)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1tusrt-0000re-9A; Wed, 19 Mar 2025 08:49:57 -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 <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJV-0006GS-0j; Wed, 19 Mar 2025 03:58:09 -0400
Received: from nyc.source.kernel.org ([147.75.193.91])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJQ-0003de-9U; Wed, 19 Mar 2025 03:58:08 -0400
Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])
 by nyc.source.kernel.org (Postfix) with ESMTP id 93941A48B6F;
 Wed, 19 Mar 2025 07:52:25 +0000 (UTC)
Received: by smtp.kernel.org (Postfix) with ESMTPS id 9CD97C4CEF0;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org
 (localhost.localdomain [127.0.0.1])
 by smtp.lore.kernel.org (Postfix) with ESMTP id 8FE41C35FFA;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;
 s=k20201202; t=1742371074;
 bh=qIHV3qXpJxMoWKLO7JpvCuXhHkSYO/5EVcOzLHwY3vs=;
 h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;
 b=gOz5sCZppajmm7Gg4t/LvJOLKelmJsxCPAJGgUYfNVVoaUzEsNO+sUL0PdYc8zNc9
 Hkoq67H/tNYgP+xaYjg0jluKMCKqBYGtYFy4gK/YtScCAWuVMAv4attpHsm4s/9xpB
 J3+uCerYMkK5YwhDJNLbeig8dh0/RI01NkPZ2sFKOptHj5euybtrWIDKblsDVemT04
 sUFW77YuQdclylgRg0smAKl06a0QVkGWzAhuT5O3rFq/dp6YgviLd1uGCvfw/IBJRu
 s4pc8CktllTMaBrdpfue4CZp1sAsFk5UJ1n0jcdJnRSSzEAk2TGr5M5LS9cWXBInIj
 haBC0epdSVkxw==
From: Nikita Shubin via B4 Relay <devnull+n.shubin.yadro.com@kernel.org>
Date: Wed, 19 Mar 2025 10:57:53 +0300
Subject: [PATCH PoC 3/7] gpiodev: Add GPIO device frontend
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Message-Id: <20250319-gpiodev-v1-3-76da4e5800a1@yadro.com>
References: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
In-Reply-To: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
To: qemu-devel@nongnu.org
Cc: Linus Walleij <linus.walleij@linaro.org>,
 Bartosz Golaszewski <brgl@bgdev.pl>,
 "Enrico Weigelt, metux IT consult" <info@metux.net>,
 Viresh Kumar <vireshk@kernel.org>, Eric Blake <eblake@redhat.com>,
 Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>,
 Paolo Bonzini <pbonzini@redhat.com>,
 =?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,
 =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>,
 =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,
 Eduardo Habkost <eduardo@habkost.net>,
 =?utf-8?q?C=C3=A9dric_Le_Goater?= <clg@kaod.org>,
 Peter Maydell <peter.maydell@linaro.org>,
 Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>,
 Jamin Lin <jamin_lin@aspeedtech.com>,
 Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>,
 qemu-arm@nongnu.org, Nikita Shubin <nikita.shubin@maquefel.me>,
 Nikita Shubin <n.shubin@yadro.com>
X-Mailer: b4 0.14.2
X-Developer-Signature: v=1; a=ed25519-sha256; t=1742371072; l=22989;
 i=n.shubin@yadro.com; s=20230718; h=from:subject:message-id;
 bh=LTQ4DRZxUgYIxufc+VjL7CHLDfHRsY0h348VPbCO93Q=;
 b=RoqpcR3Zh9HwlMSu71p/Dmq3QlGR/Atk09T0ZsYOLuYHtZ2T47HTXNNp5Us70EO8BPkK4uLau
 0fx0jSYJSaYBJwQbP1w7FHc7CTSknCFdYaNyhYyHKqyiHbJelyKPtKC
X-Developer-Key: i=n.shubin@yadro.com; a=ed25519;
 pk=vqf5YIUJ7BJv3EJFaNNxWZgGuMgDH6rwufTLflwU9ac=
X-Endpoint-Received: by B4 Relay for n.shubin@yadro.com/20230718 with
 auth_id=161
X-Original-From: Nikita Shubin <n.shubin@yadro.com>
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=147.75.193.91;
 envelope-from=devnull+n.shubin.yadro.com@kernel.org;
 helo=nyc.source.kernel.org
X-Spam_score_int: -46
X-Spam_score: -4.7
X-Spam_bar: ----
X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.332,
 DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=0.001,
 RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, SPF_HELO_NONE=0.001,
 SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Mailman-Approved-At: Wed, 19 Mar 2025 08:49:30 -0400
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Reply-To: n.shubin@yadro.com
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @kernel.org)
X-ZM-MESSAGEID: 1742388831976019000

From: Nikita Shubin <n.shubin@yadro.com>

Add GPIO device front end with helper functions to provide information
about GPIO Port to Backends and to allow Frontend to set data.

To use it GPIO device should register with qemu_gpio_fe_init()
and provide handlers with qemu_gpio_fe_set_handlers().

Notifications about config and changed value events should be done with
qemu_gpio_fe_line_event() and qemu_gpio_fe_config_event().

Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
---
 gpiodev/gpio-fe.c         | 103 ++++++++++++++++++
 gpiodev/gpio.c            | 264 ++++++++++++++++++++++++++++++++++++++++++=
+++-
 gpiodev/meson.build       |   1 +
 include/gpiodev/gpio-fe.h | 122 +++++++++++++++++++++
 include/gpiodev/gpio.h    | 200 ++++++++++++++++++++++++++++++++++-
 qom/object.c              |   1 +
 6 files changed, 684 insertions(+), 7 deletions(-)

diff --git a/gpiodev/gpio-fe.c b/gpiodev/gpio-fe.c
new file mode 100644
index 0000000000000000000000000000000000000000..3377548d8ed9961a74595686f83=
636945132f4c8
--- /dev/null
+++ b/gpiodev/gpio-fe.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * QEMU GPIO device frontend.
+ *
+ * Author: 2025 Nikita Shubin <n.shubin@yadro.com>
+ *
+ */
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qerror.h"
+
+#include "gpiodev/gpio-fe.h"
+
+bool qemu_gpio_fe_init(GpioBackend *b, Gpiodev *s, uint32_t nlines,
+                       const char *name, const char *label,
+                       Error **errp)
+{
+    if (s->be) {
+        goto unavailable;
+    } else {
+        s->be =3D b;
+    }
+
+    qemu_gpiodev_set_info(s, nlines, name, label);
+    b->gpio =3D s;
+
+    return true;
+
+unavailable:
+    error_setg(errp, "chardev '%s' is already in use", s->label);
+    return false;
+}
+
+void qemu_gpio_fe_set_handlers(GpioBackend *b,
+                               LineInfoHandler *line_info,
+                               LineGetValueHandler *get_value,
+                               LineSetValueHandler *set_value,
+                               void *opaque)
+{
+    Gpiodev *s;
+
+    s =3D b->gpio;
+    if (!s) {
+        return;
+    }
+
+    b->line_info =3D line_info;
+    b->get_value =3D get_value;
+    b->set_value =3D set_value;
+    b->opaque =3D opaque;
+}
+
+bool qemu_gpio_fe_line_event(GpioBackend *b, uint32_t offset,
+                             QEMUGpioLineEvent event)
+{
+    Gpiodev *gpio =3D b->gpio;
+
+    if (!gpio) {
+        return false;
+    }
+
+    qemu_gpio_line_event(gpio, offset, event);
+
+    return true;
+}
+
+bool qemu_gpio_fe_config_event(GpioBackend *b, uint32_t offset,
+                               QEMUGpioConfigEvent event)
+{
+    Gpiodev *gpio =3D b->gpio;
+
+    if (!gpio) {
+        return false;
+    }
+
+    qemu_gpio_config_event(gpio, offset, event);
+
+    return true;
+}
+
+void qemu_gpio_fe_deinit(GpioBackend *b, bool del)
+{
+    assert(b);
+
+    if (b->gpio) {
+        qemu_gpio_fe_set_handlers(b, NULL, NULL, NULL, NULL);
+        if (b->gpio->be =3D=3D b) {
+            b->gpio->be =3D NULL;
+        }
+
+        if (del) {
+            Object *obj =3D OBJECT(b->gpio);
+            if (obj->parent) {
+                object_unparent(obj);
+            } else {
+                object_unref(obj);
+            }
+        }
+
+        b->gpio =3D NULL;
+    }
+}
diff --git a/gpiodev/gpio.c b/gpiodev/gpio.c
index 0f84fb6c502bd6d0a5f808bc299fabd4144c2909..396de1f03d9af1910e104bd0381=
dd81025e994db 100644
--- a/gpiodev/gpio.c
+++ b/gpiodev/gpio.c
@@ -8,52 +8,227 @@
 #include "qemu/osdep.h"
=20
 #include "qapi/error.h"
+#include "qapi/qmp/qerror.h"
+#include "qemu/bitmap.h"
 #include "qemu/config-file.h"
 #include "qemu/option.h"
 #include "qemu/qemu-print.h"
 #include "qemu/help_option.h"
=20
 #include "gpiodev/gpio.h"
+#include "gpiodev/gpio-fe.h"
=20
 static Object *get_gpiodevs_root(void)
 {
     return object_get_container("gpiodevs");
 }
=20
+void qemu_gpiodev_set_info(Gpiodev *g, uint32_t nlines,
+                           const char *name, const char *label)
+{
+    g->lines =3D nlines;
+    g_strlcpy(g->name, name, sizeof(g->name));
+    g_strlcpy(g->label, label, sizeof(g->label));
+
+    g->mask.risen =3D bitmap_new(nlines);
+    g->mask.fallen =3D bitmap_new(nlines);
+    g->mask.config =3D bitmap_new(nlines);
+}
+
+void qemu_gpio_chip_info(Gpiodev *g, uint32_t *nlines,
+                         char *name, char *label)
+{
+    if (!g->be) {
+        g_strlcpy(name, "NULL", GPIO_MAX_NAME_SIZE);
+        g_strlcpy(label, "NULL", GPIO_MAX_NAME_SIZE);
+        *nlines =3D 0;
+        return;
+    }
+
+    g_strlcpy(name, g->name, GPIO_MAX_NAME_SIZE);
+    g_strlcpy(label, g->label, GPIO_MAX_NAME_SIZE);
+    *nlines =3D g->lines;
+}
+
+void qemu_gpio_line_info(Gpiodev *g, gpio_line_info *info)
+{
+    GpioBackend *be =3D g->be;
+
+    if (!be || !be->line_info) {
+        return;
+    }
+
+    be->line_info(be->opaque, info);
+}
+
+void qemu_gpio_set_line_value(Gpiodev *g, uint32_t offset, uint8_t value)
+{
+    GpioBackend *be =3D g->be;
+
+    if (!be || !be->set_value) {
+        return;
+    }
+
+    be->set_value(be->opaque, offset, value);
+}
+
+uint8_t qemu_gpio_get_line_value(Gpiodev *g, uint32_t offset)
+{
+    GpioBackend *be =3D g->be;
+
+    if (!be || !be->get_value) {
+        return 0;
+    }
+
+    return be->get_value(be->opaque, offset);
+}
+
+void qemu_gpio_add_event_watch(Gpiodev *g, uint32_t offset, uint64_t flags)
+{
+    if (flags & GPIO_EVENT_RISING_EDGE) {
+        set_bit(offset, g->mask.risen);
+    }
+
+    if (flags & GPIO_EVENT_FALLING_EDGE) {
+        set_bit(offset, g->mask.fallen);
+    }
+}
+
+void qemu_gpio_clear_event_watch(Gpiodev *g, uint32_t offset, uint64_t fla=
gs)
+{
+    if (flags & GPIO_EVENT_RISING_EDGE) {
+        clear_bit(offset, g->mask.risen);
+    }
+
+    if (flags & GPIO_EVENT_FALLING_EDGE) {
+        clear_bit(offset, g->mask.fallen);
+    }
+}
+
+void qemu_gpio_add_config_watch(Gpiodev *g, uint32_t offset)
+{
+    set_bit(offset, g->mask.config);
+}
+
+void qemu_gpio_clear_config_watch(Gpiodev *g, uint32_t offset)
+{
+    clear_bit(offset, g->mask.config);
+}
+
+void qemu_gpio_clear_watches(Gpiodev *g)
+{
+    bitmap_zero(g->mask.risen, g->lines);
+    bitmap_zero(g->mask.fallen, g->lines);
+    bitmap_zero(g->mask.config, g->lines);
+}
+
+void qemu_gpio_line_event(Gpiodev *g, uint32_t offset,
+                          QEMUGpioLineEvent event)
+{
+    GpiodevClass *gc =3D GPIODEV_GET_CLASS(g);
+    bool notify =3D false;
+
+    if (!gc->line_event) {
+        return;
+    }
+
+    if (event & GPIO_EVENT_RISING_EDGE) {
+        if (test_bit(offset, g->mask.risen)) {
+            notify =3D true;
+        }
+    }
+
+    if (event & GPIO_EVENT_FALLING_EDGE) {
+        if (test_bit(offset, g->mask.fallen)) {
+            notify =3D true;
+        }
+    }
+
+    if (notify) {
+        gc->line_event(g, offset, event);
+    }
+}
+
+void qemu_gpio_config_event(Gpiodev *g, uint32_t offset,
+                            QEMUGpioConfigEvent event)
+{
+    GpiodevClass *gc =3D GPIODEV_GET_CLASS(g);
+
+    if (!gc->config_event) {
+        return;
+    }
+
+    if (test_bit(offset, g->mask.config)) {
+        gc->config_event(g, offset, event);
+    }
+}
+
+static void qemu_gpio_finalize(Object *obj)
+{
+    Gpiodev *d =3D GPIODEV(obj);
+
+    g_free(d->mask.risen);
+    g_free(d->mask.fallen);
+    g_free(d->mask.config);
+}
+
 static const TypeInfo gpiodev_types_info[] =3D {
     {
         .name =3D TYPE_GPIODEV,
         .parent =3D TYPE_OBJECT,
         .instance_size =3D sizeof(Gpiodev),
+        .instance_finalize =3D qemu_gpio_finalize,
         .abstract =3D true,
+        .class_size =3D sizeof(GpiodevClass),
     },
 };
=20
 DEFINE_TYPES(gpiodev_types_info);
=20
-static Gpiodev *gpiodev_new(const char *id,
+static void qemu_gpio_open(Gpiodev *gpio, GpiodevBackend *backend,
+                           Error **errp)
+{
+    GpiodevClass *gc =3D GPIODEV_GET_CLASS(gpio);
+
+    if (gc->open) {
+        gc->open(gpio, backend, errp);
+    }
+}
+
+static Gpiodev *gpiodev_new(const char *id, const char *typename,
+                            GpiodevBackend *backend,
                             GMainContext *gcontext,
                             Error **errp)
 {
     Object *obj;
     Gpiodev *gpio =3D NULL;
+    Error *local_err =3D NULL;
=20
+    assert(g_str_has_prefix(typename, "gpiodev-"));
     assert(id);
=20
-    obj =3D object_new(TYPE_GPIODEV);
+    obj =3D object_new(typename);
     gpio =3D GPIODEV(obj);
     gpio->gcontext =3D gcontext;
=20
+    qemu_gpio_open(gpio, backend, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        object_unref(obj);
+        return NULL;
+    }
+
     return gpio;
 }
=20
-static Gpiodev *qemu_gpiodev_new(const char *id,
+static Gpiodev *qemu_gpiodev_new(const char *id, const char *typename,
+                                 GpiodevBackend *backend,
                                  GMainContext *gcontext,
                                  Error **errp)
 {
     Gpiodev *gpio;
=20
-    gpio =3D gpiodev_new(id, gcontext, errp);
+    gpio =3D gpiodev_new(id, typename, backend, gcontext, errp);
     if (!gpio) {
         return NULL;
     }
@@ -100,11 +275,67 @@ help_string_append(const char *name, void *opaque)
     g_string_append_printf(str, "\n  %s", name);
 }
=20
+static const GpiodevClass *gpio_get_class(const char *driver, Error **errp)
+{
+    ObjectClass *oc;
+    char *typename =3D g_strdup_printf("gpiodev-%s", driver);
+
+    oc =3D module_object_class_by_name(typename);
+    g_free(typename);
+
+    if (!object_class_dynamic_cast(oc, TYPE_GPIODEV)) {
+        error_setg(errp, "'%s' is not a valid gpio driver name", driver);
+        return NULL;
+    }
+
+    if (object_class_is_abstract(oc)) {
+        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "driver",
+                   "a non-abstract device type");
+        return NULL;
+    }
+
+    return GPIODEV_CLASS(oc);
+}
+
+static GpiodevBackend *qemu_gpio_parse_opts(QemuOpts *opts, Error **errp)
+{
+    Error *local_err =3D NULL;
+    const GpiodevClass *gc;
+    GpiodevBackend *backend =3D NULL;
+    const char *name =3D qemu_opt_get(opts, "backend");
+
+    if (name =3D=3D NULL) {
+        error_setg(errp, "gpiodev: \"%s\" missing backend",
+                   qemu_opts_id(opts));
+        return NULL;
+    }
+
+    gc =3D gpio_get_class(name, errp);
+    if (gc =3D=3D NULL) {
+        return NULL;
+    }
+
+    backend =3D g_new0(GpiodevBackend, 1);
+    if (gc->parse) {
+        gc->parse(opts, backend, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            qapi_free_GpiodevBackend(backend);
+            return NULL;
+        }
+    }
+
+    return backend;
+}
+
 Gpiodev *qemu_gpiodev_add(QemuOpts *opts, GMainContext *context,
                           Error **errp)
 {
     const char *id =3D qemu_opts_id(opts);
     const char *name =3D qemu_opt_get(opts, "backend");
+    const GpiodevClass *gc;
+    GpiodevBackend *backend =3D NULL;
+    Gpiodev *gpio =3D NULL;
=20
     if (name && is_help_option(name)) {
         GString *str =3D g_string_new("");
@@ -121,7 +352,22 @@ Gpiodev *qemu_gpiodev_add(QemuOpts *opts, GMainContext=
 *context,
         return NULL;
     }
=20
-    return qemu_gpiodev_new(id, context, errp);
+    backend =3D qemu_gpio_parse_opts(opts, errp);
+    if (backend =3D=3D NULL) {
+        return NULL;
+    }
+
+    gc =3D gpio_get_class(name, errp);
+    if (gc =3D=3D NULL) {
+        goto out;
+    }
+
+    gpio =3D qemu_gpiodev_new(id, object_class_get_name(OBJECT_CLASS(gc)),
+                            backend, context, errp);
+
+out:
+    qapi_free_GpiodevBackend(backend);
+    return gpio;
 }
=20
 static QemuOptsList qemu_gpiodev_opts =3D {
@@ -132,6 +378,14 @@ static QemuOptsList qemu_gpiodev_opts =3D {
         {
             .name =3D "backend",
             .type =3D QEMU_OPT_STRING,
+        }, {
+            .name =3D "chardev",
+            .type =3D QEMU_OPT_STRING,
+            .help =3D "Chardev id (for gpiodev-chardev)",
+        }, {
+            .name =3D "devname",
+            .type =3D QEMU_OPT_STRING,
+            .help =3D "Device name (for gpiodev-guse)",
         },
         { /* end of list */ }
     },
diff --git a/gpiodev/meson.build b/gpiodev/meson.build
index 05aeb266bce8d905753d90d917b5f8b2d265a0cf..0c7e457a11a6b01ec675006ae11=
ce0c50356407e 100644
--- a/gpiodev/meson.build
+++ b/gpiodev/meson.build
@@ -1,4 +1,5 @@
 gpiodev_ss.add(files(
+  'gpio-fe.c',
   'gpio.c',
 ))
=20
diff --git a/include/gpiodev/gpio-fe.h b/include/gpiodev/gpio-fe.h
new file mode 100644
index 0000000000000000000000000000000000000000..ba775575c3b4a20219730189108=
a480fde7e60ca
--- /dev/null
+++ b/include/gpiodev/gpio-fe.h
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * QEMU GPIO device frontend.
+ *
+ * Author: 2025 Nikita Shubin <n.shubin@yadro.com>
+ *
+ */
+#ifndef QEMU_GPIO_FE_H
+#define QEMU_GPIO_FE_H
+
+#include "qemu/main-loop.h"
+
+#include "gpiodev/gpio.h"
+
+/**
+ * LineInfoHandler: Return the gpio line info specified by offset
+ */
+typedef void LineInfoHandler(void *opaque, gpio_line_info *info);
+
+/**
+ * LineInfoHandler: Return the gpio line value specified by offset
+ */
+typedef int LineGetValueHandler(void *opaque, uint32_t offset);
+
+/**
+ * LineSetValueHandler: Set the gpio line value specified by offset
+ */
+typedef int LineSetValueHandler(void *opaque, uint32_t offset, uint8_t val=
ue);
+
+/**
+ * struct GpioBackend - back end as seen by front end
+ *
+ * The actual backend is Gpiodev
+ */
+struct GpioBackend {
+    Gpiodev *gpio;
+    LineInfoHandler *line_info;
+    LineGetValueHandler *get_value;
+    LineSetValueHandler *set_value;
+    void *opaque;
+};
+
+/**
+ * qemu_gpio_fe_deinit:
+ *
+ * @b: a GpioBackend
+ * @s: a Gpiodev
+ * @nlines: number of lines in the GPIO Port
+ * @name: name of the GPIO Port
+ * @label: label of the GPIO Port
+ * @errp: error if any
+ *
+ * Initializes a front end for the given GpioBackend and
+ * Gpiodev. Call qemu_gpio_fe_deinit() to remove the association and
+ * release the driver.
+ *
+ * nlines, name and label used for proving information
+ * via qemu_gpiodev_set_info().
+ *
+ * Returns: false on error.
+ */
+bool qemu_gpio_fe_init(GpioBackend *b, Gpiodev *s, uint32_t nlines,
+                       const char *name, const char *label,
+                       Error **errp);
+
+/**
+ * qemu_gpio_fe_set_handlers:
+ *
+ * @b: a GpioBackend
+ * @s: a Gpiodev
+ * @line_info: Line info handler to provide info about line
+ * @get_value: Get line value handler
+ * @set_value: Set line value handler
+ * @opaque: an opaque pointer for the callbacks
+ * @context: a main loop context or NULL for the default
+ *
+ * Set the front end gpio handlers.
+ *
+ */
+void qemu_gpio_fe_set_handlers(GpioBackend *b,
+                               LineInfoHandler *line_info,
+                               LineGetValueHandler *get_value,
+                               LineSetValueHandler *set_value,
+                               void *opaque);
+
+/**
+ * qemu_gpio_fe_deinit:
+ *
+ * @b: a GpioBackend
+ * @del: if true, delete the gpiodev backend
+ *
+ * Dissociate the GpioBackend from the Gpiodev.
+ *
+ * Safe to call without associated Gpiodev.
+ */
+void qemu_gpio_fe_deinit(GpioBackend *b, bool del);
+
+/**
+ * qemu_gpio_fe_line_event:
+ *
+ * @b: a GpioBackend
+ * @offset: line number offset
+ * @event: rising or falling edge event
+ *
+ * See enum QEMUGpioEvent.
+ */
+bool qemu_gpio_fe_line_event(GpioBackend *b, uint32_t offset,
+                             QEMUGpioLineEvent event);
+
+/**
+ * qemu_gpio_fe_config_event:
+ *
+ * @b: a GpioBackend
+ * @offset: line number offset
+ * @event: requested, released or input/output toggle
+ *
+ * See enum QEMUGpioConfigEvent.
+ */
+bool qemu_gpio_fe_config_event(GpioBackend *b, uint32_t offset,
+                               QEMUGpioConfigEvent event);
+
+#endif /* QEMU_GPIO_FE_H */
diff --git a/include/gpiodev/gpio.h b/include/gpiodev/gpio.h
index aca0a6090bb5264e1646fa5facace2d8f7cc47fd..2ea6c0e6af8125caacc944cdddc=
a94b1bca8c4ff 100644
--- a/include/gpiodev/gpio.h
+++ b/include/gpiodev/gpio.h
@@ -8,25 +8,221 @@
 #ifndef QEMU_GPIO_H
 #define QEMU_GPIO_H
=20
+#include "qapi/qapi-types-gpio.h"
 #include "qom/object.h"
+#include "qemu/bitops.h"
=20
 /* gpio back-end device */
 typedef struct GpioBackend GpioBackend;
=20
+#define GPIO_MAX_NAME_SIZE 32
+
+/* compatible with enum gpio_v2_line_flag */
+typedef enum QEMUGpioLineFlags {
+	GPIO_LINE_FLAG_INPUT            =3D BIT_ULL(2),
+	GPIO_LINE_FLAG_OUTPUT           =3D BIT_ULL(3),
+} QEMUGpioLineFlags;
+
+typedef enum QEMUGpioLineEvent {
+	GPIO_EVENT_RISING_EDGE	=3D 1,
+	GPIO_EVENT_FALLING_EDGE	=3D 2,
+} QEMUGpioLineEvent;
+
+typedef enum QEMUGpioConfigEvent {
+	GPIO_LINE_CHANGED_REQUESTED	=3D 1,
+	GPIO_LINE_CHANGED_RELEASED	=3D 2,
+	GPIO_LINE_CHANGED_CONFIG	=3D 3,
+} QEMUGpioConfigEvent;
+
 struct Gpiodev {
     Object parent_obj;
=20
     GpioBackend *be;
=20
+    uint32_t lines;
+    char name[GPIO_MAX_NAME_SIZE];
+	char label[GPIO_MAX_NAME_SIZE];
+
+    struct {
+        unsigned long *risen;
+        unsigned long *fallen;
+        unsigned long *config;
+    } mask;
+
     GMainContext *gcontext;
 };
=20
+#define TYPE_GPIODEV "gpiodev"
+OBJECT_DECLARE_TYPE(Gpiodev, GpiodevClass, GPIODEV)
+
 struct GpiodevClass {
     ObjectClass parent_class;
+
+    /* parse command line options and populate QAPI @backend */
+    void (*parse)(QemuOpts *opts, GpiodevBackend *backend, Error **errp);
+
+    /* called after construction, open/starts the backend */
+    void (*open)(Gpiodev *gpio, GpiodevBackend *backend, Error **errp);
+
+    /* notify backend about line event */
+    void (*line_event)(Gpiodev *g, uint32_t offset,
+                       QEMUGpioLineEvent event);
+
+    /* notify backend about config event */
+    void (*config_event)(Gpiodev *g, uint32_t offset,
+                         QEMUGpioConfigEvent event);
 };
=20
-#define TYPE_GPIODEV "gpiodev"
-OBJECT_DECLARE_TYPE(Gpiodev, GpiodevClass, GPIODEV)
+/**
+ * qemu_gpiodev_set_info:
+ *
+ * @g: a Gpiodev
+ * @nlines: number of lines in the GPIO Port
+ * @name: name of the GPIO Port
+ * @label: label of the GPIO Port
+ *
+ * Set basic info about GPIO Port, used by backends to provide data
+ * to client applications.
+ *
+ * nlines, name and label used for proving information
+ * via qemu_gpio_chip_info().
+ */
+void qemu_gpiodev_set_info(Gpiodev *g, uint32_t nlines,
+                           const char *name, const char *label);
+
+/**
+ * qemu_gpio_chip_info:
+ *
+ * @g: a Gpiodev
+ * @nlines: lines number of the GPIO Port will be set
+ * @name: name of the GPIO Port will be set
+ * @label: label of the GPIO Port will be set
+ *
+ * If GpioBackend is NULL, nlines will be set to zero and
+ * both name and label to NULL.
+ */
+void qemu_gpio_chip_info(Gpiodev *g, uint32_t *nlines,
+                         char *name, char *label);
+
+typedef struct gpio_line_info {
+	char name[GPIO_MAX_NAME_SIZE];
+	char consumer[GPIO_MAX_NAME_SIZE];
+	uint32_t offset;
+	uint64_t flags;
+} gpio_line_info;
+
+/**
+ * qemu_gpio_line_info:
+ *
+ * @g: a Gpiodev
+ * @info: info about requested line
+ *
+ * info->offset should be provided see gpio_line_info.
+ */
+void qemu_gpio_line_info(Gpiodev *g, gpio_line_info *info);
+
+/**
+ * qemu_gpio_set_line_value:
+ *
+ * @g: a Gpiodev
+ * @offset: line offset
+ * @value: line value
+ */
+void qemu_gpio_set_line_value(Gpiodev *g, uint32_t offset, uint8_t value);
+
+/**
+ * qemu_gpio_get_line_value:
+ *
+ * @g: a Gpiodev
+ * @offset: line offset
+ *
+ * returns 0 or 1 line status
+ */
+uint8_t qemu_gpio_get_line_value(Gpiodev *g, uint32_t offset);
+
+/**
+ * qemu_gpio_add_event_watch:
+ *
+ * @g: a Gpiodev
+ * @offset: line offset
+ * @flags: event flags
+ *
+ * See QEMUGpioLineEvent.
+ *
+ * Add lines specified by mask and flags to watch, called by GpiodevBacken=
d to subscribe
+ * desired lines and events.
+ */
+void qemu_gpio_add_event_watch(Gpiodev *g, uint32_t offset, uint64_t flags=
);
+
+/**
+ * qemu_gpio_clear_event_watch:
+ *
+ * @g: a Gpiodev
+ * @offset: line offset
+ * @flags: event flags
+ *
+ * See QEMUGpioLineEvent.
+ *
+ * Remove lines specified by mask and flags from watch, called by GpiodevB=
ackend.
+ */
+void qemu_gpio_clear_event_watch(Gpiodev *g, uint32_t offset, uint64_t fla=
gs);
+
+/**
+ * qemu_gpio_add_config_watch:
+ *
+ * @g: a Gpiodev
+ * @offset: line offset
+ *
+ * See QEMUGpioConfigEvent.
+ *
+ * Add lines specified by mask to watch, called by GpiodevBackend to subsc=
ribe
+ * about desired lines config change.
+ */
+void qemu_gpio_add_config_watch(Gpiodev *g, uint32_t offset);
+
+/**
+ * qemu_gpio_clear_config_watch:
+ *
+ * @g: a Gpiodev
+ * @mask: lines mask to clear
+ *
+ * See QEMUGpioConfigEvent.
+ *
+ * Remove lines specified by mask from watch, called by GpiodevBackend.
+ */
+void qemu_gpio_clear_config_watch(Gpiodev *g, uint32_t offset);
+
+void qemu_gpio_clear_watches(Gpiodev *g);
+
+
+/**
+ * qemu_gpio_line_event:
+ *
+ * @g: a Gpiodev
+ * @offset: line offset
+ * @event: event
+ *
+ * See QEMUGpioLineEvent.
+ *
+ * Called by GpioBackend to notify Gpiodev about line event, i.e. line set=
 to 0/1.
+ */
+void qemu_gpio_line_event(Gpiodev *gpio, uint32_t offset,
+                          QEMUGpioLineEvent event);
+
+/**
+ * qemu_gpio_config_event:
+ *
+ * @g: a Gpiodev
+ * @offset: line offset
+ * @event: event
+ *
+ * See QEMUGpioConfigEvent.
+ *
+ * Called by GpioBackend to notify Gpiodev about line config event,
+ * i.e. input switched to output.
+ */
+void qemu_gpio_config_event(Gpiodev *g, uint32_t offset,
+                          QEMUGpioConfigEvent event);
=20
 Gpiodev *qemu_gpiodev_add(QemuOpts *opts, GMainContext *context,
                           Error **errp);
diff --git a/qom/object.c b/qom/object.c
index 01618d06bd88b0b7049f4b6de9985d4b3c5c1dbc..e0b9095a1148be3c8de05408ef8=
9578c10226c4e 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -1730,6 +1730,7 @@ const char *object_property_get_type(Object *obj, con=
st char *name, Error **errp
=20
 static const char *const root_containers[] =3D {
     "chardevs",
+    "gpiodevs",
     "objects",
     "backend"
 };

--=20
2.45.2
From nobody Fri Apr  4 13:35:12 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=quarantine dis=none)  header.from=kernel.org
ARC-Seal: i=1; a=rsa-sha256; t=1742388847; cv=none;
	d=zohomail.com; s=zohoarc;
	b=DuXPFxNIWt+vt3EIb+OAA8DRSgaooUZ3lezoT6j1mqqBXgiMS5QBIZ7Ym7tnYOCONEm39WqVXBocvUKRl8fZp0kigAf9CtlI/m+g+V2fF7flE5pds3uXDY7eT7p9QKKIeduZhcEuBWkfO8EtlkmkaexxLyEPwtZRU4MisXNh9Co=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1742388847;
 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:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id;
	bh=TbLXloT6IlLt+KwRlJUn4y9q+aknET92PS6EnBB0tjw=;
	b=h2LCnJwkSpV3VKSSCywIk30LFvc7uUgxIFiW48WOOKAI9ymU6tKxNhEh6CCL21YTYiWVd8WQYdX2cXeP4oldsOt0zGnAVkN8qytUVWA0NHntG2sQHj9tYTZptXXO9lOkg3JSFtPOumC50Vl3HxSon5e6vv2T+LtojUxSLouypyw=
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=<devnull+n.shubin.yadro.com@kernel.org> (p=quarantine
 dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 1742388847552600.9887957381966;
 Wed, 19 Mar 2025 05:54:07 -0700 (PDT)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1tusrs-0000mo-B4; Wed, 19 Mar 2025 08:49:56 -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 <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJU-0006GR-Vi; Wed, 19 Mar 2025 03:58:09 -0400
Received: from tor.source.kernel.org ([172.105.4.254])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJQ-0003di-B2; Wed, 19 Mar 2025 03:58:08 -0400
Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])
 by tor.source.kernel.org (Postfix) with ESMTP id D2B8261160;
 Wed, 19 Mar 2025 07:57:53 +0000 (UTC)
Received: by smtp.kernel.org (Postfix) with ESMTPS id AE786C4CEF4;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org
 (localhost.localdomain [127.0.0.1])
 by smtp.lore.kernel.org (Postfix) with ESMTP id A55AFC35FFF;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;
 s=k20201202; t=1742371074;
 bh=pCiavkc0lCIP1XX9Y6CVBKB4HWoGnxvVV8ujV7FQAek=;
 h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;
 b=V0yTTDtb67aO3w4tLiU4f06iBiY39YbQUwU33y2IvzZzorm/g50RGOcDYHTBFxMrx
 kOSoi2myc/RvyO/jqVy1+N1BFYSjWxQbk+osmjtIL7kWKU5Gx8EYIR48TlpElPFa/p
 1aee7YeolDyRXy6uSnj+qX2yQ9WeW9hpaQtKiFSVbU+wFq8hY6mPdoYwmQ45g4+NXz
 loVfGJQnRFqPzxmuDxaz99To1TfPRozQpYON9V7YJ6/PV5dWIC9TMw7S0mqeFhhFun
 GvxLe4ESrmE/CUfGeP3O+ovxPskRK0JqIAeaqlQWnLFsInR4ilS0b9pVC8f6Q7Xb+H
 3mZelBMWsAuyg==
From: Nikita Shubin via B4 Relay <devnull+n.shubin.yadro.com@kernel.org>
Date: Wed, 19 Mar 2025 10:57:54 +0300
Subject: [PATCH PoC 4/7] gpiodev: Add GPIO backend over chardev
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Message-Id: <20250319-gpiodev-v1-4-76da4e5800a1@yadro.com>
References: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
In-Reply-To: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
To: qemu-devel@nongnu.org
Cc: Linus Walleij <linus.walleij@linaro.org>,
 Bartosz Golaszewski <brgl@bgdev.pl>,
 "Enrico Weigelt, metux IT consult" <info@metux.net>,
 Viresh Kumar <vireshk@kernel.org>, Eric Blake <eblake@redhat.com>,
 Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>,
 Paolo Bonzini <pbonzini@redhat.com>,
 =?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,
 =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>,
 =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,
 Eduardo Habkost <eduardo@habkost.net>,
 =?utf-8?q?C=C3=A9dric_Le_Goater?= <clg@kaod.org>,
 Peter Maydell <peter.maydell@linaro.org>,
 Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>,
 Jamin Lin <jamin_lin@aspeedtech.com>,
 Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>,
 qemu-arm@nongnu.org, Nikita Shubin <nikita.shubin@maquefel.me>,
 Nikita Shubin <n.shubin@yadro.com>
X-Mailer: b4 0.14.2
X-Developer-Signature: v=1; a=ed25519-sha256; t=1742371072; l=15807;
 i=n.shubin@yadro.com; s=20230718; h=from:subject:message-id;
 bh=5SgTiI0ydbYQARFNJTO+9VhaH4GQhgG0X6FDQsOgxCo=;
 b=9UpynEdIOObCM9wieZowdBFdaPH5tLSl2huhFW0MjhsIL4vTt/GqRF9PZSLNt+7QTe7rBaPXK
 gqh9X9ChwduD8EL+yS8gtXzsMSASjOhgpMTc1jo44g41GzGMqEEDMAN
X-Developer-Key: i=n.shubin@yadro.com; a=ed25519;
 pk=vqf5YIUJ7BJv3EJFaNNxWZgGuMgDH6rwufTLflwU9ac=
X-Endpoint-Received: by B4 Relay for n.shubin@yadro.com/20230718 with
 auth_id=161
X-Original-From: Nikita Shubin <n.shubin@yadro.com>
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=172.105.4.254;
 envelope-from=devnull+n.shubin.yadro.com@kernel.org;
 helo=tor.source.kernel.org
X-Spam_score_int: -23
X-Spam_score: -2.4
X-Spam_bar: --
X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.332,
 DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=0.001,
 RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001,
 SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Mailman-Approved-At: Wed, 19 Mar 2025 08:49:30 -0400
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Reply-To: n.shubin@yadro.com
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @kernel.org)
X-ZM-MESSAGEID: 1742388849388019100

From: Nikita Shubin <n.shubin@yadro.com>

Currently based on passing linux GPIO UAPI structs over
UNIX socket.

This is more a PoC than a real application, still this is something to
start with.

Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
---
 gpiodev/gpio-chardev.c | 479 +++++++++++++++++++++++++++++++++++++++++++++=
++++
 gpiodev/meson.build    |   1 +
 include/gpiodev/gpio.h |   2 +
 3 files changed, 482 insertions(+)

diff --git a/gpiodev/gpio-chardev.c b/gpiodev/gpio-chardev.c
new file mode 100644
index 0000000000000000000000000000000000000000..5c2ae0373e6b447ea17ee08eacd=
cb12fbaa13a9c
--- /dev/null
+++ b/gpiodev/gpio-chardev.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * QEMU GPIO Chardev based backend.
+ *
+ * Author: 2025 Nikita Shubin <n.shubin@yadro.com>
+ *
+ */
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "gpiodev/gpio.h"
+#include "chardev/char.h"
+#include "chardev/char-fe.h"
+
+#include <linux/gpio.h>
+
+/* Taken from include/linux/circ_buf.h */
+
+/*
+ * Return count in buffer.
+ */
+#define CIRC_CNT(head, tail, size) (((head) - (tail)) & ((size) - 1))
+
+/*
+ * Return space available, 0..size-1.  We always leave one free char
+ * as a completely full buffer has head =3D=3D tail, which is the same as
+ * empty.
+ */
+#define CIRC_SPACE(head, tail, size) CIRC_CNT((tail), ((head) + 1), (size))
+
+/*
+ * Return count up to the end of the buffer.  Carefully avoid
+ * accessing head and tail more than once, so they can change
+ * underneath us without returning inconsistent results.
+ */
+#define CIRC_CNT_TO_END(head, tail, size) \
+        ({int end =3D (size) - (tail); \
+          int n =3D ((head) + end) & ((size) - 1); \
+          n < end ? n : end; })
+
+/*
+ * Return space available up to the end of the buffer.
+ */
+#define CIRC_SPACE_TO_END(head, tail, size) \
+        ({int end =3D (size) - 1 - (head); \
+          int n =3D (end + (tail)) & ((size) - 1); \
+          n <=3D end ? n : end + 1; })
+
+
+typedef struct ChardevGpiodev {
+    Gpiodev parent;
+
+    CharBackend chardev;
+    size_t size;
+    size_t prod;
+    size_t cons;
+    uint8_t *cbuf;
+
+    struct gpio_v2_line_request last_request;
+    uint64_t mask;
+} ChardevGpiodev;
+
+DECLARE_INSTANCE_CHECKER(ChardevGpiodev, GPIODEV_CHARDEV,
+                         TYPE_GPIODEV_CHARDEV)
+
+static void gpio_chardev_line_event(Gpiodev *g, uint32_t offset,
+                                    QEMUGpioLineEvent event)
+{
+    ChardevGpiodev *d =3D GPIODEV_CHARDEV(g);
+    struct gpio_v2_line_event changed =3D { 0 };
+    int ret;
+
+    changed.timestamp_ns =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    changed.id =3D event;
+    changed.offset =3D offset;
+
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)&changed, sizeof(cha=
nged));
+    if (ret !=3D sizeof(changed)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed writing %d bytes\n",
+                                       __func__, ret);
+    }
+}
+
+static void gpio_chardev_config_event(Gpiodev *g, uint32_t offset,
+                                      QEMUGpioConfigEvent event)
+{
+    ChardevGpiodev *d =3D GPIODEV_CHARDEV(g);
+    struct gpio_v2_line_info_changed changed =3D { 0 };
+    int ret;
+
+    changed.timestamp_ns =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    changed.event_type =3D event;
+    changed.info.offset =3D offset;
+
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)&changed, sizeof(cha=
nged));
+    if (ret !=3D sizeof(changed)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed writing %d bytes\n",
+                                       __func__, ret);
+    }
+}
+
+static int gpio_chardev_can_read(void *opaque)
+{
+    ChardevGpiodev *s =3D GPIODEV_CHARDEV(opaque);
+
+    return CIRC_SPACE(s->prod, s->cons, s->size);
+}
+
+static int gpio_chardev_send_chip_info(ChardevGpiodev *d)
+{
+    struct gpiochip_info info =3D { 0 };
+    int ret;
+
+    qemu_gpio_chip_info(&d->parent, &info.lines, info.name, info.label);
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)&info, sizeof(info));
+    if (ret !=3D sizeof(info)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: failed writing %d bytes\n",
+                      __func__, ret);
+    }
+
+    return 8;
+}
+
+static int gpio_chardev_unwatch_line(ChardevGpiodev *d, const uint8_t *buf,
+                                     size_t len)
+{
+    uint32_t offset;
+
+    memcpy(&offset, &buf[8], sizeof(offset));
+    qemu_gpio_clear_event_watch(&d->parent, offset, -1ULL);
+
+    return 8 + 4;
+}
+
+static int gpio_chardev_send_line_info(ChardevGpiodev *d, const uint8_t *b=
uf,
+                                       size_t len)
+{
+    struct gpio_v2_line_info info =3D { 0 };
+    gpio_line_info req =3D { 0 };
+    int ret;
+
+    if (len < sizeof(info) + 8) {
+        return -EAGAIN;
+    }
+
+    memcpy(&info, &buf[8], sizeof(info));
+    req.offset =3D info.offset;
+    qemu_gpio_line_info(&d->parent, &req);
+
+    g_strlcpy(info.name, req.name, GPIO_MAX_NAME_SIZE);
+    info.flags =3D req.flags;
+
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)&info, sizeof(info));
+    if (ret !=3D sizeof(info)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed writing %d bytes\n",
+                      __func__, ret);
+    }
+
+    return sizeof(info) + 8;
+}
+
+static int gpio_chardev_line_watch(ChardevGpiodev *d, const uint8_t *buf,
+                                   size_t len)
+{
+    struct gpio_v2_line_info info =3D { 0 };
+    int ret;
+
+    if (len < sizeof(info) + 8) {
+        return -EAGAIN;
+    }
+
+    memcpy(&info, &buf[8], sizeof(info));
+    qemu_gpio_add_config_watch(&d->parent, info.offset);
+
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)&info, sizeof(info));
+    if (ret !=3D sizeof(info)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed writing %d bytes\n",
+                      __func__, ret);
+    }
+
+    return sizeof(info) + 8;
+}
+
+static uint64_t gpio_chardev_get_flags(const struct gpio_v2_line_request *=
request)
+{
+    uint64_t req_flags =3D request->config.flags;
+    uint64_t flags =3D 0;
+
+    if (req_flags & GPIO_V2_LINE_FLAG_EDGE_RISING) {
+        flags |=3D GPIO_EVENT_RISING_EDGE;
+    }
+
+    if (req_flags & GPIO_V2_LINE_FLAG_EDGE_FALLING) {
+        flags |=3D GPIO_EVENT_FALLING_EDGE;
+    }
+
+    return flags;
+}
+
+static int gpio_chardev_line_request(ChardevGpiodev *d, const uint8_t *buf,
+                                     size_t len)
+{
+    struct gpio_v2_line_request *req =3D &d->last_request;
+    uint64_t flags;
+    int ret, i;
+
+    if (len < sizeof(*req) + 8) {
+        return -EAGAIN;
+    }
+
+    /* unwatch lines before proccessing new request */
+    d->mask =3D 0;
+    for (i =3D 0; i < req->num_lines; i++) {
+        qemu_gpio_clear_event_watch(&d->parent, req->offsets[i], -1ULL);
+    }
+
+    memcpy(req, &buf[8], sizeof(*req));
+    flags =3D gpio_chardev_get_flags(req);
+    for (i =3D 0; i < req->num_lines; i++) {
+        qemu_gpio_add_event_watch(&d->parent, req->offsets[i], flags);
+        d->mask |=3D BIT_ULL(req->offsets[i]);
+    }
+
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)req, sizeof(*req));
+    if (ret !=3D sizeof(*req)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed writing %d bytes\n",
+                      __func__, ret);
+    }
+
+    return sizeof(*req) + 8;
+}
+
+static int gpio_chardev_get_line_values(ChardevGpiodev *d, const uint8_t *=
buf,
+                                        size_t len)
+{
+    struct gpio_v2_line_request *req =3D &d->last_request;
+    struct gpio_v2_line_values values =3D { 0 };
+    int ret, idx;
+
+    if (len < (sizeof(values) + 8)) {
+        return -EAGAIN;
+    }
+
+    memcpy(&values, &buf[8], sizeof(values));
+    idx =3D find_first_bit((unsigned long *)&values.mask, req->num_lines);
+    while (idx < req->num_lines) {
+        values.bits |=3D qemu_gpio_get_line_value(&d->parent, req->offsets=
[idx]);
+        idx =3D find_next_bit((unsigned long *)&values.mask, req->num_line=
s, idx + 1);
+    }
+
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)&values, sizeof(valu=
es));
+    if (ret !=3D sizeof(values)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed writing %d bytes\n",
+                      __func__, ret);
+    }
+
+    return sizeof(values) + 8;
+}
+
+static int gpio_chardev_set_line_values(ChardevGpiodev *d, const uint8_t *=
buf,
+                                        size_t len)
+{
+    struct gpio_v2_line_request *req =3D &d->last_request;
+    struct gpio_v2_line_values values =3D { 0 };
+    int ret, idx;
+
+    if (len < sizeof(values) + 8) {
+        return -EAGAIN;
+    }
+
+    memcpy(&values, &buf[8], sizeof(values));
+    idx =3D find_first_bit((unsigned long *)&values.mask, req->num_lines);
+    while (idx < req->num_lines) {
+        qemu_gpio_set_line_value(&d->parent, req->offsets[idx],
+                                 test_bit(idx, (unsigned long *)&values.bi=
ts));
+        idx =3D find_next_bit((unsigned long *)&values.mask, req->num_line=
s, idx + 1);
+    }
+
+    ret =3D qemu_chr_fe_write(&d->chardev, (uint8_t *)&values, sizeof(valu=
es));
+    if (ret !=3D sizeof(values)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: failed writing %d bytes\n",
+                      __func__, ret);
+    }
+
+    return sizeof(values) + 8;
+}
+
+static int gpio_chardev_consume_one(ChardevGpiodev *d, const uint8_t *buf,
+                                    size_t len)
+{
+    unsigned long ctl;
+    int ret;
+
+    if (len < 8) {
+        return -EAGAIN;
+    }
+
+    memcpy(&ctl, buf, 8);
+    switch (ctl) {
+    case GPIO_GET_CHIPINFO_IOCTL:
+        ret =3D gpio_chardev_send_chip_info(d);
+        break;
+    case GPIO_GET_LINEINFO_UNWATCH_IOCTL:
+        ret =3D gpio_chardev_unwatch_line(d, buf, len);
+        break;
+    case GPIO_V2_GET_LINEINFO_IOCTL:
+        ret =3D gpio_chardev_send_line_info(d, buf, len);
+        break;
+    case GPIO_V2_GET_LINEINFO_WATCH_IOCTL:
+        ret =3D gpio_chardev_line_watch(d, buf, len);
+        break;
+    case GPIO_V2_GET_LINE_IOCTL:
+        ret =3D gpio_chardev_line_request(d, buf, len);
+        break;
+    case GPIO_V2_LINE_GET_VALUES_IOCTL:
+        ret =3D gpio_chardev_get_line_values(d, buf, len);
+        break;
+    case GPIO_V2_LINE_SET_VALUES_IOCTL:
+        ret =3D gpio_chardev_set_line_values(d, buf, len);
+        break;
+    case GPIO_V2_LINE_SET_CONFIG_IOCTL:
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: unknow ctl=3D%lx\n",
+                        __func__, ctl);
+        return -EINVAL;
+    };
+
+    return ret;
+}
+
+static void gpio_chardev_consume(ChardevGpiodev *d, size_t len)
+{
+    g_autofree guint8 *buf;
+    size_t t_cons =3D d->cons;
+    int i, ret, pos =3D 0;
+    size_t left =3D len;
+
+    buf =3D g_malloc(len);
+
+    for (i =3D 0; i < len && t_cons !=3D d->prod; i++) {
+        buf[i] =3D d->cbuf[t_cons++ & (d->size - 1)];
+    }
+
+    do {
+        ret =3D gpio_chardev_consume_one(d, &buf[pos], left);
+        if (ret > 0) {
+            left -=3D ret;
+            pos +=3D ret;
+        }
+    } while (ret > 0);
+
+    /* advance */
+    d->cons +=3D pos;
+    qemu_chr_fe_accept_input(&d->chardev);
+}
+
+static void gpio_chardev_read(void *opaque, const uint8_t *buf, int len)
+{
+    ChardevGpiodev *d =3D GPIODEV_CHARDEV(opaque);
+    int i;
+
+    if (!buf || (len < 0)) {
+        return;
+    }
+
+    for (i =3D 0; i < len; i++) {
+        d->cbuf[d->prod++ & (d->size - 1)] =3D buf[i];
+        if (d->prod - d->cons > d->size) {
+            d->cons =3D d->prod - d->size;
+        }
+    }
+
+    gpio_chardev_consume(d, CIRC_CNT(d->prod, d->cons, d->size));
+}
+
+static void gpio_chardev_event(void *opaque, QEMUChrEvent event)
+{
+    ChardevGpiodev *d =3D GPIODEV_CHARDEV(opaque);
+
+    if (event =3D=3D CHR_EVENT_OPENED) {
+        d->prod =3D 0;
+        d->cons =3D 0;
+
+        /* remove watches */
+        qemu_gpio_clear_watches(&d->parent);
+    }
+}
+
+static void gpio_chardev_open(Gpiodev *gpio, GpiodevBackend *backend,
+                              Error **errp)
+{
+    GpiodevChardev *opts =3D backend->u.chardev.data;
+    ChardevGpiodev *d =3D GPIODEV_CHARDEV(gpio);
+    Object *chr_backend;
+    Chardev *chr =3D NULL;
+
+    d->size =3D opts->has_size ? opts->size : 65536;
+
+    /* The size must be power of 2 */
+    if (d->size & (d->size - 1)) {
+        error_setg(errp, "size of ringbuf chardev must be power of two");
+        return;
+    }
+
+    chr_backend =3D object_resolve_path_type(opts->chardev, TYPE_CHARDEV, =
NULL);
+    if (chr_backend) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: %s got backend\n",
+                      __func__, opts->chardev);
+        chr =3D qemu_chr_find(opts->chardev);
+    }
+
+    if (!chr) {
+        error_setg(errp, "gpiodev: chardev: no chardev %s not found", opts=
->chardev);
+        return;
+    }
+
+    d->cbuf =3D g_malloc0(d->size);
+
+    qemu_chr_fe_init(&d->chardev, chr, NULL);
+    qemu_chr_fe_set_handlers(&d->chardev,
+                             gpio_chardev_can_read,
+                             gpio_chardev_read,
+                             gpio_chardev_event, NULL, d, NULL, true);
+}
+
+static void gpio_chardev_parse(QemuOpts *opts, GpiodevBackend *backend,
+                               Error **errp)
+{
+    const char *chardev =3D qemu_opt_get(opts, "chardev");
+    GpiodevChardev *gchardev;
+    int val;
+
+    if (chardev =3D=3D NULL) {
+        error_setg(errp, "gpiodev: chardev: no chardev id given");
+        return;
+    }
+
+    backend->type =3D GPIODEV_BACKEND_KIND_CHARDEV;
+    gchardev =3D backend->u.chardev.data =3D g_new0(GpiodevChardev, 1);
+    val =3D qemu_opt_get_size(opts, "size", 0);
+    if (val !=3D 0) {
+        gchardev->has_size =3D true;
+        gchardev->size =3D val;
+    }
+    gchardev->chardev =3D g_strdup(chardev);
+}
+
+static void gpio_chardev_finalize(Object *obj)
+{
+    ChardevGpiodev *d =3D GPIODEV_CHARDEV(obj);
+
+    g_free(d->cbuf);
+}
+
+static void gpio_chardev_class_init(ObjectClass *oc, void *data)
+{
+    GpiodevClass *cc =3D GPIODEV_CLASS(oc);
+
+    cc->parse =3D &gpio_chardev_parse;
+    cc->open =3D &gpio_chardev_open;
+    cc->line_event =3D &gpio_chardev_line_event;
+    cc->config_event =3D &gpio_chardev_config_event;
+}
+
+static const TypeInfo gpio_chardev_type_info[] =3D {
+    {
+        .name =3D TYPE_GPIODEV_CHARDEV,
+        .parent =3D TYPE_GPIODEV,
+        .class_init =3D gpio_chardev_class_init,
+        .instance_size =3D sizeof(ChardevGpiodev),
+        .instance_finalize =3D gpio_chardev_finalize,
+    },
+};
+
+DEFINE_TYPES(gpio_chardev_type_info);
+
diff --git a/gpiodev/meson.build b/gpiodev/meson.build
index 0c7e457a11a6b01ec675006ae11ce0c50356407e..64d3abb4e3d72cba0c26b665515=
a0f97e82fb5d9 100644
--- a/gpiodev/meson.build
+++ b/gpiodev/meson.build
@@ -1,4 +1,5 @@
 gpiodev_ss.add(files(
+  'gpio-chardev.c',
   'gpio-fe.c',
   'gpio.c',
 ))
diff --git a/include/gpiodev/gpio.h b/include/gpiodev/gpio.h
index 2ea6c0e6af8125caacc944cdddca94b1bca8c4ff..a34d805ccc0bf5a25986b118dcc=
0b2cc0a55572c 100644
--- a/include/gpiodev/gpio.h
+++ b/include/gpiodev/gpio.h
@@ -55,6 +55,8 @@ struct Gpiodev {
 #define TYPE_GPIODEV "gpiodev"
 OBJECT_DECLARE_TYPE(Gpiodev, GpiodevClass, GPIODEV)
=20
+#define TYPE_GPIODEV_CHARDEV "gpiodev-chardev"
+
 struct GpiodevClass {
     ObjectClass parent_class;
=20

--=20
2.45.2
From nobody Fri Apr  4 13:35:12 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=quarantine dis=none)  header.from=kernel.org
ARC-Seal: i=1; a=rsa-sha256; t=1742388825; cv=none;
	d=zohomail.com; s=zohoarc;
	b=PqqrDSTH173pNYyeqR9AvbzgxmnjAX+x/fc+DrL9EImc4Xdcdm13dTC7TJ6fAf5KoDhDKhxva1gevDjV5sGtISoNA1CCQP9hhSaREJSAtQ7YOicEDQgMOgLWxJX0ecWsO6qWsjM6KZXZ2DgkqiVS2tu5FZ1lCpSnt3BF1wpk1XM=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1742388825;
 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:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id;
	bh=IZ+48d2orLFzO9JlR3aBhvRp8i2Wff1k+rqPsGV2ubQ=;
	b=SER4XQRNYZRfV8jtP5d9Ci/cXrtvdouIiS+BFiyUFD/Cuot02zOPO/SwURj3ZUAJRGYVxkheM4qpoClG/REGqqy+DGhQkN0HROVzgKxnScLCsftEGWjRpsUetiSX+xyZ9pk4hhuXGxodVwp3hsj++hSE/+Kg00Tpu2Xt2TS5VpM=
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=<devnull+n.shubin.yadro.com@kernel.org> (p=quarantine
 dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 1742388825666261.3225775177425;
 Wed, 19 Mar 2025 05:53:45 -0700 (PDT)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1tusrf-0000Pw-RE; Wed, 19 Mar 2025 08:49:43 -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 <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJT-0006G2-Sb; Wed, 19 Mar 2025 03:58:08 -0400
Received: from nyc.source.kernel.org ([2604:1380:45d1:ec00::3])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJQ-0003df-Bw; Wed, 19 Mar 2025 03:58:07 -0400
Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])
 by nyc.source.kernel.org (Postfix) with ESMTP id C2486A48FE1;
 Wed, 19 Mar 2025 07:52:25 +0000 (UTC)
Received: by smtp.kernel.org (Postfix) with ESMTPS id CBBA9C4CEF6;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org
 (localhost.localdomain [127.0.0.1])
 by smtp.lore.kernel.org (Postfix) with ESMTP id BC5E4C35FFA;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;
 s=k20201202; t=1742371074;
 bh=/FXbuVk2kxneP1NDtfBB0y/MWnfDsP3dDD2KxydWs9E=;
 h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;
 b=ASjpQQU8NZQ54aoHigkVzhqQwLHZzgh5/W9Yfa+9+Pd+jpmKqUW0D7TD6BlQY+7BN
 u2wPdVNOmB3KeYdiqS//dY9qDN7rL/ZyX3WWL0XZSI1lM+yJmP2hVlogV2NZL2otKG
 uM9WtyyeHRcO6hVsT7YMjWHtRrBb/rseT4075OKfEub9LtMKi88sGIstZmG621PZDr
 M6+aHuEoFYZR5JEywwiFaejmVIat87fBasovE7sDFKDDJUFQGmd3KHQtQBKKZOU8Wo
 pwn+kWc5Gnzrh39f4C9CkiCxejfWRc15IRlM5jPKXBIZC2C/AT5IUU6qnAxp8oBqWA
 ePLfzV7W28zgg==
From: Nikita Shubin via B4 Relay <devnull+n.shubin.yadro.com@kernel.org>
Date: Wed, 19 Mar 2025 10:57:55 +0300
Subject: [PATCH PoC 5/7] hw/gpio/aspeed: Add gpiodev support
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Message-Id: <20250319-gpiodev-v1-5-76da4e5800a1@yadro.com>
References: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
In-Reply-To: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
To: qemu-devel@nongnu.org
Cc: Linus Walleij <linus.walleij@linaro.org>,
 Bartosz Golaszewski <brgl@bgdev.pl>,
 "Enrico Weigelt, metux IT consult" <info@metux.net>,
 Viresh Kumar <vireshk@kernel.org>, Eric Blake <eblake@redhat.com>,
 Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>,
 Paolo Bonzini <pbonzini@redhat.com>,
 =?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,
 =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>,
 =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,
 Eduardo Habkost <eduardo@habkost.net>,
 =?utf-8?q?C=C3=A9dric_Le_Goater?= <clg@kaod.org>,
 Peter Maydell <peter.maydell@linaro.org>,
 Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>,
 Jamin Lin <jamin_lin@aspeedtech.com>,
 Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>,
 qemu-arm@nongnu.org, Nikita Shubin <nikita.shubin@maquefel.me>,
 Nikita Shubin <n.shubin@yadro.com>
X-Mailer: b4 0.14.2
X-Developer-Signature: v=1; a=ed25519-sha256; t=1742371072; l=9057;
 i=n.shubin@yadro.com; s=20230718; h=from:subject:message-id;
 bh=LCxwVdVyHaytyw7TlHjgwh2ubyN+clkLCBd91HD+W6s=;
 b=QY/eH0K/w+TXRSupwDbLRlk8MNe53AnT+V9sWX4EEshbkn/JpR8nRJRRsJ7onmtw1mKnV0NPj
 /slWcVxjxBNDDCSgSIZCjkWz8kTwTdGE+mMjMqeXGuDuqvQhaTvi5lp
X-Developer-Key: i=n.shubin@yadro.com; a=ed25519;
 pk=vqf5YIUJ7BJv3EJFaNNxWZgGuMgDH6rwufTLflwU9ac=
X-Endpoint-Received: by B4 Relay for n.shubin@yadro.com/20230718 with
 auth_id=161
X-Original-From: Nikita Shubin <n.shubin@yadro.com>
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=2604:1380:45d1:ec00::3;
 envelope-from=devnull+n.shubin.yadro.com@kernel.org;
 helo=nyc.source.kernel.org
X-Spam_score_int: -23
X-Spam_score: -2.4
X-Spam_bar: --
X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.332,
 DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 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-Mailman-Approved-At: Wed, 19 Mar 2025 08:49:30 -0400
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Reply-To: n.shubin@yadro.com
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @kernel.org)
X-ZM-MESSAGEID: 1742388827248019100

From: Nikita Shubin <n.shubin@yadro.com>

Add gpiodev support for:

- getting line info
- getting/setting lines
- monitoring line events
- monitoting config events

Binding is done via id, i.e.:

... -gpiodev chardev,id=3Daspeed-gpio0

Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
---
 hw/gpio/aspeed_gpio.c         | 127 ++++++++++++++++++++++++++++++++++++++=
++++
 include/hw/gpio/aspeed_gpio.h |   3 +
 2 files changed, 130 insertions(+)

diff --git a/hw/gpio/aspeed_gpio.c b/hw/gpio/aspeed_gpio.c
index a5b3f454e800dc9440600562b6295b069cb536fb..0c8b27c9a0cbef313254405dde3=
f1e1910f9bf57 100644
--- a/hw/gpio/aspeed_gpio.c
+++ b/hw/gpio/aspeed_gpio.c
@@ -291,6 +291,8 @@ static int aspeed_evaluate_irq(GPIOSets *regs, int gpio=
_prev_high, int gpio)
     return 0;
 }
=20
+static void aspeed_gpio_line_event(AspeedGPIOState *s, uint32_t set_idx, u=
int32_t pin_idx);
+
 #define nested_struct_index(ta, pa, m, tb, pb) \
         (pb - ((tb *)(((char *)pa) + offsetof(ta, m))))
=20
@@ -337,6 +339,9 @@ static void aspeed_gpio_update(AspeedGPIOState *s, GPIO=
Sets *regs,
                 /* ...trigger the line-state IRQ */
                 ptrdiff_t set =3D aspeed_gpio_set_idx(s, regs);
                 qemu_set_irq(s->gpios[set][gpio], !!(new & mask));
+
+                /* ...notify gpio backend if any */
+                aspeed_gpio_line_event(s, set, gpio);
             } else {
                 /* ...otherwise if we meet the line's current IRQ policy..=
. */
                 if (aspeed_evaluate_irq(regs, old & mask, gpio)) {
@@ -360,6 +365,18 @@ static bool aspeed_gpio_get_pin_level(AspeedGPIOState =
*s, uint32_t set_idx,
     return !!(reg_val & pin_mask);
 }
=20
+static void aspeed_gpio_line_event(AspeedGPIOState *s, uint32_t set_idx, u=
int32_t pin_idx)
+{
+    uint32_t offset =3D set_idx * ASPEED_GPIOS_PER_SET + pin_idx;
+    QEMUGpioLineEvent event =3D GPIO_EVENT_FALLING_EDGE;
+
+    if (aspeed_gpio_get_pin_level(s, set_idx, pin_idx)) {
+        event =3D GPIO_EVENT_RISING_EDGE;
+    }
+
+    qemu_gpio_fe_line_event(&s->gpiodev, offset, event);
+}
+
 static void aspeed_gpio_set_pin_level(AspeedGPIOState *s, uint32_t set_idx,
                                       uint32_t pin, bool level)
 {
@@ -659,6 +676,13 @@ static uint64_t aspeed_gpio_read(void *opaque, hwaddr =
offset, uint32_t size)
     return value;
 }
=20
+static void aspeed_gpio_config_event(AspeedGPIOState *s, uint32_t set_idx,=
 uint32_t pin_idx)
+{
+    uint32_t offset =3D set_idx * ASPEED_GPIOS_PER_SET + pin_idx;
+
+    qemu_gpio_fe_config_event(&s->gpiodev, offset, GPIO_LINE_CHANGED_CONFI=
G);
+}
+
 static void aspeed_gpio_write_index_mode(void *opaque, hwaddr offset,
                                                 uint64_t data, uint32_t si=
ze)
 {
@@ -674,6 +698,7 @@ static void aspeed_gpio_write_index_mode(void *opaque, =
hwaddr offset,
     uint32_t group_idx =3D pin_idx / GPIOS_PER_GROUP;
     uint32_t reg_value =3D 0;
     uint32_t pending =3D 0;
+    uint32_t old_direction;
=20
     set =3D &s->sets[set_idx];
     props =3D &agc->props[set_idx];
@@ -711,8 +736,12 @@ static void aspeed_gpio_write_index_mode(void *opaque,=
 hwaddr offset,
          *  data =3D ( data | ~input) & output;
          */
         reg_value =3D (reg_value | ~props->input) & props->output;
+        old_direction =3D set->direction;
         set->direction =3D update_value_control_source(set, set->direction,
                                                      reg_value);
+        if (set->direction !=3D old_direction) {
+            aspeed_gpio_config_event(s, set_idx, pin_idx);
+        }
         break;
     case gpio_reg_idx_interrupt:
         reg_value =3D set->int_enable;
@@ -813,6 +842,7 @@ static void aspeed_gpio_write(void *opaque, hwaddr offs=
et, uint64_t data,
     const AspeedGPIOReg *reg;
     GPIOSets *set;
     uint32_t cleared;
+    uint32_t old_direction;
=20
     trace_aspeed_gpio_write(offset, data);
=20
@@ -867,7 +897,17 @@ static void aspeed_gpio_write(void *opaque, hwaddr off=
set, uint64_t data,
          *  data =3D ( data | ~input) & output;
          */
         data =3D (data | ~props->input) & props->output;
+        old_direction =3D set->direction;
         set->direction =3D update_value_control_source(set, set->direction=
, data);
+        qemu_log("gpio_reg_direction: 0x%x 0x%x\n", set->direction, old_di=
rection);
+        if (set->direction !=3D old_direction) {
+            unsigned long changed =3D set->direction ^ old_direction;
+            int idx =3D find_first_bit(&changed, ASPEED_GPIOS_PER_SET);
+            while (idx < ASPEED_GPIOS_PER_SET) {
+                aspeed_gpio_config_event(s, reg->set_idx, idx);
+                idx =3D find_next_bit(&changed, ASPEED_GPIOS_PER_SET, idx =
+ 1);
+            }
+        }
         break;
     case gpio_reg_int_enable:
         set->int_enable =3D update_value_control_source(set, set->int_enab=
le,
@@ -1392,11 +1432,85 @@ static void aspeed_gpio_reset(DeviceState *dev)
     memset(s->sets, 0, sizeof(s->sets));
 }
=20
+static void aspeed_gpio_line_info(void *opaque, gpio_line_info *info)
+{
+    AspeedGPIOState *s =3D ASPEED_GPIO(opaque);
+    AspeedGPIOClass *agc =3D ASPEED_GPIO_GET_CLASS(s);
+    uint32_t group_idx =3D 0, pin_idx =3D 0, idx =3D 0;
+    uint32_t offset =3D info->offset;
+    const GPIOSetProperties *props;
+    bool direction;
+    const char *group;
+    int i, set_idx, grp_idx, pin;
+
+    for (i =3D 0; i < ASPEED_GPIO_MAX_NR_SETS; i++) {
+        props =3D &agc->props[i];
+        uint32_t skip =3D ~(props->input | props->output);
+        for (int j =3D 0; j < ASPEED_GPIOS_PER_SET; j++) {
+            if (skip >> j & 1) {
+                continue;
+            }
+
+            group_idx =3D j / GPIOS_PER_GROUP;
+            pin_idx =3D j % GPIOS_PER_GROUP;
+            if (idx =3D=3D offset) {
+                goto found;
+            }
+
+            idx++;
+        }
+    }
+
+    return;
+
+found:
+    group =3D &props->group_label[group_idx][0];
+    set_idx =3D get_set_idx(s, group, &grp_idx);
+    snprintf(info->name, sizeof(info->name), "gpio%s%d", group, pin_idx);
+    pin =3D  pin_idx + group_idx * GPIOS_PER_GROUP;
+    direction =3D !!(s->sets[set_idx].direction & BIT_ULL(pin));
+
+    if (direction) {
+        info->flags |=3D GPIO_LINE_FLAG_OUTPUT;
+    } else {
+        info->flags |=3D GPIO_LINE_FLAG_INPUT;
+    }
+
+    qemu_log("%u: %s: set_idx=3D%d, grp_idx=3D%d, group_idx=3D%u, pin_idx=
=3D%u\n", offset, info->name, set_idx, grp_idx, group_idx, pin_idx);
+}
+
+static int aspeed_gpio_get_line(void *opaque, uint32_t offset)
+{
+    AspeedGPIOState *s =3D ASPEED_GPIO(opaque);
+    int set_idx, pin_idx;
+
+    set_idx =3D offset / ASPEED_GPIOS_PER_SET;
+    pin_idx =3D offset % ASPEED_GPIOS_PER_SET;
+
+    return aspeed_gpio_get_pin_level(s, set_idx, pin_idx);
+}
+
+static int aspeed_gpio_set_line(void *opaque, uint32_t offset, uint8_t val=
ue)
+{
+    AspeedGPIOState *s =3D ASPEED_GPIO(opaque);
+    int set_idx, pin_idx;
+
+    set_idx =3D offset / ASPEED_GPIOS_PER_SET;
+    pin_idx =3D offset % ASPEED_GPIOS_PER_SET;
+
+    aspeed_gpio_set_pin_level(s, set_idx, pin_idx, value);
+
+    return 0;
+}
+
 static void aspeed_gpio_realize(DeviceState *dev, Error **errp)
 {
     AspeedGPIOState *s =3D ASPEED_GPIO(dev);
     SysBusDevice *sbd =3D SYS_BUS_DEVICE(dev);
     AspeedGPIOClass *agc =3D ASPEED_GPIO_GET_CLASS(s);
+    DeviceState *d =3D DEVICE(s);
+    Object *backend;
+    Gpiodev *gpio;
=20
     /* Interrupt parent line */
     sysbus_init_irq(sbd, &s->irq);
@@ -1417,6 +1531,19 @@ static void aspeed_gpio_realize(DeviceState *dev, Er=
ror **errp)
                           TYPE_ASPEED_GPIO, agc->mem_size);
=20
     sysbus_init_mmio(sbd, &s->iomem);
+
+    /* NOTE: or we can create one per set */
+    if (d->id) {
+        backend =3D object_resolve_path_type(d->id, TYPE_GPIODEV, NULL);
+        if (backend) {
+            gpio =3D GPIODEV(backend);
+            qemu_gpio_fe_init(&s->gpiodev, gpio, agc->nr_gpio_pins, d->id,
+                              "ASPEED GPIO", NULL);
+            qemu_gpio_fe_set_handlers(&s->gpiodev, aspeed_gpio_line_info,
+                                      aspeed_gpio_get_line,
+                                      aspeed_gpio_set_line, s);
+        }
+    }
 }
=20
 static void aspeed_gpio_init(Object *obj)
diff --git a/include/hw/gpio/aspeed_gpio.h b/include/hw/gpio/aspeed_gpio.h
index e1e6c543339ef9ddff99124d7a267e9c8544c556..f0a1207b24421f4129182fbcbb8=
8650604313a6c 100644
--- a/include/hw/gpio/aspeed_gpio.h
+++ b/include/hw/gpio/aspeed_gpio.h
@@ -12,6 +12,8 @@
=20
 #include "hw/sysbus.h"
 #include "qom/object.h"
+#include "gpiodev/gpio.h"
+#include "gpiodev/gpio-fe.h"
=20
 #define TYPE_ASPEED_GPIO "aspeed.gpio"
 OBJECT_DECLARE_TYPE(AspeedGPIOState, AspeedGPIOClass, ASPEED_GPIO)
@@ -85,6 +87,7 @@ struct AspeedGPIOState {
     SysBusDevice parent;
=20
     /*< public >*/
+    GpioBackend gpiodev;
     MemoryRegion iomem;
     int pending;
     qemu_irq irq;

--=20
2.45.2
From nobody Fri Apr  4 13:35:12 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=quarantine dis=none)  header.from=kernel.org
ARC-Seal: i=1; a=rsa-sha256; t=1742388704; cv=none;
	d=zohomail.com; s=zohoarc;
	b=n4VHdVT4aCKGiIx2pJrGZGeptwlxN/D56cQo11m5HPvrZO0vytXNgkVBNwu65lhLcS8zi0ovocnwNWSRIBWhO1+Kd8jIw9Q594pVrJyLCQ5klLrVOKmQAa+VwulO9d+n0qUlIx3MpYlU4JoHZe48FyFhVr5g1Jn+FVKMuALn7eE=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1742388704;
 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:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id;
	bh=2S+gCr/ZmS/2XzcQj+fkib4he/2WAdCRi6VEw360IkA=;
	b=fOyY1PUoZ0UZtiSzuduePAZhuO1Evwzuo2VK+EuCaN4lLpghbKCS3L0rSb4cm1o1hClw5QtlVnRv8pBbELAb7F7bcu0AzvQYM0/pHrjKnKJ5j+pQycD2Du2zA4G1u/H09GcXIpxVjg9+y6bBj3dX0XF6BTHQtjtdLviw4M8PI6Y=
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=<devnull+n.shubin.yadro.com@kernel.org> (p=quarantine
 dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 174238870453885.04468088054318;
 Wed, 19 Mar 2025 05:51:44 -0700 (PDT)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1tusrv-0000zF-H6; Wed, 19 Mar 2025 08:49:59 -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 <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJS-0006Fc-1x; Wed, 19 Mar 2025 03:58:06 -0400
Received: from nyc.source.kernel.org ([147.75.193.91])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJQ-0003dj-9l; Wed, 19 Mar 2025 03:58:05 -0400
Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])
 by nyc.source.kernel.org (Postfix) with ESMTP id C644BA48FE3;
 Wed, 19 Mar 2025 07:52:25 +0000 (UTC)
Received: by smtp.kernel.org (Postfix) with ESMTPS id E4FC3C4CEEF;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org
 (localhost.localdomain [127.0.0.1])
 by smtp.lore.kernel.org (Postfix) with ESMTP id DA704C35FFF;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;
 s=k20201202; t=1742371074;
 bh=V6JyXTsCHqBhWu72poz6myByuKukdL7x3liRuKYDlkI=;
 h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;
 b=fM4QTAfUudNUeLqravY9OudThnNDj5Y408XmDnZ96Li8z7gAQxBJvAsfHZ1+v77kV
 WWFVEW+MrjDbHXgRRpvI8xU5mh7358X+Fjr9RORhwsM/ffGp3AaqB1KTgU+b62rhG0
 mdSfk0FFv30jeQOb1JO9gN+3LfPBTBStzu/vU9IgobtkoWvrvA+rVOWBHQWYYm1Jog
 CNsf0sbtmCohiL5uoCMGM4xJjSiJecWHqyN+a21dMfc9JrTnjqbX7EXKAA2lPo7prW
 GWijFNrPwLhQVXuFK9azlmkZQ0O/1Q4RxUW//NGnEpbluRePVAce7XYdu4yPr/zhry
 kaE/hsH1J/H8g==
From: Nikita Shubin via B4 Relay <devnull+n.shubin.yadro.com@kernel.org>
Date: Wed, 19 Mar 2025 10:57:56 +0300
Subject: [PATCH PoC 6/7] hw/arm: ast2600: set id for gpio
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Message-Id: <20250319-gpiodev-v1-6-76da4e5800a1@yadro.com>
References: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
In-Reply-To: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
To: qemu-devel@nongnu.org
Cc: Linus Walleij <linus.walleij@linaro.org>,
 Bartosz Golaszewski <brgl@bgdev.pl>,
 "Enrico Weigelt, metux IT consult" <info@metux.net>,
 Viresh Kumar <vireshk@kernel.org>, Eric Blake <eblake@redhat.com>,
 Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>,
 Paolo Bonzini <pbonzini@redhat.com>,
 =?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,
 =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>,
 =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,
 Eduardo Habkost <eduardo@habkost.net>,
 =?utf-8?q?C=C3=A9dric_Le_Goater?= <clg@kaod.org>,
 Peter Maydell <peter.maydell@linaro.org>,
 Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>,
 Jamin Lin <jamin_lin@aspeedtech.com>,
 Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>,
 qemu-arm@nongnu.org, Nikita Shubin <nikita.shubin@maquefel.me>,
 Nikita Shubin <n.shubin@yadro.com>
X-Mailer: b4 0.14.2
X-Developer-Signature: v=1; a=ed25519-sha256; t=1742371072; l=1103;
 i=n.shubin@yadro.com; s=20230718; h=from:subject:message-id;
 bh=ZOgjocFiFStuSlL6CpsC0EWN3qypvGoWvCiDbIQ78mk=;
 b=Vwy8tuQYViL0KS5HXziEaSyE+3uqL/5Fka7z5rVZNFPHizyVXIDmwheLuuQutLX2PZ8CSbnH6
 1AhK/AFXhUwAtkeiNPV5mI1KLQSGIvJuoKE1/pPkQS68OIWlqJNmkPn
X-Developer-Key: i=n.shubin@yadro.com; a=ed25519;
 pk=vqf5YIUJ7BJv3EJFaNNxWZgGuMgDH6rwufTLflwU9ac=
X-Endpoint-Received: by B4 Relay for n.shubin@yadro.com/20230718 with
 auth_id=161
X-Original-From: Nikita Shubin <n.shubin@yadro.com>
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=147.75.193.91;
 envelope-from=devnull+n.shubin.yadro.com@kernel.org;
 helo=nyc.source.kernel.org
X-Spam_score_int: -46
X-Spam_score: -4.7
X-Spam_bar: ----
X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.332,
 DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=0.001,
 RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, SPF_HELO_NONE=0.001,
 SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Mailman-Approved-At: Wed, 19 Mar 2025 08:49:27 -0400
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Reply-To: n.shubin@yadro.com
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @kernel.org)
X-ZM-MESSAGEID: 1742388706858019100

From: Nikita Shubin <n.shubin@yadro.com>

Set device id for gpios to allow gpiodev binding.

Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
---
 hw/arm/aspeed_ast2600.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c
index 07210483bb29a50824c8312021bebf1ca32cac95..09ce944d6263d810db650a1e566=
c3f3b1701702d 100644
--- a/hw/arm/aspeed_ast2600.c
+++ b/hw/arm/aspeed_ast2600.c
@@ -537,6 +537,7 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev=
, Error **errp)
                        aspeed_soc_get_irq(s, ASPEED_DEV_XDMA));
=20
     /* GPIO */
+    DEVICE(&s->gpio)->id =3D g_strdup("aspeed-gpio0");
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio), errp)) {
         return;
     }
@@ -545,6 +546,7 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev=
, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio), 0,
                        aspeed_soc_get_irq(s, ASPEED_DEV_GPIO));
=20
+    DEVICE(&s->gpio_1_8v)->id =3D g_strdup("aspeed-gpio1");
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio_1_8v), errp)) {
         return;
     }

--=20
2.45.2
From nobody Fri Apr  4 13:35:12 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=quarantine dis=none)  header.from=kernel.org
ARC-Seal: i=1; a=rsa-sha256; t=1742388661; cv=none;
	d=zohomail.com; s=zohoarc;
	b=cX+Aj3DLpaY+I9RvzC3VWogtX2BxWKBAnylOL12pcmScUszndTNZaWNHwBkAjep2+B6uPSJkjJfv+y1wi+oa8eSn/8D8SYzrgryQzcKU4hhhKqX7s5Ei3Brnr2u13iOVP8kCgJjuUNCLgdl0pzsRqo9emT/bk6oMs45JoQV3Olg=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1742388661;
 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:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id;
	bh=f1X8jITHWctcgvWN80Sm55L9R1MQ36hkCrRktwgLxIE=;
	b=AZsiP4vMC9/YNgeVlzRVteBGhb6joC+BjnakQRRQkhAXHDI31FlccDMv0q7inEav0FXWsfc9PlRO9xqB8+dVY5K5fKBtybokegxZOHFQF0PhVGKZ4GSgLLwXdenmDi5iKwZ6EgmpHWJmTxfiIF+8bZMzjHSL4T/5kATNRjC0zsc=
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=<devnull+n.shubin.yadro.com@kernel.org> (p=quarantine
 dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 1742388661950285.2096247405053;
 Wed, 19 Mar 2025 05:51:01 -0700 (PDT)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1tusrz-000144-MC; Wed, 19 Mar 2025 08:50:04 -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 <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJW-0006Hy-Iz; Wed, 19 Mar 2025 03:58:10 -0400
Received: from tor.source.kernel.org ([2600:3c04:e001:324:0:1991:8:25])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <devnull+n.shubin.yadro.com@kernel.org>)
 id 1tuoJQ-0003dp-Cr; Wed, 19 Mar 2025 03:58:10 -0400
Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58])
 by tor.source.kernel.org (Postfix) with ESMTP id 2AC2C61578;
 Wed, 19 Mar 2025 07:57:54 +0000 (UTC)
Received: by smtp.kernel.org (Postfix) with ESMTPS id 108AFC4CEF5;
 Wed, 19 Mar 2025 07:57:55 +0000 (UTC)
Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org
 (localhost.localdomain [127.0.0.1])
 by smtp.lore.kernel.org (Postfix) with ESMTP id 01DB0C35FFA;
 Wed, 19 Mar 2025 07:57:55 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;
 s=k20201202; t=1742371075;
 bh=f4uF4jHe9b4+XSpkKfroBla+dWWoxruj2mXX8ewLBAU=;
 h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;
 b=VSAkBZJ14KunM0IoYSBpDtRmX17dNO+83f4XAE3uaCI8uBXSjm6AvXIxMtVRshIhA
 eu3EZx+nuSocwWokGdhBloiXF50LGRNBH0NeNu7Y+NIc1vBRPSBdZm3iINresMFHhH
 mjOINV6IWtBmqy7syHV/C5sl2d6GJ9pzxFdq60RaLADJxWmyhRrqWYlOMcWl2rCFSZ
 S+biCMfVudg61h9yocnPemWBcIq8qxQeS00hrUQwZnZeqOpEXvHSO1cxF6zrwTfvG6
 7OJ8iFYgSMekV5v00tnUNMIjKWw46BCGq/mzktMJ1+TsMYQnx1ZcZUamWrcI9Wg5v3
 B8YBf5gcuS+wg==
From: Nikita Shubin via B4 Relay <devnull+n.shubin.yadro.com@kernel.org>
Date: Wed, 19 Mar 2025 10:57:57 +0300
Subject: [PATCH PoC 7/7] gpiodev: Add gpiobackend over GUSE
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Message-Id: <20250319-gpiodev-v1-7-76da4e5800a1@yadro.com>
References: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
In-Reply-To: <20250319-gpiodev-v1-0-76da4e5800a1@yadro.com>
To: qemu-devel@nongnu.org
Cc: Linus Walleij <linus.walleij@linaro.org>,
 Bartosz Golaszewski <brgl@bgdev.pl>,
 "Enrico Weigelt, metux IT consult" <info@metux.net>,
 Viresh Kumar <vireshk@kernel.org>, Eric Blake <eblake@redhat.com>,
 Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>,
 Paolo Bonzini <pbonzini@redhat.com>,
 =?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,
 =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>,
 =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,
 Eduardo Habkost <eduardo@habkost.net>,
 =?utf-8?q?C=C3=A9dric_Le_Goater?= <clg@kaod.org>,
 Peter Maydell <peter.maydell@linaro.org>,
 Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>,
 Jamin Lin <jamin_lin@aspeedtech.com>,
 Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>,
 qemu-arm@nongnu.org, Nikita Shubin <nikita.shubin@maquefel.me>,
 Nikita Shubin <n.shubin@yadro.com>
X-Mailer: b4 0.14.2
X-Developer-Signature: v=1; a=ed25519-sha256; t=1742371072; l=25956;
 i=n.shubin@yadro.com; s=20230718; h=from:subject:message-id;
 bh=cNBgCGBMLxXgwvJD8kFv/hNFULw4AM2bNKWA3qo9Ik0=;
 b=da/v3MMHqbf3iyZmB9TJpfufqJeB1STsA8hGs9bqbbyHrwgvkALZVqcpMMVYp6kDLHyQHhCSG
 c/2q74IEYPjBQwh+RLjfAOQccPcADl9KIqXQlBPDccX4ULYyCqoqzg4
X-Developer-Key: i=n.shubin@yadro.com; a=ed25519;
 pk=vqf5YIUJ7BJv3EJFaNNxWZgGuMgDH6rwufTLflwU9ac=
X-Endpoint-Received: by B4 Relay for n.shubin@yadro.com/20230718 with
 auth_id=161
X-Original-From: Nikita Shubin <n.shubin@yadro.com>
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=2600:3c04:e001:324:0:1991:8:25;
 envelope-from=devnull+n.shubin.yadro.com@kernel.org;
 helo=tor.source.kernel.org
X-Spam_score_int: -23
X-Spam_score: -2.4
X-Spam_bar: --
X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.332,
 DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Mailman-Approved-At: Wed, 19 Mar 2025 08:49:31 -0400
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Reply-To: n.shubin@yadro.com
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @kernel.org)
X-ZM-MESSAGEID: 1742388663372019000

From: Nikita Shubin <n.shubin@yadro.com>

Add GUSE (FUSE based kernel module similiar to CUSE) based backend.

This allows transparent usage of Linux GPIO UAPI based tools like
in kernel tools/gpio or libgpiod.

libgpiod requires some modification to allow "/sys/class/guse" in
gpiod_check_gpiochip_device().

It requires guse module to be loaded and providing DEVICE()->id
for GPIO module, for example:

```
DEVICE(&s->gpio)->id =3D g_strdup("aspeed-gpio0");
```

The id should be provided to gpiodev with any `devname` that doesn't
exists in /dev:

```
-gpiodev guse,id=3Daspeed-gpio0,devname=3Dgpiochip10
```

That /dev/gpiochip10 can be used in the same way we usually operate with
gpiochip's.

Link: http://git.maquefel.me/?p=3Dqemu-gpiodev/libgpiod.git;a=3Dshortlog;h=
=3Drefs/heads/nshubin/guse-fix
Link: http://git.maquefel.me/?p=3Dqemu-gpiodev/guse.git;a=3Dsummary
Link: http://git.maquefel.me/?p=3Dqemu-gpiodev/libfuse.git;a=3Dshortlog;h=
=3Drefs/heads/nshubin/guse
Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
---
 gpiodev/gpio-guse.c    | 747 +++++++++++++++++++++++++++++++++++++++++++++=
++++
 gpiodev/meson.build    |   1 +
 include/gpiodev/gpio.h |   1 +
 qapi/gpio.json         |  31 +-
 4 files changed, 777 insertions(+), 3 deletions(-)

diff --git a/gpiodev/gpio-guse.c b/gpiodev/gpio-guse.c
new file mode 100644
index 0000000000000000000000000000000000000000..7e94c825653d42aae6e273acb79=
d5e9f8eec293c
--- /dev/null
+++ b/gpiodev/gpio-guse.c
@@ -0,0 +1,747 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * QEMU GPIO GUSE based backend.
+ *
+ * Author: 2025 Nikita Shubin <n.shubin@yadro.com>
+ *
+ */
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/log.h"
+#include "qemu/lockable.h"
+#include "qapi/error.h"
+#include "gpiodev/gpio.h"
+#include "chardev/char.h"
+#include "chardev/char-fe.h"
+
+#define FUSE_USE_VERSION            31
+#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <guse_lowlevel.h>
+#undef FUSE_USE_VERSION
+
+#include <poll.h>
+#include <linux/gpio.h>
+
+#define GUSE_DEVICE_INODE_FLAG      BIT_ULL(63)
+#define GUSE_MAX_WATCH              64
+#define GUSE_MAX_EVENTS             64
+
+typedef struct GusedevLineWatch {
+    uint64_t i_node;
+    struct fuse_pollhandle *ph;
+
+    /* since we can have multiply requests per device we need own masks */
+    struct {
+        unsigned long risen;
+        unsigned long fallen;
+
+        /* special for GPIO_V2_LINE_FLAG_OUTPUT */
+        unsigned long mask;
+    } mask;
+
+    /* required to match mask with actual offsets */
+    uint32_t num_lines;
+    uint32_t offsets[GPIO_V2_LINES_MAX];
+
+    QemuMutex event_lock;
+    uint32_t num_events;
+    struct gpio_v2_line_event events[GUSE_MAX_EVENTS];
+
+    QSIMPLEQ_ENTRY(GusedevLineWatch) next;
+} GusedevLineWatch;
+
+typedef struct GusedevConfigWatch {
+    uint64_t i_node;
+    struct fuse_pollhandle *ph;
+
+    unsigned long *mask;
+
+    uint32_t num_events;
+    struct gpio_v2_line_info_changed events[GUSE_MAX_EVENTS];
+    QSIMPLEQ_ENTRY(GusedevConfigWatch) next;
+} GusedevConfigWatch;
+
+typedef struct GusedevGpiodev {
+    Gpiodev parent;
+
+    char *devname;
+    struct fuse_session *fuse_session;
+    struct fuse_buf fuse_buf;
+
+    QemuMutex linereq_lock;
+    QSIMPLEQ_HEAD(, GusedevLineWatch) linereq;
+
+    QemuMutex configreq_lock;
+    QSIMPLEQ_HEAD(, GusedevConfigWatch) configreq;
+} GusedevGpiodev;
+
+DECLARE_INSTANCE_CHECKER(GusedevGpiodev, GPIODEV_GUSEDEV,
+                         TYPE_GPIODEV_GUSEDEV)
+
+static GusedevLineWatch *gpio_gusedev_find_linereq(GusedevGpiodev *d, uint=
64_t i_node)
+{
+    GusedevLineWatch *e;
+
+    QSIMPLEQ_FOREACH(e, &d->linereq, next) {
+        if (e->i_node =3D=3D i_node) {
+            return e;
+        }
+    }
+
+    return NULL;
+}
+
+static GusedevLineWatch *gpio_gusedev_allocate_linereq(GusedevGpiodev *d, =
uint64_t i_node)
+{
+    GusedevLineWatch *e =3D g_new0(GusedevLineWatch, 1);
+
+    e->i_node =3D i_node;
+
+    QSIMPLEQ_INSERT_TAIL(&d->linereq, e, next);
+
+    return e;
+}
+
+static void gpio_gusedev_free_linereq(GusedevGpiodev *d, GusedevLineWatch =
*w)
+{
+    GusedevLineWatch *entry, *next;
+
+    QSIMPLEQ_FOREACH_SAFE(entry, &d->linereq, next, next) {
+        if (entry->i_node =3D=3D w->i_node) {
+            QSIMPLEQ_REMOVE(&d->linereq, entry, GusedevLineWatch, next);
+            if (entry->ph) {
+                fuse_pollhandle_destroy(entry->ph);
+            }
+            g_free(entry);
+        }
+    }
+}
+
+static GusedevConfigWatch *gpio_gusedev_find_configreq(GusedevGpiodev *d, =
uint64_t i_node)
+{
+    GusedevConfigWatch *e;
+
+    QSIMPLEQ_FOREACH(e, &d->configreq, next) {
+        if (e->i_node =3D=3D i_node) {
+            return e;
+        }
+    }
+
+    return NULL;
+}
+
+static GusedevConfigWatch *gpio_gusedev_allocate_configreq(GusedevGpiodev =
*d,
+                                                           uint64_t i_node)
+{
+    GusedevConfigWatch *e =3D g_new0(GusedevConfigWatch, 1);
+
+    e->i_node =3D i_node;
+    e->mask =3D bitmap_new(d->parent.lines);
+
+    QSIMPLEQ_INSERT_TAIL(&d->configreq, e, next);
+
+    return e;
+}
+
+static void gpio_gusedev_free_configreq(GusedevGpiodev *d,
+                                        GusedevConfigWatch *w)
+{
+    GusedevConfigWatch *entry, *next;
+
+    QSIMPLEQ_FOREACH_SAFE(entry, &d->configreq, next, next) {
+        if (entry->i_node =3D=3D w->i_node) {
+            QSIMPLEQ_REMOVE(&d->configreq, entry, GusedevConfigWatch, next=
);
+            if (entry->ph) {
+                fuse_pollhandle_destroy(entry->ph);
+            }
+            g_free(entry->mask);
+            g_free(entry);
+        }
+    }
+}
+
+static inline uint64_t timespec_to_ns(struct timespec ts)
+{
+    return (uint64_t)ts.tv_nsec + 1000000000ULL * (uint64_t)ts.tv_sec;
+}
+
+static void gpio_gusedev_push_config(GusedevGpiodev *d, uint32_t offset,
+                                     enum gpio_v2_line_changed_type event)
+{
+    GusedevConfigWatch *e;
+    struct timespec ts;
+    uint64_t ts_ns;
+
+    timespec_get(&ts, TIME_UTC);
+    ts_ns =3D timespec_to_ns(ts);
+
+    QEMU_LOCK_GUARD(&d->configreq_lock);
+    QSIMPLEQ_FOREACH(e, &d->configreq, next) {
+        if (test_bit(offset, e->mask)) {
+            struct gpio_v2_line_info_changed *changed;
+            uint32_t num_events =3D e->num_events;
+            if (++num_events > GUSE_MAX_EVENTS) {
+                qemu_log_mask(LOG_GUEST_ERROR, "%s: max config events numb=
er exeeded\n",
+                          __func__);
+                continue;
+            }
+
+            changed =3D &e->events[e->num_events];
+            changed->timestamp_ns =3D ts_ns;
+            changed->event_type =3D event;
+            changed->info.offset =3D offset;
+
+            e->num_events =3D num_events;
+
+            if (e->ph) {
+                fuse_notify_poll(e->ph);
+                fuse_pollhandle_destroy(e->ph);
+                e->ph =3D NULL;
+            }
+        }
+    }
+}
+
+static void gpio_gusedev_push_event(GusedevGpiodev *d, uint32_t offset,
+                                    enum gpio_v2_line_event_id event)
+{
+    GusedevLineWatch *e;
+    struct timespec ts;
+    uint64_t ts_ns;
+
+    timespec_get(&ts, TIME_UTC);
+    ts_ns =3D timespec_to_ns(ts);
+
+    QEMU_LOCK_GUARD(&d->linereq_lock);
+    QSIMPLEQ_FOREACH(e, &d->linereq, next) {
+        bool notify =3D false;
+        if ((event & GPIO_V2_LINE_EVENT_RISING_EDGE)
+            && test_bit(offset, &e->mask.risen)) {
+            notify =3D true;
+        }
+
+        if ((event & GPIO_V2_LINE_EVENT_FALLING_EDGE)
+            && test_bit(offset, &e->mask.fallen)) {
+            notify =3D true;
+        }
+
+        if (notify) {
+            struct gpio_v2_line_event *info;
+            uint32_t num_events =3D e->num_events;
+            if (++num_events > GUSE_MAX_EVENTS) {
+                qemu_log_mask(LOG_GUEST_ERROR, "%s: max config events numb=
er exeeded\n",
+                          __func__);
+                continue;
+            }
+
+            info =3D &e->events[e->num_events];
+            info->timestamp_ns =3D ts_ns;
+            info->id =3D event;
+            info->offset =3D offset;
+
+            e->num_events =3D num_events;
+
+            if (e->ph) {
+                fuse_notify_poll(e->ph);
+                fuse_pollhandle_destroy(e->ph);
+                e->ph =3D NULL;
+            }
+        }
+    }
+}
+
+static void gpio_gusedev_line_event(Gpiodev *g, uint32_t offset,
+                                    QEMUGpioLineEvent event)
+{
+    GusedevGpiodev *d =3D GPIODEV_GUSEDEV(g);
+
+    gpio_gusedev_push_event(d, offset, (enum gpio_v2_line_event_id)event);
+}
+
+static void gpio_gusedev_config_event(Gpiodev *g, uint32_t offset,
+                                      QEMUGpioConfigEvent event)
+{
+    GusedevGpiodev *d =3D GPIODEV_GUSEDEV(g);
+
+    gpio_gusedev_push_config(d, offset, (enum gpio_v2_line_changed_type)ev=
ent);
+}
+
+static void gusedev_init(void *userdata, struct fuse_conn_info *conn)
+{
+    (void)userdata;
+
+    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+    conn->no_interrupt =3D 1;
+}
+
+static void gusedev_destroy(void *private_data)
+{
+    (void)private_data;
+}
+
+static void gusedev_open(fuse_req_t req, fuse_ino_t ino,
+                         struct fuse_file_info *fi)
+{
+    fuse_reply_open(req, fi);
+}
+
+static void gusedev_release(fuse_req_t req, fuse_ino_t ino,
+                            struct fuse_file_info *fi)
+{
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+
+    if (ino & GUSE_DEVICE_INODE_FLAG) {
+        GusedevConfigWatch *e;
+
+        e =3D gpio_gusedev_find_configreq(d, ino);
+        if (e) {
+            gpio_gusedev_free_configreq(d, e);
+        }
+    } else {
+        GusedevLineWatch *e;
+
+        e =3D gpio_gusedev_find_linereq(d, ino);
+        if (e) {
+            gpio_gusedev_free_linereq(d, e);
+        }
+    }
+
+    fuse_reply_err(req, 0);
+}
+
+static void gusedev_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_=
t off,
+                         struct fuse_file_info *fi)
+{
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+
+    if (ino & GUSE_DEVICE_INODE_FLAG) {
+        GusedevConfigWatch *e;
+
+        e =3D gpio_gusedev_find_configreq(d, ino);
+        if (e && e->num_events) {
+            fuse_reply_buf(req, (char *)&e->events, sizeof(e->events[0]) *=
 e->num_events);
+            e->num_events =3D 0;
+            return;
+        }
+    } else {
+        GusedevLineWatch *e;
+
+        e =3D gpio_gusedev_find_linereq(d, ino);
+        if (e && e->num_events) {
+            fuse_reply_buf(req, (char *)&e->events, sizeof(e->events[0]) *=
 e->num_events);
+            e->num_events =3D 0;
+            return;
+        }
+    }
+
+    fuse_reply_buf(req, NULL, 0);
+}
+
+static void gusedev_poll_config(fuse_req_t req, fuse_ino_t ino, struct fus=
e_file_info *fi,
+                                struct fuse_pollhandle *ph)
+{
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    GusedevConfigWatch *e;
+
+    QEMU_LOCK_GUARD(&d->configreq_lock);
+    e =3D gpio_gusedev_find_configreq(d, ino);
+    if (!e) {
+        fuse_reply_poll(req, POLLERR);
+        return;
+    }
+
+    if (ph) {
+        if (e->ph) {
+            fuse_pollhandle_destroy(e->ph);
+        }
+
+        e->ph =3D ph;
+    }
+
+    if (e->num_events) {
+        fuse_reply_poll(req, POLLIN);
+    } else {
+        fuse_reply_poll(req, 0);
+    }
+}
+
+static void gusedev_poll_line(fuse_req_t req, fuse_ino_t ino, struct fuse_=
file_info *fi,
+                                struct fuse_pollhandle *ph)
+{
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    GusedevLineWatch *e;
+
+    QEMU_LOCK_GUARD(&d->linereq_lock);
+    e =3D gpio_gusedev_find_linereq(d, ino);
+    if (!e) {
+        fuse_reply_poll(req, POLLERR);
+        return;
+    }
+
+    if (ph) {
+        if (e->ph) {
+            fuse_pollhandle_destroy(e->ph);
+        }
+
+        e->ph =3D ph;
+    }
+
+    if (e->num_events) {
+        fuse_reply_poll(req, POLLIN);
+    } else {
+        fuse_reply_poll(req, 0);
+    }
+}
+
+static void gusedev_poll(fuse_req_t req, fuse_ino_t ino, struct fuse_file_=
info *fi,
+                         struct fuse_pollhandle *ph)
+{
+    if (ino & GUSE_DEVICE_INODE_FLAG) {
+        gusedev_poll_config(req, ino, fi, ph);
+    } else {
+        gusedev_poll_line(req, ino, fi, ph);
+    }
+}
+
+static int gusedev_chipinfo(fuse_req_t req)
+{
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    struct gpiochip_info info =3D { 0 };
+
+    qemu_gpio_chip_info(&d->parent, &info.lines, info.name, info.label);
+
+    return fuse_reply_ioctl(req, 0, &info, sizeof(info));
+}
+
+static int gusedev_lineinfo(fuse_req_t req, const void *in_buf)
+{
+    struct gpio_v2_line_info *in =3D (struct gpio_v2_line_info *)in_buf;
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    struct gpio_v2_line_info reply =3D { 0 };
+    uint32_t offset =3D in->offset;
+    gpio_line_info info =3D { 0 };
+
+    if (offset > d->parent.lines) {
+        return fuse_reply_err(req, EINVAL);
+    }
+
+    info.offset =3D offset;
+    qemu_gpio_line_info(&d->parent, &info);
+    g_strlcpy(reply.name, info.name, GPIO_MAX_NAME_SIZE);
+    reply.flags =3D info.flags;
+
+    return fuse_reply_ioctl(req, 0, &reply, sizeof(reply));
+}
+
+static int gusedev_linerequest(fuse_req_t req, fuse_ino_t ino,
+                               const void *in_buf)
+{
+    struct gpio_v2_line_request *in =3D (struct gpio_v2_line_request *)in_=
buf;
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    struct gpio_v2_line_request reply;
+    GusedevLineWatch *watch;
+    int i;
+
+    /* line request not available for device inode */
+    if (ino & GUSE_DEVICE_INODE_FLAG) {
+        return fuse_reply_err(req, EINVAL);
+    }
+
+    watch =3D gpio_gusedev_allocate_linereq(d, ino);
+    if (!watch) {
+        return fuse_reply_err(req, ENOMEM);
+    }
+
+    for (i =3D 0; i < in->num_lines; i++) {
+        bool notify =3D false;
+
+        if (in->config.flags & GPIO_V2_LINE_FLAG_INPUT) {
+            if (in->config.flags & GPIO_V2_LINE_FLAG_EDGE_RISING) {
+                watch->mask.risen |=3D BIT_ULL(in->offsets[i]);
+                qemu_gpio_add_event_watch(&d->parent, in->offsets[i],
+                                          GPIO_EVENT_RISING_EDGE);
+                notify =3D true;
+            }
+
+            if (in->config.flags & GPIO_V2_LINE_FLAG_EDGE_FALLING) {
+                watch->mask.fallen |=3D BIT_ULL(in->offsets[i]);
+                qemu_gpio_add_event_watch(&d->parent, in->offsets[i],
+                                          GPIO_EVENT_FALLING_EDGE);
+                notify =3D true;
+            }
+        /* TODO: check if lines are input and don't allow to change direct=
ion */
+        } else if (in->config.flags & GPIO_V2_LINE_FLAG_OUTPUT) {
+            watch->mask.mask |=3D BIT_ULL(in->offsets[i]);
+        }
+
+        /* dispatch config change event */
+        if (notify) {
+            gpio_gusedev_push_config(d, in->offsets[i],
+                                     GPIO_V2_LINE_CHANGED_REQUESTED);
+        }
+    }
+
+    memcpy(&reply, in_buf, sizeof(reply));
+
+    return fuse_reply_ioctl(req, 0, &reply, sizeof(reply));
+}
+
+static int gusedev_get_line_values(fuse_req_t req, fuse_ino_t ino,
+                                   const void *in_buf)
+{
+    struct gpio_v2_line_values *values =3D (struct gpio_v2_line_values *)i=
n_buf;
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    struct gpio_v2_line_values reply =3D { 0 };
+    GusedevLineWatch *e;
+    int idx;
+
+    e =3D gpio_gusedev_find_linereq(d, ino);
+    if (!e) {
+        return fuse_reply_err(req, EINVAL);
+    }
+
+    idx =3D find_first_bit((unsigned long *)values->mask, e->num_lines);
+    while (idx < e->num_lines) {
+        reply.bits |=3D qemu_gpio_get_line_value(&d->parent, e->offsets[id=
x]);
+        idx =3D find_next_bit((unsigned long *)values->mask, e->num_lines,=
 idx + 1);
+    }
+
+    reply.mask =3D values->mask;
+
+    return fuse_reply_ioctl(req, 0, &reply, sizeof(reply));
+}
+
+/* TODO: merge with gusedev_set_line_values() */
+static int gusedev_set_line_values(fuse_req_t req, fuse_ino_t ino,
+                                   const void *in_buf)
+{
+    struct gpio_v2_line_values *values =3D (struct gpio_v2_line_values *)i=
n_buf;
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    struct gpio_v2_line_values reply =3D { 0 };
+    GusedevLineWatch *e;
+    int idx;
+
+    e =3D gpio_gusedev_find_linereq(d, ino);
+    if (!e) {
+        return fuse_reply_err(req, EINVAL);
+    }
+
+    idx =3D find_first_bit((unsigned long *)&values->mask, e->num_lines);
+    while (idx < e->num_lines) {
+        uint8_t bit =3D test_bit(idx, (unsigned long *)&values->bits);
+        qemu_gpio_set_line_value(&d->parent, e->offsets[idx], bit);
+        idx =3D find_next_bit((unsigned long *)&values->mask, e->num_lines=
, idx + 1);
+    }
+
+    reply.bits =3D values->bits;
+    reply.mask =3D values->mask;
+
+    return fuse_reply_ioctl(req, 0, &reply, sizeof(reply));
+}
+
+static int gusedev_set_line_watch(fuse_req_t req, fuse_ino_t ino,
+                                  const void *in_buf, bool watch)
+{
+    struct gpio_v2_line_info *info =3D (struct gpio_v2_line_info *)in_buf;
+    GusedevGpiodev *d =3D fuse_req_userdata(req);
+    struct gpio_v2_line_info reply =3D { 0 };
+    GusedevConfigWatch *e;
+
+    e =3D gpio_gusedev_find_configreq(d, ino);
+
+    /*
+     * If not found allocate it, because unlike linereq configreq is added=
 separately
+     * per each line.
+     */
+    if (!e) {
+        e =3D gpio_gusedev_allocate_configreq(d, ino);
+    }
+
+    if (watch) {
+        qemu_gpio_add_config_watch(&d->parent, info->offset);
+        set_bit(info->offset, e->mask);
+    } else {
+        qemu_gpio_clear_config_watch(&d->parent, info->offset);
+        clear_bit(info->offset, e->mask);
+    }
+
+    memcpy(&reply, info, sizeof(reply));
+
+    return fuse_reply_ioctl(req, 0, &reply, sizeof(reply));
+}
+
+static void gusedev_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+                          void *arg, struct fuse_file_info *fi, unsigned f=
lags,
+                          const void *in_buf, size_t in_bufsz, size_t out_=
bufsz)
+{
+    bool add_watch =3D false;
+    int ret;
+
+    if (flags & FUSE_IOCTL_COMPAT) {
+        fuse_reply_err(req, ENOSYS);
+        return;
+    }
+
+    switch (cmd) {
+    case GPIO_GET_CHIPINFO_IOCTL:
+        ret =3D gusedev_chipinfo(req);
+        break;
+    case GPIO_V2_GET_LINEINFO_IOCTL:
+        ret =3D gusedev_lineinfo(req, in_buf);
+        break;
+    /* GPIO_V2_GET_LINE_IOCTL is also processed by guse module. */
+    case GPIO_V2_GET_LINE_IOCTL:
+        ret =3D gusedev_linerequest(req, ino, in_buf);
+        break;
+    case GPIO_V2_LINE_GET_VALUES_IOCTL:
+        ret =3D gusedev_get_line_values(req, ino, in_buf);
+        break;
+    case GPIO_V2_LINE_SET_VALUES_IOCTL:
+        ret =3D gusedev_set_line_values(req, ino, in_buf);
+        break;
+    case GPIO_V2_GET_LINEINFO_WATCH_IOCTL:
+        add_watch =3D true;
+        /* fallthrough */
+    case GPIO_GET_LINEINFO_UNWATCH_IOCTL:
+        ret =3D gusedev_set_line_watch(req, ino, in_buf, add_watch);
+        break;
+    case GPIO_V2_LINE_SET_CONFIG_IOCTL:
+    default:
+        ret =3D fuse_reply_err(req, EINVAL);
+    }
+
+    if (ret) {
+        qemu_log_mask(LOG_GUEST_ERROR, "gusedev_ioctl() failed with %d\n",
+                      ret);
+    }
+}
+
+static const struct guse_cdev_lowlevel_ops gusedev_glop =3D {
+    .init       =3D gusedev_init,
+    .destroy    =3D gusedev_destroy,
+    .open       =3D gusedev_open,
+    .release    =3D gusedev_release,
+    .read       =3D gusedev_read,
+    .poll       =3D gusedev_poll,
+    .ioctl      =3D gusedev_ioctl,
+};
+
+static void read_from_fuse_export(void *opaque)
+{
+    GusedevGpiodev *guse =3D opaque;
+    int ret;
+
+    do {
+        ret =3D fuse_session_receive_buf(guse->fuse_session, &guse->fuse_b=
uf);
+    } while (ret =3D=3D -EINTR);
+
+    if (ret < 0) {
+        return;
+    }
+
+    fuse_session_process_buf(guse->fuse_session, &guse->fuse_buf);
+}
+
+static int setup_guse_export(GusedevGpiodev *guse, Error **errp)
+{
+    char dev_name[128] =3D "DEVNAME=3D";
+    const char *dev_info_argv[] =3D { dev_name };
+    char *curdir =3D get_current_dir_name();
+    struct fuse_session *session =3D NULL;
+    const char *argv[3];
+    struct guse_info ci;
+    int multithreaded;
+    AioContext *ctx;
+
+    strncat(dev_name, guse->devname, sizeof(dev_name) - sizeof("DEVNAME=3D=
"));
+
+    argv[0] =3D ""; /* Dummy program name */
+    argv[1] =3D "-d";
+    argv[2] =3D NULL;
+
+    ci.dev_major =3D 0;
+    ci.dev_minor =3D 0;
+    ci.dev_info_argc =3D 1;
+    ci.dev_info_argv =3D dev_info_argv;
+
+    session =3D guse_lowlevel_setup(ARRAY_SIZE(argv) - 1, (char **)argv, &=
ci, &gusedev_glop,
+                                  &multithreaded, guse);
+    if (session =3D=3D NULL) {
+        error_setg(errp, "guse_lowlevel_setup failed");
+        errno =3D EINVAL;
+        return -1;
+    }
+
+    /* FIXME: fuse_daemonize() calls chdir("/") */
+    chdir(curdir);
+    g_free(curdir);
+
+    ctx =3D iohandler_get_aio_context();
+
+    aio_set_fd_handler(ctx, fuse_session_fd(session),
+                       read_from_fuse_export, NULL,
+                       NULL, NULL, guse);
+
+    guse->fuse_session =3D session;
+
+    return 0;
+}
+
+static void gpio_gusedev_open(Gpiodev *gpio, GpiodevBackend *backend,
+                              Error **errp)
+{
+    GpiodevGusedev *opts =3D backend->u.gusedev.data;
+    GusedevGpiodev *d =3D GPIODEV_GUSEDEV(gpio);
+
+    d->devname =3D g_strdup(opts->devname);
+
+    QSIMPLEQ_INIT(&d->linereq);
+    QSIMPLEQ_INIT(&d->configreq);
+
+    qemu_mutex_init(&d->linereq_lock);
+    qemu_mutex_init(&d->configreq_lock);
+
+    setup_guse_export(d, errp);
+}
+
+static void gpio_gusedev_parse(QemuOpts *opts, GpiodevBackend *backend,
+                               Error **errp)
+{
+    const char *devname =3D qemu_opt_get(opts, "devname");
+    /* TODO: add bool debug for fuse debug */
+    GpiodevGusedev *ggusedev;
+
+    if (devname =3D=3D NULL) {
+        error_setg(errp, "gpiodev: gusedev: no devname given");
+        return;
+    }
+
+    backend->type =3D GPIODEV_BACKEND_KIND_GUSEDEV;
+    ggusedev =3D backend->u.gusedev.data =3D g_new0(GpiodevGusedev, 1);
+    ggusedev->devname =3D g_strdup(devname);
+}
+
+static void gpio_gusedev_class_init(ObjectClass *oc, void *data)
+{
+    GpiodevClass *cc =3D GPIODEV_CLASS(oc);
+
+    cc->parse =3D &gpio_gusedev_parse;
+    cc->open =3D &gpio_gusedev_open;
+    cc->line_event =3D &gpio_gusedev_line_event;
+    cc->config_event =3D &gpio_gusedev_config_event;
+}
+
+static const TypeInfo gpio_gusedev_type_info[] =3D {
+    {
+        .name =3D TYPE_GPIODEV_GUSEDEV,
+        .parent =3D TYPE_GPIODEV,
+        .class_init =3D gpio_gusedev_class_init,
+        .instance_size =3D sizeof(GusedevGpiodev),
+        /* .instance_finalize =3D gpio_gusedev_finalize, */
+    },
+};
+
+DEFINE_TYPES(gpio_gusedev_type_info);
+
diff --git a/gpiodev/meson.build b/gpiodev/meson.build
index 64d3abb4e3d72cba0c26b665515a0f97e82fb5d9..32eae1c3f8bc856e8b7f4a4bb49=
d796147f59da7 100644
--- a/gpiodev/meson.build
+++ b/gpiodev/meson.build
@@ -4,4 +4,5 @@ gpiodev_ss.add(files(
   'gpio.c',
 ))
=20
+gpiodev_ss.add(when: fuse, if_true: files('gpio-guse.c'))
 gpiodev_ss =3D gpiodev_ss.apply({})
diff --git a/include/gpiodev/gpio.h b/include/gpiodev/gpio.h
index a34d805ccc0bf5a25986b118dcc0b2cc0a55572c..d3b95410d3a570480d187354ad3=
84f9a23b102e6 100644
--- a/include/gpiodev/gpio.h
+++ b/include/gpiodev/gpio.h
@@ -56,6 +56,7 @@ struct Gpiodev {
 OBJECT_DECLARE_TYPE(Gpiodev, GpiodevClass, GPIODEV)
=20
 #define TYPE_GPIODEV_CHARDEV "gpiodev-chardev"
+#define TYPE_GPIODEV_GUSEDEV "gpiodev-guse"
=20
 struct GpiodevClass {
     ObjectClass parent_class;
diff --git a/qapi/gpio.json b/qapi/gpio.json
index 1c2b7af36813ff52cbb3a44e64a2e5a5d8658d62..e3cdca793260212622a30947eae=
a61bd523e98fb 100644
--- a/qapi/gpio.json
+++ b/qapi/gpio.json
@@ -21,12 +21,36 @@
 ##
 # @GpiodevBackendKind:
 #
-# @chardev: chardevs
+# @chardev: Gpio dev over chardev backend
+# @gusedev: Gpio dev over GUSE FUSE module
 #
 # Since: 9.2
 ##
 { 'enum': 'GpiodevBackendKind',
-  'data': [ 'chardev' ] }
+  'data': [ 'chardev',
+            { 'name': 'gusedev', 'if': 'CONFIG_LINUX' } ] }
+
+##
+# @GpiodevGusedev:
+#
+# Configuration info for guse gpiodevs.
+#
+# @devname: Name of device created in /dev
+#
+# Since: 9.2
+##
+  { 'struct': 'GpiodevGusedev',
+    'data': { 'devname': 'str' } }
+
+##
+# @GpiodevGusedevWrapper:
+#
+# @data: Configuration info for chardev gpiodevs
+#
+# Since: 9.2
+##
+{ 'struct': 'GpiodevGusedevWrapper',
+  'data': { 'data': 'GpiodevGusedev' } }
=20
 ##
 # @GpiodevChardev:
@@ -65,4 +89,5 @@
 { 'union': 'GpiodevBackend',
   'base': { 'type': 'GpiodevBackendKind' },
   'discriminator': 'type',
-  'data': { 'chardev': 'GpiodevChardevWrapper' } }
\ No newline at end of file
+  'data': { 'chardev': 'GpiodevChardevWrapper',
+            'gusedev': { 'type': 'GpiodevGusedevWrapper', 'if': 'CONFIG_LI=
NUX' } } }
\ No newline at end of file

--=20
2.45.2