Linux Documentation
 help / color / mirror / Atom feed
* [PATCH v5 1/3] dt-bindings: trivial-devices: Add Delta E50SN12051
From: Colin Huang via B4 Relay @ 2026-05-08  9:44 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Kevin Chang, Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
	Colin Huang
In-Reply-To: <20260508-add-e50sn12051-v5-0-abebdcc29665@gmail.com>

From: Colin Huang <u8813345@gmail.com>

Add 600W Non-isolated 1/8th Brick DC/DC Power Modules, E50SN12051.

Signed-off-by: Colin Huang <u8813345@gmail.com>
---
 Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
index 23fd4513933a..0f10368a1386 100644
--- a/Documentation/devicetree/bindings/trivial-devices.yaml
+++ b/Documentation/devicetree/bindings/trivial-devices.yaml
@@ -100,6 +100,8 @@ properties:
             # Delta Electronics DPS920AB 920W 54V Power Supply
           - delta,dps920ab
             # 1/4 Brick DC/DC Regulated Power Module
+          - delta,e50sn12051
+            # 600W Non-isolated 1/8th Brick DC/DC Power Modules
           - delta,q54sj108a2
             # 1300W 1/4 Brick DC/DC Regulated Power Module
           - delta,q54sn120a1

-- 
2.34.1



^ permalink raw reply related

* [PATCH v5 0/3] add support for Delta E50SN12051
From: Colin Huang via B4 Relay @ 2026-05-08  9:44 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Kevin Chang, Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
	Colin Huang

Delta E50SN12051 is a 600W non-isolated 1/8th brick DC-DC power module.
With this series applied, voltage, current and temperature sensors can 
be exposed through the standard hwmon interface.

Changes in this series:
- Add Delta E50SN12051 to trivial-devices.yaml
- Add hwmon documentation describing the supported sensors
- Add PMBus driver support for Delta E50SN12051

Signed-off-by: Colin Huang <u8813345@gmail.com>
---
Changes in v5:
- Add Delta E50SN12051 to trivial-devices.yaml instead of introducing a new binding
- Link to v4: https://lore.kernel.org/r/20260507-add-e50sn12051-v4-0-ff2b3768ac7e@gmail.com

Changes in v4:
- Rewrite binding description to be hardware-centric and OS-agnostic.
- Rename example node from hwmon@40 to a generic power-module@40.
- Link to v3: https://lore.kernel.org/r/20260430-add-e50sn12051-v3-0-f6d4e043ec7c@gmail.com

Changes in v3:
- Simplify to pass &e50sn12051_info directly to pmbus_do_probe() 
  to avoid the extra memory allocation
- Link to v2: https://lore.kernel.org/r/20260430-add-e50sn12051-v2-0-d76fe42482ab@gmail.com

Changes in v2:
- Add %YAML 1.2 declaration in binding document.
- Use a generic node name in examples in binding document.
- Add MODULE_IMPORT_NS("PMBUS")
- Remove unused / incorrect headers
- Link to v1: https://lore.kernel.org/r/20260429-add-e50sn12051-v1-0-c101e6c80bbb@gmail.com

---
Colin Huang (3):
      dt-bindings: trivial-devices: Add Delta E50SN12051
      Documentation/hwmon: add Delta E50SN12051 documentation
      hwmon: (pmbus) add support for Delta E50SN12051

 .../devicetree/bindings/trivial-devices.yaml       |  2 +
 Documentation/hwmon/e50sn12051.rst                 | 81 ++++++++++++++++++++++
 Documentation/hwmon/index.rst                      |  1 +
 drivers/hwmon/pmbus/Kconfig                        |  9 +++
 drivers/hwmon/pmbus/Makefile                       |  1 +
 drivers/hwmon/pmbus/e50sn12051.c                   | 52 ++++++++++++++
 6 files changed, 146 insertions(+)
---
base-commit: b584e7d50af502462349910bf4ed30057620b69f
change-id: 20260316-add-e50sn12051-ad828ad6777a

Best regards,
-- 
Colin Huang <u8813345@gmail.com>



^ permalink raw reply

* [PATCH v5 3/3] hwmon: (pmbus) add support for Delta E50SN12051
From: Colin Huang via B4 Relay @ 2026-05-08  9:44 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Kevin Chang, Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
	Colin Huang
In-Reply-To: <20260508-add-e50sn12051-v5-0-abebdcc29665@gmail.com>

From: Colin Huang <u8813345@gmail.com>

Add the pmbus driver for Delta E50SN12051 600W Non-isolated
1/8th Brick DCDC Power Modules.

Signed-off-by: Colin Huang <u8813345@gmail.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
---
 drivers/hwmon/pmbus/Kconfig      |  9 +++++++
 drivers/hwmon/pmbus/Makefile     |  1 +
 drivers/hwmon/pmbus/e50sn12051.c | 52 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+)

diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 8f4bff375ecb..4fbfcbc4a9c5 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -161,6 +161,15 @@ config SENSORS_DPS920AB
 	  This driver can also be built as a module. If so, the module will
 	  be called dps920ab.
 
+config SENSORS_E50SN12051
+	tristate "Delta E50SN12051 Power Modules"
+	help
+	  If you say yes here you get hardware monitoring support for Delta
+	  E50SN12051 Power Modules.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called e50sn12051.
+
 config SENSORS_INA233
 	tristate "Texas Instruments INA233 and compatibles"
 	help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 7129b62bc00f..fc910bf34fe3 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_FSP_3Y)	+= fsp-3y.o
 obj-$(CONFIG_SENSORS_HAC300S)	+= hac300s.o
 obj-$(CONFIG_SENSORS_IBM_CFFPS)	+= ibm-cffps.o
 obj-$(CONFIG_SENSORS_DPS920AB)	+= dps920ab.o
+obj-$(CONFIG_SENSORS_E50SN12051) += e50sn12051.o
 obj-$(CONFIG_SENSORS_INA233)	+= ina233.o
 obj-$(CONFIG_SENSORS_INSPUR_IPSPS) += inspur-ipsps.o
 obj-$(CONFIG_SENSORS_IR35221)	+= ir35221.o
