From nobody Fri Jun 12 07:36:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EDC213E51CC; Wed, 13 May 2026 09:11:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663505; cv=none; b=JUjPUgIWnAELPh3fw2zm0F8Vez301x1Y+fjYPEd8/Kc1vGmwp0EcipVa43zhpoa13lsx+tyTRAuzD4DRNL/Ll5ygPuaC+X31fFt9MiZcU9FaM/l4VB7tFuasfQ78xFwVSBkSsl01V3LZ1XuHx74NTXDqzmORPAtAmTU7k5MpiT8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663505; c=relaxed/simple; bh=0NkzIJSkYtlvpvJ7vDF87jKOYvCLQY5DcnMfefeBaqU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sDo9sws/GbJHm/LH1GFD6+URSRN82V8cCxGyYA8l6ipd+wi0B1eCs/TBhoIsVsLiY5zd+00MVd1Uj5+K4ZOsrGqHOxiy/j2uPoF092h9JpS6DVtDSNZ07m4MCOvyRY0jwYKPX/9g5MjhhMJTmaaO8jKnbMdMtAlXL8BL/lBNW4Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=IKBUtfz8; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="IKBUtfz8" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A0F5FC2BCB8; Wed, 13 May 2026 09:11:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778663504; bh=0NkzIJSkYtlvpvJ7vDF87jKOYvCLQY5DcnMfefeBaqU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IKBUtfz8yQN0uDPc9/9FF6gxKs3jppYUOBgMawkQVbmxLNU+9C5nHfj399UmWedR7 /g0umDKAT9W/K0B4OyPJiZ9wS671Cj45aAqGrCkCx59RukCeP4+7mo62z7lfZeB2G7 D8blrOlCDk8BvpZuTaHWgn1F+CBzbylFhyk4AMuD4oFqiVvuxTEHCTrAE+eI0qqdJ4 iMJPP5pjP8pl90i7ZY4Rg4RqZy26JWThZX/gYVm54r0b8qZjDUntOu+na3XUejkSOe wnMItMFAzUhmPDvUhWKQffMalwpAE7OCYv1WKWTSYxuKHdqXHIJAqqmJyJRZ6a0YzE yPmgCrhvAThmQ== From: Tzung-Bi Shih To: Arnd Bergmann , Greg Kroah-Hartman , Bartosz Golaszewski , Linus Walleij Cc: Benson Leung , tzungbi@kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, driver-core@lists.linux.dev, linux-doc@vger.kernel.org, linux-gpio@vger.kernel.org, "Rafael J. Wysocki" , Danilo Krummrich , Jonathan Corbet , Shuah Khan , Laurent Pinchart , Wolfram Sang , Jason Gunthorpe , Johan Hovold , "Paul E . McKenney" , Bartosz Golaszewski Subject: [PATCH v11 1/5] revocable: Revocable resource management Date: Wed, 13 May 2026 17:10:39 +0800 Message-ID: <20260513091043.6766-2-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260513091043.6766-1-tzungbi@kernel.org> References: <20260513091043.6766-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The "revocable" mechanism is a synchronization primitive designed to manage safe access to resources that can be asynchronously removed or invalidated. Its primary purpose is to prevent Use-After-Free (UAF) errors when interacting with resources whose lifetimes are not guaranteed to outlast their consumers. This is particularly useful in systems where resources can disappear unexpectedly, such as those provided by hot-pluggable devices like USB. When a consumer holds a reference to such a resource, the underlying device might be removed, causing the resource's memory to be freed. Subsequent access attempts by the consumer would then lead to UAF errors. Revocable addresses this by providing a form of "weak reference" and a controlled access method. It allows a resource consumer to safely attempt to access the resource. The mechanism guarantees that any access granted is valid for the duration of its use. If the resource has already been revoked (i.e., freed), the access attempt will fail safely, typically by returning NULL, instead of causing a crash. It uses a provider/consumer model built on Sleepable RCU (SRCU) to guarantee safe memory access: - A resource provider, such as a driver for a hot-pluggable device, allocates a struct revocable and initializes it with a pointer to the resource. - A resource consumer that wants to access the resource allocates a struct revocable_consumer containing a reference to the provider. - To access the resource, the consumer uses revocable_try_access(). This function enters an SRCU read-side critical section and returns the pointer to the resource. If the provider has already freed the resource, it returns NULL. After use, the consumer calls revocable_withdraw_access() to exit the SRCU critical section. There are some macro level helpers for doing that. The API provides the following contract: - revocable_try_access() can be safely called from both process and atomic contexts. - It is permitted to sleep within the critical section established between revocable_try_access() and revocable_withdraw_access(). - revocable_try_access() and the matching revocable_withdraw_access() must occur in the same context. For example, it is illegal to invoke revocable_withdraw_access() in an irq handler if the matching revocable_try_access() was invoked in process context. - When the provider needs to remove the resource, it calls revocable_revoke(). This function sets the internal resource pointer to NULL and then calls synchronize_srcu() to wait for all current readers to finish before the resource can be completely torn down. Reviewed-by: Bartosz Golaszewski Tested-by: Bartosz Golaszewski Signed-off-by: Tzung-Bi Shih --- v11: - Add Reviewed-by and Tested-by tags. v10: https://lore.kernel.org/all/20260508105448.31799-2-tzungbi@kernel.org - Drop unused header. - Unify handling of embedded and dynamic allocation. - Rename: - struct revocable_consumer -> struct revocable_handle. - revocable_init() -> revocable_handle_init(). - revocable_deinit() -> revocable_handle_deinit(). - revocable_embed_init() -> revocable_init(). v9: https://lore.kernel.org/all/20260427135841.96266-2-tzungbi@kernel.org - Add revocable_embed_init() and revocable_embed_destroy() for embedded resource provider per https://lore.kernel.org/all/CAMRc=3DMehkJc-js=3DWk9vBAcXOpazqjtYDLPUEhmbN= 8U7Wu2YpgA@mail.gmail.com v8: https://lore.kernel.org/all/20260213092307.858908-2-tzungbi@kernel.org - Squash: - fdeb3ca3cca8 revocable: Remove redundant synchronize_srcu() call - 4d7dc4d1a62d revocable: Fix races in revocable_alloc() using RCU - 377563ce0653 revocable: fix SRCU index corruption by requiring caller-p= rovided storage - Rename macro names: - REVOCABLE_TRY_ACCESS_WITH() -> revocable_try_access_with(). - REVOCABLE_TRY_ACCESS_SCOPED() -> revocable_try_access_with_scoped(). - Rename terminologies as now normal users should only "see" provider handles, using a shorter name for provider handle to echo the main concept. - struct revocable -> struct revocable_consumer. - struct revocable_provider -> struct revocable. - revocable_provider_alloc() -> revocable_alloc(). - revocable_provider_revoke() -> revocable_revoke(). - New APIs: - revocable_get(). - revocable_put(). - revocable_try_access_or_return_err(). - revocable_try_access_or_return(). - revocable_try_access_or_return_void(). - revocable_try_access_or_return_err_scoped(). - revocable_try_access_or_return_scoped(). - revocable_try_access_or_void_scoped(). - revocable_try_access_or_skip_scoped(). - Add API contract that revocable_try_access() works from process and atomic context while also allowing sleeping inside the critical sections. - Add revocable.h to the DRIVER CORE entry in MAINTAINERS. v7: https://lore.kernel.org/all/20260116080235.350305-2-tzungbi@kernel.org - "2025" -> "2026" in copyright. - Documentation/ - Rephrase section "Revocable vs. Devres (devm)". - Include sections for struct revocable_provider and struct revocable. - Minor rename: "revocable" -> "access_rev" for DEFINE_FREE(). - Add Acked-by tag. v6: https://lore.kernel.org/all/20251106152330.11733-2-tzungbi@kernel.org - Rename REVOCABLE_TRY_ACCESS_WITH() -> REVOCABLE_TRY_ACCESS_SCOPED(). - Add new REVOCABLE_TRY_ACCESS_WITH(). - Remove Acked-by tags as the API names changed a bit. v5: https://lore.kernel.org/all/20251016054204.1523139-2-tzungbi@kernel.org - No changes. v4: https://lore.kernel.org/all/20250923075302.591026-2-tzungbi@kernel.org - Rename: - revocable_provider_free() -> revocable_provider_revoke(). - REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH(). - revocable_release() -> revocable_withdraw_access(). - rcu_dereference() -> srcu_dereference() to fix a warning from lock debugg= ing. - Move most docs to kernel-doc, include them in Documentation/, and modify = the commit message accordingly. - Fix some doc errors. - Add Acked-by tags. v3: https://lore.kernel.org/all/20250912081718.3827390-2-tzungbi@kernel.org - No changes. v2: https://lore.kernel.org/all/20250820081645.847919-2-tzungbi@kernel.org - Rename "ref_proxy" -> "revocable". - Add introduction in kernel-doc format in revocable.c. - Add MAINTAINERS entry. - Add copyright. - Move from lib/ to drivers/base/. - EXPORT_SYMBOL() -> EXPORT_SYMBOL_GPL(). - Add Documentation/. - Rename _get() -> try_access(); _put() -> release(). - Fix a sparse warning by removing the redundant __rcu annotations. - Fix a sparse warning by adding __acquires() and __releases() annotations. v1: https://lore.kernel.org/all/20250814091020.1302888-2-tzungbi@kernel.org A way to verify Documentation/: - `make O=3Dbuild SPHINXDIRS=3Ddriver-api/driver-model/ htmldocs`. --- .../driver-api/driver-model/index.rst | 1 + .../driver-api/driver-model/revocable.rst | 384 ++++++++++++++++++ MAINTAINERS | 9 + drivers/base/Makefile | 2 +- drivers/base/revocable.c | 267 ++++++++++++ include/linux/revocable.h | 204 ++++++++++ 6 files changed, 866 insertions(+), 1 deletion(-) create mode 100644 Documentation/driver-api/driver-model/revocable.rst create mode 100644 drivers/base/revocable.c create mode 100644 include/linux/revocable.h diff --git a/Documentation/driver-api/driver-model/index.rst b/Documentatio= n/driver-api/driver-model/index.rst index abeb4b36636b..cc90b20bb192 100644 --- a/Documentation/driver-api/driver-model/index.rst +++ b/Documentation/driver-api/driver-model/index.rst @@ -14,3 +14,4 @@ Driver Model overview platform porting + revocable diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Document= ation/driver-api/driver-model/revocable.rst new file mode 100644 index 000000000000..9a20c2032695 --- /dev/null +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -0,0 +1,384 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D +Revocable Resource Management +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D + +Overview +=3D=3D=3D=3D=3D=3D=3D=3D + +.. kernel-doc:: drivers/base/revocable.c + :doc: Overview + +Revocable vs. Devres (devm) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + +Revocable and Devres address different problems in resource management: + +* **Devres:** Primarily addresses **resource leaks**. The lifetime of t= he + resources is tied to the lifetime of the device. The resource is + automatically freed when the device is unbound. This cleanup happens + irrespective of any potential active users. + +* **Revocable:** Primarily addresses **invalid memory access**, + such as Use-After-Free (UAF). It's an independent synchronization + primitive that decouples consumer access from the resource's actual + presence. Consumers interact with a "revocable object" (an intermedia= ry), + not the underlying resource directly. This revocable object persists = as + long as there are active references to it from consumer handles. + +**Key Distinctions & How They Complement Each Other:** + +1. **Reference Target:** Consumers hold a reference to the *revocable obj= ect*, + not the encapsulated resource itself. + +2. **Resource Lifetime vs. Access:** The underlying resource's lifetime is + independent of the number of references to the revocable object. The + resource can be freed at any point. A common scenario is the resource + being freed by `devres` when the providing device is unbound. + +3. **Safe Access:** Revocable provides a safe way to attempt access. Bef= ore + using the resource, a consumer uses the Revocable API (e.g., + revocable_try_access()). This function checks if the resource is still + valid. It returns a pointer to the resource only if it hasn't been + revoked; otherwise, it returns NULL. This prevents UAF by providing a + clear signal that the resource is gone. + +4. **Complementary Usage:** `devres` and Revocable work well together. + `devres` can handle the automatic allocation and deallocation of a + resource tied to a device. The Revocable mechanism can be layered on = top + to provide safe access for consumers whose lifetimes might extend beyo= nd + the provider device's lifetime. For instance, a userspace program mig= ht + keep a character device file open even after the physical device has b= een + removed. In this case: + + * `devres` frees the device-specific resource upon unbinding. + * The Revocable mechanism ensures that any subsequent operations on = the + open file handle, which attempt to access the now-freed resource, + will fail gracefully (e.g., revocable_try_access() returns NULL) + instead of causing a UAF. + +In summary, `devres` ensures resources are *released* to prevent leaks, wh= ile +the Revocable mechanism ensures that attempts to *access* these resources = are +done safely, even if the resource has been released. + +API and Usage +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +For Resource Providers +---------------------- + +There are two ways to manage the resource provider handle (``struct revoca= ble``): + +Dynamic Allocation +~~~~~~~~~~~~~~~~~~ + +If the lifetime of the ``struct revocable`` is not tied to another specific +kernel object, or if multiple independent consumers need to hold reference= s, +dynamic allocation should be used. + +* **Creation:** Use revocable_alloc() to allocate and initialize. +* **Ownership:** The caller receives a reference, and the provider holds + another. +* **Revocation:** Call revocable_revoke() when the resource is going awa= y. + This drops the provider's reference. +* **Cleanup:** The caller *must* call revocable_put() to release its ref= erence + when it no longer needs the handle. The memory is freed automatically= when + the last reference is dropped. + +Embedded Allocation +~~~~~~~~~~~~~~~~~~~ + +If the ``struct revocable`` can be embedded within a parent kernel object +(e.g., a foo_device struct), this method can be simpler as the lifetime is +inherently tied to the parent. + +* **Initialization:** Declare a ``struct revocable`` within your parent + structure and initialize it with revocable_init(). +* **Ownership:** The caller receives a reference, and the provider holds + another. +* **Revocation:** Call revocable_revoke() when the resource is going awa= y. + This drops the provider's reference. +* **Cleanup:** The owner *must* call revocable_put() during the parent + object's teardown process and ensuring no more consumers can access + it. This cleans up internal resources like the SRCU domain. The memo= ry + for the ``struct revocable`` is freed when the parent object is freed. + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_alloc + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_init + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_revoke + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_get + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_put + +Example Usage (Dynamic Allocation) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: c + + struct foo_device { + struct revocable *rev; + ... + }; + + int foo_device_probe(struct device *dev) + { + struct foo_device *foo_dev; + void *res; + int ret; + + foo_dev =3D devm_kzalloc(dev, sizeof(*foo_dev), GFP_KERNEL); + if (!foo_dev) + return -ENOMEM; + + // Acquire the actual resource. + res =3D ...(...); + + // Allocate the revocable handle. + foo_dev->rev =3D revocable_alloc(res); + if (!foo_dev->rev) + return -ENOMEM; + + dev_set_drvdata(dev, foo_dev); + // ... further device setup ... + return 0; + } + + void foo_device_remove(struct device *dev) + { + struct foo_device *foo_dev =3D dev_get_drvdata(dev); + + // Drop the reference. + revocable_put(foo_dev->rev); + } + + // Provider side would use revocable_revoke() on foo_dev->rev. + // Consumer side would use revocable_try_access_* macros on foo_dev->r= ev. + +Example Usage (Embedded Allocation) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: c + + struct foo_device { + struct revocable rev; + ... + }; + + int foo_device_probe(struct device *dev) + { + struct foo_device *foo_dev; + void *res; + int ret; + + foo_dev =3D devm_kzalloc(dev, sizeof(*foo_dev), GFP_KERNEL); + if (!foo_dev) + return -ENOMEM; + + // Acquire the actual resource. + res =3D ...(...); + + // Initialize the embedded revocable. + ret =3D revocable_init(&foo_dev->rev, res); + if (ret) + return ret; + + dev_set_drvdata(dev, foo_dev); + // ... further device setup ... + return 0; + } + + void foo_device_remove(struct device *dev) + { + struct foo_device *foo_dev =3D dev_get_drvdata(dev); + + // Cleanup the embedded revocable internal state. + revocable_put(&foo_dev->rev); + } + + // Provider side would use revocable_revoke() on &foo_dev->rev. + // Consumer side would use revocable_try_access_* macros on &foo_dev->= rev. + +For Resource Consumers +---------------------- +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_handle + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_handle_init + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_handle_deinit + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_try_access + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_withdraw_access + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_try_access_with + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + int consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + revocable_try_access_with(rev, res); + // Always check if the resource is valid. + if (!res) { + pr_warn("Resource is not available\n"); + return -EAGAIN; + } + + // 'res' is guaranteed to be valid until this function exits. + do_something_with(res); + return 0; + } // revocable_withdraw_access() is automatically called here. + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_try_access_or_return_err + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + int consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + // Returns -ENXIO if access fails. + revocable_try_access_or_return_err(rev, res, -ENXIO); + + // 'res' is guaranteed to be valid if we reach here. + do_something_with(res); + return 0; + } // revocable_withdraw_access() is automatically called here. + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_try_access_or_return + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + int consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + // Returns -ENODEV if access fails. + revocable_try_access_or_return(rev, res); + + // 'res' is guaranteed to be valid if we reach here. + do_something_with(res); + return 0; + } // revocable_withdraw_access() is automatically called here. + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_try_access_with_scoped + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + int consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + revocable_try_access_with_scoped(rev, res) { + // Always check if the resource is valid. + if (!res) { + pr_warn("Resource is not available\n"); + return -EAGAIN; + } + + // 'res' is valid for the rest of this block. + do_something_with(res); + } + // revocable_withdraw_access() is automatically called here. + + return 0; + } + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_try_access_or_return_err_scoped + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + int consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + // Returns -ENXIO if access fails. + revocable_try_access_or_return_err_scoped(rev, res, -ENXIO) { + // 'res' is guaranteed to be valid in this block. + do_something_with(res); + } + // revocable_withdraw_access() is automatically called here. + + return 0; // Only reached if resource was accessed. + } + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_try_access_or_return_scoped + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + int consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + // Returns -ENODEV if access fails. + revocable_try_access_or_return_scoped(rev, res) { + // 'res' is guaranteed to be valid in this block. + do_something_with(res); + } + // revocable_withdraw_access() is automatically called here. + + return 0; // Only reached if resource was accessed. + } + +.. kernel-doc:: include/linux/revocable.h + :identifiers: revocable_try_access_or_skip_scoped + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + int consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + revocable_try_access_or_skip_scoped(rev, res) { + // This block is ONLY entered if 'res' is not NULL. + do_something_with(res); + } + // revocable_withdraw_access() is automatically called here. + + return 0; + } diff --git a/MAINTAINERS b/MAINTAINERS index b2040011a386..424847de7a17 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7813,6 +7813,7 @@ F: include/linux/fwnode.h F: include/linux/kobj* F: include/linux/ksysfs.h F: include/linux/property.h +F: include/linux/revocable.h F: include/linux/sysfs.h F: kernel/ksysfs.c F: lib/kobj* @@ -22862,6 +22863,14 @@ F: include/uapi/linux/rseq.h F: kernel/rseq.c F: tools/testing/selftests/rseq/ =20 +REVOCABLE RESOURCE MANAGEMENT +M: Tzung-Bi Shih +L: driver-core@lists.linux.dev +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-co= re.git +F: drivers/base/revocable.c +F: include/linux/revocable.h + RFKILL M: Johannes Berg L: linux-wireless@vger.kernel.org diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 8074a10183dc..bdf854694e39 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -6,7 +6,7 @@ obj-y :=3D component.o core.o bus.o dd.o syscore.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ topology.o container.o property.o cacheinfo.o \ - swnode.o faux.o + swnode.o faux.o revocable.o obj-$(CONFIG_AUXILIARY_BUS) +=3D auxiliary.o obj-$(CONFIG_DEVTMPFS) +=3D devtmpfs.o obj-y +=3D power/ diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c new file mode 100644 index 000000000000..3fb747d749ab --- /dev/null +++ b/drivers/base/revocable.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 Google LLC + * + * Revocable resource management + */ + +#include +#include +#include +#include + +/** + * DOC: Overview + * + * The "revocable" mechanism is a synchronization primitive designed to + * manage safe access to resources that can be asynchronously removed or + * invalidated. Its primary purpose is to prevent Use-After-Free (UAF) + * errors when interacting with resources whose lifetimes are not + * guaranteed to outlast their consumers. + * + * This is particularly useful in systems where resources can disappear + * unexpectedly, such as those provided by hot-pluggable devices like + * USB. When a consumer holds a reference to such a resource, the + * underlying device might be removed, causing the resource's memory to + * be freed. Subsequent access attempts by the consumer would then lead + * to UAF errors. + * + * Revocable addresses this by providing a form of "weak reference" and + * a controlled access method. It allows a resource consumer to safely + * attempt to access the resource. The mechanism guarantees that any + * access granted is valid for the duration of its use. If the resource + * has already been revoked (i.e., freed), the access attempt will fail + * safely, typically by returning NULL, instead of causing a crash. + * + * It uses a provider/consumer model built on Sleepable RCU (SRCU) to + * guarantee safe memory access: + * + * - A resource provider, such as a driver for a hot-pluggable device, + * allocates a struct revocable and initializes it with a pointer + * to the resource. + * + * - A resource consumer that wants to access the resource allocates a + * struct revocable_handle containing a reference to the provider. + * + * - To access the resource, the consumer uses revocable_try_access(). + * This function enters an SRCU read-side critical section and returns + * the pointer to the resource. If the provider has already freed the + * resource, it returns NULL. After use, the consumer calls + * revocable_withdraw_access() to exit the SRCU critical section. There + * are some macro level helpers for doing that. + * + * The API provides the following contract: + * + * - revocable_try_access() can be safely called from both process and + * atomic contexts. + * - It is permitted to sleep within the critical section established + * between revocable_try_access() and revocable_withdraw_access(). + * - revocable_try_access() and the matching revocable_withdraw_access() + * must occur in the same context. For example, it is illegal to + * invoke revocable_withdraw_access() in an irq handler if the matching + * revocable_try_access() was invoked in process context. + * + * - When the provider needs to remove the resource, it calls + * revocable_revoke(). This function sets the internal resource + * pointer to NULL and then calls synchronize_srcu() to wait for all + * current readers to finish before the resource can be completely torn + * down. + */ + +static void revocable_release(struct kref *kref) +{ + struct revocable *rev =3D container_of(kref, typeof(*rev), kref); + + cleanup_srcu_struct(&rev->srcu); + + if (!rev->embedded) + kfree(rev); +} + +/** + * revocable_alloc() - Allocate struct revocable. + * @res: The pointer of resource. + * + * This allocates a resource provider handle and holds 2 initial reference + * counts to the handle. If revocable_alloc() succeed: + * + * - The provider should call revocable_revoke() for dropping a reference. + * - The caller should call revocable_put() for dropping another reference. + * + * Return: The pointer of struct revocable. NULL on errors. + */ +struct revocable *revocable_alloc(void *res) +{ + struct revocable *rev; + int ret; + + rev =3D kzalloc_obj(*rev); + if (!rev) + return NULL; + + ret =3D revocable_init(rev, res); + if (ret) { + kfree(rev); + return NULL; + } + + rev->embedded =3D false; + return rev; +} +EXPORT_SYMBOL_GPL(revocable_alloc); + +/** + * revocable_init() - Initialize struct revocable. + * @rev: The pointer of resource provider. + * @res: The pointer of resource. + * + * This initializes a resource provider handle embedded within another + * structure and holds 2 initial reference counts to the handle. + * + * If revocable_init() succeed: + * + * - The provider should call revocable_revoke() for dropping a reference. + * - The caller should call revocable_put() for dropping another reference. + */ +int revocable_init(struct revocable *rev, void *res) +{ + int ret; + + ret =3D init_srcu_struct(&rev->srcu); + if (ret) + return ret; + + RCU_INIT_POINTER(rev->res, res); + kref_init(&rev->kref); + kref_get(&rev->kref); + rev->embedded =3D true; + return 0; +} +EXPORT_SYMBOL_GPL(revocable_init); + +/** + * revocable_revoke() - Revoke the managed resource. + * @rev: The pointer of resource provider. + * + * This sets the resource `(struct revocable *)->res` to NULL to indicate + * the resource has gone. + * + * This drops a reference count to the resource provider. If it is the + * final reference, revocable_release() will be called to free the internal + * resources. + */ +void revocable_revoke(struct revocable *rev) +{ + rcu_assign_pointer(rev->res, NULL); + synchronize_srcu(&rev->srcu); + revocable_put(rev); +} +EXPORT_SYMBOL_GPL(revocable_revoke); + +/** + * revocable_get() - Increase a reference count to the provider handle. + * @rev: The pointer of resource provider. + * + * This increments the reference count. + */ +void revocable_get(struct revocable *rev) +{ + kref_get(&rev->kref); +} +EXPORT_SYMBOL_GPL(revocable_get); + +/** + * revocable_put() - Decrease a reference count to the provider handle. + * @rev: The pointer of resource provider. + * + * This drops a reference count to the resource provider. If it is the + * final reference, revocable_release() will be called to free the internal + * resources. + */ +void revocable_put(struct revocable *rev) +{ + kref_put(&rev->kref, revocable_release); +} +EXPORT_SYMBOL_GPL(revocable_put); + +/** + * revocable_handle_init() - Initialize struct revocable_handle. + * @rev: The pointer of resource provider. + * @rh: The pointer of resource_handle. + * + * This initializes a handle owned by the consumer and holds a reference + * count to the resource provider. + */ +void revocable_handle_init(struct revocable *rev, struct revocable_handle = *rh) +{ + revocable_get(rev); + rh->rev =3D rev; +} +EXPORT_SYMBOL_GPL(revocable_handle_init); + +/** + * revocable_handle_deinit() - Deinitialize struct revocable_handle. + * @rh: The pointer of resource_handle. + * + * This drops a reference count to the resource provider. If it is the + * final reference, revocable_release() will be called to free the internal + * resources. + */ +void revocable_handle_deinit(struct revocable_handle *rh) +{ + struct revocable *rev =3D rh->rev; + + revocable_put(rev); +} +EXPORT_SYMBOL_GPL(revocable_handle_deinit); + +/** + * revocable_try_access() - Try to access the resource. + * @rh: The pointer of resource_handle. + * + * This tries to de-reference to the resource and enters a SRCU critical + * section. + * + * The function is safe to be called from both process and atomic contexts. + * While holding the access (i.e. before calling revocable_withdraw_access= ()), + * the caller is allowed to sleep. + * + * Note that revocable_try_access() and the matching + * revocable_withdraw_access() must occur in the same context. For exampl= e, it + * is illegal to invoke revocable_withdraw_access() in an irq handler if t= he + * matching revocable_try_access() was invoked in process context. + * + * Return: The pointer to the resource. NULL if the resource has gone. + */ +void *revocable_try_access(struct revocable_handle *rh) + __acquires(&rh->rev->srcu) +{ + struct revocable *rev =3D rh->rev; + + rh->idx =3D srcu_read_lock(&rev->srcu); + return srcu_dereference(rev->res, &rev->srcu); +} +EXPORT_SYMBOL_GPL(revocable_try_access); + +/** + * revocable_withdraw_access() - Stop accessing to the resource. + * @rh: The pointer of resource_handle. + * + * Call this function to indicate the resource is no longer used. It exits + * the SRCU critical section. + * + * The function is safe to be called from both process and atomic contexts. + * + * Note that revocable_try_access() and the matching + * revocable_withdraw_access() must occur in the same context. For exampl= e, it + * is illegal to invoke revocable_withdraw_access() in an irq handler if t= he + * matching revocable_try_access() was invoked in process context. + */ +void revocable_withdraw_access(struct revocable_handle *rh) + __releases(&rh->rev->srcu) +{ + struct revocable *rev =3D rh->rev; + + srcu_read_unlock(&rev->srcu, rh->idx); +} +EXPORT_SYMBOL_GPL(revocable_withdraw_access); diff --git a/include/linux/revocable.h b/include/linux/revocable.h new file mode 100644 index 000000000000..b66d41b92ee5 --- /dev/null +++ b/include/linux/revocable.h @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2026 Google LLC + */ + +#ifndef __LINUX_REVOCABLE_H +#define __LINUX_REVOCABLE_H + +#include +#include +#include + +/** + * struct revocable - A handle for resource provider. + * @srcu: The SRCU to protect the resource. + * @res: The pointer of resource. It can point to anything. + * @kref: The refcount for this handle. + * @embedded: Indicate if the handle is embedded in another struct. + * + * Note: All members of this structure are intended to be opaque and should + * not be accessed directly by the users. + */ +struct revocable { + struct srcu_struct srcu; + void __rcu *res; + struct kref kref; + bool embedded; +}; + +/** + * struct revocable_handle - A handle for resource consumer. + * @rev: The pointer of resource provider. + * @idx: The index for the SRCU critical section. + * + * Note: All members of this structure are intended to be opaque and should + * not be accessed directly by the users. + */ +struct revocable_handle { + struct revocable *rev; + int idx; +}; + +struct revocable *revocable_alloc(void *res); +int revocable_init(struct revocable *rev, void *res); +void revocable_revoke(struct revocable *rev); +void revocable_get(struct revocable *rev); +void revocable_put(struct revocable *rev); + +void revocable_handle_init(struct revocable *rev, struct revocable_handle = *rh); +void revocable_handle_deinit(struct revocable_handle *rh); +void *revocable_try_access(struct revocable_handle *rh) + __acquires(&rh->rev->srcu); +void revocable_withdraw_access(struct revocable_handle *rh) + __releases(&rh->rev->srcu); + +DEFINE_FREE(access_rev, struct revocable_handle *, { + revocable_withdraw_access(_T); + revocable_handle_deinit(_T); +}) + +#define _revocable_try_access_with(_rev, _rh, _res) \ + struct revocable_handle _rh; \ + struct revocable_handle *__UNIQUE_ID(name) __free(access_rev) =3D &_rh; \ + \ + revocable_handle_init(_rev, &_rh); \ + _res =3D revocable_try_access(&_rh) + +/** + * revocable_try_access_with() - A helper for accessing revocable resource + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * + * The macro simplifies the access-release cycle for consumers, ensuring t= hat + * corresponding revocable_withdraw_access() and revocable_handle_deinit()= are + * called, even in the case of an early exit. + * + * It creates a local variable in the current scope. @_res is populated w= ith + * the result of revocable_try_access(). Callers **must** check if @_res = is + * ``NULL`` before using it. The revocable_withdraw_access() function is + * automatically called when the scope is exited. + * + * Note: It shares the same issue with guard() in cleanup.h. No goto stat= ements + * are allowed before the helper. Otherwise, the compiler fails with + * "jump bypasses initialization of variable with __attribute__((cleanup))= ". + */ +#define revocable_try_access_with(_rev, _res) \ + _revocable_try_access_with(_rev, __UNIQUE_ID(name), _res) + +/** + * revocable_try_access_or_return_err() - Variant of revocable_try_access_= with() + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * @_err: The error code to return if resource is revoked. + * + * Similar to revocable_try_access_with() but returns from the current fun= ction + * with @_err if the resource is revoked. Callers don't need to check @_r= es for + * ``NULL`` as this handles the revocation case by returning early. + */ +#define revocable_try_access_or_return_err(_rev, _res, _err) \ + _revocable_try_access_with(_rev, __UNIQUE_ID(name), _res); \ + if (!_res) \ + return _err + +/** + * revocable_try_access_or_return() - Variant of revocable_try_access_with= () + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * + * Similar to revocable_try_access_or_return_err() but returns -ENODEV if = the + * resource is revoked. + */ +#define revocable_try_access_or_return(_rev, _res) \ + revocable_try_access_or_return_err(_rev, _res, -ENODEV) + +/** + * revocable_try_access_or_return_void() - Variant of revocable_try_access= _with() + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * + * Similar to revocable_try_access_or_return_err() but returns void if the + * resource is revoked. + */ +#define revocable_try_access_or_return_void(_rev, _res) \ + revocable_try_access_or_return_err(_rev, _res, ) + +#define _revocable_try_access_with_scoped(_rev, _rh, _label, _res) \ + for (struct revocable_handle _rh, \ + *__UNIQUE_ID(name) __free(access_rev) =3D &_rh; \ + ({ revocable_handle_init(_rev, &_rh); \ + _res =3D revocable_try_access(&_rh); \ + true; }); \ + ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ + } else + +/** + * revocable_try_access_with_scoped() - Variant of revocable_try_access_wi= th() + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * + * Similar to revocable_try_access_with() but with an explicit scope from a + * temporary ``for`` loop. + */ +#define revocable_try_access_with_scoped(_rev, _res) \ + _revocable_try_access_with_scoped(_rev, __UNIQUE_ID(name), \ + __UNIQUE_ID(label), _res) + +/** + * revocable_try_access_or_return_err_scoped() - Variant of revocable_try_= access_with_scoped() + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * @_err: The error code to return if resource is revoked. + * + * Similar to revocable_try_access_with_scoped() but returns from the curr= ent + * function with @_err if the resource is revoked. Callers don't need to = check + * @_res for ``NULL`` as this handles the revocation case by returning ear= ly. + */ +#define revocable_try_access_or_return_err_scoped(_rev, _res, _err) \ + _revocable_try_access_with_scoped(_rev, __UNIQUE_ID(name), \ + __UNIQUE_ID(label), _res) \ + if (!_res) { \ + return _err; \ + } else + +/** + * revocable_try_access_or_return_scoped() - Variant of revocable_try_acce= ss_with_scoped() + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * + * Similar to revocable_try_access_or_return_err_scoped() but returns -ENO= DEV + * if the resource is revoked. + */ +#define revocable_try_access_or_return_scoped(_rev, _res) \ + revocable_try_access_or_return_err_scoped(_rev, _res, -ENODEV) + +/** + * revocable_try_access_or_return_void_scoped() - Variant of revocable_try= _access_with_scoped() + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * + * Similar to revocable_try_access_or_return_err_scoped() but returns void + * if the resource is revoked. + */ +#define revocable_try_access_or_return_void_scoped(_rev, _res) \ + revocable_try_access_or_return_err_scoped(_rev, _res, ) + +/** + * revocable_try_access_or_skip_scoped() - Variant of revocable_try_access= _with_scoped() + * @_rev: The pointer of resource provider. + * @_res: A pointer variable that will be assigned the resource. + * + * Similar to revocable_try_access_with_scoped() but skips the following c= ode + * block if the resource is revoked. + */ +#define revocable_try_access_or_skip_scoped(_rev, _res) \ + _revocable_try_access_with_scoped(_rev, __UNIQUE_ID(name), \ + __UNIQUE_ID(label), _res) \ + if (!_res) { \ + break; \ + } else + +#endif /* __LINUX_REVOCABLE_H */ --=20 2.51.0 From nobody Fri Jun 12 07:36:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8443C3E5A0B; Wed, 13 May 2026 09:11:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663508; cv=none; b=f6bvEvf/DeAwyQHo9S05W6xO38wDY8Kc9Kwo5NZgPaffdirepYI98ixl+Wiuf9m9xgYQww9O80PEFH6xpav7t0/UrKuK8Ry1tCNr7Uh+e+HjJfeZ1lykKOh0dDQCyuXdKDne37tbvgGXWpaG0Awu9bID9FnN5hj+7s6TUPS/+5U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663508; c=relaxed/simple; bh=1KZndV5SJKsaAAM++jaA27myr0QkOO/SO6ts0pSWRfA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rZEl5Lb/eDG6Kknb2RtwnoQ6ipKXRKy45lyHqgEv3DLk4M45cCClFte+oipc45aYZP128hiwX081dVaFg0r4ZVEZCF4dO0RemkbTUQWlF57ssP7hlWJwGvZoUfGTfbc8/IOESsjSiF5qDqhPxkmGNIsxZm9KuXXb6u91tuyz4Eo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=rmNaef2/; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="rmNaef2/" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A794DC2BCC7; Wed, 13 May 2026 09:11:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778663508; bh=1KZndV5SJKsaAAM++jaA27myr0QkOO/SO6ts0pSWRfA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rmNaef2/QZnkif9QQgrYTm8cRZHLsIsDhEbxc4unXTpdlKcaBYIMPww/y4toOs1nO l1aEjtQoCaMdHVV+u5BZD3p6wcvtFrOcFKiOHt4c+MhTuaepvJMEeHG73Aa1fWBdJb rf+8aOAGY60Hv5zUazIivyEMsu4o6IfvBo2kKRR8T0rov7AhlDy3jC2sxtg7Q1NxYs 6NX9tVY1rQIrQNnHthChnRVe5S03lsbyLHfRZ9SW/QQWDRikNnOySCbL48vsb8Y+Ky 5dYFkcCQCG3vWOeGpQDq9rlsJD9zq26DuXfB6Q/tNduNxCAmN0TPNdnps+sDBr24r6 UokrNkpy94Fyw== From: Tzung-Bi Shih To: Arnd Bergmann , Greg Kroah-Hartman , Bartosz Golaszewski , Linus Walleij Cc: Benson Leung , tzungbi@kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, driver-core@lists.linux.dev, linux-doc@vger.kernel.org, linux-gpio@vger.kernel.org, "Rafael J. Wysocki" , Danilo Krummrich , Jonathan Corbet , Shuah Khan , Laurent Pinchart , Wolfram Sang , Jason Gunthorpe , Johan Hovold , "Paul E . McKenney" , Bartosz Golaszewski Subject: [PATCH v11 2/5] revocable: Add KUnit test cases Date: Wed, 13 May 2026 17:10:40 +0800 Message-ID: <20260513091043.6766-3-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260513091043.6766-1-tzungbi@kernel.org> References: <20260513091043.6766-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add KUnit test cases for the revocable API. The test cases cover the following scenarios: - Basic: Verifies that a consumer can successfully access the resource. - Revocation: Verifies that after the provider revokes the resource, the consumer correctly receives a NULL pointer on a subsequent access. - Try Access Macro: Same as "Revocation" but uses the macro level helpers. - Concurrent Access: Verifies multiple threads can access the resource. Reviewed-by: Bartosz Golaszewski Signed-off-by: Tzung-Bi Shih --- v11: - Move the test to drivers/base/test/. - Add R-b tag. v10: https://lore.kernel.org/all/20260508105448.31799-3-tzungbi@kernel.org - Merge revocable_test_try_access_macro*() cases. - Change revocable API usages accordingly. v9: https://lore.kernel.org/all/20260427135841.96266-3-tzungbi@kernel.org - Add test cases for embedded resource provider. v8: https://lore.kernel.org/all/20260213092307.858908-3-tzungbi@kernel.org - Squash: - c259cd7ea3c9 revocable: fix missing module license and description - a243f7fb11fe revocable: Add KUnit test for provider lifetime races - 988357628c2c revocable: Add KUnit test for concurrent access - Change accordingly due to its dependency "revocable: Revocable resource management" changes. v7: https://lore.kernel.org/all/20260116080235.350305-3-tzungbi@kernel.org - "2025" -> "2026" in copyright. - Rename the test name "macro" -> "try_access_macro". v6: https://lore.kernel.org/all/20251106152330.11733-3-tzungbi@kernel.org - Rename REVOCABLE_TRY_ACCESS_WITH() -> REVOCABLE_TRY_ACCESS_SCOPED(). - Add tests for new REVOCABLE_TRY_ACCESS_WITH(). v5: https://lore.kernel.org/all/20251016054204.1523139-3-tzungbi@kernel.org - No changes. v4: https://lore.kernel.org/all/20250923075302.591026-3-tzungbi@kernel.org - REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH(). - revocable_release() -> revocable_withdraw_access(). v3: https://lore.kernel.org/all/20250912081718.3827390-3-tzungbi@kernel.org - No changes. v2: https://lore.kernel.org/all/20250820081645.847919-3-tzungbi@kernel.org - New in the series. A way to run the test: $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=3Dy \ revocable_test Or $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=3Dy \ --kconfig_add CONFIG_PROVE_LOCKING=3Dy \ --kconfig_add CONFIG_DEBUG_KERNEL=3Dy \ --kconfig_add CONFIG_DEBUG_INFO=3Dy \ --kconfig_add CONFIG_DEBUG_INFO_DWARF5=3Dy \ --kconfig_add CONFIG_KASAN=3Dy \ --kconfig_add CONFIG_DETECT_HUNG_TASK=3Dy \ --kconfig_add CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=3D"10" \ --arch=3Dx86_64 \ --make_options=3D"C=3D1 W=3D1" \ revocable_test --- MAINTAINERS | 1 + drivers/base/test/Kconfig | 5 + drivers/base/test/Makefile | 2 + drivers/base/test/revocable-test.c | 406 +++++++++++++++++++++++++++++ 4 files changed, 414 insertions(+) create mode 100644 drivers/base/test/revocable-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 424847de7a17..24c884e19cd5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22869,6 +22869,7 @@ L: driver-core@lists.linux.dev S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-co= re.git F: drivers/base/revocable.c +F: drivers/base/revocable_test.c F: include/linux/revocable.h =20 RFKILL diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig index 2756870615cc..fde950fcfac9 100644 --- a/drivers/base/test/Kconfig +++ b/drivers/base/test/Kconfig @@ -18,3 +18,8 @@ config DRIVER_PE_KUNIT_TEST tristate "KUnit Tests for property entry API" if !KUNIT_ALL_TESTS depends on KUNIT default KUNIT_ALL_TESTS + +config REVOCABLE_KUNIT_TEST + tristate "KUnit tests for revocable" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile index e321dfc7e922..7b5832d38436 100644 --- a/drivers/base/test/Makefile +++ b/drivers/base/test/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_DM_KUNIT_TEST) +=3D platform-device-test.o =20 obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) +=3D property-entry-test.o CFLAGS_property-entry-test.o +=3D $(DISABLE_STRUCTLEAK_PLUGIN) + +obj-$(CONFIG_REVOCABLE_KUNIT_TEST) +=3D revocable-test.o diff --git a/drivers/base/test/revocable-test.c b/drivers/base/test/revocab= le-test.c new file mode 100644 index 000000000000..85ec83412bbf --- /dev/null +++ b/drivers/base/test/revocable-test.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 Google LLC + * + * KUnit tests for the revocable API. + * + * The test cases cover the following scenarios: + * + * - Basic: Verifies that a consumer can successfully access the resource. + * + * - Revocation: Verifies that after the provider revokes the resource, + * the consumer correctly receives a NULL pointer on a subsequent access. + * + * - Try Access Macro: Same as "Revocation" but uses the macro level + * helpers. + * + * - Concurrent Access: Verifies multiple threads can access the resource. + */ + +#include + +#include +#include +#include +#include +#include + +static int get_refcount(struct revocable *rev) +{ + return refcount_read(&rev->kref.refcount); +} + +static void revocable_test_basic(struct kunit *test) +{ + struct revocable *rev; + struct revocable_handle rh; + void *real_res =3D (void *)0x12345678, *res; + + rev =3D revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_FALSE(test, rev->embedded); + + revocable_handle_init(rev, &rh); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + + res =3D revocable_try_access(&rh); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rh); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + revocable_handle_deinit(&rh); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + revocable_put(rev); +} + +static void revocable_embedded_test_basic(struct kunit *test) +{ + struct revocable rev; + struct revocable_handle rh; + void *real_res =3D (void *)0x12345678, *res; + + revocable_init(&rev, real_res); + KUNIT_EXPECT_TRUE(test, rev.embedded); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 2); + + revocable_handle_init(&rev, &rh); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 3); + + res =3D revocable_try_access(&rh); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rh); + + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 3); + revocable_handle_deinit(&rh); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 2); + revocable_revoke(&rev); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 1); + revocable_put(&rev); +} + +static void revocable_test_revocation(struct kunit *test) +{ + struct revocable *rev; + struct revocable_handle rh; + void *real_res =3D (void *)0x12345678, *res; + + rev =3D revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_FALSE(test, rev->embedded); + + revocable_handle_init(rev, &rh); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + + res =3D revocable_try_access(&rh); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rh); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + res =3D revocable_try_access(&rh); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + revocable_withdraw_access(&rh); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + revocable_handle_deinit(&rh); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + revocable_put(rev); +} + +static void revocable_embedded_test_revocation(struct kunit *test) +{ + struct revocable rev; + struct revocable_handle rh; + void *real_res =3D (void *)0x12345678, *res; + + revocable_init(&rev, real_res); + KUNIT_EXPECT_TRUE(test, rev.embedded); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 2); + + revocable_handle_init(&rev, &rh); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 3); + + res =3D revocable_try_access(&rh); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rh); + + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 3); + revocable_revoke(&rev); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 2); + + res =3D revocable_try_access(&rh); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + revocable_withdraw_access(&rh); + + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 2); + revocable_handle_deinit(&rh); + KUNIT_EXPECT_EQ(test, get_refcount(&rev), 1); + revocable_put(&rev); +} + +static int call_revocable_try_access_or_return_err(struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_err(rev, res, -ENXIO); + return 0; +} + +static int call_revocable_try_access_or_return(struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return(rev, res); + return 0; +} + +static void call_revocable_try_access_or_return_void(struct kunit *test, + struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_void(rev, res); + KUNIT_FAIL(test, "unreachable"); +} + +static int call_revocable_try_access_or_return_err_scoped(struct revocable= *rev) +{ + void *res; + + revocable_try_access_or_return_err_scoped(rev, res, -ENXIO) {} + return 0; +} + +static int call_revocable_try_access_or_return_scoped(struct revocable *re= v) +{ + void *res; + + revocable_try_access_or_return_scoped(rev, res) {} + return 0; +} + +static void call_revocable_try_access_or_return_void_scoped(struct kunit *= test, + struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_void_scoped(rev, res) {} + KUNIT_FAIL(test, "unreachable"); +} + +static void revocable_test_try_access_macro(struct kunit *test) +{ + struct revocable *rev; + void *real_res =3D (void *)0x12345678, *res; + int ret; + bool accessed; + + rev =3D revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_FALSE(test, rev->embedded); + + { + revocable_try_access_with(rev, res); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + } + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + accessed =3D false; + revocable_try_access_with_scoped(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + accessed =3D true; + } + KUNIT_EXPECT_TRUE(test, accessed); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + { + revocable_try_access_with(rev, res); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + } + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + accessed =3D false; + revocable_try_access_with_scoped(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + accessed =3D true; + } + KUNIT_EXPECT_TRUE(test, accessed); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + ret =3D call_revocable_try_access_or_return_err(rev); + KUNIT_EXPECT_EQ(test, ret, -ENXIO); + + ret =3D call_revocable_try_access_or_return(rev); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); + + call_revocable_try_access_or_return_void(test, rev); + + ret =3D call_revocable_try_access_or_return_err_scoped(rev); + KUNIT_EXPECT_EQ(test, ret, -ENXIO); + + ret =3D call_revocable_try_access_or_return_scoped(rev); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); + + call_revocable_try_access_or_return_void_scoped(test, rev); + + accessed =3D false; + revocable_try_access_or_skip_scoped(rev, res) + accessed =3D true; + KUNIT_EXPECT_FALSE(test, accessed); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + revocable_put(rev); +} + +struct test_concurrent_access_context { + struct completion started, enter; + struct task_struct *thread; + + union { + /* Used by test provider. */ + struct revocable *rev; + + /* Used by test consumer. */ + struct { + struct completion exit; + struct revocable_handle rh; + struct kunit *test; + void *expected_res; + }; + }; +}; + +static int test_concurrent_access_provider(void *data) +{ + struct test_concurrent_access_context *ctx =3D data; + + complete(&ctx->started); + + wait_for_completion(&ctx->enter); + revocable_revoke(ctx->rev); + + return 0; +} + +static int test_concurrent_access_consumer(void *data) +{ + struct test_concurrent_access_context *ctx =3D data; + void *res; + + complete(&ctx->started); + + wait_for_completion(&ctx->enter); + res =3D revocable_try_access(&ctx->rh); + KUNIT_EXPECT_PTR_EQ(ctx->test, res, ctx->expected_res); + + wait_for_completion(&ctx->exit); + revocable_withdraw_access(&ctx->rh); + + return 0; +} + +static void revocable_test_concurrent_access(struct kunit *test) +{ + struct revocable *rev; + void *real_res =3D (void *)0x12345678; + struct test_concurrent_access_context *ctx; + int i; + + rev =3D revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_FALSE(test, rev->embedded); + + ctx =3D kunit_kmalloc_array(test, 3, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + for (i =3D 0; i < 3; ++i) { + ctx[i].test =3D test; + init_completion(&ctx[i].started); + init_completion(&ctx[i].enter); + + if (i =3D=3D 0) { + /* Transfer the ownership of provider reference too. */ + ctx[i].rev =3D rev; + ctx[i].thread =3D kthread_run( + test_concurrent_access_provider, ctx + i, + "revocable_%d", i); + } else { + init_completion(&ctx[i].exit); + revocable_handle_init(rev, &ctx[i].rh); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2 + i); + + ctx[i].thread =3D kthread_run( + test_concurrent_access_consumer, ctx + i, + "revocable_handle_%d", i); + } + KUNIT_ASSERT_FALSE(test, IS_ERR(ctx[i].thread)); + + wait_for_completion(&ctx[i].started); + } + + ctx[1].expected_res =3D real_res; + /* consumer1 enters read-side critical section. */ + complete(&ctx[1].enter); + msleep(100); + + /* provider0 revokes the resource. */ + complete(&ctx[0].enter); + msleep(100); + /* provider0 can't exit. It's waiting for the grace period. */ + KUNIT_EXPECT_EQ(test, get_refcount(rev), 4); + + ctx[2].expected_res =3D NULL; + /* consumer2 enters read-side critical section. */ + complete(&ctx[2].enter); + msleep(100); + + /* consumer{1,2} exit read-side critical section. */ + for (i =3D 1; i < 3; ++i) { + complete(&ctx[i].exit); + kthread_stop(ctx[i].thread); + revocable_handle_deinit(&ctx[i].rh); + } + + kthread_stop(ctx[0].thread); + /* provider0 exits as all readers exit their critical section. */ + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + /* Drop the caller reference. */ + revocable_put(rev); +} + +static struct kunit_case revocable_test_cases[] =3D { + KUNIT_CASE(revocable_test_basic), + KUNIT_CASE(revocable_embedded_test_basic), + KUNIT_CASE(revocable_test_revocation), + KUNIT_CASE(revocable_embedded_test_revocation), + KUNIT_CASE(revocable_test_try_access_macro), + KUNIT_CASE(revocable_test_concurrent_access), + {} +}; + +static struct kunit_suite revocable_test_suite =3D { + .name =3D "revocable_test", + .test_cases =3D revocable_test_cases, +}; + +kunit_test_suite(revocable_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tzung-Bi Shih "); +MODULE_DESCRIPTION("KUnit tests for the revocable API"); --=20 2.51.0 From nobody Fri Jun 12 07:36:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 902333E5EF7; Wed, 13 May 2026 09:11:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663512; cv=none; b=B+eleFzOU13qFu8R1QwNBQUtvdEFGCiADRMbPIb7xD3SADA2tSbGf0MrJFsljnLeCvDEH62WxxQqE1uOk3pD8R3cjRmGte0uh+C7L+bSjroIfeEBjd/v3M5o+LFYkfHoQablWfDKGwQ6nzp+pMYSKlVHbbERnToTHL3aMAedCSs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663512; c=relaxed/simple; bh=3WNb9RBykiiRcbsrWg+VsLnkPYf5X0akzCxR1z7nSkQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uLphT5guPvZ818SKpnlWzDRVRPzfaXew3/haz7cw2WVAw8QFlXPvHCaQmepBdW1Wglx7i6D88CwqseYOgq0WBD8pLuqwlczqTR24oIUuFDOn8OxyYXdQSnwDwWgvlSboAXyDu7UKYi2qHmoPnppG+ZJgP0Wfs4rJB2pXqZzI4rA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VNRGAfAl; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="VNRGAfAl" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 85DA5C2BCB7; Wed, 13 May 2026 09:11:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778663512; bh=3WNb9RBykiiRcbsrWg+VsLnkPYf5X0akzCxR1z7nSkQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VNRGAfAlz6qTU4uK+YW73tUbHHJX7m1BHI4qI6E+tQudGRPQcgs2XKTFxcKfUlBvI t7c0cp4XREJsEbKBy5A5dglrqeVnIOKW6VffMxDu3mZpGSj6JvxydFHF5ypLQwpXyl DnLici9bet3H6C4Z+VvfrfwLub5jPS7A62/raL1DaUSBG0ivHN9Ho+81AWY//bX2OS bw57uvu5B0dT59msKV752IppqH3FQYX5je19B3QLV9OstI/QA1WWJvRC8hQrWi0Nfu dspJM92PttR7CEHoOwkRmzOaD/G8mkmO6kybaS4wwE2MllXWT7WACt7xPY2SpbSIHY lxQG/57G+qMrw== From: Tzung-Bi Shih To: Arnd Bergmann , Greg Kroah-Hartman , Bartosz Golaszewski , Linus Walleij Cc: Benson Leung , tzungbi@kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, driver-core@lists.linux.dev, linux-doc@vger.kernel.org, linux-gpio@vger.kernel.org, "Rafael J. Wysocki" , Danilo Krummrich , Jonathan Corbet , Shuah Khan , Laurent Pinchart , Wolfram Sang , Jason Gunthorpe , Johan Hovold , "Paul E . McKenney" , Bartosz Golaszewski Subject: [PATCH v11 3/5] gpio: Leverage revocable for accessing struct gpio_chip Date: Wed, 13 May 2026 17:10:41 +0800 Message-ID: <20260513091043.6766-4-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260513091043.6766-1-tzungbi@kernel.org> References: <20260513091043.6766-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The underlying chip can be removed asynchronously. `gdev->srcu` is used to ensure the synchronization before accessing `gdev->chip`. Revocable encapsulates the details. Leverage revocable for accessing the struct gpio_chip and remove the `gdev->srcu`. Tested-by: Bartosz Golaszewski Signed-off-by: Tzung-Bi Shih --- v11: - Add Tested-by tag. - Squash - https://lore.kernel.org/all/20260508105448.31799-5-tzungbi@kernel.org - https://lore.kernel.org/all/20260508105448.31799-6-tzungbi@kernel.org - https://lore.kernel.org/all/20260508105448.31799-7-tzungbi@kernel.org - https://lore.kernel.org/all/20260508105448.31799-8-tzungbi@kernel.org v10: https://lore.kernel.org/all/20260508105448.31799-4-tzungbi@kernel.org - Change revocable API usages accordingly. v9: https://lore.kernel.org/all/20260427135841.96266-4-tzungbi@kernel.org - New to the series. - Use static allocated resource provider. - Rename "chip_rp" -> "chip_rev". v4 - v8: - Doesn't exist. v3: https://lore.kernel.org/all/20260213092958.864411-8-tzungbi@kernel.org - Change revocable API usages accordingly. v2: https://lore.kernel.org/all/20260203061059.975605-8-tzungbi@kernel.org - Change usages accordingly after applying https://lore.kernel.org/all/20260129143733.45618-2-tzungbi@kernel.org. - Add __rcu for `chip_rp`. - Pass pointer of pointer to revocable_provider_revoke(). - Rebase accordingly after applying https://lore.kernel.org/all/20260203060210.972243-1-tzungbi@kernel.org. v1: https://lore.kernel.org/all/20260116081036.352286-13-tzungbi@kernel.org --- drivers/gpio/gpiolib-cdev.c | 77 ++++------ drivers/gpio/gpiolib-sysfs.c | 31 ++--- drivers/gpio/gpiolib.c | 263 ++++++++++++++--------------------- drivers/gpio/gpiolib.h | 28 +--- 4 files changed, 150 insertions(+), 249 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index f36b7c06996d..4837497c5e6e 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -210,11 +211,9 @@ static long linehandle_ioctl(struct file *file, unsign= ed int cmd, DECLARE_BITMAP(vals, GPIOHANDLES_MAX); unsigned int i; int ret; + struct gpio_chip *gc; =20 - guard(srcu)(&lh->gdev->srcu); - - if (!rcu_access_pointer(lh->gdev->chip)) - return -ENODEV; + revocable_try_access_or_return(&lh->gdev->chip_rev, gc); =20 switch (cmd) { case GPIOHANDLE_GET_LINE_VALUES_IOCTL: @@ -1432,11 +1431,9 @@ static long linereq_ioctl(struct file *file, unsigne= d int cmd, { struct linereq *lr =3D file->private_data; void __user *ip =3D (void __user *)arg; + struct gpio_chip *gc; =20 - guard(srcu)(&lr->gdev->srcu); - - if (!rcu_access_pointer(lr->gdev->chip)) - return -ENODEV; + revocable_try_access_or_return(&lr->gdev->chip_rev, gc); =20 switch (cmd) { case GPIO_V2_LINE_GET_VALUES_IOCTL: @@ -1463,10 +1460,10 @@ static __poll_t linereq_poll(struct file *file, { struct linereq *lr =3D file->private_data; __poll_t events =3D 0; + struct gpio_chip *gc; =20 - guard(srcu)(&lr->gdev->srcu); - - if (!rcu_access_pointer(lr->gdev->chip)) + revocable_try_access_with(&lr->gdev->chip_rev, gc); + if (!gc) return EPOLLHUP | EPOLLERR; =20 poll_wait(file, &lr->wait, wait); @@ -1485,11 +1482,9 @@ static ssize_t linereq_read(struct file *file, char = __user *buf, struct gpio_v2_line_event le; ssize_t bytes_read =3D 0; int ret; + struct gpio_chip *gc; =20 - guard(srcu)(&lr->gdev->srcu); - - if (!rcu_access_pointer(lr->gdev->chip)) - return -ENODEV; + revocable_try_access_or_return(&lr->gdev->chip_rev, gc); =20 if (count < sizeof(le)) return -EINVAL; @@ -1759,10 +1754,10 @@ static __poll_t lineevent_poll(struct file *file, { struct lineevent_state *le =3D file->private_data; __poll_t events =3D 0; + struct gpio_chip *gc; =20 - guard(srcu)(&le->gdev->srcu); - - if (!rcu_access_pointer(le->gdev->chip)) + revocable_try_access_with(&le->gdev->chip_rev, gc); + if (!gc) return EPOLLHUP | EPOLLERR; =20 poll_wait(file, &le->wait, wait); @@ -1797,11 +1792,9 @@ static ssize_t lineevent_read(struct file *file, cha= r __user *buf, ssize_t bytes_read =3D 0; ssize_t ge_size; int ret; + struct gpio_chip *gc; =20 - guard(srcu)(&le->gdev->srcu); - - if (!rcu_access_pointer(le->gdev->chip)) - return -ENODEV; + revocable_try_access_or_return(&le->gdev->chip_rev, gc); =20 /* * When compatible system call is being used the struct gpioevent_data, @@ -1879,11 +1872,9 @@ static long lineevent_ioctl(struct file *file, unsig= ned int cmd, struct lineevent_state *le =3D file->private_data; void __user *ip =3D (void __user *)arg; struct gpiohandle_data ghd; + struct gpio_chip *gc; =20 - guard(srcu)(&le->gdev->srcu); - - if (!rcu_access_pointer(le->gdev->chip)) - return -ENODEV; + revocable_try_access_or_return(&le->gdev->chip_rev, gc); =20 /* * We can get the value for an event line but not set it, @@ -2165,10 +2156,9 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *= desc, u32 debounce_period_us; unsigned long dflags; const char *label; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return; + revocable_try_access_or_return_void(&desc->gdev->chip_rev, gc); =20 memset(info, 0, sizeof(*info)); info->offset =3D gpiod_hwgpio(desc); @@ -2201,10 +2191,10 @@ static void gpio_desc_to_lineinfo(struct gpio_desc = *desc, test_bit(GPIOD_FLAG_IS_HOGGED, &dflags) || test_bit(GPIOD_FLAG_EXPORT, &dflags) || test_bit(GPIOD_FLAG_SYSFS, &dflags) || - !gpiochip_line_is_valid(guard.gc, info->offset)) { + !gpiochip_line_is_valid(gc, info->offset)) { info->flags |=3D GPIO_V2_LINE_FLAG_USED; } else if (!atomic) { - if (!pinctrl_gpio_can_use_line(guard.gc, info->offset)) + if (!pinctrl_gpio_can_use_line(gc, info->offset)) info->flags |=3D GPIO_V2_LINE_FLAG_USED; } =20 @@ -2385,12 +2375,10 @@ static long gpio_ioctl(struct file *file, unsigned = int cmd, unsigned long arg) struct gpio_chardev_data *cdev =3D file->private_data; struct gpio_device *gdev =3D cdev->gdev; void __user *ip =3D (void __user *)arg; - - guard(srcu)(&gdev->srcu); + struct gpio_chip *gc; =20 /* We fail any subsequent ioctl():s when the chip is gone */ - if (!rcu_access_pointer(gdev->chip)) - return -ENODEV; + revocable_try_access_or_return(&gdev->chip_rev, gc); =20 /* Fill in the struct and pass to userspace */ switch (cmd) { @@ -2448,12 +2436,9 @@ static void lineinfo_changed_func(struct work_struct= *work) * Pin functions are in general much more static and while it's * not 100% bullet-proof, it's good enough for most cases. */ - scoped_guard(srcu, &ctx->gdev->srcu) { - gc =3D srcu_dereference(ctx->gdev->chip, &ctx->gdev->srcu); - if (gc && - !pinctrl_gpio_can_use_line(gc, ctx->chg.info.offset)) + revocable_try_access_or_skip_scoped(&ctx->gdev->chip_rev, gc) + if (!pinctrl_gpio_can_use_line(gc, ctx->chg.info.offset)) ctx->chg.info.flags |=3D GPIO_V2_LINE_FLAG_USED; - } } =20 ret =3D kfifo_in_spinlocked(&ctx->cdev->events, &ctx->chg, 1, @@ -2534,10 +2519,10 @@ static __poll_t lineinfo_watch_poll(struct file *fi= le, { struct gpio_chardev_data *cdev =3D file->private_data; __poll_t events =3D 0; + struct gpio_chip *gc; =20 - guard(srcu)(&cdev->gdev->srcu); - - if (!rcu_access_pointer(cdev->gdev->chip)) + revocable_try_access_with(&cdev->gdev->chip_rev, gc); + if (!gc) return EPOLLHUP | EPOLLERR; =20 poll_wait(file, &cdev->wait, pollt); @@ -2557,11 +2542,9 @@ static ssize_t lineinfo_watch_read(struct file *file= , char __user *buf, ssize_t bytes_read =3D 0; int ret; size_t event_size; + struct gpio_chip *gc; =20 - guard(srcu)(&cdev->gdev->srcu); - - if (!rcu_access_pointer(cdev->gdev->chip)) - return -ENODEV; + revocable_try_access_or_return(&cdev->gdev->chip_rev, gc); =20 #ifndef CONFIG_GPIO_CDEV_V1 event_size =3D sizeof(struct gpio_v2_line_info_changed); diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index fc06b0c2881b..c40320433ff7 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -215,10 +216,9 @@ static int gpio_sysfs_request_irq(struct gpiod_data *d= ata, unsigned char flags) struct gpio_desc *desc =3D data->desc; unsigned long irq_flags; int ret; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 data->irq =3D gpiod_to_irq(desc); if (data->irq < 0) @@ -244,7 +244,7 @@ static int gpio_sysfs_request_irq(struct gpiod_data *da= ta, unsigned char flags) * Remove this redundant call (along with the corresponding unlock) * when those drivers have been fixed. */ - ret =3D gpiochip_lock_as_irq(guard.gc, gpiod_hwgpio(desc)); + ret =3D gpiochip_lock_as_irq(gc, gpiod_hwgpio(desc)); if (ret < 0) goto err_clr_bits; =20 @@ -258,7 +258,7 @@ static int gpio_sysfs_request_irq(struct gpiod_data *da= ta, unsigned char flags) return 0; =20 err_unlock: - gpiochip_unlock_as_irq(guard.gc, gpiod_hwgpio(desc)); + gpiochip_unlock_as_irq(gc, gpiod_hwgpio(desc)); err_clr_bits: clear_bit(GPIOD_FLAG_EDGE_RISING, &desc->flags); clear_bit(GPIOD_FLAG_EDGE_FALLING, &desc->flags); @@ -273,14 +273,13 @@ static int gpio_sysfs_request_irq(struct gpiod_data *= data, unsigned char flags) static void gpio_sysfs_free_irq(struct gpiod_data *data) { struct gpio_desc *desc =3D data->desc; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return; + revocable_try_access_or_return_void(&desc->gdev->chip_rev, gc); =20 data->irq_flags =3D 0; free_irq(data->irq, data); - gpiochip_unlock_as_irq(guard.gc, gpiod_hwgpio(desc)); + gpiochip_unlock_as_irq(gc, gpiod_hwgpio(desc)); clear_bit(GPIOD_FLAG_EDGE_RISING, &desc->flags); clear_bit(GPIOD_FLAG_EDGE_FALLING, &desc->flags); } @@ -473,13 +472,12 @@ static DEVICE_ATTR_RO(ngpio); static int export_gpio_desc(struct gpio_desc *desc) { int offset, ret; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 offset =3D gpiod_hwgpio(desc); - if (!gpiochip_line_is_valid(guard.gc, offset)) { + if (!gpiochip_line_is_valid(gc, offset)) { pr_debug_ratelimited("%s: GPIO %d masked\n", __func__, gpiod_hwgpio(desc)); return -EINVAL; @@ -732,6 +730,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction= _may_change) struct gpio_device *gdev; struct attribute **attrs; int status; + struct gpio_chip *gc; =20 /* can't export until sysfs is available ... */ if (!class_is_registered(&gpio_class)) { @@ -744,9 +743,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction= _may_change) return -EINVAL; } =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 if (test_and_set_bit(GPIOD_FLAG_EXPORT, &desc->flags)) return -EPERM; @@ -769,7 +766,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction= _may_change) =20 desc_data->desc =3D desc; mutex_init(&desc_data->mutex); - if (guard.gc->direction_input && guard.gc->direction_output) + if (gc->direction_input && gc->direction_output) desc_data->direction_can_change =3D direction_may_change; else desc_data->direction_can_change =3D false; diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 1e6dce430dca..5ce12f3b753f 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -334,7 +335,10 @@ EXPORT_SYMBOL(gpio_device_get_label); */ struct gpio_chip *gpio_device_get_chip(struct gpio_device *gdev) { - return rcu_dereference_check(gdev->chip, 1); + struct gpio_chip *gc; + + revocable_try_access_with(&gdev->chip_rev, gc); + return gc; } EXPORT_SYMBOL_GPL(gpio_device_get_chip); =20 @@ -424,8 +428,6 @@ static int gpiochip_get_direction(struct gpio_chip *gc,= unsigned int offset) { int ret; =20 - lockdep_assert_held(&gc->gpiodev->srcu); - if (WARN_ON(!gc->get_direction)) return -EOPNOTSUPP; =20 @@ -453,14 +455,13 @@ int gpiod_get_direction(struct gpio_desc *desc) unsigned long flags; unsigned int offset; int ret; + struct gpio_chip *gc; =20 ret =3D validate_desc(desc, __func__); if (ret <=3D 0) return -EINVAL; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 offset =3D gpiod_hwgpio(desc); flags =3D READ_ONCE(desc->flags); @@ -473,7 +474,7 @@ int gpiod_get_direction(struct gpio_desc *desc) test_bit(GPIOD_FLAG_IS_OUT, &flags)) return 0; =20 - ret =3D gpiochip_get_direction(guard.gc, offset); + ret =3D gpiochip_get_direction(gc, offset); if (ret < 0) return ret; =20 @@ -561,9 +562,7 @@ static struct gpio_desc *gpio_name_to_desc(const char *= const name) =20 list_for_each_entry_srcu(gdev, &gpio_devices, list, srcu_read_lock_held(&gpio_devices_srcu)) { - guard(srcu)(&gdev->srcu); - - gc =3D srcu_dereference(gdev->chip, &gdev->srcu); + revocable_try_access_with(&gdev->chip_rev, gc); if (!gc) continue; =20 @@ -874,10 +873,10 @@ static void gpiodev_release(struct device *dev) synchronize_srcu(&gdev->desc_srcu); cleanup_srcu_struct(&gdev->desc_srcu); =20 + revocable_put(&gdev->chip_rev); ida_free(&gpio_ida, gdev->id); kfree_const(gdev->label); kfree(gdev->descs); - cleanup_srcu_struct(&gdev->srcu); kfree(gdev); } =20 @@ -1049,9 +1048,7 @@ static void gpiochip_setup_devs(void) =20 list_for_each_entry_srcu(gdev, &gpio_devices, list, srcu_read_lock_held(&gpio_devices_srcu)) { - guard(srcu)(&gdev->srcu); - - gc =3D srcu_dereference(gdev->chip, &gdev->srcu); + revocable_try_access_with(&gdev->chip_rev, gc); if (!gc) { dev_err(&gdev->dev, "Underlying GPIO chip is gone\n"); continue; @@ -1154,14 +1151,9 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc,= void *data, goto err_free_gdev; gdev->id =3D ret; =20 - ret =3D init_srcu_struct(&gdev->srcu); - if (ret) - goto err_free_ida; - rcu_assign_pointer(gdev->chip, gc); - ret =3D init_srcu_struct(&gdev->desc_srcu); if (ret) - goto err_cleanup_gdev_srcu; + goto err_free_ida; =20 ret =3D dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id); if (ret) @@ -1210,6 +1202,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc,= void *data, else gdev->owner =3D THIS_MODULE; =20 + ret =3D revocable_init(&gdev->chip_rev, gc); + if (ret) + goto err_put_device; + scoped_guard(mutex, &gpio_devices_lock) { /* * TODO: this allocates a Linux GPIO number base in the global @@ -1224,7 +1220,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, = void *data, if (base < 0) { ret =3D base; base =3D 0; - goto err_put_device; + goto err_revoke_chip_rev; } =20 /* @@ -1244,7 +1240,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, = void *data, ret =3D gpiodev_add_to_list_unlocked(gdev); if (ret) { gpiochip_err(gc, "GPIO integer space overlap, cannot add chip\n"); - goto err_put_device; + goto err_revoke_chip_rev; } } =20 @@ -1343,14 +1339,14 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc= , void *data, scoped_guard(mutex, &gpio_devices_lock) list_del_rcu(&gdev->list); synchronize_srcu(&gpio_devices_srcu); +err_revoke_chip_rev: + revocable_revoke(&gdev->chip_rev); err_put_device: gpio_device_put(gdev); goto err_print_message; =20 err_cleanup_desc_srcu: cleanup_srcu_struct(&gdev->desc_srcu); -err_cleanup_gdev_srcu: - cleanup_srcu_struct(&gdev->srcu); err_free_ida: ida_free(&gpio_ida, gdev->id); err_free_gdev: @@ -1387,8 +1383,7 @@ void gpiochip_remove(struct gpio_chip *gc) synchronize_srcu(&gpio_devices_srcu); =20 /* Numb the device, cancelling all outstanding operations */ - rcu_assign_pointer(gdev->chip, NULL); - synchronize_srcu(&gdev->srcu); + revocable_revoke(&gdev->chip_rev); gpio_device_teardown_shared(gdev); gpiochip_irqchip_remove(gc); acpi_gpiochip_remove(gc); @@ -1449,11 +1444,11 @@ struct gpio_device *gpio_device_find(const void *da= ta, if (!device_is_registered(&gdev->dev)) continue; =20 - guard(srcu)(&gdev->srcu); - - gc =3D srcu_dereference(gdev->chip, &gdev->srcu); + revocable_try_access_with(&gdev->chip_rev, gc); + if (!gc) + continue; =20 - if (gc && match(gc, data)) + if (match(gc, data)) return gpio_device_get(gdev); } =20 @@ -2554,16 +2549,15 @@ int gpiod_request_commit(struct gpio_desc *desc, co= nst char *label) { unsigned int offset; int ret; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 if (test_and_set_bit(GPIOD_FLAG_REQUESTED, &desc->flags)) return -EBUSY; =20 offset =3D gpiod_hwgpio(desc); - if (!gpiochip_line_is_valid(guard.gc, offset)) { + if (!gpiochip_line_is_valid(gc, offset)) { ret =3D -EINVAL; goto out_clear_bit; } @@ -2572,15 +2566,15 @@ int gpiod_request_commit(struct gpio_desc *desc, co= nst char *label) * before IRQs are enabled, for non-sleeping (SOC) GPIOs. */ =20 - if (guard.gc->request) { - ret =3D guard.gc->request(guard.gc, offset); + if (gc->request) { + ret =3D gc->request(gc, offset); if (ret > 0) ret =3D -EBADE; if (ret) goto out_clear_bit; } =20 - if (guard.gc->get_direction) + if (gc->get_direction) gpiod_get_direction(desc); =20 ret =3D desc_set_label(desc, label ? : "?"); @@ -2617,16 +2611,17 @@ int gpiod_request(struct gpio_desc *desc, const cha= r *label) void gpiod_free_commit(struct gpio_desc *desc) { unsigned long flags; + struct gpio_chip *gc; =20 might_sleep(); =20 - CLASS(gpio_chip_guard, guard)(desc); + revocable_try_access_or_return_void(&desc->gdev->chip_rev, gc); =20 flags =3D READ_ONCE(desc->flags); =20 - if (guard.gc && test_bit(GPIOD_FLAG_REQUESTED, &flags)) { - if (guard.gc->free) - guard.gc->free(guard.gc, gpiod_hwgpio(desc)); + if (test_bit(GPIOD_FLAG_REQUESTED, &flags)) { + if (gc->free) + gc->free(gc, gpiod_hwgpio(desc)); =20 clear_bit(GPIOD_FLAG_ACTIVE_LOW, &flags); clear_bit(GPIOD_FLAG_REQUESTED, &flags); @@ -2778,15 +2773,14 @@ EXPORT_SYMBOL_GPL(gpiochip_free_own_desc); int gpio_do_set_config(struct gpio_desc *desc, unsigned long config) { int ret; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 - if (!guard.gc->set_config) + if (!gc->set_config) return -ENOTSUPP; =20 - ret =3D guard.gc->set_config(guard.gc, gpiod_hwgpio(desc), config); + ret =3D gc->set_config(gc, gpiod_hwgpio(desc), config); if (ret > 0) ret =3D -EBADE; =20 @@ -2899,8 +2893,6 @@ static int gpiochip_direction_input(struct gpio_chip = *gc, unsigned int offset) { int ret; =20 - lockdep_assert_held(&gc->gpiodev->srcu); - if (WARN_ON(!gc->direction_input)) return -EOPNOTSUPP; =20 @@ -2916,8 +2908,6 @@ static int gpiochip_direction_output(struct gpio_chip= *gc, unsigned int offset, { int ret; =20 - lockdep_assert_held(&gc->gpiodev->srcu); - if (WARN_ON(!gc->direction_output)) return -EOPNOTSUPP; =20 @@ -2955,17 +2945,16 @@ EXPORT_SYMBOL_GPL(gpiod_direction_input); int gpiod_direction_input_nonotify(struct gpio_desc *desc) { int ret =3D 0, dir; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 /* * It is legal to have no .get() and .direction_input() specified if * the chip is output-only, but you can't specify .direction_input() * and not support the .get() operation, that doesn't make sense. */ - if (!guard.gc->get && guard.gc->direction_input) { + if (!gc->get && gc->direction_input) { gpiod_warn(desc, "%s: missing get() but have direction_input()\n", __func__); @@ -2978,11 +2967,10 @@ int gpiod_direction_input_nonotify(struct gpio_desc= *desc) * direction (if .get_direction() is supported) else we silently * assume we are in input mode after this. */ - if (guard.gc->direction_input) { - ret =3D gpiochip_direction_input(guard.gc, - gpiod_hwgpio(desc)); - } else if (guard.gc->get_direction) { - dir =3D gpiochip_get_direction(guard.gc, gpiod_hwgpio(desc)); + if (gc->direction_input) { + ret =3D gpiochip_direction_input(gc, gpiod_hwgpio(desc)); + } else if (gc->get_direction) { + dir =3D gpiochip_get_direction(gc, gpiod_hwgpio(desc)); if (dir < 0) return dir; =20 @@ -3007,8 +2995,6 @@ static int gpiochip_set(struct gpio_chip *gc, unsigne= d int offset, int value) { int ret; =20 - lockdep_assert_held(&gc->gpiodev->srcu); - if (WARN_ON(unlikely(!gc->set))) return -EOPNOTSUPP; =20 @@ -3022,31 +3008,28 @@ static int gpiochip_set(struct gpio_chip *gc, unsig= ned int offset, int value) static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int v= alue) { int val =3D !!value, ret =3D 0, dir; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 /* * It's OK not to specify .direction_output() if the gpiochip is * output-only, but if there is then not even a .set() operation it * is pretty tricky to drive the output line. */ - if (!guard.gc->set && !guard.gc->direction_output) { + if (!gc->set && !gc->direction_output) { gpiod_warn(desc, "%s: missing set() and direction_output() operations\n", __func__); return -EIO; } =20 - if (guard.gc->direction_output) { - ret =3D gpiochip_direction_output(guard.gc, - gpiod_hwgpio(desc), val); + if (gc->direction_output) { + ret =3D gpiochip_direction_output(gc, gpiod_hwgpio(desc), val); } else { /* Check that we are in output mode if we can */ - if (guard.gc->get_direction) { - dir =3D gpiochip_get_direction(guard.gc, - gpiod_hwgpio(desc)); + if (gc->get_direction) { + dir =3D gpiochip_get_direction(gc, gpiod_hwgpio(desc)); if (dir < 0) return dir; =20 @@ -3061,7 +3044,7 @@ static int gpiod_direction_output_raw_commit(struct g= pio_desc *desc, int value) * If we can't actively set the direction, we are some * output-only chip, so just drive the output as desired. */ - ret =3D gpiochip_set(guard.gc, gpiod_hwgpio(desc), val); + ret =3D gpiochip_set(gc, gpiod_hwgpio(desc), val); if (ret) return ret; } @@ -3199,20 +3182,18 @@ int gpiod_direction_output_nonotify(struct gpio_des= c *desc, int value) int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long fla= gs) { int ret; + struct gpio_chip *gc; =20 VALIDATE_DESC(desc); =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 - if (!guard.gc->en_hw_timestamp) { + if (!gc->en_hw_timestamp) { gpiod_warn(desc, "%s: hw ts not supported\n", __func__); return -ENOTSUPP; } =20 - ret =3D guard.gc->en_hw_timestamp(guard.gc, - gpiod_hwgpio(desc), flags); + ret =3D gc->en_hw_timestamp(gc, gpiod_hwgpio(desc), flags); if (ret) gpiod_warn(desc, "%s: hw ts request failed\n", __func__); =20 @@ -3232,20 +3213,18 @@ EXPORT_SYMBOL_GPL(gpiod_enable_hw_timestamp_ns); int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long fl= ags) { int ret; + struct gpio_chip *gc; =20 VALIDATE_DESC(desc); =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 - if (!guard.gc->dis_hw_timestamp) { + if (!gc->dis_hw_timestamp) { gpiod_warn(desc, "%s: hw ts not supported\n", __func__); return -ENOTSUPP; } =20 - ret =3D guard.gc->dis_hw_timestamp(guard.gc, gpiod_hwgpio(desc), - flags); + ret =3D gc->dis_hw_timestamp(gc, gpiod_hwgpio(desc), flags); if (ret) gpiod_warn(desc, "%s: hw ts release failed\n", __func__); =20 @@ -3363,8 +3342,6 @@ static int gpiochip_get(struct gpio_chip *gc, unsigne= d int offset) { int ret; =20 - lockdep_assert_held(&gc->gpiodev->srcu); - /* Make sure this is called after checking for gc->get(). */ ret =3D gc->get(gc, offset); if (ret > 1) { @@ -3406,18 +3383,10 @@ static int gpio_chip_get_value(struct gpio_chip *gc= , const struct gpio_desc *des =20 static int gpiod_get_raw_value_commit(const struct gpio_desc *desc) { - struct gpio_device *gdev; struct gpio_chip *gc; int value; =20 - /* FIXME Unable to use gpio_chip_guard due to const desc. */ - gdev =3D desc->gdev; - - guard(srcu)(&gdev->srcu); - - gc =3D srcu_dereference(gdev->chip, &gdev->srcu); - if (!gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 value =3D gpio_chip_get_value(gc, desc); value =3D value < 0 ? value : !!value; @@ -3428,8 +3397,6 @@ static int gpiod_get_raw_value_commit(const struct gp= io_desc *desc) static int gpio_chip_get_multiple(struct gpio_chip *gc, unsigned long *mask, unsigned long *bits) { - lockdep_assert_held(&gc->gpiodev->srcu); - if (gc->get_multiple) { int ret; =20 @@ -3456,9 +3423,10 @@ static int gpio_chip_get_multiple(struct gpio_chip *= gc, /* The 'other' chip must be protected with its GPIO device's SRCU. */ static bool gpio_device_chip_cmp(struct gpio_device *gdev, struct gpio_chi= p *gc) { - guard(srcu)(&gdev->srcu); + struct gpio_chip *chip; =20 - return gc =3D=3D srcu_dereference(gdev->chip, &gdev->srcu); + revocable_try_access_with(&gdev->chip_rev, chip); + return chip ? chip =3D=3D gc : false; } =20 int gpiod_get_array_value_complex(bool raw, bool can_sleep, @@ -3481,11 +3449,7 @@ int gpiod_get_array_value_complex(bool raw, bool can= _sleep, if (!can_sleep) WARN_ON(array_info->gdev->can_sleep); =20 - guard(srcu)(&array_info->gdev->srcu); - gc =3D srcu_dereference(array_info->gdev->chip, - &array_info->gdev->srcu); - if (!gc) - return -ENODEV; + revocable_try_access_or_return(&array_info->gdev->chip_rev, gc); =20 ret =3D gpio_chip_get_multiple(gc, array_info->get_mask, value_bitmap); @@ -3509,31 +3473,29 @@ int gpiod_get_array_value_complex(bool raw, bool ca= n_sleep, unsigned long *mask, *bits; int first, j; =20 - CLASS(gpio_chip_guard, guard)(desc_array[i]); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc_array[i]->gdev->chip_rev, gc); =20 - if (likely(guard.gc->ngpio <=3D FASTPATH_NGPIO)) { + if (likely(gc->ngpio <=3D FASTPATH_NGPIO)) { mask =3D fastpath_mask; bits =3D fastpath_bits; } else { gfp_t flags =3D can_sleep ? GFP_KERNEL : GFP_ATOMIC; =20 - mask =3D bitmap_alloc(guard.gc->ngpio, flags); + mask =3D bitmap_alloc(gc->ngpio, flags); if (!mask) return -ENOMEM; =20 - bits =3D bitmap_alloc(guard.gc->ngpio, flags); + bits =3D bitmap_alloc(gc->ngpio, flags); if (!bits) { bitmap_free(mask); return -ENOMEM; } } =20 - bitmap_zero(mask, guard.gc->ngpio); + bitmap_zero(mask, gc->ngpio); =20 if (!can_sleep) - WARN_ON(guard.gc->can_sleep); + WARN_ON(gc->can_sleep); =20 /* collect all inputs belonging to the same chip */ first =3D i; @@ -3548,9 +3510,9 @@ int gpiod_get_array_value_complex(bool raw, bool can_= sleep, i =3D find_next_zero_bit(array_info->get_mask, array_size, i); } while ((i < array_size) && - gpio_device_chip_cmp(desc_array[i]->gdev, guard.gc)); + gpio_device_chip_cmp(desc_array[i]->gdev, gc)); =20 - ret =3D gpio_chip_get_multiple(guard.gc, mask, bits); + ret =3D gpio_chip_get_multiple(gc, mask, bits); if (ret) { if (mask !=3D fastpath_mask) bitmap_free(mask); @@ -3699,15 +3661,14 @@ EXPORT_SYMBOL_GPL(gpiod_get_array_value); static int gpio_set_open_drain_value_commit(struct gpio_desc *desc, bool v= alue) { int ret =3D 0, offset =3D gpiod_hwgpio(desc); + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 if (value) { - ret =3D gpiochip_direction_input(guard.gc, offset); + ret =3D gpiochip_direction_input(gc, offset); } else { - ret =3D gpiochip_direction_output(guard.gc, offset, 0); + ret =3D gpiochip_direction_output(gc, offset, 0); if (!ret) set_bit(GPIOD_FLAG_IS_OUT, &desc->flags); } @@ -3728,17 +3689,16 @@ static int gpio_set_open_drain_value_commit(struct = gpio_desc *desc, bool value) static int gpio_set_open_source_value_commit(struct gpio_desc *desc, bool = value) { int ret =3D 0, offset =3D gpiod_hwgpio(desc); + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 if (value) { - ret =3D gpiochip_direction_output(guard.gc, offset, 1); + ret =3D gpiochip_direction_output(gc, offset, 1); if (!ret) set_bit(GPIOD_FLAG_IS_OUT, &desc->flags); } else { - ret =3D gpiochip_direction_input(guard.gc, offset); + ret =3D gpiochip_direction_input(gc, offset); } trace_gpio_direction(desc_to_gpio(desc), !value, ret); if (ret < 0) @@ -3751,15 +3711,15 @@ static int gpio_set_open_source_value_commit(struct= gpio_desc *desc, bool value) =20 static int gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value) { + struct gpio_chip *gc; + if (unlikely(!test_bit(GPIOD_FLAG_IS_OUT, &desc->flags))) return -EPERM; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 trace_gpio_value(desc_to_gpio(desc), 0, value); - return gpiochip_set(guard.gc, gpiod_hwgpio(desc), value); + return gpiochip_set(gc, gpiod_hwgpio(desc), value); } =20 /* @@ -3780,8 +3740,6 @@ static int gpiochip_set_multiple(struct gpio_chip *gc, unsigned int i; int ret; =20 - lockdep_assert_held(&gc->gpiodev->srcu); - if (gc->set_multiple) { ret =3D gc->set_multiple(gc, mask, bits); if (ret > 0) @@ -3826,11 +3784,7 @@ int gpiod_set_array_value_complex(bool raw, bool can= _sleep, return -EPERM; } =20 - guard(srcu)(&array_info->gdev->srcu); - gc =3D srcu_dereference(array_info->gdev->chip, - &array_info->gdev->srcu); - if (!gc) - return -ENODEV; + revocable_try_access_or_return(&array_info->gdev->chip_rev, gc); =20 if (!raw && !bitmap_empty(array_info->invert_mask, array_size)) bitmap_xor(value_bitmap, value_bitmap, @@ -3854,31 +3808,30 @@ int gpiod_set_array_value_complex(bool raw, bool ca= n_sleep, unsigned long *mask, *bits; int count =3D 0; =20 - CLASS(gpio_chip_guard, guard)(desc_array[i]); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc_array[i]->gdev->chip_rev, + gc); =20 - if (likely(guard.gc->ngpio <=3D FASTPATH_NGPIO)) { + if (likely(gc->ngpio <=3D FASTPATH_NGPIO)) { mask =3D fastpath_mask; bits =3D fastpath_bits; } else { gfp_t flags =3D can_sleep ? GFP_KERNEL : GFP_ATOMIC; =20 - mask =3D bitmap_alloc(guard.gc->ngpio, flags); + mask =3D bitmap_alloc(gc->ngpio, flags); if (!mask) return -ENOMEM; =20 - bits =3D bitmap_alloc(guard.gc->ngpio, flags); + bits =3D bitmap_alloc(gc->ngpio, flags); if (!bits) { bitmap_free(mask); return -ENOMEM; } } =20 - bitmap_zero(mask, guard.gc->ngpio); + bitmap_zero(mask, gc->ngpio); =20 if (!can_sleep) - WARN_ON(guard.gc->can_sleep); + WARN_ON(gc->can_sleep); =20 do { struct gpio_desc *desc =3D desc_array[i]; @@ -3917,10 +3870,10 @@ int gpiod_set_array_value_complex(bool raw, bool ca= n_sleep, i =3D find_next_zero_bit(array_info->set_mask, array_size, i); } while ((i < array_size) && - gpio_device_chip_cmp(desc_array[i]->gdev, guard.gc)); + gpio_device_chip_cmp(desc_array[i]->gdev, gc)); /* push collected bits to outputs */ if (count !=3D 0) { - ret =3D gpiochip_set_multiple(guard.gc, mask, bits); + ret =3D gpiochip_set_multiple(gc, mask, bits); if (ret) return ret; } @@ -4126,7 +4079,6 @@ EXPORT_SYMBOL_GPL(gpiod_is_shared); */ int gpiod_to_irq(const struct gpio_desc *desc) { - struct gpio_device *gdev; struct gpio_chip *gc; int offset; int ret; @@ -4135,12 +4087,7 @@ int gpiod_to_irq(const struct gpio_desc *desc) if (ret <=3D 0) return -EINVAL; =20 - gdev =3D desc->gdev; - /* FIXME Cannot use gpio_chip_guard due to const desc. */ - guard(srcu)(&gdev->srcu); - gc =3D srcu_dereference(gdev->chip, &gdev->srcu); - if (!gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 offset =3D gpiod_hwgpio(desc); if (gc->to_irq) { @@ -5100,18 +5047,16 @@ int gpiod_hog(struct gpio_desc *desc, const char *n= ame, struct gpio_desc *local_desc; int hwnum; int ret; + struct gpio_chip *gc; =20 - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; + revocable_try_access_or_return(&desc->gdev->chip_rev, gc); =20 if (test_and_set_bit(GPIOD_FLAG_IS_HOGGED, &desc->flags)) return 0; =20 hwnum =3D gpiod_hwgpio(desc); =20 - local_desc =3D gpiochip_request_own_desc(guard.gc, hwnum, name, - lflags, dflags); + local_desc =3D gpiochip_request_own_desc(gc, hwnum, name, lflags, dflags); if (IS_ERR(local_desc)) { clear_bit(GPIOD_FLAG_IS_HOGGED, &desc->flags); ret =3D PTR_ERR(local_desc); @@ -5481,9 +5426,7 @@ static int gpiolib_seq_show(struct seq_file *s, void = *v) if (priv->newline) seq_putc(s, '\n'); =20 - guard(srcu)(&gdev->srcu); - - gc =3D srcu_dereference(gdev->chip, &gdev->srcu); + revocable_try_access_with(&gdev->chip_rev, gc); if (!gc) { seq_printf(s, "%s: (dangling chip)\n", dev_name(&gdev->dev)); return 0; diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index dc4cb61a9318..efbff4a1cd4e 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +32,6 @@ struct fwnode_handle; * @chrdev: character device for the GPIO device * @id: numerical ID number for the GPIO chip * @owner: helps prevent removal of modules exporting active GPIOs - * @chip: pointer to the corresponding gpiochip, holding static * data for this device * @descs: array of ngpio descriptors. * @valid_mask: If not %NULL, holds bitmask of GPIOs which are valid to be @@ -54,7 +54,7 @@ struct fwnode_handle; * process context * @device_notifier: used to notify character device wait queues about the= GPIO * device being unregistered - * @srcu: protects the pointer to the underlying GPIO chip + * @chip_rev: revocable provider handle for the corresponding struct gpio_= chip. * @pin_ranges: range of pins served by the GPIO driver * * This state container holds most of the runtime variable data @@ -67,7 +67,6 @@ struct gpio_device { struct cdev chrdev; int id; struct module *owner; - struct gpio_chip __rcu *chip; struct gpio_desc *descs; unsigned long *valid_mask; struct srcu_struct desc_srcu; @@ -81,7 +80,7 @@ struct gpio_device { rwlock_t line_state_lock; struct workqueue_struct *line_state_wq; struct blocking_notifier_head device_notifier; - struct srcu_struct srcu; + struct revocable chip_rev; =20 #ifdef CONFIG_PINCTRL /* @@ -225,27 +224,6 @@ struct gpio_desc { =20 #define gpiod_not_found(desc) (IS_ERR(desc) && PTR_ERR(desc) =3D=3D -ENOE= NT) =20 -struct gpio_chip_guard { - struct gpio_device *gdev; - struct gpio_chip *gc; - int idx; -}; - -DEFINE_CLASS(gpio_chip_guard, - struct gpio_chip_guard, - srcu_read_unlock(&_T.gdev->srcu, _T.idx), - ({ - struct gpio_chip_guard _guard; - - _guard.gdev =3D desc->gdev; - _guard.idx =3D srcu_read_lock(&_guard.gdev->srcu); - _guard.gc =3D srcu_dereference(_guard.gdev->chip, - &_guard.gdev->srcu); - - _guard; - }), - struct gpio_desc *desc) - int gpiod_request(struct gpio_desc *desc, const char *label); int gpiod_request_commit(struct gpio_desc *desc, const char *label); void gpiod_free(struct gpio_desc *desc); --=20 2.51.0 From nobody Fri Jun 12 07:36:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0F5583E5A24; Wed, 13 May 2026 09:11:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663517; cv=none; b=VKU4Y4kdNbR+X5MRTppCqpqo6ru3vS7V0hWz9F5mgU6bcs+sMn9GRnrkQpoPbb6yPewgQ30kEifOE5NT0Iwljo0JDR25Ny76Hz/NtwZJDvxuJ6iI+/eLlOevNIQs/IB02EmQbf2yeoLrnu58dVumIpO9ILzeJZrcpcg7NY3z+/Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663517; c=relaxed/simple; bh=zg4myjGWRU1O/73Zb1JpG/HVv39/PyjlxQy0TMJ2sFQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oEl2OEDOV0XmXE/omJcORLxbTtZ4vx3TPPapBmfc0YvUbpCJYa8DWtf2yVK6CUMUK4qUvkIRRq3C9pRAMd+eCRLkG/+q0RIYf5iI6YdxUO4EP8f7DpFP8ydSwW6SLlMne7lk1/kktIckiKx8fxoQ9f8Yvwcm5qSVq9jn13rfCtk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=MB+c318J; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="MB+c318J" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 89CCEC2BCB8; Wed, 13 May 2026 09:11:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778663515; bh=zg4myjGWRU1O/73Zb1JpG/HVv39/PyjlxQy0TMJ2sFQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MB+c318JcsvR62eMW9ImhwYQy5a0LROKfJfg7df70ba9xKKohC27FDvPyq5skb2k9 jPhvcZJ+E8+9/0+u+gzoTAtPwSFQHaAoekcP43hb5ih2Z2BfFvFS6dhD1r8nFq6W6V xnluKKOPbF/nTA818AgQijod8Cp5XSZSYFlRSOppJUfdTSoYP1TXcKIaWKgXl2gfDo qIvIbtD18sXiY6BjBh1qicrNYF0VPm3fjOySOyPnbfv4GugVSDoPrpCKOtXZt7M5xw T6582ajr1rRnhi423w4ItRr/CXS+AIr2ccH6RRWgOtZYbBd5/rxvTezgL4sE4NdBLM DllU0B8r+QPaA== From: Tzung-Bi Shih To: Arnd Bergmann , Greg Kroah-Hartman , Bartosz Golaszewski , Linus Walleij Cc: Benson Leung , tzungbi@kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, driver-core@lists.linux.dev, linux-doc@vger.kernel.org, linux-gpio@vger.kernel.org, "Rafael J. Wysocki" , Danilo Krummrich , Jonathan Corbet , Shuah Khan , Laurent Pinchart , Wolfram Sang , Jason Gunthorpe , Johan Hovold , "Paul E . McKenney" Subject: [PATCH v11 4/5] platform/chrome: Protect cros_ec_device lifecycle with revocable Date: Wed, 13 May 2026 17:10:42 +0800 Message-ID: <20260513091043.6766-5-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260513091043.6766-1-tzungbi@kernel.org> References: <20260513091043.6766-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The cros_ec_device can be unregistered when the underlying device is removed. Other kernel drivers that interact with the EC may hold a pointer to the cros_ec_device, creating a risk of a use-after-free error if the EC device is removed while still being referenced. To prevent this, leverage the revocable and convert the underlying device drivers to resource providers of cros_ec_device. --- v11: - No changes. v10: https://lore.kernel.org/all/20260508105448.31799-9-tzungbi@kernel.org - No changes. v9: https://lore.kernel.org/all/20260427135841.96266-9-tzungbi@kernel.org - New to the series. - Change revocable API usages accordingly. - Rename "revocable_provider" -> "its_rev". v5 - v8: - Doesn't exist. v4: https://lore.kernel.org/all/20250923075302.591026-5-tzungbi@kernel.org - No changes. v3: https://lore.kernel.org/all/20250912081718.3827390-5-tzungbi@kernel.org - Initialize the revocable provider in cros_ec_device_alloc() instead of spreading in protocol device drivers. v2: https://lore.kernel.org/all/20250820081645.847919-5-tzungbi@kernel.org - Rename "ref_proxy" -> "revocable". v1: https://lore.kernel.org/all/20250814091020.1302888-3-tzungbi@kernel.org Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec.c | 11 +++++++++++ include/linux/platform_data/cros_ec_proto.h | 3 +++ 2 files changed, 14 insertions(+) diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cr= os_ec.c index 1da79e3d215b..2702a1bbfeb5 100644 --- a/drivers/platform/chrome/cros_ec.c +++ b/drivers/platform/chrome/cros_ec.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include =20 @@ -37,6 +38,7 @@ static void cros_ec_device_free(void *data) =20 mutex_destroy(&ec_dev->lock); lockdep_unregister_key(&ec_dev->lockdep_key); + revocable_revoke(ec_dev->its_rev); } =20 struct cros_ec_device *cros_ec_device_alloc(struct device *dev) @@ -47,6 +49,15 @@ struct cros_ec_device *cros_ec_device_alloc(struct devic= e *dev) if (!ec_dev) return NULL; =20 + ec_dev->its_rev =3D revocable_alloc(ec_dev); + if (!ec_dev->its_rev) + return NULL; + /* + * Drop the extra reference for the caller as the caller is the + * resource provider. + */ + revocable_put(ec_dev->its_rev); + ec_dev->din_size =3D sizeof(struct ec_host_response) + sizeof(struct ec_response_get_protocol_info) + EC_MAX_RESPONSE_OVERHEAD; diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/pl= atform_data/cros_ec_proto.h index de14923720a5..e8c3bd03403c 100644 --- a/include/linux/platform_data/cros_ec_proto.h +++ b/include/linux/platform_data/cros_ec_proto.h @@ -12,6 +12,7 @@ #include #include #include +#include =20 #include =20 @@ -165,6 +166,7 @@ struct cros_ec_command { * @pd: The platform_device used by the mfd driver to interface with the * PD behind an EC. * @panic_notifier: EC panic notifier. + * @its_rev: The revocable_provider to this device. */ struct cros_ec_device { /* These are used by other drivers that want to talk to the EC */ @@ -211,6 +213,7 @@ struct cros_ec_device { struct platform_device *pd; =20 struct blocking_notifier_head panic_notifier; + struct revocable *its_rev; }; =20 /** --=20 2.51.0 From nobody Fri Jun 12 07:36:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7ED25357D0E; Wed, 13 May 2026 09:12:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663520; cv=none; b=goy9Y7v+NM66G+TPiPAh7FY0pwf51XkmY9/NvXMGWyAr1kat/W0ELSc/uDelj3XX3hIkH0rtn4e46DA5YPqeDxWzj9myCmdDITpWuc18QaGd5wv6MJ5tZmw8Jly1Ux2mah+mA9woUAlpMjshA0MEHqHv5LVVS1EcG0bVP1CN6tc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778663520; c=relaxed/simple; bh=6VrA4lInkEZim0QbZieKFauE6Qiqk5WJluu39mLQ5Y4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mZB/EOZMkGNY0Q2eauGrfDaG43WNiIfDsTxKrJhjijMKHFxylZUfE1HONbX+G105GdQs3/m2WSW5AHAyzNejbQ/d59KD/49StGSYbn9KLhi3MLAIPxGCYN3KLuIM/2SGArYxWiQUB3rhsY5qJYok0pi9FD5bFY1IjpV8cbSvqsE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=p/JdOihV; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="p/JdOihV" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 44EE1C2BCB7; Wed, 13 May 2026 09:11:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1778663519; bh=6VrA4lInkEZim0QbZieKFauE6Qiqk5WJluu39mLQ5Y4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=p/JdOihVh55NVtzU0RS2ert0KdPa8c+iYubOX8wAcgtHVAprXCvxS55gwdwROUxUX ldjxseHvddTG8ZLdRwRzpdWzcWAFl4Mqz3SAJ8s3DoYolL80FFmc3AGPvFwJXuw4y4 PmZeGp8gLpbHFbWrUxfDZnPum+yXuzgjO8QYfHO1aOq0WkFBcD6Vi5dIfORssOtNKM bptPKdmWFCUJWbyr6lCpvxPB4jVf6EXGXdIeENY2X6er1CswjnjIzy15ocPz1wrcF5 LFmY7fPPoI8L4VRaqfHrvA3sZUdwvAAahL1mbiJQ+ZPWCNs9olNnyNgYi5068fM2Cy zIPDevDnRi5sQ== From: Tzung-Bi Shih To: Arnd Bergmann , Greg Kroah-Hartman , Bartosz Golaszewski , Linus Walleij Cc: Benson Leung , tzungbi@kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, driver-core@lists.linux.dev, linux-doc@vger.kernel.org, linux-gpio@vger.kernel.org, "Rafael J. Wysocki" , Danilo Krummrich , Jonathan Corbet , Shuah Khan , Laurent Pinchart , Wolfram Sang , Jason Gunthorpe , Johan Hovold , "Paul E . McKenney" Subject: [PATCH v11 5/5] platform/chrome: cros_ec_chardev: Consume cros_ec_device via revocable Date: Wed, 13 May 2026 17:10:43 +0800 Message-ID: <20260513091043.6766-6-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260513091043.6766-1-tzungbi@kernel.org> References: <20260513091043.6766-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The cros_ec_chardev driver provides a character device interface to the ChromeOS EC. A file handle to this device can remain open in userspace even if the underlying EC device is removed. This creates a classic use-after-free vulnerability. Any file operation (ioctl, release, etc.) on the open handle after the EC device has gone would access a stale pointer, leading to a system crash. To prevent this, leverage the revocable and convert cros_ec_chardev to a resource consumer of cros_ec_device. --- v11: - No changes. v10: https://lore.kernel.org/all/20260508105448.31799-10-tzungbi@kernel.org - No changes. v9: https://lore.kernel.org/all/20260427135841.96266-10-tzungbi@kernel.org - New to the series. - Change revocable API usages accordingly. v4 - v8: - Doesn't exist. v3: https://lore.kernel.org/all/20250912081718.3827390-6-tzungbi@kernel.org - Use specific labels for different cleanup in cros_ec_chardev_open(). v2: https://lore.kernel.org/all/20250820081645.847919-6-tzungbi@kernel.org - Rename "ref_proxy" -> "revocable". - Fix a sparse warning by removing the redundant __rcu annotation. v1: https://lore.kernel.org/all/20250814091020.1302888-4-tzungbi@kernel.org Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_chardev.c | 80 +++++++++++++++++------ 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/c= hrome/cros_ec_chardev.c index 002be3352100..c597dc92d519 100644 --- a/drivers/platform/chrome/cros_ec_chardev.c +++ b/drivers/platform/chrome/cros_ec_chardev.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,7 @@ #define CROS_MAX_EVENT_LEN PAGE_SIZE =20 struct chardev_priv { - struct cros_ec_device *ec_dev; + struct revocable *rev; struct notifier_block notifier; wait_queue_head_t wait_event; unsigned long event_mask; @@ -55,6 +56,7 @@ static int ec_get_version(struct chardev_priv *priv, char= *str, int maxlen) }; struct ec_response_get_version *resp; struct cros_ec_command *msg; + struct cros_ec_device *ec_dev; int ret; =20 msg =3D kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); @@ -64,12 +66,19 @@ static int ec_get_version(struct chardev_priv *priv, ch= ar *str, int maxlen) msg->command =3D EC_CMD_GET_VERSION + priv->cmd_offset; msg->insize =3D sizeof(*resp); =20 - ret =3D cros_ec_cmd_xfer_status(priv->ec_dev, msg); - if (ret < 0) { - snprintf(str, maxlen, - "Unknown EC version, returned error: %d\n", - msg->result); - goto exit; + revocable_try_access_with_scoped(priv->rev, ec_dev) { + if (!ec_dev) { + ret =3D -ENODEV; + goto exit; + } + + ret =3D cros_ec_cmd_xfer_status(ec_dev, msg); + if (ret < 0) { + snprintf(str, maxlen, + "Unknown EC version, returned error: %d\n", + msg->result); + goto exit; + } } =20 resp =3D (struct ec_response_get_version *)msg->data; @@ -92,10 +101,15 @@ static int cros_ec_chardev_mkbp_event(struct notifier_= block *nb, { struct chardev_priv *priv =3D container_of(nb, struct chardev_priv, notifier); - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device *ec_dev; struct ec_event *event; - unsigned long event_bit =3D 1 << ec_dev->event_data.event_type; - int total_size =3D sizeof(*event) + ec_dev->event_size; + unsigned long event_bit; + int total_size; + + revocable_try_access_or_return_err(priv->rev, ec_dev, NOTIFY_DONE); + + event_bit =3D 1 << ec_dev->event_data.event_type; + total_size =3D sizeof(*event) + ec_dev->event_size; =20 if (!(event_bit & priv->event_mask) || (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) @@ -166,7 +180,8 @@ static int cros_ec_chardev_open(struct inode *inode, st= ruct file *filp) if (!priv) return -ENOMEM; =20 - priv->ec_dev =3D ec_dev; + priv->rev =3D ec_dev->its_rev; + revocable_get(priv->rev); priv->cmd_offset =3D ec->cmd_offset; filp->private_data =3D priv; INIT_LIST_HEAD(&priv->events); @@ -178,6 +193,7 @@ static int cros_ec_chardev_open(struct inode *inode, st= ruct file *filp) &priv->notifier); if (ret) { dev_err(ec_dev->dev, "failed to register event notifier\n"); + revocable_put(priv->rev); kfree(priv); } =20 @@ -251,11 +267,13 @@ static ssize_t cros_ec_chardev_read(struct file *filp= , char __user *buffer, static int cros_ec_chardev_release(struct inode *inode, struct file *filp) { struct chardev_priv *priv =3D filp->private_data; - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device *ec_dev; struct ec_event *event, *e; =20 - blocking_notifier_chain_unregister(&ec_dev->event_notifier, - &priv->notifier); + revocable_try_access_or_skip_scoped(priv->rev, ec_dev) + blocking_notifier_chain_unregister(&ec_dev->event_notifier, + &priv->notifier); + revocable_put(priv->rev); =20 list_for_each_entry_safe(event, e, &priv->events, node) { list_del(&event->node); @@ -273,6 +291,7 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev_p= riv *priv, void __user *a { struct cros_ec_command *s_cmd; struct cros_ec_command u_cmd; + struct cros_ec_device *ec_dev; long ret; =20 if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) @@ -299,10 +318,17 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev= _priv *priv, void __user *a } =20 s_cmd->command +=3D priv->cmd_offset; - ret =3D cros_ec_cmd_xfer(priv->ec_dev, s_cmd); - /* Only copy data to userland if data was received. */ - if (ret < 0) - goto exit; + revocable_try_access_with_scoped(priv->rev, ec_dev) { + if (!ec_dev) { + ret =3D -ENODEV; + goto exit; + } + + ret =3D cros_ec_cmd_xfer(ec_dev, s_cmd); + /* Only copy data to userland if data was received. */ + if (ret < 0) + goto exit; + } =20 if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize)) ret =3D -EFAULT; @@ -313,10 +339,12 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev= _priv *priv, void __user *a =20 static long cros_ec_chardev_ioctl_readmem(struct chardev_priv *priv, void = __user *arg) { - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device *ec_dev; struct cros_ec_readmem s_mem =3D { }; long num; =20 + revocable_try_access_or_return(priv->rev, ec_dev); + /* Not every platform supports direct reads */ if (!ec_dev->cmd_readmem) return -ENOTTY; @@ -370,11 +398,25 @@ static const struct file_operations chardev_fops =3D { #endif }; =20 +static void cros_ec_chardev_free(void *data) +{ + struct revocable *rev =3D data; + + revocable_put(rev); +} + static int cros_ec_chardev_probe(struct platform_device *pdev) { struct cros_ec_dev *ec =3D dev_get_drvdata(pdev->dev.parent); struct cros_ec_platform *ec_platform =3D dev_get_platdata(ec->dev); + struct revocable *rev =3D ec->ec_dev->its_rev; struct miscdevice *misc; + int ret; + + revocable_get(rev); + ret =3D devm_add_action_or_reset(&pdev->dev, cros_ec_chardev_free, rev); + if (ret) + return ret; =20 /* Create a char device: we want to create it anew */ misc =3D devm_kzalloc(&pdev->dev, sizeof(*misc), GFP_KERNEL); --=20 2.51.0