diff --git a/drivers/hwmon/pmbus/e50sn12051.c b/drivers/hwmon/pmbus/e50sn12051.c
new file mode 100644
index 000000000000..efb4d62b2603
--- /dev/null
+++ b/drivers/hwmon/pmbus/e50sn12051.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for E50SN12051
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include "pmbus.h"
+
+static struct pmbus_driver_info e50sn12051_info = {
+	.pages = 1,
+	.format[PSC_VOLTAGE_IN] = linear,
+	.format[PSC_VOLTAGE_OUT] = linear,
+	.format[PSC_CURRENT_OUT] = linear,
+	.format[PSC_TEMPERATURE] = linear,
+	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
+		   PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
+		   PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
+		   PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static const struct i2c_device_id e50sn12051_id[] = { { "e50sn12051", 0 }, {} };
+MODULE_DEVICE_TABLE(i2c, e50sn12051_id);
+
+static const struct of_device_id e50sn12051_of_match[] = {
+	{ .compatible = "delta,e50sn12051" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, e50sn12051_of_match);
+
+static int e50sn12051_probe(struct i2c_client *client)
+{
+	return pmbus_do_probe(client, &e50sn12051_info);
+}
+
+static struct i2c_driver e50sn12051_driver = {
+	.driver = {
+		.name = "e50sn12051",
+		.of_match_table = e50sn12051_of_match,
+	},
+	.probe = e50sn12051_probe,
+
+	.id_table = e50sn12051_id,
+};
+
+module_i2c_driver(e50sn12051_driver);
+
+MODULE_AUTHOR("Kevin Chang <kevin.chang2@amd.com>");
+MODULE_DESCRIPTION("PMBus driver for E50SN12051");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");

-- 
2.34.1



^ permalink raw reply related

* [PATCH v5 2/3] Documentation/hwmon: add Delta E50SN12051 documentation
From: Colin Huang via B4 Relay @ 2026-05-08  9:44 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Kevin Chang, Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Colin Huang,
	Colin Huang
In-Reply-To: <20260508-add-e50sn12051-v5-0-abebdcc29665@gmail.com>

From: Colin Huang <u8813345@gmail.com>

Document the hardware monitoring support for the Delta E50SN12051
device.

The documentation describes the supported sensors exposed via the
hwmon subsystem, including voltage, current, and temperature measurements.

Signed-off-by: Colin Huang <u8813345@gmail.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
---
 Documentation/hwmon/e50sn12051.rst | 81 ++++++++++++++++++++++++++++++++++++++
 Documentation/hwmon/index.rst      |  1 +
 2 files changed, 82 insertions(+)

diff --git a/Documentation/hwmon/e50sn12051.rst b/Documentation/hwmon/e50sn12051.rst
new file mode 100644
index 000000000000..cbbfa7895d82
--- /dev/null
+++ b/Documentation/hwmon/e50sn12051.rst
@@ -0,0 +1,81 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver e50sn12051
+========================
+
+Supported chips:
+
+  * Delta E50SN12051
+
+    Prefix: 'e50sn12051'
+
+    Addresses scanned: -
+
+    Datasheet:
+
+      Provided by Delta upon request and NDA
+
+Description
+-----------
+
+E50SN12051 is a 600W non-isolated 1/8th brick DC-DC power module.
+
+This driver supports the E50SN12051 PMBus compliant monitor device.
+
+The device provides measurements for:
+
+* Input voltage
+* Output current
+* Output voltage
+* Temperature
+
+The driver is based on the PMBus core and exposes standard hwmon
+sysfs attributes.
+
+Sysfs attributes
+----------------
+
+======================= =======================================================
+curr1_label             "iout1"
+curr1_crit              Critical maximum current.
+                        From IOUT_OC_FAULT_LIMIT register.
+curr1_crit_alarm        Current critical high alarm.
+                        From IOUT_OC_FAULT status.
+curr1_input             Measured current.
+                        From READ_IOUT register.
+curr1_max               Maximum current.
+                        From IOUT_OC_WARN_LIMIT register.
+curr1_max_alarm         Current high alarm.
+                        From IOUT_OC_WARN_LIMIT status.
+
+in1_label               "vin"
+in1_alarm               Input voltage alarm.
+in1_input               Measured voltage.
+                        From READ_VIN register.
+
+in2_label               "vout1"
+in2_crit                Critical maximum voltage.
+                        From VOUT_OV_FAULT_LIMIT register.
+in2_crit_alarm          Voltage critical high alarm.
+                        From VOLTAGE_OV_FAULT status.
+in2_input               Measured voltage.
+                        From READ_VOUT register.
+in2_max                 Maximum voltage.
+                        From VOUT_OV_WARN_LIMIT register.
+in2_max_alarm           Voltage high alarm.
+                        From VOLTAGE_OV_WARNING status.
+
+temp1_crit              Critical high temperature.
+                        From OT_FAULT_LIMIT register.
+temp1_crit_alarm        Module temperature critical high alarm.
+                        Set by comparing READ_TEMPERATURE_1 with OT_FAULT_LIMIT
+                        if TEMP_OT_FAULT status is set.
+temp1_input             Measured module's hot spot temperature.
+                        From READ_TEMPERATURE_1 register.
+temp1_max               Maximum temperature.
+                        From OT_WARN_LIMIT register.
+temp1_max_alarm         Module temperature high alarm.
+                        Set by comparing READ_TEMPERATURE_1 with OT_WARN_LIMIT if
+                        TEMP_OT_WARNING status is set.
+======================= =======================================================
+
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 8b655e5d6b68..ba08655fc496 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -68,6 +68,7 @@ Hardware Monitoring Kernel Drivers
    drivetemp
    ds1621
    ds620
+   e50sn12051
    emc1403
    emc2103
    emc2305

-- 
2.34.1



^ permalink raw reply related

* Re: [PATCH v2 11/11] MAINTAINERS: use a URL for pin-init maintainer's profile entry
From: Miguel Ojeda @ 2026-05-08  9:45 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Gary Guo, Benno Lossin, Jonathan Corbet, Linux Doc Mailing List,
	linux-kernel, rust-for-linux, Björn Roy Baron, Alice Ryhl,
	Andreas Hindborg, Boqun Feng, Danilo Krummrich, Miguel Ojeda,
	Trevor Gross
In-Reply-To: <20260508091644.420bd440@foz.lan>

On Fri, May 8, 2026 at 9:16 AM Mauro Carvalho Chehab
<mchehab+huawei@kernel.org> wrote:
>
> Just my two cents, but all license files that are applies to the Kernel
> are already under LICENSES/ directory, and there is a document defining
> how SPDX and LICENSES should be used:
>
>         Documentation/process/license-rules.rst.

Yeah, we are aware, thank you; the files were annotated back then with
SPDX identifiers for that reason, and the links were intended to point
to the original files to show that they were indeed licensed that way.

> With regards to patch 11/11, I'll send a new version just changing
> the "P" tag without dropping the .md file.

Thanks!

Cheers,
Miguel

^ permalink raw reply

* [PATCH v1 4/4] iio: imu: inv_icm42600: Suppress driver_override sysfs attribute
From: Andy Shevchenko @ 2026-05-08  9:42 UTC (permalink / raw)
  To: Danilo Krummrich, Andy Shevchenko, Mark Brown, driver-core,
	linux-doc, linux-kernel, linux-iio, linux-spi
  Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Jonathan Corbet,
	Shuah Khan, Jean-Baptiste Maneyrol, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko
In-Reply-To: <20260508095224.1275645-1-andriy.shevchenko@linux.intel.com>

The driver makes no sense for the devices without associated driver data.
Instead of checking for absence of it, disable driver_override feature
and drop the no more required check.

Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
 drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
index 13e2e7d38638..cb8c501d86c3 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
@@ -50,20 +50,15 @@ static int inv_icm42600_spi_bus_setup(struct inv_icm42600_state *st)
 
 static int inv_icm42600_probe(struct spi_device *spi)
 {
-	const void *match;
 	enum inv_icm42600_chip chip;
 	struct regmap *regmap;
 
-	match = device_get_match_data(&spi->dev);
-	if (!match)
-		return -EINVAL;
-	chip = (uintptr_t)match;
-
 	/* use SPI specific regmap */
 	regmap = devm_regmap_init_spi(spi, &inv_icm42600_spi_regmap_config);
 	if (IS_ERR(regmap))
 		return PTR_ERR(regmap);
 
+	chip = (uintptr_t)device_get_match_data(&spi->dev);
 	return inv_icm42600_core_probe(regmap, chip, inv_icm42600_spi_bus_setup);
 }
 
@@ -115,6 +110,7 @@ static struct spi_driver inv_icm42600_driver = {
 		.name = "inv-icm42600-spi",
 		.of_match_table = inv_icm42600_of_matches,
 		.pm = pm_ptr(&inv_icm42600_pm_ops),
+		.suppress_override_attrs = true,
 	},
 	.id_table = inv_icm42600_id,
 	.probe = inv_icm42600_probe,
-- 
2.50.1


^ permalink raw reply related

* [PATCH v1 2/4] spi: Support suppress_override_attrs flag
From: Andy Shevchenko @ 2026-05-08  9:42 UTC (permalink / raw)
  To: Danilo Krummrich, Andy Shevchenko, Mark Brown, driver-core,
	linux-doc, linux-kernel, linux-iio, linux-spi
  Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Jonathan Corbet,
	Shuah Khan, Jean-Baptiste Maneyrol, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko
In-Reply-To: <20260508095224.1275645-1-andriy.shevchenko@linux.intel.com>

Some device driver may want to suppress the driver_override sysfs attribute
by specifying a certain flag in their struct device_driver. Since SPI uses
explicit attribute instantiation, add that support here.

Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
 drivers/spi/spi.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 5f57de24b9f7..40e738f8cbb7 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -214,8 +214,19 @@ static struct attribute *spi_dev_attrs[] = {
 	NULL,
 };
 
+static umode_t spi_dev_attr_is_visible(struct kobject *kobj, struct attribute *attr, int i)
+{
+	struct device *dev = kobj_to_dev(kobj);
+
+	if (attr == &dev_attr_driver_override.attr)
+		return dev->driver->suppress_override_attrs ? 0 : attr->mode;
+
+	return attr->mode;
+}
+
 static const struct attribute_group spi_dev_group = {
 	.attrs  = spi_dev_attrs,
+	.is_visible	= spi_dev_attr_is_visible,
 };
 
 static struct attribute *spi_device_statistics_attrs[] = {
-- 
2.50.1


^ permalink raw reply related

* [PATCH v1 1/4] driver core: allow certain drivers prohibit override via sysfs
From: Andy Shevchenko @ 2026-05-08  9:42 UTC (permalink / raw)
  To: Danilo Krummrich, Andy Shevchenko, Mark Brown, driver-core,
	linux-doc, linux-kernel, linux-iio, linux-spi
  Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Jonathan Corbet,
	Shuah Khan, Jean-Baptiste Maneyrol, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko
In-Reply-To: <20260508095224.1275645-1-andriy.shevchenko@linux.intel.com>

Many drivers, that may be instantiate via ACPI, OF, or, in some cases,
user space won't work without necessary driver data. These are, e.g.,
most of the drivers in IIO subsystem. Trying to override the driver
for the device that has no matching entry makes no sense in such cases
and might lead to a crash, when the driver is not prepared for that.
Instead of adding a NULL check for driver data pointer in each of that
drivers, effectively meaning a dead code for normal functionality,
introduce a special attribute in the struct device_driver to allow
drivers just to hide the attribute for good.

Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
 Documentation/driver-api/driver-model/binding.rst | 4 ++++
 drivers/base/bus.c                                | 4 ++--
 include/linux/device/driver.h                     | 2 ++
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/Documentation/driver-api/driver-model/binding.rst b/Documentation/driver-api/driver-model/binding.rst
index fa0888c2b3b9..19b565a2de86 100644
--- a/Documentation/driver-api/driver-model/binding.rst
+++ b/Documentation/driver-api/driver-model/binding.rst
@@ -147,3 +147,7 @@ Additional helpers are available:
 
 - ``device_set_driver_override()`` - set or clear the override from kernel code.
 - ``device_has_driver_override()`` - check whether an override is set.
+
+Any driver, which bus has ``driver_override`` flag set, can suppress that
+behaviour by setting the ``suppress_override_attrs`` flag in their ``struct
+device_driver``. In this case the sysfs attribute will not show.
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index d17bd91490ee..16a530b91a00 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -594,7 +594,7 @@ int bus_add_device(struct device *dev)
 out_subsys:
 	sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
 out_override:
-	if (dev->bus->driver_override)
+	if (dev->bus->driver_override && !dev->driver->suppress_override_attrs)
 		device_remove_group(dev, &driver_override_dev_group);
 out_groups:
 	device_remove_groups(dev, sp->bus->dev_groups);
@@ -653,7 +653,7 @@ void bus_remove_device(struct device *dev)
 
 	sysfs_remove_link(&dev->kobj, "subsystem");
 	sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
-	if (dev->bus->driver_override)
+	if (dev->bus->driver_override && !dev->driver->suppress_override_attrs)
 		device_remove_group(dev, &driver_override_dev_group);
 	device_remove_groups(dev, dev->bus->dev_groups);
 	if (klist_node_attached(&dev->p->knode_bus))
diff --git a/include/linux/device/driver.h b/include/linux/device/driver.h
index bbc67ec513ed..4b1c1774fd43 100644
--- a/include/linux/device/driver.h
+++ b/include/linux/device/driver.h
@@ -55,6 +55,7 @@ enum probe_type {
  * @owner:	The module owner.
  * @mod_name:	Used for built-in modules.
  * @suppress_bind_attrs: Disables bind/unbind via sysfs.
+ * @suppress_override_attrs: Disables driver_override via sysfs.
  * @probe_type:	Type of the probe (synchronous or asynchronous) to use.
  * @of_match_table: The open firmware table.
  * @acpi_match_table: The ACPI match table.
@@ -103,6 +104,7 @@ struct device_driver {
 	const char		*mod_name;	/* used for built-in modules */
 
 	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
+	bool suppress_override_attrs;	/* disables driver_override via sysfs */
 	enum probe_type probe_type;
 
 	const struct of_device_id	*of_match_table;
-- 
2.50.1


^ permalink raw reply related

* [PATCH v1 0/4] driver core, iio: suppress driver_override
From: Andy Shevchenko @ 2026-05-08  9:42 UTC (permalink / raw)
  To: Danilo Krummrich, Andy Shevchenko, Mark Brown, driver-core,
	linux-doc, linux-kernel, linux-iio, linux-spi
  Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Jonathan Corbet,
	Shuah Khan, Jean-Baptiste Maneyrol, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko

Recently Sashiko started reporting the missed NULL check of
spi_get_device_match_data() or device_get_match_data() in SPI drivers
in IIO subsystem. It appears that the way to crash can be made by use
of driver_override sysfs attribute. However, many drivers, that may be
instantiate via ACPI, OF, or, in some cases, user space won't work
without necessary driver data. These are, e.g., most of the drivers
in IIO subsystem. Trying to override the driver for the device that
has no matching entry makes no sense in such cases and might lead to
a crash, when the driver is not prepared for that.  Instead of adding
a NULL check for driver data pointer in each of that drivers, effectively
meaning a dead code for normal functionality, introduce a special
attribute in the struct device_driver to allow drivers just to hide
the attribute for good.

The last two patches are the examples of use and code simplification
at the same time.

I consider getting an Ack from Mark for SPI, and from Jonathan for IIO
and route this via driver core, while providing an immutable branch/tag
for the above mentioned stakeholders.

Note, this doesn't change the state of affairs for some busses that
do not have driver_override flag set while using custom approach.
On a brief look it's the s390 crypto case which may not ever need
the above and hence left untouched.

Andy Shevchenko (4):
  driver core: allow certain drivers prohibit override via sysfs
  spi: Support suppress_override_attrs flag
  iio: imu: inv_mpu6050: Suppress driver_override sysfs attribute
  iio: imu: inv_icm42600: Suppress driver_override sysfs attribute

 Documentation/driver-api/driver-model/binding.rst |  4 ++++
 drivers/base/bus.c                                |  4 ++--
 drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c   |  8 ++------
 drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c         |  3 +--
 drivers/spi/spi.c                                 | 11 +++++++++++
 include/linux/device/driver.h                     |  2 ++
 6 files changed, 22 insertions(+), 10 deletions(-)

-- 
2.50.1


^ permalink raw reply

* [PATCH v1 3/4] iio: imu: inv_mpu6050: Suppress driver_override sysfs attribute
From: Andy Shevchenko @ 2026-05-08  9:42 UTC (permalink / raw)
  To: Danilo Krummrich, Andy Shevchenko, Mark Brown, driver-core,
	linux-doc, linux-kernel, linux-iio, linux-spi
  Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Jonathan Corbet,
	Shuah Khan, Jean-Baptiste Maneyrol, Jonathan Cameron,
	David Lechner, Nuno Sá, Andy Shevchenko
In-Reply-To: <20260508095224.1275645-1-andriy.shevchenko@linux.intel.com>

The driver makes no sense for the devices without associated driver data.
Instead of checking for absence of it, disable driver_override feature
and drop the no more required check.

Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
 drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c
index 1f4c62142b60..bdd4ba4e117e 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c
@@ -46,8 +46,6 @@ static int inv_mpu_probe(struct spi_device *spi)
 	} else if ((match = device_get_match_data(&spi->dev))) {
 		chip_type = (uintptr_t)match;
 		name = dev_name(&spi->dev);
-	} else {
-		return -ENODEV;
 	}
 
 	regmap = devm_regmap_init_spi(spi, &inv_mpu_regmap_config);
@@ -175,6 +173,7 @@ static struct spi_driver inv_mpu_driver = {
 		.acpi_match_table = inv_acpi_match,
 		.name	=	"inv-mpu6000-spi",
 		.pm     =       pm_ptr(&inv_mpu_pmops),
+		.suppress_override_attrs = true,
 	},
 };
 
-- 
2.50.1


^ permalink raw reply related

* Re: [PATCH v9 2/6] iio: adc: ad4691: add initial driver for AD4691 family
From: Andy Shevchenko @ 2026-05-08  9:53 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Sabau, Radu bogdan, Lars-Peter Clausen, Hennerich, Michael,
	David Lechner, Sa, Nuno, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
	linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org
In-Reply-To: <af1qN1Bvio1v6TKG@ashevche-desk.local>

On Fri, May 08, 2026 at 07:44:39AM +0300, Andy Shevchenko wrote:
> On Thu, May 07, 2026 at 03:15:49PM +0100, Jonathan Cameron wrote:
> > On Thu, 7 May 2026 09:26:00 +0000
> > "Sabau, Radu bogdan" <Radu.Sabau@analog.com> wrote:

...

> > > > +	st->info = spi_get_device_match_data(spi);  
> > > 
> > > "Is it possible for st->info to be NULL here?
> > > If the driver is manually bound to a device with a non-matching name using
> > > the sysfs bind interface in combination with driver_override,
> > > spi_get_device_match_data() could return NULL, which would cause a NULL
> > > pointer dereference later in the probe sequence."
> > > 
> > > Agreed, will add the NULL check with -ENODEV immediately after
> > > spi_get_device_match_data().
> > 
> > Andy, you seeing this one?  Looks like we are putting these checks back in again.
> > Whilst anyone forcing a bind like this is onto a looser anyway we shouldn't
> > crash due to a null dereference.
> 
> We should find a way how to disable that combination from the start. The driver
> makes no sense to be instantiated from user space. Actually most of the drivers
> nowadays should not be bound to the devices without driver data.

The 20260508095224.1275645-1-andriy.shevchenko@linux.intel.com has been just sent.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v8 1/4] bug/kunit: Core support for suppressing warning backtraces
From: David Gow @ 2026-05-08 10:10 UTC (permalink / raw)
  To: Albert Esteve
  Cc: Arnd Bergmann, Brendan Higgins, Rae Moar, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Jonathan Corbet, Shuah Khan, Andrew Morton, Paul Walmsley,
	Palmer Dabbelt, Albert Ou, Alexandre Ghiti, linux-kernel,
	linux-arch, linux-kselftest, kunit-dev, dri-devel, workflows,
	linux-riscv, linux-doc, peterz, Alessandro Carminati,
	Guenter Roeck, Kees Cook
In-Reply-To: <CADSE00L5OCSNH_XsUHSvi_wAwZWiC2=bUGmiXVAu-LZ=XtvDjQ@mail.gmail.com>

Le 08/05/2026 à 14:52, Albert Esteve a écrit :
> On Wed, May 6, 2026 at 12:11 PM Albert Esteve <aesteve@redhat.com> wrote:
>>
>> On Wed, May 6, 2026 at 11:40 AM David Gow <david@davidgow.net> wrote:
>>>
>>> Le 04/05/2026 à 3:41 PM, Albert Esteve a écrit :
>>>> From: Alessandro Carminati <acarmina@redhat.com>
>>>>
>>>> Some unit tests intentionally trigger warning backtraces by passing bad
>>>> parameters to kernel API functions. Such unit tests typically check the
>>>> return value from such calls, not the existence of the warning backtrace.
>>>>
>>>> Such intentionally generated warning backtraces are neither desirable
>>>> nor useful for a number of reasons:
>>>> - They can result in overlooked real problems.
>>>> - A warning that suddenly starts to show up in unit tests needs to be
>>>>     investigated and has to be marked to be ignored, for example by
>>>>     adjusting filter scripts. Such filters are ad hoc because there is
>>>>     no real standard format for warnings. On top of that, such filter
>>>>     scripts would require constant maintenance.
>>>>
>>>> Solve the problem by providing a means to suppress warning backtraces
>>>> originating from the current kthread while executing test code. Since
>>>> each KUnit test runs in its own kthread, this effectively scopes
>>>> suppression to the test that enabled it. Limit changes to generic code
>>>> to the absolute minimum.
>>>>
>>>> Implementation details:
>>>> Suppression is integrated into the existing KUnit hooks infrastructure
>>>> in test-bug.h, reusing the kunit_running static branch for zero
>>>> overhead when no tests are running.
>>>>
>>>> Suppression is checked at three points in the warning path:
>>>> - In warn_slowpath_fmt(), the check runs before any output, fully
>>>>     suppressing both message and backtrace. This covers architectures
>>>>     without __WARN_FLAGS.
>>>> - In __warn_printk(), the check suppresses the warning message text.
>>>>     This covers architectures that define __WARN_FLAGS but not their own
>>>>     __WARN_printf (arm64, loongarch, parisc, powerpc, riscv, sh), where
>>>>     the message is printed before the trap enters __report_bug().
>>>> - In __report_bug(), the check runs before __warn() is called,
>>>>     suppressing the backtrace and stack dump.
>>>>
>>>> To avoid double-counting on architectures where both __warn_printk()
>>>> and __report_bug() run for the same warning, kunit_is_suppressed_warning()
>>>> takes a bool parameter: true to increment the suppression counter
>>>> (used in warn_slowpath_fmt and __report_bug), false to check only
>>>> (used in __warn_printk).
>>>>
>>>> The suppression state is dynamically allocated via kunit_kzalloc() and
>>>> tied to the KUnit test lifecycle via kunit_add_action(), ensuring
>>>> automatic cleanup at test exit. Writer-side access to the global
>>>> suppression list is serialized with a spinlock; readers use RCU.
>>>>
>>>> Three API forms are provided:
>>>> - kunit_warning_suppress(test) { ... }: scoped, uses __cleanup for
>>>>     automatic teardown on scope exit, kunit_add_action() as safety net
>>>>     for abnormal exits (e.g. kthread_exit from failed assertions).
>>>>     Suppression handle is only accessible inside the block.
>>>> - KUNIT_START/END_SUPPRESSED_WARNING(test): manual macros for larger
>>>>     blocks or when warning counts need to be checked after suppression
>>>>     ends. Limited to one pair per scope.
>>>> - kunit_start/end_suppress_warning(test): direct functions returning
>>>>     an explicit handle, for retaining the handle within the test,
>>>>     or for cross-function usage.
>>>>
>>>> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
>>>> Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
>>>> Reviewed-by: Kees Cook <kees@kernel.org>
>>>> Signed-off-by: Albert Esteve <aesteve@redhat.com>
>>>> ---
>>>
>>> This looks pretty good to me, thanks.
>>>
>>> Reviewed-by: David Gow <david@davidgow.net>
>>>
>>> It's maybe slightly over-the-top to now have three different ways of
>>> enabling warning suppression: I'd probably personally get rid of
>>> KUNIT_START/END_SUPPRESSED_WARNING() if we had to lose one. But if
>>> there's a real reason to prefer keeping all three, it's not actually a
>>> problem to do so.
>>
>> Thanks for the review!
>>
>> I think the three forms earn their keep: the scoped form is the go-to
>> for most cases, but the macros avoid indentation without requiring
>> users to manage a raw pointer. I initially removed the macros and
>> added them back later. Direct calls to the functions will be less
>> frequent, used only when you need the handle.
>>
>> That said, if it becomes a maintenance burden, the macros are the
>> easiest to drop since they're thin wrappers. Let me know if you prefer
>> them to be dropped, and I will send a v9 with that and the
>> `KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT` additions to the drm test
>> patch.
>>
>> BR,
>> Albert.
>>
>>>
>>> Regardless, this series is looking pretty ready to me. Let me know if
>>> you're planning a v9, otherwise we'll take this when you're ready.
> 
> I have been thinking, and despite my last response arguing against it,
> I think I will send that v9 and remove
> KUNIT_START/END_SUPPRESSED_WARNING(). Since the scoped approach with a
> reduced extent should be the default style for most cases, the direct
> calls can cover any other use cases without needing macros. Plus,
> since macros set the handler name for you, it seems odd not to be able
> to use them more than once per test.

Yeah, that was a part of my thought: all of the possible uses of 
KUNIT_START/END_SUPPRESSED_WARNING() can be replaced with the scoped 
version except for the one case where you want to check the number of 
warnings specifically after the scope. I'm not sure the latter would be 
common, and in the few places it's useful, the direct calls are not 
_that_ much more difficult to use.

> I hope that's ok. After that I think it should be ready (at least from my side).
> 
Sounds good: assuming no last-minute disasters, we'll take v9 then.

Cheers,
-- David

^ permalink raw reply

* [PATCH 0/3] drm/i915: reorganize display documentation
From: Jani Nikula @ 2026-05-08 10:20 UTC (permalink / raw)
  To: intel-gfx, intel-xe, dri-devel, linux-doc
  Cc: rodrigo.vivi, Matthew Brost, Thomas Hellström,
	joonas.lahtinen, tursulin, Jani Nikula

Move Intel display documentation under Documentation/gpu/intel-display,
and reorganize into smaller documents. There's lots of room for
improvement, but it's a start.

I intend to merge this through drm-intel-next, as it's fairly isolated.

BR,
Jani.

Jani Nikula (3):
  Documentation/gpu: add dedicated documentation for Intel display
  Documentation/gpu: use === for Intel display section heading
    underlines
  Documentation/gpu: add remaining DOC: comments to Intel display
    documentation

 Documentation/gpu/drivers.rst                 |   1 +
 Documentation/gpu/drm-kms.rst                 |   3 +
 Documentation/gpu/i915.rst                    | 202 +-----------------
 .../gpu/intel-display/async-flip.rst          |   8 +
 Documentation/gpu/intel-display/atomic.rst    |  11 +
 Documentation/gpu/intel-display/audio.rst     |  23 ++
 Documentation/gpu/intel-display/casf.rst      |   8 +
 Documentation/gpu/intel-display/cdclk.rst     |  11 +
 Documentation/gpu/intel-display/cmtg.rst      |   8 +
 Documentation/gpu/intel-display/dmc.rst       |  26 +++
 Documentation/gpu/intel-display/dpio.rst      |   8 +
 Documentation/gpu/intel-display/dpll.rst      |  14 ++
 Documentation/gpu/intel-display/drrs.rst      |  11 +
 Documentation/gpu/intel-display/dsb.rst       |  11 +
 Documentation/gpu/intel-display/fbc.rst       |  11 +
 .../gpu/intel-display/fifo-underrun.rst       |  11 +
 .../gpu/intel-display/frontbuffer.rst         |  14 ++
 Documentation/gpu/intel-display/hotplug.rst   |  11 +
 Documentation/gpu/intel-display/index.rst     |  44 ++++
 Documentation/gpu/intel-display/plane.rst     |  11 +
 Documentation/gpu/intel-display/psr.rst       |  11 +
 Documentation/gpu/intel-display/snps-phy.rst  |   8 +
 Documentation/gpu/intel-display/vbt.rst       |  14 ++
 Documentation/gpu/xe/index.rst                |   5 +
 24 files changed, 290 insertions(+), 195 deletions(-)
 create mode 100644 Documentation/gpu/intel-display/async-flip.rst
 create mode 100644 Documentation/gpu/intel-display/atomic.rst
 create mode 100644 Documentation/gpu/intel-display/audio.rst
 create mode 100644 Documentation/gpu/intel-display/casf.rst
 create mode 100644 Documentation/gpu/intel-display/cdclk.rst
 create mode 100644 Documentation/gpu/intel-display/cmtg.rst
 create mode 100644 Documentation/gpu/intel-display/dmc.rst
 create mode 100644 Documentation/gpu/intel-display/dpio.rst
 create mode 100644 Documentation/gpu/intel-display/dpll.rst
 create mode 100644 Documentation/gpu/intel-display/drrs.rst
 create mode 100644 Documentation/gpu/intel-display/dsb.rst
 create mode 100644 Documentation/gpu/intel-display/fbc.rst
 create mode 100644 Documentation/gpu/intel-display/fifo-underrun.rst
 create mode 100644 Documentation/gpu/intel-display/frontbuffer.rst
 create mode 100644 Documentation/gpu/intel-display/hotplug.rst
 create mode 100644 Documentation/gpu/intel-display/index.rst
 create mode 100644 Documentation/gpu/intel-display/plane.rst
 create mode 100644 Documentation/gpu/intel-display/psr.rst
 create mode 100644 Documentation/gpu/intel-display/snps-phy.rst
 create mode 100644 Documentation/gpu/intel-display/vbt.rst

-- 
2.47.3


^ permalink raw reply

* [PATCH 1/3] Documentation/gpu: add dedicated documentation for Intel display
From: Jani Nikula @ 2026-05-08 10:20 UTC (permalink / raw)
  To: intel-gfx, intel-xe, dri-devel, linux-doc
  Cc: rodrigo.vivi, Matthew Brost, Thomas Hellström,
	joonas.lahtinen, tursulin, Jani Nikula
In-Reply-To: <cover.1778235406.git.jani.nikula@intel.com>

Nowadays, the display support for drm/i915 and drm/xe is shared between
the drivers, even though the code is located under drm/i915/display.

The drm/i915 documentation has everything, including display topics, in
one huge page, while the drm/xe documentation is well-organized but
hardly mentions display. It's not great, to put it mildly.

Split out the Intel display documentation to a dedicated directory,
Documentation/gpu/intel-display. Also directly split the
functionality/feature documentation to dedicated pages to keep the main
index page high level and readable. We'll want to organize this further,
but just sort them alphabetically for starters.

Drop the boilerplate documentation sections that don't actually document
anything.

Cross-reference drm/i915, drm/xe, and intel-display.

Signed-off-by: Jani Nikula <jani.nikula@intel.com>

---

Tip: 'git show --color-moved' conveniently highlights what gets moved
and what gets deleted.
---
 Documentation/gpu/drivers.rst                 |   1 +
 Documentation/gpu/drm-kms.rst                 |   3 +
 Documentation/gpu/i915.rst                    | 202 +-----------------
 .../gpu/intel-display/async-flip.rst          |   8 +
 Documentation/gpu/intel-display/audio.rst     |  23 ++
 Documentation/gpu/intel-display/cdclk.rst     |  11 +
 Documentation/gpu/intel-display/dmc.rst       |  26 +++
 Documentation/gpu/intel-display/dpio.rst      |   8 +
 Documentation/gpu/intel-display/dpll.rst      |  14 ++
 Documentation/gpu/intel-display/drrs.rst      |  11 +
 Documentation/gpu/intel-display/dsb.rst       |  11 +
 Documentation/gpu/intel-display/fbc.rst       |  11 +
 .../gpu/intel-display/fifo-underrun.rst       |  11 +
 .../gpu/intel-display/frontbuffer.rst         |  14 ++
 Documentation/gpu/intel-display/hotplug.rst   |  11 +
 Documentation/gpu/intel-display/index.rst     |  40 ++++
 Documentation/gpu/intel-display/plane.rst     |  11 +
 Documentation/gpu/intel-display/psr.rst       |  11 +
 Documentation/gpu/intel-display/vbt.rst       |  14 ++
 Documentation/gpu/xe/index.rst                |   5 +
 20 files changed, 251 insertions(+), 195 deletions(-)
 create mode 100644 Documentation/gpu/intel-display/async-flip.rst
 create mode 100644 Documentation/gpu/intel-display/audio.rst
 create mode 100644 Documentation/gpu/intel-display/cdclk.rst
 create mode 100644 Documentation/gpu/intel-display/dmc.rst
 create mode 100644 Documentation/gpu/intel-display/dpio.rst
 create mode 100644 Documentation/gpu/intel-display/dpll.rst
 create mode 100644 Documentation/gpu/intel-display/drrs.rst
 create mode 100644 Documentation/gpu/intel-display/dsb.rst
 create mode 100644 Documentation/gpu/intel-display/fbc.rst
 create mode 100644 Documentation/gpu/intel-display/fifo-underrun.rst
 create mode 100644 Documentation/gpu/intel-display/frontbuffer.rst
 create mode 100644 Documentation/gpu/intel-display/hotplug.rst
 create mode 100644 Documentation/gpu/intel-display/index.rst
 create mode 100644 Documentation/gpu/intel-display/plane.rst
 create mode 100644 Documentation/gpu/intel-display/psr.rst
 create mode 100644 Documentation/gpu/intel-display/vbt.rst

diff --git a/Documentation/gpu/drivers.rst b/Documentation/gpu/drivers.rst
index 2e13e0ad7e88..20d2c454aa1d 100644
--- a/Documentation/gpu/drivers.rst
+++ b/Documentation/gpu/drivers.rst
@@ -8,6 +8,7 @@ GPU Driver Documentation
    amdgpu/index
    i915
    imagination/index
+   intel-display/index
    mcde
    meson
    nouveau
diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
index fbe0583eb84c..a125052f46bc 100644
--- a/Documentation/gpu/drm-kms.rst
+++ b/Documentation/gpu/drm-kms.rst
@@ -1,3 +1,6 @@
+
+.. _drm-kms:
+
 =========================
 Kernel Mode Setting (KMS)
 =========================
diff --git a/Documentation/gpu/i915.rst b/Documentation/gpu/i915.rst
index eba09c3ddce4..0c9d68758533 100644
--- a/Documentation/gpu/i915.rst
+++ b/Documentation/gpu/i915.rst
@@ -1,3 +1,6 @@
+
+.. _drm/i915:
+
 ===========================
  drm/i915 Intel GFX Driver
 ===========================
@@ -7,6 +10,9 @@ models) integrated GFX chipsets with both Intel display and rendering
 blocks. This excludes a set of SoC platforms with an SGX rendering unit,
 those have basic support through the gma500 drm driver.
 
+The display, or :ref:`drm-kms`, support for drm/i915 is provided by
+:ref:`drm/intel-display`, and shared with :ref:`drm/xe <drm/xe>`.
+
 Core Driver Infrastructure
 ==========================
 
@@ -64,200 +70,6 @@ Workarounds
 .. kernel-doc:: drivers/gpu/drm/i915/gt/intel_workarounds.c
    :doc: Hardware workarounds
 
-Display Hardware Handling
-=========================
-
-This section covers everything related to the display hardware including
-the mode setting infrastructure, plane, sprite and cursor handling and
-display, output probing and related topics.
-
-Mode Setting Infrastructure
----------------------------
-
-The i915 driver is thus far the only DRM driver which doesn't use the
-common DRM helper code to implement mode setting sequences. Thus it has
-its own tailor-made infrastructure for executing a display configuration
-change.
-
-Frontbuffer Tracking
---------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_frontbuffer.c
-   :doc: frontbuffer tracking
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_frontbuffer.h
-   :internal:
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_frontbuffer.c
-   :internal:
-
-Display FIFO Underrun Reporting
--------------------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fifo_underrun.c
-   :doc: fifo underrun handling
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fifo_underrun.c
-   :internal:
-
-Plane Configuration
--------------------
-
-This section covers plane configuration and composition with the primary
-plane, sprites, cursors and overlays. This includes the infrastructure
-to do atomic vsync'ed updates of all this state and also tightly coupled
-topics like watermark setup and computation, framebuffer compression and
-panel self refresh.
-
-Atomic Plane Helpers
---------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_plane.c
-   :doc: atomic plane helpers
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_plane.c
-   :internal:
-
-Asynchronous Page Flip
-----------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_display.c
-   :doc: asynchronous flip implementation
-
-Output Probing
---------------
-
-This section covers output probing and related infrastructure like the
-hotplug interrupt storm detection and mitigation code. Note that the
-i915 driver still uses most of the common DRM helper code for output
-probing, so those sections fully apply.
-
-Hotplug
--------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_hotplug.c
-   :doc: Hotplug
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_hotplug.c
-   :internal:
-
-High Definition Audio
----------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_audio.c
-   :doc: High Definition Audio over HDMI and Display Port
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_audio.c
-   :internal:
-
-.. kernel-doc:: include/drm/intel/i915_component.h
-   :internal:
-
-Intel HDMI LPE Audio Support
-----------------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_lpe_audio.c
-   :doc: LPE Audio integration for HDMI or DP playback
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_lpe_audio.c
-   :internal:
-
-Panel Self Refresh PSR (PSR/SRD)
---------------------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_psr.c
-   :doc: Panel Self Refresh (PSR/SRD)
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_psr.c
-   :internal:
-
-Frame Buffer Compression (FBC)
-------------------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fbc.c
-   :doc: Frame Buffer Compression (FBC)
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fbc.c
-   :internal:
-
-Display Refresh Rate Switching (DRRS)
--------------------------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_drrs.c
-   :doc: Display Refresh Rate Switching (DRRS)
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_drrs.c
-   :internal:
-
-DPIO
-----
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpio_phy.c
-   :doc: DPIO
-
-DMC Firmware Support
---------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc.c
-   :doc: DMC Firmware Support
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc.c
-   :internal:
-
-DMC Flip Queue
---------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_flipq.c
-   :doc: DMC Flip Queue
-
-DMC wakelock support
---------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc_wl.c
-   :doc: DMC wakelock support
-
-Video BIOS Table (VBT)
-----------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_bios.c
-   :doc: Video BIOS Table (VBT)
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_bios.c
-   :internal:
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_vbt_defs.h
-   :internal:
-
-Display clocks
---------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_cdclk.c
-   :doc: CDCLK / RAWCLK
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_cdclk.c
-   :internal:
-
-Display PLLs
-------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpll_mgr.c
-   :doc: Display PLLs
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpll_mgr.c
-   :internal:
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpll_mgr.h
-   :internal:
-
-Display State Buffer
---------------------
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dsb.c
-   :doc: DSB
-
-.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dsb.c
-   :internal:
-
 GT Programming
 ==============
 
@@ -568,7 +380,7 @@ The HuC FW layout is the same as the GuC one, see `GuC Firmware Layout`_
 
 DMC
 ---
-See `DMC Firmware Support`_
+See :ref:`drm/intel-display/dmc`.
 
 Tracing
 =======
diff --git a/Documentation/gpu/intel-display/async-flip.rst b/Documentation/gpu/intel-display/async-flip.rst
new file mode 100644
index 000000000000..e4ae4012efc5
--- /dev/null
+++ b/Documentation/gpu/intel-display/async-flip.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Asynchronous Page Flip
+----------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_display.c
+   :doc: asynchronous flip implementation
diff --git a/Documentation/gpu/intel-display/audio.rst b/Documentation/gpu/intel-display/audio.rst
new file mode 100644
index 000000000000..7d3c1b514b0e
--- /dev/null
+++ b/Documentation/gpu/intel-display/audio.rst
@@ -0,0 +1,23 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+High Definition Audio
+---------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_audio.c
+   :doc: High Definition Audio over HDMI and Display Port
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_audio.c
+   :internal:
+
+.. kernel-doc:: include/drm/intel/i915_component.h
+   :internal:
+
+Intel HDMI LPE Audio Support
+----------------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_lpe_audio.c
+   :doc: LPE Audio integration for HDMI or DP playback
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_lpe_audio.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/cdclk.rst b/Documentation/gpu/intel-display/cdclk.rst
new file mode 100644
index 000000000000..231b22a733e7
--- /dev/null
+++ b/Documentation/gpu/intel-display/cdclk.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Display clocks
+--------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_cdclk.c
+   :doc: CDCLK / RAWCLK
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_cdclk.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/dmc.rst b/Documentation/gpu/intel-display/dmc.rst
new file mode 100644
index 000000000000..2fcdbd457d79
--- /dev/null
+++ b/Documentation/gpu/intel-display/dmc.rst
@@ -0,0 +1,26 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+.. _drm/intel-display/dmc:
+
+DMC Firmware Support
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc.c
+   :doc: DMC Firmware Support
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc.c
+   :internal:
+
+
+DMC Flip Queue
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_flipq.c
+   :doc: DMC Flip Queue
+
+DMC wakelock support
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc_wl.c
+   :doc: DMC wakelock support
diff --git a/Documentation/gpu/intel-display/dpio.rst b/Documentation/gpu/intel-display/dpio.rst
new file mode 100644
index 000000000000..32e6f299f256
--- /dev/null
+++ b/Documentation/gpu/intel-display/dpio.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+DPIO
+----
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpio_phy.c
+   :doc: DPIO
diff --git a/Documentation/gpu/intel-display/dpll.rst b/Documentation/gpu/intel-display/dpll.rst
new file mode 100644
index 000000000000..35e8168ccfb9
--- /dev/null
+++ b/Documentation/gpu/intel-display/dpll.rst
@@ -0,0 +1,14 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Display PLLs
+------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpll_mgr.c
+   :doc: Display PLLs
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpll_mgr.c
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpll_mgr.h
+   :internal:
diff --git a/Documentation/gpu/intel-display/drrs.rst b/Documentation/gpu/intel-display/drrs.rst
new file mode 100644
index 000000000000..adb413f300f1
--- /dev/null
+++ b/Documentation/gpu/intel-display/drrs.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Display Refresh Rate Switching (DRRS)
+-------------------------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_drrs.c
+   :doc: Display Refresh Rate Switching (DRRS)
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_drrs.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/dsb.rst b/Documentation/gpu/intel-display/dsb.rst
new file mode 100644
index 000000000000..cbd40b0a4e7b
--- /dev/null
+++ b/Documentation/gpu/intel-display/dsb.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Display State Buffer
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dsb.c
+   :doc: DSB
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dsb.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/fbc.rst b/Documentation/gpu/intel-display/fbc.rst
new file mode 100644
index 000000000000..40f9d16bdebd
--- /dev/null
+++ b/Documentation/gpu/intel-display/fbc.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Frame Buffer Compression (FBC)
+------------------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fbc.c
+   :doc: Frame Buffer Compression (FBC)
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fbc.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/fifo-underrun.rst b/Documentation/gpu/intel-display/fifo-underrun.rst
new file mode 100644
index 000000000000..50731f3a1f03
--- /dev/null
+++ b/Documentation/gpu/intel-display/fifo-underrun.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Display FIFO Underrun Reporting
+-------------------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fifo_underrun.c
+   :doc: fifo underrun handling
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_fifo_underrun.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/frontbuffer.rst b/Documentation/gpu/intel-display/frontbuffer.rst
new file mode 100644
index 000000000000..2a1bc63ba6b4
--- /dev/null
+++ b/Documentation/gpu/intel-display/frontbuffer.rst
@@ -0,0 +1,14 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Frontbuffer Tracking
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_frontbuffer.c
+   :doc: frontbuffer tracking
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_frontbuffer.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_frontbuffer.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/hotplug.rst b/Documentation/gpu/intel-display/hotplug.rst
new file mode 100644
index 000000000000..4cd9dd5ac8fc
--- /dev/null
+++ b/Documentation/gpu/intel-display/hotplug.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Hotplug
+-------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_hotplug.c
+   :doc: Hotplug
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_hotplug.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/index.rst b/Documentation/gpu/intel-display/index.rst
new file mode 100644
index 000000000000..8d40363b8f90
--- /dev/null
+++ b/Documentation/gpu/intel-display/index.rst
@@ -0,0 +1,40 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+.. _drm/intel-display:
+
+====================
+Intel Display Driver
+====================
+
+The Intel display driver provides the display, or :ref:`drm-kms`, support for
+both the :ref:`drm/xe <drm/xe>` and :ref:`drm/i915 <drm/i915>` Intel GPU
+drivers.
+
+The source code currently resides under ``drivers/gpu/drm/i915/display`` due to
+historical reasons, and it's compiled separately into both drm/xe and drm/i915
+kernel modules.
+
+The drm/xe and drm/i915 drivers are the "core" or "parent" drivers for display,
+as they initialize and own the drm device, and pass that on to the display
+driver. The display driver isn't an independent driver in that sense.
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Detailed display topics
+
+   async-flip
+   audio
+   cdclk
+   dmc
+   dpio
+   dpll
+   drrs
+   dsb
+   fbc
+   fifo-underrun
+   frontbuffer
+   hotplug
+   plane
+   psr
+   vbt
diff --git a/Documentation/gpu/intel-display/plane.rst b/Documentation/gpu/intel-display/plane.rst
new file mode 100644
index 000000000000..41cf6571aab0
--- /dev/null
+++ b/Documentation/gpu/intel-display/plane.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Atomic Plane Helpers
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_plane.c
+   :doc: atomic plane helpers
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_plane.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/psr.rst b/Documentation/gpu/intel-display/psr.rst
new file mode 100644
index 000000000000..134c905f500e
--- /dev/null
+++ b/Documentation/gpu/intel-display/psr.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Panel Self Refresh PSR (PSR/SRD)
+--------------------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_psr.c
+   :doc: Panel Self Refresh (PSR/SRD)
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_psr.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/vbt.rst b/Documentation/gpu/intel-display/vbt.rst
new file mode 100644
index 000000000000..bbc7ee183f1b
--- /dev/null
+++ b/Documentation/gpu/intel-display/vbt.rst
@@ -0,0 +1,14 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Video BIOS Table (VBT)
+----------------------
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_bios.c
+   :doc: Video BIOS Table (VBT)
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_bios.c
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_vbt_defs.h
+   :internal:
diff --git a/Documentation/gpu/xe/index.rst b/Documentation/gpu/xe/index.rst
index 874ffcb6da3a..665c0e93601c 100644
--- a/Documentation/gpu/xe/index.rst
+++ b/Documentation/gpu/xe/index.rst
@@ -1,5 +1,7 @@
 .. SPDX-License-Identifier: (GPL-2.0+ OR MIT)
 
+.. _drm/xe:
+
 =======================
 drm/xe Intel GFX Driver
 =======================
@@ -8,6 +10,9 @@ The drm/xe driver supports some future GFX cards with rendering, display,
 compute and media. Support for currently available platforms like TGL, ADL,
 DG2, etc is provided to prototype the driver.
 
+The display, or :ref:`drm-kms`, support for drm/xe is provided by
+:ref:`drm/intel-display`, and shared with :ref:`drm/i915 <drm/i915>`.
+
 .. toctree::
    :titlesonly:
 
-- 
2.47.3


^ permalink raw reply related

* [PATCH 2/3] Documentation/gpu: use === for Intel display section heading underlines
From: Jani Nikula @ 2026-05-08 10:20 UTC (permalink / raw)
  To: intel-gfx, intel-xe, dri-devel, linux-doc
  Cc: rodrigo.vivi, Matthew Brost, Thomas Hellström,
	joonas.lahtinen, tursulin, Jani Nikula
In-Reply-To: <cover.1778235406.git.jani.nikula@intel.com>

Prefer to use === instead of --- for top level section heading
underlines to allow using the latter for sub-headings later.

While at it, fix the underline lenghts where needed.

Signed-off-by: Jani Nikula <jani.nikula@intel.com>
---
 Documentation/gpu/intel-display/async-flip.rst    | 2 +-
 Documentation/gpu/intel-display/audio.rst         | 4 ++--
 Documentation/gpu/intel-display/cdclk.rst         | 2 +-
 Documentation/gpu/intel-display/dmc.rst           | 6 +++---
 Documentation/gpu/intel-display/dpio.rst          | 2 +-
 Documentation/gpu/intel-display/dpll.rst          | 2 +-
 Documentation/gpu/intel-display/drrs.rst          | 2 +-
 Documentation/gpu/intel-display/dsb.rst           | 2 +-
 Documentation/gpu/intel-display/fbc.rst           | 2 +-
 Documentation/gpu/intel-display/fifo-underrun.rst | 2 +-
 Documentation/gpu/intel-display/frontbuffer.rst   | 2 +-
 Documentation/gpu/intel-display/hotplug.rst       | 2 +-
 Documentation/gpu/intel-display/plane.rst         | 2 +-
 Documentation/gpu/intel-display/psr.rst           | 2 +-
 Documentation/gpu/intel-display/vbt.rst           | 2 +-
 15 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/Documentation/gpu/intel-display/async-flip.rst b/Documentation/gpu/intel-display/async-flip.rst
index e4ae4012efc5..40f93e885bb7 100644
--- a/Documentation/gpu/intel-display/async-flip.rst
+++ b/Documentation/gpu/intel-display/async-flip.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Asynchronous Page Flip
-----------------------
+======================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_display.c
    :doc: asynchronous flip implementation
diff --git a/Documentation/gpu/intel-display/audio.rst b/Documentation/gpu/intel-display/audio.rst
index 7d3c1b514b0e..eef95df75f8d 100644
--- a/Documentation/gpu/intel-display/audio.rst
+++ b/Documentation/gpu/intel-display/audio.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 High Definition Audio
----------------------
+=====================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_audio.c
    :doc: High Definition Audio over HDMI and Display Port
@@ -14,7 +14,7 @@ High Definition Audio
    :internal:
 
 Intel HDMI LPE Audio Support
-----------------------------
+============================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_lpe_audio.c
    :doc: LPE Audio integration for HDMI or DP playback
diff --git a/Documentation/gpu/intel-display/cdclk.rst b/Documentation/gpu/intel-display/cdclk.rst
index 231b22a733e7..a66d623b0ec9 100644
--- a/Documentation/gpu/intel-display/cdclk.rst
+++ b/Documentation/gpu/intel-display/cdclk.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Display clocks
---------------
+==============
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_cdclk.c
    :doc: CDCLK / RAWCLK
diff --git a/Documentation/gpu/intel-display/dmc.rst b/Documentation/gpu/intel-display/dmc.rst
index 2fcdbd457d79..4368da4c7048 100644
--- a/Documentation/gpu/intel-display/dmc.rst
+++ b/Documentation/gpu/intel-display/dmc.rst
@@ -4,7 +4,7 @@
 .. _drm/intel-display/dmc:
 
 DMC Firmware Support
---------------------
+====================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc.c
    :doc: DMC Firmware Support
@@ -14,13 +14,13 @@ DMC Firmware Support
 
 
 DMC Flip Queue
---------------------
+==============
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_flipq.c
    :doc: DMC Flip Queue
 
 DMC wakelock support
---------------------
+====================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_dmc_wl.c
    :doc: DMC wakelock support
diff --git a/Documentation/gpu/intel-display/dpio.rst b/Documentation/gpu/intel-display/dpio.rst
index 32e6f299f256..84d92ac162f8 100644
--- a/Documentation/gpu/intel-display/dpio.rst
+++ b/Documentation/gpu/intel-display/dpio.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 DPIO
-----
+====
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpio_phy.c
    :doc: DPIO
diff --git a/Documentation/gpu/intel-display/dpll.rst b/Documentation/gpu/intel-display/dpll.rst
index 35e8168ccfb9..c750352e0ae5 100644
--- a/Documentation/gpu/intel-display/dpll.rst
+++ b/Documentation/gpu/intel-display/dpll.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Display PLLs
-------------
+============
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_dpll_mgr.c
    :doc: Display PLLs
diff --git a/Documentation/gpu/intel-display/drrs.rst b/Documentation/gpu/intel-display/drrs.rst
index adb413f300f1..a5aaba63d6b9 100644
--- a/Documentation/gpu/intel-display/drrs.rst
+++ b/Documentation/gpu/intel-display/drrs.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Display Refresh Rate Switching (DRRS)
--------------------------------------
+=====================================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_drrs.c
    :doc: Display Refresh Rate Switching (DRRS)
diff --git a/Documentation/gpu/intel-display/dsb.rst b/Documentation/gpu/intel-display/dsb.rst
index cbd40b0a4e7b..857aca59995a 100644
--- a/Documentation/gpu/intel-display/dsb.rst
+++ b/Documentation/gpu/intel-display/dsb.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Display State Buffer
---------------------
+====================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_dsb.c
    :doc: DSB
diff --git a/Documentation/gpu/intel-display/fbc.rst b/Documentation/gpu/intel-display/fbc.rst
index 40f9d16bdebd..de9e19021f50 100644
--- a/Documentation/gpu/intel-display/fbc.rst
+++ b/Documentation/gpu/intel-display/fbc.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Frame Buffer Compression (FBC)
-------------------------------
+==============================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_fbc.c
    :doc: Frame Buffer Compression (FBC)
diff --git a/Documentation/gpu/intel-display/fifo-underrun.rst b/Documentation/gpu/intel-display/fifo-underrun.rst
index 50731f3a1f03..5d8f01921506 100644
--- a/Documentation/gpu/intel-display/fifo-underrun.rst
+++ b/Documentation/gpu/intel-display/fifo-underrun.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Display FIFO Underrun Reporting
--------------------------------
+===============================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_fifo_underrun.c
    :doc: fifo underrun handling
diff --git a/Documentation/gpu/intel-display/frontbuffer.rst b/Documentation/gpu/intel-display/frontbuffer.rst
index 2a1bc63ba6b4..7ae38e0827bf 100644
--- a/Documentation/gpu/intel-display/frontbuffer.rst
+++ b/Documentation/gpu/intel-display/frontbuffer.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Frontbuffer Tracking
---------------------
+====================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_frontbuffer.c
    :doc: frontbuffer tracking
diff --git a/Documentation/gpu/intel-display/hotplug.rst b/Documentation/gpu/intel-display/hotplug.rst
index 4cd9dd5ac8fc..f33bc0087c27 100644
--- a/Documentation/gpu/intel-display/hotplug.rst
+++ b/Documentation/gpu/intel-display/hotplug.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Hotplug
--------
+=======
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_hotplug.c
    :doc: Hotplug
diff --git a/Documentation/gpu/intel-display/plane.rst b/Documentation/gpu/intel-display/plane.rst
index 41cf6571aab0..59932a82051b 100644
--- a/Documentation/gpu/intel-display/plane.rst
+++ b/Documentation/gpu/intel-display/plane.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Atomic Plane Helpers
---------------------
+====================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_plane.c
    :doc: atomic plane helpers
diff --git a/Documentation/gpu/intel-display/psr.rst b/Documentation/gpu/intel-display/psr.rst
index 134c905f500e..63e56abcdd56 100644
--- a/Documentation/gpu/intel-display/psr.rst
+++ b/Documentation/gpu/intel-display/psr.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Panel Self Refresh PSR (PSR/SRD)
---------------------------------
+================================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_psr.c
    :doc: Panel Self Refresh (PSR/SRD)
diff --git a/Documentation/gpu/intel-display/vbt.rst b/Documentation/gpu/intel-display/vbt.rst
index bbc7ee183f1b..be69f7fd7b39 100644
--- a/Documentation/gpu/intel-display/vbt.rst
+++ b/Documentation/gpu/intel-display/vbt.rst
@@ -2,7 +2,7 @@
 .. Copyright © 2026 Intel Corporation
 
 Video BIOS Table (VBT)
-----------------------
+======================
 
 .. kernel-doc:: drivers/gpu/drm/i915/display/intel_bios.c
    :doc: Video BIOS Table (VBT)
-- 
2.47.3


^ permalink raw reply related

* [PATCH 3/3] Documentation/gpu: add remaining DOC: comments to Intel display documentation
From: Jani Nikula @ 2026-05-08 10:20 UTC (permalink / raw)
  To: intel-gfx, intel-xe, dri-devel, linux-doc
  Cc: rodrigo.vivi, Matthew Brost, Thomas Hellström,
	joonas.lahtinen, tursulin, Jani Nikula
In-Reply-To: <cover.1778235406.git.jani.nikula@intel.com>

Not all of the overview DOC: comments in the display driver are
incorporated into the documentation. Add the missing ones, including
some function documentation.

Signed-off-by: Jani Nikula <jani.nikula@intel.com>
---
 Documentation/gpu/intel-display/atomic.rst   | 11 +++++++++++
 Documentation/gpu/intel-display/casf.rst     |  8 ++++++++
 Documentation/gpu/intel-display/cmtg.rst     |  8 ++++++++
 Documentation/gpu/intel-display/index.rst    |  4 ++++
 Documentation/gpu/intel-display/snps-phy.rst |  8 ++++++++
 5 files changed, 39 insertions(+)
 create mode 100644 Documentation/gpu/intel-display/atomic.rst
 create mode 100644 Documentation/gpu/intel-display/casf.rst
 create mode 100644 Documentation/gpu/intel-display/cmtg.rst
 create mode 100644 Documentation/gpu/intel-display/snps-phy.rst

diff --git a/Documentation/gpu/intel-display/atomic.rst b/Documentation/gpu/intel-display/atomic.rst
new file mode 100644
index 000000000000..43a473181e7a
--- /dev/null
+++ b/Documentation/gpu/intel-display/atomic.rst
@@ -0,0 +1,11 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Atomic Modeset Support
+======================
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_atomic.c
+   :doc: atomic modeset support
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_atomic.c
+   :internal:
diff --git a/Documentation/gpu/intel-display/casf.rst b/Documentation/gpu/intel-display/casf.rst
new file mode 100644
index 000000000000..406778ccd94c
--- /dev/null
+++ b/Documentation/gpu/intel-display/casf.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Content Adaptive Sharpness Filter (CASF)
+========================================
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_casf.c
+   :doc: Content Adaptive Sharpness Filter (CASF)
diff --git a/Documentation/gpu/intel-display/cmtg.rst b/Documentation/gpu/intel-display/cmtg.rst
new file mode 100644
index 000000000000..04edd0bd165d
--- /dev/null
+++ b/Documentation/gpu/intel-display/cmtg.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Common Primary Timing Generator (CMTG)
+======================================
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_cmtg.c
+   :doc: Common Primary Timing Generator (CMTG)
diff --git a/Documentation/gpu/intel-display/index.rst b/Documentation/gpu/intel-display/index.rst
index 8d40363b8f90..01c3d1e576b7 100644
--- a/Documentation/gpu/intel-display/index.rst
+++ b/Documentation/gpu/intel-display/index.rst
@@ -24,8 +24,11 @@ driver. The display driver isn't an independent driver in that sense.
    :caption: Detailed display topics
 
    async-flip
+   atomic
    audio
+   casf
    cdclk
+   cmtg
    dmc
    dpio
    dpll
@@ -37,4 +40,5 @@ driver. The display driver isn't an independent driver in that sense.
    hotplug
    plane
    psr
+   snps-phy
    vbt
diff --git a/Documentation/gpu/intel-display/snps-phy.rst b/Documentation/gpu/intel-display/snps-phy.rst
new file mode 100644
index 000000000000..c9e333fa7f62
--- /dev/null
+++ b/Documentation/gpu/intel-display/snps-phy.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: MIT
+.. Copyright © 2026 Intel Corporation
+
+Synopsis PHY support
+====================
+
+.. kernel-doc:: drivers/gpu/drm/i915/display/intel_snps_phy.c
+   :doc: Synopsis PHY support
-- 
2.47.3


^ permalink raw reply related

* [PATCH v10 0/9] drivers/base: Introduce revocable
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney

This series introduces the "revocable" mechanism, a synchronization
primitive designed to prevent Use-After-Free errors.

- Patch 1 introduces the revocable which is an implementation of ideas
  from the talk [1].

- Patch 2 adds KUnit test cases.

- Patches 3 to 7 transitions the UAF prevention logic within the GPIO
  core (gpiolib) to use the "revocable" mechanism.

  The existing code aims to prevent UAF issues when the underlying GPIO
  chip is removed.  They replace that custom logic with the generic
  "revocable" API, which is designed to handle such lifecycle
  dependencies.  There should be no changes in behavior.

- Patches 8 to 9 uses "revocable" mechanism to fix an UAF in
  cros_ec_chardev driver.  Alternatively, [2] is a series for fixing the
  same issue without using "revocable".

Since v9, there are two ways to manage the resource provider handle.
- Embedded allocation: patches 3 to 7 might be the potential user.
- Dynamic allocation: patches 8 to 9 might be the potential user.

[1] https://lpc.events/event/17/contributions/1627/
[2] https://lore.kernel.org/all/20260427134659.95181-1-tzungbi@kernel.org

---
v10:
- Unify handling of embedded and dynamic allocation.

v9: https://lore.kernel.org/all/20260427135841.96266-1-tzungbi@kernel.org
- Rebase onto v7.1-rc1.
- Remove the selftests patch as it makes less sense to test revocable
  APIs via kselftests.
- Merge patches 7 to 11 from
  https://lore.kernel.org/all/20260213092958.864411-1-tzungbi@kernel.org
  into the series.
- Merge patch from
  https://lore.kernel.org/all/20250923075302.591026-5-tzungbi@kernel.org
- Merge patch from
  https://lore.kernel.org/all/20250912081718.3827390-6-tzungbi@kernel.org

v8: https://lore.kernel.org/all/20260213092307.858908-1-tzungbi@kernel.org
- Rework on the revocable APIs.  See changelog in [PATCH v8 1/3] for details.

v7: https://lore.kernel.org/all/20260116080235.350305-1-tzungbi@kernel.org
- Rebase onto next-20260115.

v6: https://lore.kernel.org/all/20251106152330.11733-1-tzungbi@kernel.org
- Rebase onto next-20251106.
- Separate revocable core and use cases.

v5: https://lore.kernel.org/all/20251016054204.1523139-1-tzungbi@kernel.org
- Rebase onto next-20251015.
- Add more context about the PoC.
- Support multiple revocable providers in the PoC.

v4: https://lore.kernel.org/all/20250923075302.591026-1-tzungbi@kernel.org
- Rebase onto next-20250922.
- Remove the 5th patch from v3.
- Add fops replacement PoC in 5th - 7th patches.

v3: https://lore.kernel.org/all/20250912081718.3827390-1-tzungbi@kernel.org
- Rebase onto https://lore.kernel.org/all/20250828083601.856083-1-tzungbi@kernel.org
  and next-20250912.
- The 4th patch changed accordingly.

v2: https://lore.kernel.org/all/20250820081645.847919-1-tzungbi@kernel.org
- Rename "ref_proxy" -> "revocable".
- Add test cases in Kunit and selftest.

v1: https://lore.kernel.org/all/20250814091020.1302888-1-tzungbi@kernel.org

Tzung-Bi Shih (9):
  revocable: Revocable resource management
  revocable: Add KUnit test cases
  gpio: Add revocable provider handle for struct gpio_chip
  gpio: cdev: Leverage revocable for accessing struct gpio_chip
  gpio: Remove gpio_chip_guard by using revocable
  gpio: Leverage revocable for accessing struct gpio_chip
  gpio: Remove unused `chip` and `srcu` in struct gpio_device
  platform/chrome: Protect cros_ec_device lifecycle with revocable
  platform/chrome: cros_ec_chardev: Consume cros_ec_device via revocable

 .../driver-api/driver-model/index.rst         |   1 +
 .../driver-api/driver-model/revocable.rst     | 384 +++++++++++++++++
 MAINTAINERS                                   |  10 +
 drivers/base/Kconfig                          |   5 +
 drivers/base/Makefile                         |   5 +-
 drivers/base/revocable.c                      | 267 ++++++++++++
 drivers/base/revocable_test.c                 | 405 ++++++++++++++++++
 drivers/gpio/gpiolib-cdev.c                   |  77 ++--
 drivers/gpio/gpiolib-sysfs.c                  |  31 +-
 drivers/gpio/gpiolib.c                        | 263 +++++-------
 drivers/gpio/gpiolib.h                        |  28 +-
 drivers/platform/chrome/cros_ec.c             |  11 +
 drivers/platform/chrome/cros_ec_chardev.c     |  80 +++-
 include/linux/platform_data/cros_ec_proto.h   |   3 +
 include/linux/revocable.h                     | 204 +++++++++
 15 files changed, 1505 insertions(+), 269 deletions(-)
 create mode 100644 Documentation/driver-api/driver-model/revocable.rst
 create mode 100644 drivers/base/revocable.c
 create mode 100644 drivers/base/revocable_test.c
 create mode 100644 include/linux/revocable.h

-- 
2.51.0


^ permalink raw reply

* [PATCH v10 1/9] revocable: Revocable resource management
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

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.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- 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=MehkJc-js=Wk9vBAcXOpazqjtYDLPUEhmbN8U7Wu2YpgA@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-provided 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 debugging.
- 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=build SPHINXDIRS=driver-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/Documentation/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/Documentation/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
+
+==============================
+Revocable Resource Management
+==============================
+
+Overview
+========
+
+.. kernel-doc:: drivers/base/revocable.c
+   :doc: Overview
+
+Revocable vs. Devres (devm)
+===========================
+
+Revocable and Devres address different problems in resource management:
+
+*   **Devres:** Primarily addresses **resource leaks**.  The lifetime of the
+    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 intermediary),
+    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 object*,
+    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.  Before
+    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 beyond
+    the provider device's lifetime.  For instance, a userspace program might
+    keep a character device file open even after the physical device has been
+    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, while
+the Revocable mechanism ensures that attempts to *access* these resources are
+done safely, even if the resource has been released.
+
+API and Usage
+=============
+
+For Resource Providers
+----------------------
+
+There are two ways to manage the resource provider handle (``struct revocable``):
+
+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 references,
+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 away.
+    This drops the provider's reference.
+*   **Cleanup:** The caller *must* call revocable_put() to release its reference
+    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 away.
+    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 memory
+    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 = devm_kzalloc(dev, sizeof(*foo_dev), GFP_KERNEL);
+        if (!foo_dev)
+            return -ENOMEM;
+
+        // Acquire the actual resource.
+        res = ...(...);
+
+        // Allocate the revocable handle.
+        foo_dev->rev = 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 = 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->rev.
+
+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 = devm_kzalloc(dev, sizeof(*foo_dev), GFP_KERNEL);
+        if (!foo_dev)
+            return -ENOMEM;
+
+        // Acquire the actual resource.
+        res = ...(...);
+
+        // Initialize the embedded revocable.
+        ret = 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 = 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 2fb1c75afd16..6ec3a7cb5e6b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7808,6 +7808,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*
@@ -22824,6 +22825,14 @@ F:	include/uapi/linux/rseq.h
 F:	kernel/rseq.c
 F:	tools/testing/selftests/rseq/
 
+REVOCABLE RESOURCE MANAGEMENT
+M:	Tzung-Bi Shih <tzungbi@kernel.org>
+L:	driver-core@lists.linux.dev
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git
+F:	drivers/base/revocable.c
+F:	include/linux/revocable.h
+
 RFKILL
 M:	Johannes Berg <johannes@sipsolutions.net>
 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			:= 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) += auxiliary.o
 obj-$(CONFIG_DEVTMPFS)	+= devtmpfs.o
 obj-y			+= 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 <linux/kref.h>
+#include <linux/revocable.h>
+#include <linux/slab.h>
+#include <linux/srcu.h>
+
+/**
+ * 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 = 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 = kzalloc_obj(*rev);
+	if (!rev)
+		return NULL;
+
+	ret = revocable_init(rev, res);
+	if (ret) {
+		kfree(rev);
+		return NULL;
+	}
+
+	rev->embedded = 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 = 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 = 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 = 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 = 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 example, it
+ * is illegal to invoke revocable_withdraw_access() in an irq handler if the
+ * 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 = rh->rev;
+
+	rh->idx = 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 example, it
+ * is illegal to invoke revocable_withdraw_access() in an irq handler if the
+ * matching revocable_try_access() was invoked in process context.
+ */
+void revocable_withdraw_access(struct revocable_handle *rh)
+	__releases(&rh->rev->srcu)
+{
+	struct revocable *rev = 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 <linux/cleanup.h>
+#include <linux/kref.h>
+#include <linux/srcu.h>
+
+/**
+ * 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) = &_rh;	\
+										\
+	revocable_handle_init(_rev, &_rh);					\
+	_res = 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 that
+ * 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 with
+ * 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 statements
+ * 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 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 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) = &_rh;		\
+	     ({ revocable_handle_init(_rev, &_rh);				\
+		_res = revocable_try_access(&_rh);				\
+		true; });							\
+	     ({ goto _label; }))						\
+		if (0) {							\
+_label:										\
+			break;							\
+		} else
+
+/**
+ * revocable_try_access_with_scoped() - 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_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 current
+ * 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 early.
+ */
+#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_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 -ENODEV
+ * 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 code
+ * 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 */
-- 
2.51.0


^ permalink raw reply related

* [PATCH v10 2/9] revocable: Add KUnit test cases
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

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.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- 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=y \
        revocable_test
Or
$ ./tools/testing/kunit/kunit.py run \
        --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=y \
        --kconfig_add CONFIG_PROVE_LOCKING=y \
        --kconfig_add CONFIG_DEBUG_KERNEL=y \
        --kconfig_add CONFIG_DEBUG_INFO=y \
        --kconfig_add CONFIG_DEBUG_INFO_DWARF5=y \
        --kconfig_add CONFIG_KASAN=y \
        --kconfig_add CONFIG_DETECT_HUNG_TASK=y \
        --kconfig_add CONFIG_DEFAULT_HUNG_TASK_TIMEOUT="10" \
        --arch=x86_64 \
        --make_options="C=1 W=1" \
        revocable_test

---
 MAINTAINERS                   |   1 +
 drivers/base/Kconfig          |   5 +
 drivers/base/Makefile         |   3 +
 drivers/base/revocable_test.c | 405 ++++++++++++++++++++++++++++++++++
 4 files changed, 414 insertions(+)
 create mode 100644 drivers/base/revocable_test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6ec3a7cb5e6b..14aa035ef431 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22831,6 +22831,7 @@ L:	driver-core@lists.linux.dev
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git
 F:	drivers/base/revocable.c
+F:	drivers/base/revocable_test.c
 F:	include/linux/revocable.h
 
 RFKILL
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index f7d385cbd3ba..921f7e812ba5 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -259,3 +259,8 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT
 	  work on.
 
 endmenu
+
+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/Makefile b/drivers/base/Makefile
index bdf854694e39..5fd19abbc83e 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -35,3 +35,6 @@ ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
 # define_trace.h needs to know how to find our header
 CFLAGS_trace.o		:= -I$(src)
 obj-$(CONFIG_TRACING)	+= trace.o
+
+# KUnit test cases
+obj-$(CONFIG_REVOCABLE_KUNIT_TEST)	+= revocable_test.o
diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c
new file mode 100644
index 000000000000..bb776a747ff7
--- /dev/null
+++ b/drivers/base/revocable_test.c
@@ -0,0 +1,405 @@
+// 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 <kunit/test.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/refcount.h>
+#include <linux/revocable.h>
+
+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 = (void *)0x12345678, *res;
+
+	rev = 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 = 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 = (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 = 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 = (void *)0x12345678, *res;
+
+	rev = 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 = 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 = 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 = (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 = 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 = 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 *rev)
+{
+	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 = (void *)0x12345678, *res;
+	int ret;
+	bool accessed;
+
+	rev = 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 = 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 = 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 = false;
+	revocable_try_access_with_scoped(rev, res) {
+		KUNIT_EXPECT_PTR_EQ(test, res, NULL);
+		KUNIT_EXPECT_EQ(test, get_refcount(rev), 2);
+		accessed = true;
+	}
+	KUNIT_EXPECT_TRUE(test, accessed);
+	KUNIT_EXPECT_EQ(test, get_refcount(rev), 1);
+
+	ret = call_revocable_try_access_or_return_err(rev);
+	KUNIT_EXPECT_EQ(test, ret, -ENXIO);
+
+	ret = call_revocable_try_access_or_return(rev);
+	KUNIT_EXPECT_EQ(test, ret, -ENODEV);
+
+	call_revocable_try_access_or_return_void(test, rev);
+
+	ret = call_revocable_try_access_or_return_err_scoped(rev);
+	KUNIT_EXPECT_EQ(test, ret, -ENXIO);
+
+	ret = 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 = false;
+	revocable_try_access_or_skip_scoped(rev, res)
+		accessed = 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 = 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 = data;
+	void *res;
+
+	complete(&ctx->started);
+
+	wait_for_completion(&ctx->enter);
+	res = 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 = (void *)0x12345678;
+	struct test_concurrent_access_context *ctx;
+	int i;
+
+	rev = 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 = kunit_kmalloc_array(test, 3, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	for (i = 0; i < 3; ++i) {
+		ctx[i].test = test;
+		init_completion(&ctx[i].started);
+		init_completion(&ctx[i].enter);
+
+		if (i == 0) {
+			/* Transfer the ownership of provider reference too. */
+			ctx[i].rev = rev;
+			ctx[i].thread = 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 = 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 = 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 = NULL;
+	/* consumer2 enters read-side critical section. */
+	complete(&ctx[2].enter);
+	msleep(100);
+
+	/* consumer{1,2} exit read-side critical section. */
+	for (i = 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[] = {
+	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 = {
+	.name = "revocable_test",
+	.test_cases = revocable_test_cases,
+};
+
+kunit_test_suite(revocable_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tzung-Bi Shih <tzungbi@kernel.org>");
+MODULE_DESCRIPTION("KUnit tests for the revocable API");
-- 
2.51.0


^ permalink raw reply related

* [PATCH v10 3/9] gpio: Add revocable provider handle for struct gpio_chip
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

The underlying chip can be removed asynchronously.  `gdev->srcu` is used
to ensure the synchronization before accessing `gdev->chip`.

Revocable encapsulates the details.  Add revocable provider handle for
the corresponding struct gpio_chip in struct gpio_device so that it can
start to hide the synchronization details.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- 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.c | 13 +++++++++++--
 drivers/gpio/gpiolib.h |  3 +++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 1e6dce430dca..9046fd923af0 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -23,6 +23,7 @@
 #include <linux/nospec.h>
 #include <linux/of.h>
 #include <linux/pinctrl/consumer.h>
+#include <linux/revocable.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <linux/srcu.h>
@@ -874,6 +875,7 @@ static void gpiodev_release(struct device *dev)
 	synchronize_srcu(&gdev->desc_srcu);
 	cleanup_srcu_struct(&gdev->desc_srcu);
 
+	revocable_put(&gdev->chip_rev);
 	ida_free(&gpio_ida, gdev->id);
 	kfree_const(gdev->label);
 	kfree(gdev->descs);
@@ -1210,6 +1212,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 	else
 		gdev->owner = THIS_MODULE;
 
+	ret = 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 +1230,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 			if (base < 0) {
 				ret = base;
 				base = 0;
-				goto err_put_device;
+				goto err_revoke_chip_rev;
 			}
 
 			/*
@@ -1244,7 +1250,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 		ret = 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;
 		}
 	}
 
@@ -1343,6 +1349,8 @@ 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;
@@ -1389,6 +1397,7 @@ void gpiochip_remove(struct gpio_chip *gc)
 	/* 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);
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index dc4cb61a9318..4e2e98f61f5a 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -16,6 +16,7 @@
 #include <linux/gpio/driver.h>
 #include <linux/module.h>
 #include <linux/notifier.h>
+#include <linux/revocable.h>
 #include <linux/spinlock.h>
 #include <linux/string.h>
 #include <linux/srcu.h>
@@ -55,6 +56,7 @@ struct fwnode_handle;
  * @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
@@ -82,6 +84,7 @@ struct gpio_device {
 	struct workqueue_struct	*line_state_wq;
 	struct blocking_notifier_head device_notifier;
 	struct srcu_struct	srcu;
+	struct revocable	chip_rev;
 
 #ifdef CONFIG_PINCTRL
 	/*
-- 
2.51.0


^ permalink raw reply related

* [PATCH v10 4/9] gpio: cdev: Leverage revocable for accessing struct gpio_chip
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

Struct gpio_device now provides a revocable provider to the underlying
struct gpio_chip.  Leverage revocable for accessing the struct
gpio_chip.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- No changes.

v9: https://lore.kernel.org/all/20260427135841.96266-5-tzungbi@kernel.org
- New to the series.
- Rename "chip_rp" -> "chip_rev".

v4 - v8:
- Doesn't exist.

v3: https://lore.kernel.org/all/20260213092958.864411-9-tzungbi@kernel.org
- Change revocable API usages accordingly.

v2: https://lore.kernel.org/all/20260203061059.975605-9-tzungbi@kernel.org
- Change usages accordingly after applying
  https://lore.kernel.org/all/20260129143733.45618-4-tzungbi@kernel.org.
  - Preserve a local storage for `struct revocable`.
- Combine multiple patches (see "v1:").
- Fix a race condition reported in
  https://lore.kernel.org/all/CAMRc=McDaipt85OHm0MksLkuf6E79dY1uNSqqbcJnoQTUs81Pw@mail.gmail.com/
  and analyzed in
  https://lore.kernel.org/all/aXEEUWwkxHZzCnaI@tzungbi-laptop/.
  In v1, the blocking_notifier_chain_unregister() will be skipped if the
  chip has been removed, leading an UAF in gpiolib_cdev_unregister().
  In v2, it won't skip blocking_notifier_chain_unregister().

v1:
- https://lore.kernel.org/all/20260116081036.352286-14-tzungbi@kernel.org
- https://lore.kernel.org/all/20260116081036.352286-15-tzungbi@kernel.org
- https://lore.kernel.org/all/20260116081036.352286-16-tzungbi@kernel.org
- https://lore.kernel.org/all/20260116081036.352286-17-tzungbi@kernel.org
- https://lore.kernel.org/all/20260116081036.352286-18-tzungbi@kernel.org

---
 drivers/gpio/gpiolib-cdev.c | 68 ++++++++++++++-----------------------
 1 file changed, 26 insertions(+), 42 deletions(-)

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index f36b7c06996d..d8a7ccb406a5 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -22,6 +22,7 @@
 #include <linux/overflow.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/poll.h>
+#include <linux/revocable.h>
 #include <linux/seq_file.h>
 #include <linux/spinlock.h>
 #include <linux/string.h>
@@ -210,11 +211,9 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
 	DECLARE_BITMAP(vals, GPIOHANDLES_MAX);
 	unsigned int i;
 	int ret;
+	struct gpio_chip *gc;
 
-	guard(srcu)(&lh->gdev->srcu);
-
-	if (!rcu_access_pointer(lh->gdev->chip))
-		return -ENODEV;
+	revocable_try_access_or_return(&lh->gdev->chip_rev, gc);
 
 	switch (cmd) {
 	case GPIOHANDLE_GET_LINE_VALUES_IOCTL:
@@ -1432,11 +1431,9 @@ static long linereq_ioctl(struct file *file, unsigned int cmd,
 {
 	struct linereq *lr = file->private_data;
 	void __user *ip = (void __user *)arg;
+	struct gpio_chip *gc;
 
-	guard(srcu)(&lr->gdev->srcu);
-
-	if (!rcu_access_pointer(lr->gdev->chip))
-		return -ENODEV;
+	revocable_try_access_or_return(&lr->gdev->chip_rev, gc);
 
 	switch (cmd) {
 	case GPIO_V2_LINE_GET_VALUES_IOCTL:
@@ -1463,10 +1460,10 @@ static __poll_t linereq_poll(struct file *file,
 {
 	struct linereq *lr = file->private_data;
 	__poll_t events = 0;
+	struct gpio_chip *gc;
 
-	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;
 
 	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 = 0;
 	int ret;
+	struct gpio_chip *gc;
 
-	guard(srcu)(&lr->gdev->srcu);
-
-	if (!rcu_access_pointer(lr->gdev->chip))
-		return -ENODEV;
+	revocable_try_access_or_return(&lr->gdev->chip_rev, gc);
 
 	if (count < sizeof(le))
 		return -EINVAL;
@@ -1759,10 +1754,10 @@ static __poll_t lineevent_poll(struct file *file,
 {
 	struct lineevent_state *le = file->private_data;
 	__poll_t events = 0;
+	struct gpio_chip *gc;
 
-	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;
 
 	poll_wait(file, &le->wait, wait);
@@ -1797,11 +1792,9 @@ static ssize_t lineevent_read(struct file *file, char __user *buf,
 	ssize_t bytes_read = 0;
 	ssize_t ge_size;
 	int ret;
+	struct gpio_chip *gc;
 
-	guard(srcu)(&le->gdev->srcu);
-
-	if (!rcu_access_pointer(le->gdev->chip))
-		return -ENODEV;
+	revocable_try_access_or_return(&le->gdev->chip_rev, gc);
 
 	/*
 	 * When compatible system call is being used the struct gpioevent_data,
@@ -1879,11 +1872,9 @@ static long lineevent_ioctl(struct file *file, unsigned int cmd,
 	struct lineevent_state *le = file->private_data;
 	void __user *ip = (void __user *)arg;
 	struct gpiohandle_data ghd;
+	struct gpio_chip *gc;
 
-	guard(srcu)(&le->gdev->srcu);
-
-	if (!rcu_access_pointer(le->gdev->chip))
-		return -ENODEV;
+	revocable_try_access_or_return(&le->gdev->chip_rev, gc);
 
 	/*
 	 * We can get the value for an event line but not set it,
@@ -2385,12 +2376,10 @@ static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	struct gpio_chardev_data *cdev = file->private_data;
 	struct gpio_device *gdev = cdev->gdev;
 	void __user *ip = (void __user *)arg;
-
-	guard(srcu)(&gdev->srcu);
+	struct gpio_chip *gc;
 
 	/* 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);
 
 	/* Fill in the struct and pass to userspace */
 	switch (cmd) {
@@ -2448,12 +2437,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 = 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 |= GPIO_V2_LINE_FLAG_USED;
-		}
 	}
 
 	ret = kfifo_in_spinlocked(&ctx->cdev->events, &ctx->chg, 1,
@@ -2534,10 +2520,10 @@ static __poll_t lineinfo_watch_poll(struct file *file,
 {
 	struct gpio_chardev_data *cdev = file->private_data;
 	__poll_t events = 0;
+	struct gpio_chip *gc;
 
-	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;
 
 	poll_wait(file, &cdev->wait, pollt);
@@ -2557,11 +2543,9 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
 	ssize_t bytes_read = 0;
 	int ret;
 	size_t event_size;
+	struct gpio_chip *gc;
 
-	guard(srcu)(&cdev->gdev->srcu);
-
-	if (!rcu_access_pointer(cdev->gdev->chip))
-		return -ENODEV;
+	revocable_try_access_or_return(&cdev->gdev->chip_rev, gc);
 
 #ifndef CONFIG_GPIO_CDEV_V1
 	event_size = sizeof(struct gpio_v2_line_info_changed);
-- 
2.51.0


^ permalink raw reply related

* [PATCH v10 5/9] gpio: Remove gpio_chip_guard by using revocable
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

Struct gpio_device now provides a revocable provider to the underlying
struct gpio_chip.  Leverage revocable for accessing the struct
gpio_chip instead of using gpio_chip_guard.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- No changes.

v9: https://lore.kernel.org/all/20260427135841.96266-6-tzungbi@kernel.org
- New to the series.
- Rename "chip_rp" -> "chip_rev".

v4 - v8:
- Doesn't exist.

v3: https://lore.kernel.org/all/20260213092958.864411-10-tzungbi@kernel.org
- Change revocable API usages accordingly.

v2: https://lore.kernel.org/all/20260203061059.975605-10-tzungbi@kernel.org
- Separate from v1 for including gpio_chip_guard only.

v1: https://lore.kernel.org/all/20260116081036.352286-23-tzungbi@kernel.org

---
 drivers/gpio/gpiolib-cdev.c  |   9 +-
 drivers/gpio/gpiolib-sysfs.c |  31 +++----
 drivers/gpio/gpiolib.c       | 164 ++++++++++++++++-------------------
 drivers/gpio/gpiolib.h       |  21 -----
 4 files changed, 91 insertions(+), 134 deletions(-)

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index d8a7ccb406a5..4837497c5e6e 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -2156,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;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return;
+	revocable_try_access_or_return_void(&desc->gdev->chip_rev, gc);
 
 	memset(info, 0, sizeof(*info));
 	info->offset = gpiod_hwgpio(desc);
@@ -2192,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 |= 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 |= GPIO_V2_LINE_FLAG_USED;
 	}
 
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 <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/printk.h>
+#include <linux/revocable.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/srcu.h>
@@ -215,10 +216,9 @@ static int gpio_sysfs_request_irq(struct gpiod_data *data, unsigned char flags)
 	struct gpio_desc *desc = data->desc;
 	unsigned long irq_flags;
 	int ret;
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	data->irq = gpiod_to_irq(desc);
 	if (data->irq < 0)
@@ -244,7 +244,7 @@ static int gpio_sysfs_request_irq(struct gpiod_data *data, unsigned char flags)
 	 * Remove this redundant call (along with the corresponding unlock)
 	 * when those drivers have been fixed.
 	 */
-	ret = gpiochip_lock_as_irq(guard.gc, gpiod_hwgpio(desc));
+	ret = gpiochip_lock_as_irq(gc, gpiod_hwgpio(desc));
 	if (ret < 0)
 		goto err_clr_bits;
 
@@ -258,7 +258,7 @@ static int gpio_sysfs_request_irq(struct gpiod_data *data, unsigned char flags)
 	return 0;
 
 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 = data->desc;
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return;
+	revocable_try_access_or_return_void(&desc->gdev->chip_rev, gc);
 
 	data->irq_flags = 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;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	offset = 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;
 
 	/* 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;
 	}
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	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)
 
 	desc_data->desc = 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 = direction_may_change;
 	else
 		desc_data->direction_can_change = false;
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 9046fd923af0..0389d5f55282 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -454,14 +454,13 @@ int gpiod_get_direction(struct gpio_desc *desc)
 	unsigned long flags;
 	unsigned int offset;
 	int ret;
+	struct gpio_chip *gc;
 
 	ret = validate_desc(desc, __func__);
 	if (ret <= 0)
 		return -EINVAL;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	offset = gpiod_hwgpio(desc);
 	flags = READ_ONCE(desc->flags);
@@ -474,7 +473,7 @@ int gpiod_get_direction(struct gpio_desc *desc)
 	    test_bit(GPIOD_FLAG_IS_OUT, &flags))
 		return 0;
 
-	ret = gpiochip_get_direction(guard.gc, offset);
+	ret = gpiochip_get_direction(gc, offset);
 	if (ret < 0)
 		return ret;
 
@@ -2563,16 +2562,15 @@ int gpiod_request_commit(struct gpio_desc *desc, const char *label)
 {
 	unsigned int offset;
 	int ret;
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	if (test_and_set_bit(GPIOD_FLAG_REQUESTED, &desc->flags))
 		return -EBUSY;
 
 	offset = gpiod_hwgpio(desc);
-	if (!gpiochip_line_is_valid(guard.gc, offset)) {
+	if (!gpiochip_line_is_valid(gc, offset)) {
 		ret = -EINVAL;
 		goto out_clear_bit;
 	}
@@ -2581,15 +2579,15 @@ int gpiod_request_commit(struct gpio_desc *desc, const char *label)
 	 * before IRQs are enabled, for non-sleeping (SOC) GPIOs.
 	 */
 
-	if (guard.gc->request) {
-		ret = guard.gc->request(guard.gc, offset);
+	if (gc->request) {
+		ret = gc->request(gc, offset);
 		if (ret > 0)
 			ret = -EBADE;
 		if (ret)
 			goto out_clear_bit;
 	}
 
-	if (guard.gc->get_direction)
+	if (gc->get_direction)
 		gpiod_get_direction(desc);
 
 	ret = desc_set_label(desc, label ? : "?");
@@ -2626,16 +2624,17 @@ int gpiod_request(struct gpio_desc *desc, const char *label)
 void gpiod_free_commit(struct gpio_desc *desc)
 {
 	unsigned long flags;
+	struct gpio_chip *gc;
 
 	might_sleep();
 
-	CLASS(gpio_chip_guard, guard)(desc);
+	revocable_try_access_or_return_void(&desc->gdev->chip_rev, gc);
 
 	flags = READ_ONCE(desc->flags);
 
-	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));
 
 		clear_bit(GPIOD_FLAG_ACTIVE_LOW, &flags);
 		clear_bit(GPIOD_FLAG_REQUESTED, &flags);
@@ -2787,15 +2786,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;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
-	if (!guard.gc->set_config)
+	if (!gc->set_config)
 		return -ENOTSUPP;
 
-	ret = guard.gc->set_config(guard.gc, gpiod_hwgpio(desc), config);
+	ret = gc->set_config(gc, gpiod_hwgpio(desc), config);
 	if (ret > 0)
 		ret = -EBADE;
 
@@ -2964,17 +2962,16 @@ EXPORT_SYMBOL_GPL(gpiod_direction_input);
 int gpiod_direction_input_nonotify(struct gpio_desc *desc)
 {
 	int ret = 0, dir;
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	/*
 	 * 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__);
@@ -2987,11 +2984,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 = gpiochip_direction_input(guard.gc,
-					       gpiod_hwgpio(desc));
-	} else if (guard.gc->get_direction) {
-		dir = gpiochip_get_direction(guard.gc, gpiod_hwgpio(desc));
+	if (gc->direction_input) {
+		ret = gpiochip_direction_input(gc, gpiod_hwgpio(desc));
+	} else if (gc->get_direction) {
+		dir = gpiochip_get_direction(gc, gpiod_hwgpio(desc));
 		if (dir < 0)
 			return dir;
 
@@ -3031,31 +3027,28 @@ static int gpiochip_set(struct gpio_chip *gc, unsigned int offset, int value)
 static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value)
 {
 	int val = !!value, ret = 0, dir;
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	/*
 	 * 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;
 	}
 
-	if (guard.gc->direction_output) {
-		ret = gpiochip_direction_output(guard.gc,
-						gpiod_hwgpio(desc), val);
+	if (gc->direction_output) {
+		ret = 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 = gpiochip_get_direction(guard.gc,
-						     gpiod_hwgpio(desc));
+		if (gc->get_direction) {
+			dir = gpiochip_get_direction(gc, gpiod_hwgpio(desc));
 			if (dir < 0)
 				return dir;
 
@@ -3070,7 +3063,7 @@ static int gpiod_direction_output_raw_commit(struct gpio_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 = gpiochip_set(guard.gc, gpiod_hwgpio(desc), val);
+		ret = gpiochip_set(gc, gpiod_hwgpio(desc), val);
 		if (ret)
 			return ret;
 	}
@@ -3208,20 +3201,18 @@ int gpiod_direction_output_nonotify(struct gpio_desc *desc, int value)
 int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags)
 {
 	int ret;
+	struct gpio_chip *gc;
 
 	VALIDATE_DESC(desc);
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
-	if (!guard.gc->en_hw_timestamp) {
+	if (!gc->en_hw_timestamp) {
 		gpiod_warn(desc, "%s: hw ts not supported\n", __func__);
 		return -ENOTSUPP;
 	}
 
-	ret = guard.gc->en_hw_timestamp(guard.gc,
-					gpiod_hwgpio(desc), flags);
+	ret = gc->en_hw_timestamp(gc, gpiod_hwgpio(desc), flags);
 	if (ret)
 		gpiod_warn(desc, "%s: hw ts request failed\n", __func__);
 
@@ -3241,20 +3232,18 @@ EXPORT_SYMBOL_GPL(gpiod_enable_hw_timestamp_ns);
 int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags)
 {
 	int ret;
+	struct gpio_chip *gc;
 
 	VALIDATE_DESC(desc);
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
-	if (!guard.gc->dis_hw_timestamp) {
+	if (!gc->dis_hw_timestamp) {
 		gpiod_warn(desc, "%s: hw ts not supported\n", __func__);
 		return -ENOTSUPP;
 	}
 
-	ret = guard.gc->dis_hw_timestamp(guard.gc, gpiod_hwgpio(desc),
-					 flags);
+	ret = gc->dis_hw_timestamp(gc, gpiod_hwgpio(desc), flags);
 	if (ret)
 		gpiod_warn(desc, "%s: hw ts release failed\n", __func__);
 
@@ -3518,31 +3507,29 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
 		unsigned long *mask, *bits;
 		int first, j;
 
-		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);
 
-		if (likely(guard.gc->ngpio <= FASTPATH_NGPIO)) {
+		if (likely(gc->ngpio <= FASTPATH_NGPIO)) {
 			mask = fastpath_mask;
 			bits = fastpath_bits;
 		} else {
 			gfp_t flags = can_sleep ? GFP_KERNEL : GFP_ATOMIC;
 
-			mask = bitmap_alloc(guard.gc->ngpio, flags);
+			mask = bitmap_alloc(gc->ngpio, flags);
 			if (!mask)
 				return -ENOMEM;
 
-			bits = bitmap_alloc(guard.gc->ngpio, flags);
+			bits = bitmap_alloc(gc->ngpio, flags);
 			if (!bits) {
 				bitmap_free(mask);
 				return -ENOMEM;
 			}
 		}
 
-		bitmap_zero(mask, guard.gc->ngpio);
+		bitmap_zero(mask, gc->ngpio);
 
 		if (!can_sleep)
-			WARN_ON(guard.gc->can_sleep);
+			WARN_ON(gc->can_sleep);
 
 		/* collect all inputs belonging to the same chip */
 		first = i;
@@ -3557,9 +3544,9 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
 				i = 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));
 
-		ret = gpio_chip_get_multiple(guard.gc, mask, bits);
+		ret = gpio_chip_get_multiple(gc, mask, bits);
 		if (ret) {
 			if (mask != fastpath_mask)
 				bitmap_free(mask);
@@ -3708,15 +3695,14 @@ EXPORT_SYMBOL_GPL(gpiod_get_array_value);
 static int gpio_set_open_drain_value_commit(struct gpio_desc *desc, bool value)
 {
 	int ret = 0, offset = gpiod_hwgpio(desc);
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	if (value) {
-		ret = gpiochip_direction_input(guard.gc, offset);
+		ret = gpiochip_direction_input(gc, offset);
 	} else {
-		ret = gpiochip_direction_output(guard.gc, offset, 0);
+		ret = gpiochip_direction_output(gc, offset, 0);
 		if (!ret)
 			set_bit(GPIOD_FLAG_IS_OUT, &desc->flags);
 	}
@@ -3737,17 +3723,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 = 0, offset = gpiod_hwgpio(desc);
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	if (value) {
-		ret = gpiochip_direction_output(guard.gc, offset, 1);
+		ret = gpiochip_direction_output(gc, offset, 1);
 		if (!ret)
 			set_bit(GPIOD_FLAG_IS_OUT, &desc->flags);
 	} else {
-		ret = gpiochip_direction_input(guard.gc, offset);
+		ret = gpiochip_direction_input(gc, offset);
 	}
 	trace_gpio_direction(desc_to_gpio(desc), !value, ret);
 	if (ret < 0)
@@ -3760,15 +3745,15 @@ static int gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value)
 
 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;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	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);
 }
 
 /*
@@ -3863,31 +3848,30 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
 		unsigned long *mask, *bits;
 		int count = 0;
 
-		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);
 
-		if (likely(guard.gc->ngpio <= FASTPATH_NGPIO)) {
+		if (likely(gc->ngpio <= FASTPATH_NGPIO)) {
 			mask = fastpath_mask;
 			bits = fastpath_bits;
 		} else {
 			gfp_t flags = can_sleep ? GFP_KERNEL : GFP_ATOMIC;
 
-			mask = bitmap_alloc(guard.gc->ngpio, flags);
+			mask = bitmap_alloc(gc->ngpio, flags);
 			if (!mask)
 				return -ENOMEM;
 
-			bits = bitmap_alloc(guard.gc->ngpio, flags);
+			bits = bitmap_alloc(gc->ngpio, flags);
 			if (!bits) {
 				bitmap_free(mask);
 				return -ENOMEM;
 			}
 		}
 
-		bitmap_zero(mask, guard.gc->ngpio);
+		bitmap_zero(mask, gc->ngpio);
 
 		if (!can_sleep)
-			WARN_ON(guard.gc->can_sleep);
+			WARN_ON(gc->can_sleep);
 
 		do {
 			struct gpio_desc *desc = desc_array[i];
@@ -3926,10 +3910,10 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
 				i = 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 != 0) {
-			ret = gpiochip_set_multiple(guard.gc, mask, bits);
+			ret = gpiochip_set_multiple(gc, mask, bits);
 			if (ret)
 				return ret;
 		}
@@ -5109,18 +5093,16 @@ int gpiod_hog(struct gpio_desc *desc, const char *name,
 	struct gpio_desc *local_desc;
 	int hwnum;
 	int ret;
+	struct gpio_chip *gc;
 
-	CLASS(gpio_chip_guard, guard)(desc);
-	if (!guard.gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	if (test_and_set_bit(GPIOD_FLAG_IS_HOGGED, &desc->flags))
 		return 0;
 
 	hwnum = gpiod_hwgpio(desc);
 
-	local_desc = gpiochip_request_own_desc(guard.gc, hwnum, name,
-					       lflags, dflags);
+	local_desc = gpiochip_request_own_desc(gc, hwnum, name, lflags, dflags);
 	if (IS_ERR(local_desc)) {
 		clear_bit(GPIOD_FLAG_IS_HOGGED, &desc->flags);
 		ret = PTR_ERR(local_desc);
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index 4e2e98f61f5a..90ad9b7fdfdd 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -228,27 +228,6 @@ struct gpio_desc {
 
 #define gpiod_not_found(desc)		(IS_ERR(desc) && PTR_ERR(desc) == -ENOENT)
 
-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 = desc->gdev;
-		_guard.idx = srcu_read_lock(&_guard.gdev->srcu);
-		_guard.gc = 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);
-- 
2.51.0


^ permalink raw reply related

* [PATCH v10 6/9] gpio: Leverage revocable for accessing struct gpio_chip
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

Struct gpio_device now provides a revocable provider to the underlying
struct gpio_chip.  Leverage revocable for accessing the struct
gpio_chip.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- No changes.

v9: https://lore.kernel.org/all/20260427135841.96266-7-tzungbi@kernel.org
- New to the series.
- Rename "chip_rp" -> "chip_rev".

v4 - v8:
- Doesn't exist.

v3: https://lore.kernel.org/all/20260213092958.864411-11-tzungbi@kernel.org
- Change revocable API usages accordingly.

v2: https://lore.kernel.org/all/20260203061059.975605-11-tzungbi@kernel.org
- Separate from v1(a) for excluding gpio_chip_guard and combine v1(b).

v1(a):
- https://lore.kernel.org/all/20260116081036.352286-23-tzungbi@kernel.org
v1(b):
- https://lore.kernel.org/all/20260116081036.352286-19-tzungbi@kernel.org

---
 drivers/gpio/gpiolib.c | 60 +++++++++++++-----------------------------
 1 file changed, 18 insertions(+), 42 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 0389d5f55282..d2d9807da98d 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -335,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);
 
@@ -561,9 +564,7 @@ static struct gpio_desc *gpio_name_to_desc(const char * const name)
 
 	list_for_each_entry_srcu(gdev, &gpio_devices, list,
 				 srcu_read_lock_held(&gpio_devices_srcu)) {
-		guard(srcu)(&gdev->srcu);
-
-		gc = srcu_dereference(gdev->chip, &gdev->srcu);
+		revocable_try_access_with(&gdev->chip_rev, gc);
 		if (!gc)
 			continue;
 
@@ -1050,9 +1051,7 @@ static void gpiochip_setup_devs(void)
 
 	list_for_each_entry_srcu(gdev, &gpio_devices, list,
 				 srcu_read_lock_held(&gpio_devices_srcu)) {
-		guard(srcu)(&gdev->srcu);
-
-		gc = 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;
@@ -1457,11 +1456,11 @@ struct gpio_device *gpio_device_find(const void *data,
 		if (!device_is_registered(&gdev->dev))
 			continue;
 
-		guard(srcu)(&gdev->srcu);
-
-		gc = srcu_dereference(gdev->chip, &gdev->srcu);
+		revocable_try_access_with(&gdev->chip_rev, gc);
+		if (!gc)
+			continue;
 
-		if (gc && match(gc, data))
+		if (match(gc, data))
 			return gpio_device_get(gdev);
 	}
 
@@ -3404,18 +3403,10 @@ static int gpio_chip_get_value(struct gpio_chip *gc, const struct gpio_desc *des
 
 static int gpiod_get_raw_value_commit(const struct gpio_desc *desc)
 {
-	struct gpio_device *gdev;
 	struct gpio_chip *gc;
 	int value;
 
-	/* FIXME Unable to use gpio_chip_guard due to const desc. */
-	gdev = desc->gdev;
-
-	guard(srcu)(&gdev->srcu);
-
-	gc = srcu_dereference(gdev->chip, &gdev->srcu);
-	if (!gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	value = gpio_chip_get_value(gc, desc);
 	value = value < 0 ? value : !!value;
@@ -3454,9 +3445,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_chip *gc)
 {
-	guard(srcu)(&gdev->srcu);
+	struct gpio_chip *chip;
 
-	return gc == srcu_dereference(gdev->chip, &gdev->srcu);
+	revocable_try_access_with(&gdev->chip_rev, chip);
+	return chip ? chip == gc : false;
 }
 
 int gpiod_get_array_value_complex(bool raw, bool can_sleep,
@@ -3479,11 +3471,7 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
 		if (!can_sleep)
 			WARN_ON(array_info->gdev->can_sleep);
 
-		guard(srcu)(&array_info->gdev->srcu);
-		gc = 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);
 
 		ret = gpio_chip_get_multiple(gc, array_info->get_mask,
 					     value_bitmap);
@@ -3820,11 +3808,7 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
 				return -EPERM;
 		}
 
-		guard(srcu)(&array_info->gdev->srcu);
-		gc = 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);
 
 		if (!raw && !bitmap_empty(array_info->invert_mask, array_size))
 			bitmap_xor(value_bitmap, value_bitmap,
@@ -4119,7 +4103,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;
@@ -4128,12 +4111,7 @@ int gpiod_to_irq(const struct gpio_desc *desc)
 	if (ret <= 0)
 		return -EINVAL;
 
-	gdev = desc->gdev;
-	/* FIXME Cannot use gpio_chip_guard due to const desc. */
-	guard(srcu)(&gdev->srcu);
-	gc = srcu_dereference(gdev->chip, &gdev->srcu);
-	if (!gc)
-		return -ENODEV;
+	revocable_try_access_or_return(&desc->gdev->chip_rev, gc);
 
 	offset = gpiod_hwgpio(desc);
 	if (gc->to_irq) {
@@ -5472,9 +5450,7 @@ static int gpiolib_seq_show(struct seq_file *s, void *v)
 	if (priv->newline)
 		seq_putc(s, '\n');
 
-	guard(srcu)(&gdev->srcu);
-
-	gc = 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;
-- 
2.51.0


^ permalink raw reply related

* [PATCH v10 7/9] gpio: Remove unused `chip` and `srcu` in struct gpio_device
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

`chip` and `srcu` in struct gpio_device are unused as their usages are
replaced to use revocable.  Remove them.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- No changes.

v9: https://lore.kernel.org/all/20260427135841.96266-8-tzungbi@kernel.org
- New to the series.
- No changes.

v4 - v8:
- Doesn't exist.

v3: https://lore.kernel.org/all/20260213092958.864411-12-tzungbi@kernel.org
- No changes.

v2: https://lore.kernel.org/all/20260203061059.975605-12-tzungbi@kernel.org
- No changes.

v1: https://lore.kernel.org/all/20260116081036.352286-24-tzungbi@kernel.org

---
 drivers/gpio/gpiolib.c | 26 +-------------------------
 drivers/gpio/gpiolib.h |  4 ----
 2 files changed, 1 insertion(+), 29 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index d2d9807da98d..5ce12f3b753f 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -428,8 +428,6 @@ static int gpiochip_get_direction(struct gpio_chip *gc, unsigned int offset)
 {
 	int ret;
 
-	lockdep_assert_held(&gc->gpiodev->srcu);
-
 	if (WARN_ON(!gc->get_direction))
 		return -EOPNOTSUPP;
 
@@ -879,7 +877,6 @@ static void gpiodev_release(struct device *dev)
 	ida_free(&gpio_ida, gdev->id);
 	kfree_const(gdev->label);
 	kfree(gdev->descs);
-	cleanup_srcu_struct(&gdev->srcu);
 	kfree(gdev);
 }
 
@@ -1154,14 +1151,9 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 		goto err_free_gdev;
 	gdev->id = ret;
 
-	ret = init_srcu_struct(&gdev->srcu);
-	if (ret)
-		goto err_free_ida;
-	rcu_assign_pointer(gdev->chip, gc);
-
 	ret = init_srcu_struct(&gdev->desc_srcu);
 	if (ret)
-		goto err_cleanup_gdev_srcu;
+		goto err_free_ida;
 
 	ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
 	if (ret)
@@ -1355,8 +1347,6 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 
 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:
@@ -1393,8 +1383,6 @@ void gpiochip_remove(struct gpio_chip *gc)
 	synchronize_srcu(&gpio_devices_srcu);
 
 	/* 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);
@@ -2905,8 +2893,6 @@ static int gpiochip_direction_input(struct gpio_chip *gc, unsigned int offset)
 {
 	int ret;
 
-	lockdep_assert_held(&gc->gpiodev->srcu);
-
 	if (WARN_ON(!gc->direction_input))
 		return -EOPNOTSUPP;
 
@@ -2922,8 +2908,6 @@ static int gpiochip_direction_output(struct gpio_chip *gc, unsigned int offset,
 {
 	int ret;
 
-	lockdep_assert_held(&gc->gpiodev->srcu);
-
 	if (WARN_ON(!gc->direction_output))
 		return -EOPNOTSUPP;
 
@@ -3011,8 +2995,6 @@ static int gpiochip_set(struct gpio_chip *gc, unsigned int offset, int value)
 {
 	int ret;
 
-	lockdep_assert_held(&gc->gpiodev->srcu);
-
 	if (WARN_ON(unlikely(!gc->set)))
 		return -EOPNOTSUPP;
 
@@ -3360,8 +3342,6 @@ static int gpiochip_get(struct gpio_chip *gc, unsigned int offset)
 {
 	int ret;
 
-	lockdep_assert_held(&gc->gpiodev->srcu);
-
 	/* Make sure this is called after checking for gc->get(). */
 	ret = gc->get(gc, offset);
 	if (ret > 1) {
@@ -3417,8 +3397,6 @@ static int gpiod_get_raw_value_commit(const struct gpio_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;
 
@@ -3762,8 +3740,6 @@ static int gpiochip_set_multiple(struct gpio_chip *gc,
 	unsigned int i;
 	int ret;
 
-	lockdep_assert_held(&gc->gpiodev->srcu);
-
 	if (gc->set_multiple) {
 		ret = gc->set_multiple(gc, mask, bits);
 		if (ret > 0)
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index 90ad9b7fdfdd..efbff4a1cd4e 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -32,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
@@ -55,7 +54,6 @@ 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
  *
@@ -69,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;
@@ -83,7 +80,6 @@ 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;
 
 #ifdef CONFIG_PINCTRL
-- 
2.51.0


^ permalink raw reply related

* [PATCH v10 8/9] platform/chrome: Protect cros_ec_device lifecycle with revocable
From: Tzung-Bi Shih @ 2026-05-08 10:54 UTC (permalink / raw)
  To: Arnd Bergmann, Greg Kroah-Hartman, Bartosz Golaszewski,
	Linus Walleij
  Cc: Benson Leung, tzungbi, linux-kernel, chrome-platform, driver-core,
	linux-doc, linux-gpio, Rafael J. Wysocki, Danilo Krummrich,
	Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
	Jason Gunthorpe, Johan Hovold, Paul E . McKenney
In-Reply-To: <20260508105448.31799-1-tzungbi@kernel.org>

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.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v10:
- 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

---
 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/cros_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 <linux/platform_device.h>
 #include <linux/platform_data/cros_ec_commands.h>
 #include <linux/platform_data/cros_ec_proto.h>
+#include <linux/revocable.h>
 #include <linux/slab.h>
 #include <linux/suspend.h>
 
@@ -37,6 +38,7 @@ static void cros_ec_device_free(void *data)
 
 	mutex_destroy(&ec_dev->lock);
 	lockdep_unregister_key(&ec_dev->lockdep_key);
+	revocable_revoke(ec_dev->its_rev);
 }
 
 struct cros_ec_device *cros_ec_device_alloc(struct device *dev)
@@ -47,6 +49,15 @@ struct cros_ec_device *cros_ec_device_alloc(struct device *dev)
 	if (!ec_dev)
 		return NULL;
 
+	ec_dev->its_rev = 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 = 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/platform_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 <linux/lockdep_types.h>
 #include <linux/mutex.h>
 #include <linux/notifier.h>
+#include <linux/revocable.h>
 
 #include <linux/platform_data/cros_ec_commands.h>
 
@@ -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;
 
 	struct blocking_notifier_head panic_notifier;
+	struct revocable *its_rev;
 };
 
 /**
-- 
2.51.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox