From 3365b71bc4c574dd954e7954595c85bb75b6912c Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:25 +0000 Subject: [PATCH 01/79] rust: auxiliary: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-1-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/auxiliary.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/helpers/auxiliary.c b/rust/helpers/auxiliary.c index 8b5e0fea4493..dd1c130843a0 100644 --- a/rust/helpers/auxiliary.c +++ b/rust/helpers/auxiliary.c @@ -2,12 +2,14 @@ #include -void rust_helper_auxiliary_device_uninit(struct auxiliary_device *adev) +__rust_helper void +rust_helper_auxiliary_device_uninit(struct auxiliary_device *adev) { return auxiliary_device_uninit(adev); } -void rust_helper_auxiliary_device_delete(struct auxiliary_device *adev) +__rust_helper void +rust_helper_auxiliary_device_delete(struct auxiliary_device *adev) { return auxiliary_device_delete(adev); } From 93c7fa7416126dd29f78321f408044b474de8b8b Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:38 +0000 Subject: [PATCH 02/79] rust: device: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-14-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/device.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/helpers/device.c b/rust/helpers/device.c index 9a4316bafedf..a8ab931a9bd1 100644 --- a/rust/helpers/device.c +++ b/rust/helpers/device.c @@ -2,26 +2,26 @@ #include -int rust_helper_devm_add_action(struct device *dev, - void (*action)(void *), - void *data) +__rust_helper int rust_helper_devm_add_action(struct device *dev, + void (*action)(void *), + void *data) { return devm_add_action(dev, action, data); } -int rust_helper_devm_add_action_or_reset(struct device *dev, - void (*action)(void *), - void *data) +__rust_helper int rust_helper_devm_add_action_or_reset(struct device *dev, + void (*action)(void *), + void *data) { return devm_add_action_or_reset(dev, action, data); } -void *rust_helper_dev_get_drvdata(const struct device *dev) +__rust_helper void *rust_helper_dev_get_drvdata(const struct device *dev) { return dev_get_drvdata(dev); } -void rust_helper_dev_set_drvdata(struct device *dev, void *data) +__rust_helper void rust_helper_dev_set_drvdata(struct device *dev, void *data) { dev_set_drvdata(dev, data); } From 8a03afe94763108537e33b48378bbc7472b4cc9d Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:39 +0000 Subject: [PATCH 03/79] rust: dma: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-15-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/dma.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rust/helpers/dma.c b/rust/helpers/dma.c index 6e741c197242..8c7e8d7b888e 100644 --- a/rust/helpers/dma.c +++ b/rust/helpers/dma.c @@ -2,20 +2,23 @@ #include -void *rust_helper_dma_alloc_attrs(struct device *dev, size_t size, - dma_addr_t *dma_handle, gfp_t flag, - unsigned long attrs) +__rust_helper void *rust_helper_dma_alloc_attrs(struct device *dev, size_t size, + dma_addr_t *dma_handle, + gfp_t flag, unsigned long attrs) { return dma_alloc_attrs(dev, size, dma_handle, flag, attrs); } -void rust_helper_dma_free_attrs(struct device *dev, size_t size, void *cpu_addr, - dma_addr_t dma_handle, unsigned long attrs) +__rust_helper void rust_helper_dma_free_attrs(struct device *dev, size_t size, + void *cpu_addr, + dma_addr_t dma_handle, + unsigned long attrs) { dma_free_attrs(dev, size, cpu_addr, dma_handle, attrs); } -int rust_helper_dma_set_mask_and_coherent(struct device *dev, u64 mask) +__rust_helper int rust_helper_dma_set_mask_and_coherent(struct device *dev, + u64 mask) { return dma_set_mask_and_coherent(dev, mask); } From 7aab0122a1497656992cd08c8df5a6e3939f779a Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:43 +0000 Subject: [PATCH 04/79] rust: io: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-19-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/io.c | 64 +++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/rust/helpers/io.c b/rust/helpers/io.c index c475913c69e6..397810864a24 100644 --- a/rust/helpers/io.c +++ b/rust/helpers/io.c @@ -3,140 +3,144 @@ #include #include -void __iomem *rust_helper_ioremap(phys_addr_t offset, size_t size) +__rust_helper void __iomem *rust_helper_ioremap(phys_addr_t offset, size_t size) { return ioremap(offset, size); } -void __iomem *rust_helper_ioremap_np(phys_addr_t offset, size_t size) +__rust_helper void __iomem *rust_helper_ioremap_np(phys_addr_t offset, + size_t size) { return ioremap_np(offset, size); } -void rust_helper_iounmap(void __iomem *addr) +__rust_helper void rust_helper_iounmap(void __iomem *addr) { iounmap(addr); } -u8 rust_helper_readb(const void __iomem *addr) +__rust_helper u8 rust_helper_readb(const void __iomem *addr) { return readb(addr); } -u16 rust_helper_readw(const void __iomem *addr) +__rust_helper u16 rust_helper_readw(const void __iomem *addr) { return readw(addr); } -u32 rust_helper_readl(const void __iomem *addr) +__rust_helper u32 rust_helper_readl(const void __iomem *addr) { return readl(addr); } #ifdef CONFIG_64BIT -u64 rust_helper_readq(const void __iomem *addr) +__rust_helper u64 rust_helper_readq(const void __iomem *addr) { return readq(addr); } #endif -void rust_helper_writeb(u8 value, void __iomem *addr) +__rust_helper void rust_helper_writeb(u8 value, void __iomem *addr) { writeb(value, addr); } -void rust_helper_writew(u16 value, void __iomem *addr) +__rust_helper void rust_helper_writew(u16 value, void __iomem *addr) { writew(value, addr); } -void rust_helper_writel(u32 value, void __iomem *addr) +__rust_helper void rust_helper_writel(u32 value, void __iomem *addr) { writel(value, addr); } #ifdef CONFIG_64BIT -void rust_helper_writeq(u64 value, void __iomem *addr) +__rust_helper void rust_helper_writeq(u64 value, void __iomem *addr) { writeq(value, addr); } #endif -u8 rust_helper_readb_relaxed(const void __iomem *addr) +__rust_helper u8 rust_helper_readb_relaxed(const void __iomem *addr) { return readb_relaxed(addr); } -u16 rust_helper_readw_relaxed(const void __iomem *addr) +__rust_helper u16 rust_helper_readw_relaxed(const void __iomem *addr) { return readw_relaxed(addr); } -u32 rust_helper_readl_relaxed(const void __iomem *addr) +__rust_helper u32 rust_helper_readl_relaxed(const void __iomem *addr) { return readl_relaxed(addr); } #ifdef CONFIG_64BIT -u64 rust_helper_readq_relaxed(const void __iomem *addr) +__rust_helper u64 rust_helper_readq_relaxed(const void __iomem *addr) { return readq_relaxed(addr); } #endif -void rust_helper_writeb_relaxed(u8 value, void __iomem *addr) +__rust_helper void rust_helper_writeb_relaxed(u8 value, void __iomem *addr) { writeb_relaxed(value, addr); } -void rust_helper_writew_relaxed(u16 value, void __iomem *addr) +__rust_helper void rust_helper_writew_relaxed(u16 value, void __iomem *addr) { writew_relaxed(value, addr); } -void rust_helper_writel_relaxed(u32 value, void __iomem *addr) +__rust_helper void rust_helper_writel_relaxed(u32 value, void __iomem *addr) { writel_relaxed(value, addr); } #ifdef CONFIG_64BIT -void rust_helper_writeq_relaxed(u64 value, void __iomem *addr) +__rust_helper void rust_helper_writeq_relaxed(u64 value, void __iomem *addr) { writeq_relaxed(value, addr); } #endif -resource_size_t rust_helper_resource_size(struct resource *res) +__rust_helper resource_size_t rust_helper_resource_size(struct resource *res) { return resource_size(res); } -struct resource *rust_helper_request_mem_region(resource_size_t start, - resource_size_t n, - const char *name) +__rust_helper struct resource * +rust_helper_request_mem_region(resource_size_t start, resource_size_t n, + const char *name) { return request_mem_region(start, n, name); } -void rust_helper_release_mem_region(resource_size_t start, resource_size_t n) +__rust_helper void rust_helper_release_mem_region(resource_size_t start, + resource_size_t n) { release_mem_region(start, n); } -struct resource *rust_helper_request_region(resource_size_t start, - resource_size_t n, const char *name) +__rust_helper struct resource *rust_helper_request_region(resource_size_t start, + resource_size_t n, + const char *name) { return request_region(start, n, name); } -struct resource *rust_helper_request_muxed_region(resource_size_t start, - resource_size_t n, - const char *name) +__rust_helper struct resource * +rust_helper_request_muxed_region(resource_size_t start, resource_size_t n, + const char *name) { return request_muxed_region(start, n, name); } -void rust_helper_release_region(resource_size_t start, resource_size_t n) +__rust_helper void rust_helper_release_region(resource_size_t start, + resource_size_t n) { release_region(start, n); } From c7ff956344e4c4cc87de6c1a53a1fcbb5fd5fd78 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:44 +0000 Subject: [PATCH 05/79] rust: irq: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-20-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/irq.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/helpers/irq.c b/rust/helpers/irq.c index 1faca428e2c0..a61fad333866 100644 --- a/rust/helpers/irq.c +++ b/rust/helpers/irq.c @@ -2,8 +2,10 @@ #include -int rust_helper_request_irq(unsigned int irq, irq_handler_t handler, - unsigned long flags, const char *name, void *dev) +__rust_helper int rust_helper_request_irq(unsigned int irq, + irq_handler_t handler, + unsigned long flags, const char *name, + void *dev) { return request_irq(irq, handler, flags, name, dev); } From 593e0b22340c3b6aa19c0fa3c466b0809a7ba0d1 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:50 +0000 Subject: [PATCH 06/79] rust: pci: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-26-a2e13cbc17a6@google.com [ Consider latest helper additions. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/helpers/pci.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/rust/helpers/pci.c b/rust/helpers/pci.c index bf8173979c5e..e44905317d75 100644 --- a/rust/helpers/pci.c +++ b/rust/helpers/pci.c @@ -2,41 +2,44 @@ #include -u16 rust_helper_pci_dev_id(struct pci_dev *dev) +__rust_helper u16 rust_helper_pci_dev_id(struct pci_dev *dev) { return PCI_DEVID(dev->bus->number, dev->devfn); } -resource_size_t rust_helper_pci_resource_start(struct pci_dev *pdev, int bar) +__rust_helper resource_size_t +rust_helper_pci_resource_start(struct pci_dev *pdev, int bar) { return pci_resource_start(pdev, bar); } -resource_size_t rust_helper_pci_resource_len(struct pci_dev *pdev, int bar) +__rust_helper resource_size_t rust_helper_pci_resource_len(struct pci_dev *pdev, + int bar) { return pci_resource_len(pdev, bar); } -bool rust_helper_dev_is_pci(const struct device *dev) +__rust_helper bool rust_helper_dev_is_pci(const struct device *dev) { return dev_is_pci(dev); } #ifndef CONFIG_PCI_MSI -int rust_helper_pci_alloc_irq_vectors(struct pci_dev *dev, - unsigned int min_vecs, - unsigned int max_vecs, - unsigned int flags) +__rust_helper int rust_helper_pci_alloc_irq_vectors(struct pci_dev *dev, + unsigned int min_vecs, + unsigned int max_vecs, + unsigned int flags) { return pci_alloc_irq_vectors(dev, min_vecs, max_vecs, flags); } -void rust_helper_pci_free_irq_vectors(struct pci_dev *dev) +__rust_helper void rust_helper_pci_free_irq_vectors(struct pci_dev *dev) { pci_free_irq_vectors(dev); } -int rust_helper_pci_irq_vector(struct pci_dev *pdev, unsigned int nvec) +__rust_helper int rust_helper_pci_irq_vector(struct pci_dev *pdev, + unsigned int nvec) { return pci_irq_vector(pdev, nvec); } From d17772fcb55cf70b7b6ccd78ef6fdefbf66542b8 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:52 +0000 Subject: [PATCH 07/79] rust: platform: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-28-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/helpers/platform.c b/rust/helpers/platform.c index 1ce89c1a36f7..188b3240f32d 100644 --- a/rust/helpers/platform.c +++ b/rust/helpers/platform.c @@ -2,7 +2,7 @@ #include -bool rust_helper_dev_is_platform(const struct device *dev) +__rust_helper bool rust_helper_dev_is_platform(const struct device *dev) { return dev_is_platform(dev); } From 74ca60bd85c4b6952f5c7700c89407c4041535a7 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:55 +0000 Subject: [PATCH 08/79] rust: property: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-31-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/property.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/helpers/property.c b/rust/helpers/property.c index 08f68e2dac4a..8fb9900533ef 100644 --- a/rust/helpers/property.c +++ b/rust/helpers/property.c @@ -2,7 +2,7 @@ #include -void rust_helper_fwnode_handle_put(struct fwnode_handle *fwnode) +__rust_helper void rust_helper_fwnode_handle_put(struct fwnode_handle *fwnode) { fwnode_handle_put(fwnode); } From e4b3118b61b6d93d4289069b9cccbffe8e714aa0 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:38:00 +0000 Subject: [PATCH 09/79] rust: scatterlist: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251202-define-rust-helper-v1-36-a2e13cbc17a6@google.com Signed-off-by: Danilo Krummrich --- rust/helpers/scatterlist.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rust/helpers/scatterlist.c b/rust/helpers/scatterlist.c index 80c956ee09ab..f3c41ea5e201 100644 --- a/rust/helpers/scatterlist.c +++ b/rust/helpers/scatterlist.c @@ -2,23 +2,25 @@ #include -dma_addr_t rust_helper_sg_dma_address(struct scatterlist *sg) +__rust_helper dma_addr_t rust_helper_sg_dma_address(struct scatterlist *sg) { return sg_dma_address(sg); } -unsigned int rust_helper_sg_dma_len(struct scatterlist *sg) +__rust_helper unsigned int rust_helper_sg_dma_len(struct scatterlist *sg) { return sg_dma_len(sg); } -struct scatterlist *rust_helper_sg_next(struct scatterlist *sg) +__rust_helper struct scatterlist *rust_helper_sg_next(struct scatterlist *sg) { return sg_next(sg); } -void rust_helper_dma_unmap_sgtable(struct device *dev, struct sg_table *sgt, - enum dma_data_direction dir, unsigned long attrs) +__rust_helper void rust_helper_dma_unmap_sgtable(struct device *dev, + struct sg_table *sgt, + enum dma_data_direction dir, + unsigned long attrs) { return dma_unmap_sgtable(dev, sgt, dir, attrs); } From 962cdb95b6753c9ef19f2163809091e8baa9085f Mon Sep 17 00:00:00 2001 From: Marko Turk Date: Wed, 10 Dec 2025 12:25:35 +0100 Subject: [PATCH 10/79] rust: pci: document Bar's endianness conversion Document that the Bar's MMIO backend always assumes little-endian devices and that its operations automatically convert to CPU endianness. Signed-off-by: Marko Turk Link: https://lore.kernel.org/lkml/DE7F6RR1NAKW.3DJYO44O73941@kernel.org/ Link: https://patch.msgid.link/20251210112503.62925-1-mt@markoturk.info [ Drop unrelated spelling fix. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/pci/io.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 0d55c3139b6f..c250b7c29d2d 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -18,6 +18,9 @@ use core::ops::Deref; /// A PCI BAR to perform I/O-Operations on. /// +/// I/O backend assumes that the device is little-endian and will automatically +/// convert from little-endian to CPU endianness. +/// /// # Invariants /// /// `Bar` always holds an `IoRaw` inststance that holds a valid pointer to the start of the I/O From 2e2b4135d1cb32fc310f21e395ee7313a3681bee Mon Sep 17 00:00:00 2001 From: Shankari Anand Date: Sun, 23 Nov 2025 14:54:31 +0530 Subject: [PATCH 11/79] rust: device: Update ARef and AlwaysRefCounted imports from sync::aref Update call sites to import `ARef` and `AlwaysRefCounted` from `sync::aref` instead of `types`. This aligns with the ongoing effort to move `ARef` and `AlwaysRefCounted` to sync. Suggested-by: Benno Lossin Link: https://github.com/Rust-for-Linux/linux/issues/1173 Signed-off-by: Shankari Anand Link: https://patch.msgid.link/20251123092438.182251-4-shankari.ak0208@gmail.com Signed-off-by: Danilo Krummrich --- rust/kernel/device.rs | 4 ++-- rust/kernel/device/property.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index c79be2e2bfe3..21bde8d95185 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -158,7 +158,7 @@ static_assert!(core::mem::size_of::() >= core::mem::size_ /// `bindings::device::release` is valid to be called from any thread, hence `ARef` can be /// dropped from any thread. /// -/// [`AlwaysRefCounted`]: kernel::types::AlwaysRefCounted +/// [`AlwaysRefCounted`]: kernel::sync::aref::AlwaysRefCounted /// [`impl_device_context_deref`]: kernel::impl_device_context_deref /// [`pci::Device`]: kernel::pci::Device /// [`platform::Device`]: kernel::platform::Device @@ -540,7 +540,7 @@ pub trait DeviceContext: private::Sealed {} /// [`Device`]. It is the only [`DeviceContext`] for which it is valid to implement /// [`AlwaysRefCounted`] for. /// -/// [`AlwaysRefCounted`]: kernel::types::AlwaysRefCounted +/// [`AlwaysRefCounted`]: kernel::sync::aref::AlwaysRefCounted pub struct Normal; /// The [`Core`] context is the context of a bus specific device when it appears as argument of diff --git a/rust/kernel/device/property.rs b/rust/kernel/device/property.rs index 3a332a8c53a9..413221817ef1 100644 --- a/rust/kernel/device/property.rs +++ b/rust/kernel/device/property.rs @@ -14,7 +14,8 @@ use crate::{ fmt, prelude::*, str::{CStr, CString}, - types::{ARef, Opaque}, + sync::aref::ARef, + types::Opaque, }; /// A reference-counted fwnode_handle. @@ -359,7 +360,7 @@ impl fmt::Debug for FwNodeReferenceArgs { } // SAFETY: Instances of `FwNode` are always reference-counted. -unsafe impl crate::types::AlwaysRefCounted for FwNode { +unsafe impl crate::sync::aref::AlwaysRefCounted for FwNode { fn inc_ref(&self) { // SAFETY: The existence of a shared reference guarantees that the // refcount is non-zero. From 2da67beda68776842fd0a26f2374e42a5e9b12c8 Mon Sep 17 00:00:00 2001 From: Shankari Anand Date: Sun, 23 Nov 2025 14:54:35 +0530 Subject: [PATCH 12/79] rust: scatterlist: Update ARef imports to use sync::aref Update call sites in `scatterlist.rs` to import `ARef` from `sync::aref` instead of `types`. This aligns with the ongoing effort to move `ARef` and `AlwaysRefCounted` to sync. Suggested-by: Benno Lossin Link: https://github.com/Rust-for-Linux/linux/issues/1173 Signed-off-by: Shankari Anand Link: https://patch.msgid.link/20251123092438.182251-8-shankari.ak0208@gmail.com [ Change subject prefix to from 'kernel' to 'scatterlist'. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/scatterlist.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/kernel/scatterlist.rs b/rust/kernel/scatterlist.rs index 196fdb9a75e7..b83c468b5c63 100644 --- a/rust/kernel/scatterlist.rs +++ b/rust/kernel/scatterlist.rs @@ -38,7 +38,8 @@ use crate::{ io::ResourceSize, page, prelude::*, - types::{ARef, Opaque}, + sync::aref::ARef, + types::Opaque, }; use core::{ops::Deref, ptr::NonNull}; From 1b89d4a6bb4cd7cfd7eb2e3621f04fda956e4ef3 Mon Sep 17 00:00:00 2001 From: Shankari Anand Date: Sun, 23 Nov 2025 14:54:37 +0530 Subject: [PATCH 13/79] samples: rust: debugfs: Update ARef imports to use sync::aref Update call sites in `rust_debugfs.rs` to import `ARef` from `sync::aref` instead of `types`. This aligns with the ongoing effort to move `ARef` and `AlwaysRefCounted` to sync. Suggested-by: Benno Lossin Link: https://github.com/Rust-for-Linux/linux/issues/1173 Signed-off-by: Shankari Anand Link: https://patch.msgid.link/20251123092438.182251-10-shankari.ak0208@gmail.com [ Add 'debugfs' to the commit subject; be consistent with the existing import style for now. - Danilo ] Signed-off-by: Danilo Krummrich --- samples/rust/rust_debugfs.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs index 025e8f9d12de..8d190952984f 100644 --- a/samples/rust/rust_debugfs.rs +++ b/samples/rust/rust_debugfs.rs @@ -37,9 +37,10 @@ use kernel::debugfs::{Dir, File}; use kernel::new_mutex; use kernel::prelude::*; use kernel::sizes::*; +use kernel::sync::aref::ARef; use kernel::sync::atomic::{Atomic, Relaxed}; use kernel::sync::Mutex; -use kernel::{acpi, device::Core, of, platform, str::CString, types::ARef}; +use kernel::{acpi, device::Core, of, platform, str::CString}; kernel::module_platform_driver! { type: RustDebugFs, From 9202cef05d6b61a03475b744c7f0622cd8be8e90 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 18 Dec 2025 17:56:11 +0100 Subject: [PATCH 14/79] rust: debugfs: use "kernel vertical" style for imports Convert all imports in the debugfs Rust module to use "kernel vertical" style. With this subsequent patches neither introduce unrelated changes nor leave an inconsistent import pattern. While at it, drop unnecessary imports covered by prelude::*. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20251218165626.450264-1-dakr@kernel.org [ Apply the same change to the debugfs sample code. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs.rs | 46 ++++++++++++++++++------ rust/kernel/debugfs/callback_adapters.rs | 21 +++++++---- rust/kernel/debugfs/entry.rs | 14 +++++--- rust/kernel/debugfs/file_ops.rs | 25 ++++++++----- rust/kernel/debugfs/traits.rs | 43 ++++++++++++++++------ samples/rust/rust_debugfs.rs | 32 ++++++++++++----- samples/rust/rust_debugfs_scoped.rs | 21 +++++++---- 7 files changed, 147 insertions(+), 55 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index facad81e8290..536a320334bf 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -8,28 +8,52 @@ // When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful. #![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))] -use crate::fmt; -use crate::prelude::*; -use crate::str::CStr; #[cfg(CONFIG_DEBUG_FS)] use crate::sync::Arc; -use crate::uaccess::UserSliceReader; -use core::marker::PhantomData; -use core::marker::PhantomPinned; +use crate::{ + fmt, + prelude::*, + str::CStr, + uaccess::UserSliceReader, // +}; + #[cfg(CONFIG_DEBUG_FS)] use core::mem::ManuallyDrop; -use core::ops::Deref; +use core::{ + marker::{ + PhantomData, + PhantomPinned, // + }, + ops::Deref, +}; mod traits; -pub use traits::{BinaryReader, BinaryReaderMut, BinaryWriter, Reader, Writer}; +pub use traits::{ + BinaryReader, + BinaryReaderMut, + BinaryWriter, + Reader, + Writer, // +}; mod callback_adapters; -use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter}; +use callback_adapters::{ + FormatAdapter, + NoWriter, + WritableAdapter, // +}; + mod file_ops; use file_ops::{ - BinaryReadFile, BinaryReadWriteFile, BinaryWriteFile, FileOps, ReadFile, ReadWriteFile, - WriteFile, + BinaryReadFile, + BinaryReadWriteFile, + BinaryWriteFile, + FileOps, + ReadFile, + ReadWriteFile, + WriteFile, // }; + #[cfg(CONFIG_DEBUG_FS)] mod entry; #[cfg(CONFIG_DEBUG_FS)] diff --git a/rust/kernel/debugfs/callback_adapters.rs b/rust/kernel/debugfs/callback_adapters.rs index a260d8dee051..dee7d021e18c 100644 --- a/rust/kernel/debugfs/callback_adapters.rs +++ b/rust/kernel/debugfs/callback_adapters.rs @@ -4,12 +4,21 @@ //! Adapters which allow the user to supply a write or read implementation as a value rather //! than a trait implementation. If provided, it will override the trait implementation. -use super::{Reader, Writer}; -use crate::fmt; -use crate::prelude::*; -use crate::uaccess::UserSliceReader; -use core::marker::PhantomData; -use core::ops::Deref; +use super::{ + Reader, + Writer, // +}; + +use crate::{ + fmt, + prelude::*, + uaccess::UserSliceReader, // +}; + +use core::{ + marker::PhantomData, + ops::Deref, // +}; /// # Safety /// diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs index 706cb7f73d6c..5ed1303f2fe6 100644 --- a/rust/kernel/debugfs/entry.rs +++ b/rust/kernel/debugfs/entry.rs @@ -1,10 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. -use crate::debugfs::file_ops::FileOps; -use crate::ffi::c_void; -use crate::str::{CStr, CStrExt as _}; -use crate::sync::Arc; +use crate::{ + debugfs::file_ops::FileOps, + prelude::*, + str::{ + CStr, + CStrExt as _, // + }, + sync::Arc, +}; + use core::marker::PhantomData; /// Owning handle to a DebugFS entry. diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs index 8a0442d6dd7a..ad19360540ba 100644 --- a/rust/kernel/debugfs/file_ops.rs +++ b/rust/kernel/debugfs/file_ops.rs @@ -1,14 +1,23 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. -use super::{BinaryReader, BinaryWriter, Reader, Writer}; -use crate::debugfs::callback_adapters::Adapter; -use crate::fmt; -use crate::fs::file; -use crate::prelude::*; -use crate::seq_file::SeqFile; -use crate::seq_print; -use crate::uaccess::UserSlice; +use super::{ + BinaryReader, + BinaryWriter, + Reader, + Writer, // +}; + +use crate::{ + debugfs::callback_adapters::Adapter, + fmt, + fs::file, + prelude::*, + seq_file::SeqFile, + seq_print, + uaccess::UserSlice, // +}; + use core::marker::PhantomData; #[cfg(CONFIG_DEBUG_FS)] diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index 3eee60463fd5..8c39524b6a99 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -3,17 +3,38 @@ //! Traits for rendering or updating values exported to DebugFS. -use crate::alloc::Allocator; -use crate::fmt; -use crate::fs::file; -use crate::prelude::*; -use crate::sync::atomic::{Atomic, AtomicBasicOps, AtomicType, Relaxed}; -use crate::sync::Arc; -use crate::sync::Mutex; -use crate::transmute::{AsBytes, FromBytes}; -use crate::uaccess::{UserSliceReader, UserSliceWriter}; -use core::ops::{Deref, DerefMut}; -use core::str::FromStr; +use crate::{ + alloc::Allocator, + fmt, + fs::file, + prelude::*, + sync::{ + atomic::{ + Atomic, + AtomicBasicOps, + AtomicType, + Relaxed, // + }, + Arc, + Mutex, // + }, + transmute::{ + AsBytes, + FromBytes, // + }, + uaccess::{ + UserSliceReader, + UserSliceWriter, // + }, +}; + +use core::{ + ops::{ + Deref, + DerefMut, // + }, + str::FromStr, +}; /// A trait for types that can be written into a string. /// diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs index 8d190952984f..3d6ad697e2b7 100644 --- a/samples/rust/rust_debugfs.rs +++ b/samples/rust/rust_debugfs.rs @@ -32,15 +32,29 @@ //! ``` use core::str::FromStr; -use kernel::c_str; -use kernel::debugfs::{Dir, File}; -use kernel::new_mutex; -use kernel::prelude::*; -use kernel::sizes::*; -use kernel::sync::aref::ARef; -use kernel::sync::atomic::{Atomic, Relaxed}; -use kernel::sync::Mutex; -use kernel::{acpi, device::Core, of, platform, str::CString}; +use kernel::{ + acpi, + c_str, + debugfs::{ + Dir, + File, // + }, + device::Core, + new_mutex, + of, + platform, + prelude::*, + sizes::*, + str::CString, + sync::{ + aref::ARef, + atomic::{ + Atomic, + Relaxed, // + }, + Mutex, + }, // +}; kernel::module_platform_driver! { type: RustDebugFs, diff --git a/samples/rust/rust_debugfs_scoped.rs b/samples/rust/rust_debugfs_scoped.rs index 702a6546d3fb..cb5d3dfdc599 100644 --- a/samples/rust/rust_debugfs_scoped.rs +++ b/samples/rust/rust_debugfs_scoped.rs @@ -6,12 +6,21 @@ //! `Scope::dir` to create a variety of files without the need to separately //! track them all. -use kernel::debugfs::{Dir, Scope}; -use kernel::prelude::*; -use kernel::sizes::*; -use kernel::sync::atomic::Atomic; -use kernel::sync::Mutex; -use kernel::{c_str, new_mutex, str::CString}; +use kernel::{ + c_str, + debugfs::{ + Dir, + Scope, // + }, + new_mutex, + prelude::*, + sizes::*, + str::CString, + sync::{ + atomic::Atomic, + Mutex, // + }, +}; module! { type: RustScopedDebugFs, From 6fc4b5eb63c7c4c1f2251277ad1f0d04ac047d91 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:35:27 +0100 Subject: [PATCH 15/79] rust: auxiliary: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Acked-by: Greg Kroah-Hartman Reviewed-by: Alice Ryhl Reviewed-by: Benno Lossin Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-driver-core-v1-1-1142a177d0fd@gmail.com Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_auxiliary.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 5761ea314f44..1e4fb23cfcb0 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -5,7 +5,7 @@ //! To make this driver probe, QEMU must be run with `-device pci-testdev`. use kernel::{ - auxiliary, c_str, + auxiliary, device::{Bound, Core}, devres::Devres, driver, @@ -19,7 +19,7 @@ use core::any::TypeId; use pin_init::PinInit; const MODULE_NAME: &CStr = ::NAME; -const AUXILIARY_NAME: &CStr = c_str!("auxiliary"); +const AUXILIARY_NAME: &CStr = c"auxiliary"; struct AuxiliaryDriver; From f0c6ea853bd7f48aeec231e9378fc17cf36b9109 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:35:28 +0100 Subject: [PATCH 16/79] rust: device: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Acked-by: Greg Kroah-Hartman Reviewed-by: Alice Ryhl Reviewed-by: Benno Lossin Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-driver-core-v1-2-1142a177d0fd@gmail.com Signed-off-by: Danilo Krummrich --- rust/kernel/device.rs | 4 +--- rust/kernel/device/property.rs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 21bde8d95185..a13f6ee24b09 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -12,8 +12,6 @@ use crate::{ }; use core::{any::TypeId, marker::PhantomData, ptr}; -#[cfg(CONFIG_PRINTK)] -use crate::c_str; use crate::str::CStrExt as _; pub mod property; @@ -463,7 +461,7 @@ impl Device { bindings::_dev_printk( klevel.as_ptr().cast::(), self.as_raw(), - c_str!("%pA").as_char_ptr(), + c"%pA".as_char_ptr(), core::ptr::from_ref(&msg).cast::(), ) }; diff --git a/rust/kernel/device/property.rs b/rust/kernel/device/property.rs index 413221817ef1..5aead835fbbc 100644 --- a/rust/kernel/device/property.rs +++ b/rust/kernel/device/property.rs @@ -179,11 +179,11 @@ impl FwNode { /// # Examples /// /// ``` - /// # use kernel::{c_str, device::{Device, property::FwNode}, str::CString}; + /// # use kernel::{device::{Device, property::FwNode}, str::CString}; /// fn examples(dev: &Device) -> Result { /// let fwnode = dev.fwnode().ok_or(ENOENT)?; - /// let b: u32 = fwnode.property_read(c_str!("some-number")).required_by(dev)?; - /// if let Some(s) = fwnode.property_read::(c_str!("some-str")).optional() { + /// let b: u32 = fwnode.property_read(c"some-number").required_by(dev)?; + /// if let Some(s) = fwnode.property_read::(c"some-str").optional() { /// // ... /// } /// Ok(()) From 1114c87e49642165a224c28ceaa333e8504ff122 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:35:29 +0100 Subject: [PATCH 17/79] rust: platform: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Acked-by: Greg Kroah-Hartman Reviewed-by: Alice Ryhl Reviewed-by: Benno Lossin Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-driver-core-v1-3-1142a177d0fd@gmail.com [ Use kernel vertical import style; discard unrelated faux changes. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/platform.rs | 13 ++++++++---- samples/rust/rust_driver_platform.rs | 30 +++++++++++++--------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index ed889f079cab..bddb593cee7b 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -137,8 +137,13 @@ macro_rules! module_platform_driver { /// # Examples /// ///``` -/// # use kernel::{acpi, bindings, c_str, device::Core, of, platform}; -/// +/// # use kernel::{ +/// # acpi, +/// # bindings, +/// # device::Core, +/// # of, +/// # platform, +/// # }; /// struct MyDriver; /// /// kernel::of_device_table!( @@ -146,7 +151,7 @@ macro_rules! module_platform_driver { /// MODULE_OF_TABLE, /// ::IdInfo, /// [ -/// (of::DeviceId::new(c_str!("test,device")), ()) +/// (of::DeviceId::new(c"test,device"), ()) /// ] /// ); /// @@ -155,7 +160,7 @@ macro_rules! module_platform_driver { /// MODULE_ACPI_TABLE, /// ::IdInfo, /// [ -/// (acpi::DeviceId::new(c_str!("LNUXBEEF")), ()) +/// (acpi::DeviceId::new(c"LNUXBEEF"), ()) /// ] /// ); /// diff --git a/samples/rust/rust_driver_platform.rs b/samples/rust/rust_driver_platform.rs index 6bf4f0c9633d..a3044d773176 100644 --- a/samples/rust/rust_driver_platform.rs +++ b/samples/rust/rust_driver_platform.rs @@ -63,7 +63,7 @@ //! use kernel::{ - acpi, c_str, + acpi, device::{ self, property::{FwNodeReferenceArgs, NArgs}, @@ -85,14 +85,14 @@ kernel::of_device_table!( OF_TABLE, MODULE_OF_TABLE, ::IdInfo, - [(of::DeviceId::new(c_str!("test,rust-device")), Info(42))] + [(of::DeviceId::new(c"test,rust-device"), Info(42))] ); kernel::acpi_device_table!( ACPI_TABLE, MODULE_ACPI_TABLE, ::IdInfo, - [(acpi::DeviceId::new(c_str!("LNUXBEEF")), Info(0))] + [(acpi::DeviceId::new(c"LNUXBEEF"), Info(0))] ); impl platform::Driver for SampleDriver { @@ -124,49 +124,47 @@ impl SampleDriver { fn properties_parse(dev: &device::Device) -> Result { let fwnode = dev.fwnode().ok_or(ENOENT)?; - if let Ok(idx) = - fwnode.property_match_string(c_str!("compatible"), c_str!("test,rust-device")) - { + if let Ok(idx) = fwnode.property_match_string(c"compatible", c"test,rust-device") { dev_info!(dev, "matched compatible string idx = {}\n", idx); } - let name = c_str!("compatible"); + let name = c"compatible"; let prop = fwnode.property_read::(name).required_by(dev)?; dev_info!(dev, "'{name}'='{prop:?}'\n"); - let name = c_str!("test,bool-prop"); - let prop = fwnode.property_read_bool(c_str!("test,bool-prop")); + let name = c"test,bool-prop"; + let prop = fwnode.property_read_bool(c"test,bool-prop"); dev_info!(dev, "'{name}'='{prop}'\n"); - if fwnode.property_present(c_str!("test,u32-prop")) { + if fwnode.property_present(c"test,u32-prop") { dev_info!(dev, "'test,u32-prop' is present\n"); } - let name = c_str!("test,u32-optional-prop"); + let name = c"test,u32-optional-prop"; let prop = fwnode.property_read::(name).or(0x12); dev_info!(dev, "'{name}'='{prop:#x}' (default = 0x12)\n"); // A missing required property will print an error. Discard the error to // prevent properties_parse from failing in that case. - let name = c_str!("test,u32-required-prop"); + let name = c"test,u32-required-prop"; let _ = fwnode.property_read::(name).required_by(dev); - let name = c_str!("test,u32-prop"); + let name = c"test,u32-prop"; let prop: u32 = fwnode.property_read(name).required_by(dev)?; dev_info!(dev, "'{name}'='{prop:#x}'\n"); - let name = c_str!("test,i16-array"); + let name = c"test,i16-array"; let prop: [i16; 4] = fwnode.property_read(name).required_by(dev)?; dev_info!(dev, "'{name}'='{prop:?}'\n"); let len = fwnode.property_count_elem::(name)?; dev_info!(dev, "'{name}' length is {len}\n"); - let name = c_str!("test,i16-array"); + let name = c"test,i16-array"; let prop: KVec = fwnode.property_read_array_vec(name, 4)?.required_by(dev)?; dev_info!(dev, "'{name}'='{prop:?}' (KVec)\n"); for child in fwnode.children() { - let name = c_str!("test,ref-arg"); + let name = c"test,ref-arg"; let nargs = NArgs::N(2); let prop: FwNodeReferenceArgs = child.property_get_reference_args(name, nargs, 0)?; dev_info!(dev, "'{name}'='{prop:?}'\n"); From 0250ea325cda689525139ae5f069974e7ed6d886 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:35:30 +0100 Subject: [PATCH 18/79] rust: io: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-driver-core-v1-4-1142a177d0fd@gmail.com [ Use kernel vertical import style. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/io/mem.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index b03b82cd531b..e4878c131c6d 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -5,7 +5,6 @@ use core::ops::Deref; use crate::{ - c_str, device::{ Bound, Device, // @@ -52,7 +51,12 @@ impl<'a> IoRequest<'a> { /// illustration purposes. /// /// ```no_run - /// use kernel::{bindings, c_str, platform, of, device::Core}; + /// use kernel::{ + /// bindings, + /// device::Core, + /// of, + /// platform, + /// }; /// struct SampleDriver; /// /// impl platform::Driver for SampleDriver { @@ -110,7 +114,12 @@ impl<'a> IoRequest<'a> { /// illustration purposes. /// /// ```no_run - /// use kernel::{bindings, c_str, platform, of, device::Core}; + /// use kernel::{ + /// bindings, + /// device::Core, + /// of, + /// platform, + /// }; /// struct SampleDriver; /// /// impl platform::Driver for SampleDriver { @@ -172,7 +181,7 @@ impl ExclusiveIoMem { fn ioremap(resource: &Resource) -> Result { let start = resource.start(); let size = resource.size(); - let name = resource.name().unwrap_or(c_str!("")); + let name = resource.name().unwrap_or_default(); let region = resource .request_region( From 644672e93a1aa6bfc3ebc102cbf9b8efad16e786 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:35:31 +0100 Subject: [PATCH 19/79] rust: irq: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-driver-core-v1-5-1142a177d0fd@gmail.com Signed-off-by: Danilo Krummrich --- rust/kernel/irq/request.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rust/kernel/irq/request.rs b/rust/kernel/irq/request.rs index b150563fdef8..67769800117c 100644 --- a/rust/kernel/irq/request.rs +++ b/rust/kernel/irq/request.rs @@ -139,7 +139,6 @@ impl<'a> IrqRequest<'a> { /// [`Completion::wait_for_completion()`]: kernel::sync::Completion::wait_for_completion /// /// ``` -/// use kernel::c_str; /// use kernel::device::{Bound, Device}; /// use kernel::irq::{self, Flags, IrqRequest, IrqReturn, Registration}; /// use kernel::prelude::*; @@ -167,7 +166,7 @@ impl<'a> IrqRequest<'a> { /// handler: impl PinInit, /// request: IrqRequest<'_>, /// ) -> Result>> { -/// let registration = Registration::new(request, Flags::SHARED, c_str!("my_device"), handler); +/// let registration = Registration::new(request, Flags::SHARED, c"my_device", handler); /// /// let registration = Arc::pin_init(registration, GFP_KERNEL)?; /// @@ -340,7 +339,6 @@ impl ThreadedHandler for Box { /// [`Mutex`](kernel::sync::Mutex) to provide interior mutability. /// /// ``` -/// use kernel::c_str; /// use kernel::device::{Bound, Device}; /// use kernel::irq::{ /// self, Flags, IrqRequest, IrqReturn, ThreadedHandler, ThreadedIrqReturn, @@ -381,7 +379,7 @@ impl ThreadedHandler for Box { /// request: IrqRequest<'_>, /// ) -> Result>> { /// let registration = -/// ThreadedRegistration::new(request, Flags::SHARED, c_str!("my_device"), handler); +/// ThreadedRegistration::new(request, Flags::SHARED, c"my_device", handler); /// /// let registration = Arc::pin_init(registration, GFP_KERNEL)?; /// From f47a8f595a5ed5a9602d71c31671e121da00c0e6 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:35:32 +0100 Subject: [PATCH 20/79] rust: debugfs: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-driver-core-v1-6-1142a177d0fd@gmail.com Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 536a320334bf..513cc5750e63 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -126,9 +126,8 @@ impl Dir { /// # Examples /// /// ``` - /// # use kernel::c_str; /// # use kernel::debugfs::Dir; - /// let debugfs = Dir::new(c_str!("parent")); + /// let debugfs = Dir::new(c"parent"); /// ``` pub fn new(name: &CStr) -> Self { Dir::create(name, None) @@ -139,10 +138,9 @@ impl Dir { /// # Examples /// /// ``` - /// # use kernel::c_str; /// # use kernel::debugfs::Dir; - /// let parent = Dir::new(c_str!("parent")); - /// let child = parent.subdir(c_str!("child")); + /// let parent = Dir::new(c"parent"); + /// let child = parent.subdir(c"child"); /// ``` pub fn subdir(&self, name: &CStr) -> Self { Dir::create(name, Some(self)) @@ -156,11 +154,10 @@ impl Dir { /// # Examples /// /// ``` - /// # use kernel::c_str; /// # use kernel::debugfs::Dir; /// # use kernel::prelude::*; - /// # let dir = Dir::new(c_str!("my_debugfs_dir")); - /// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?; + /// # let dir = Dir::new(c"my_debugfs_dir"); + /// let file = KBox::pin_init(dir.read_only_file(c"foo", 200), GFP_KERNEL)?; /// // "my_debugfs_dir/foo" now contains the number 200. /// // The file is removed when `file` is dropped. /// # Ok::<(), Error>(()) @@ -185,11 +182,10 @@ impl Dir { /// # Examples /// /// ``` - /// # use kernel::c_str; /// # use kernel::debugfs::Dir; /// # use kernel::prelude::*; - /// # let dir = Dir::new(c_str!("my_debugfs_dir")); - /// let file = KBox::pin_init(dir.read_binary_file(c_str!("foo"), [0x1, 0x2]), GFP_KERNEL)?; + /// # let dir = Dir::new(c"my_debugfs_dir"); + /// let file = KBox::pin_init(dir.read_binary_file(c"foo", [0x1, 0x2]), GFP_KERNEL)?; /// # Ok::<(), Error>(()) /// ``` pub fn read_binary_file<'a, T, E: 'a>( @@ -212,12 +208,11 @@ impl Dir { /// /// ``` /// # use core::sync::atomic::{AtomicU32, Ordering}; - /// # use kernel::c_str; /// # use kernel::debugfs::Dir; /// # use kernel::prelude::*; - /// # let dir = Dir::new(c_str!("foo")); + /// # let dir = Dir::new(c"foo"); /// let file = KBox::pin_init( - /// dir.read_callback_file(c_str!("bar"), + /// dir.read_callback_file(c"bar", /// AtomicU32::new(3), /// &|val, f| { /// let out = val.load(Ordering::Relaxed); From 652ff12476986bb9aa6749b9dc28c305992e81d0 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:35:33 +0100 Subject: [PATCH 21/79] samples: rust: debugfs: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-driver-core-v1-7-1142a177d0fd@gmail.com Signed-off-by: Danilo Krummrich --- samples/rust/rust_debugfs.rs | 17 ++++++++--------- samples/rust/rust_debugfs_scoped.rs | 19 +++++++------------ 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs index 3d6ad697e2b7..0963efe19f93 100644 --- a/samples/rust/rust_debugfs.rs +++ b/samples/rust/rust_debugfs.rs @@ -34,7 +34,6 @@ use core::str::FromStr; use kernel::{ acpi, - c_str, debugfs::{ Dir, File, // @@ -113,7 +112,7 @@ kernel::acpi_device_table!( ACPI_TABLE, MODULE_ACPI_TABLE, ::IdInfo, - [(acpi::DeviceId::new(c_str!("LNUXBEEF")), ())] + [(acpi::DeviceId::new(c"LNUXBEEF"), ())] ); impl platform::Driver for RustDebugFs { @@ -140,34 +139,34 @@ impl platform::Driver for RustDebugFs { impl RustDebugFs { fn build_counter(dir: &Dir) -> impl PinInit>> + '_ { - dir.read_write_file(c_str!("counter"), Atomic::::new(0)) + dir.read_write_file(c"counter", Atomic::::new(0)) } fn build_inner(dir: &Dir) -> impl PinInit>> + '_ { - dir.read_write_file(c_str!("pair"), new_mutex!(Inner { x: 3, y: 10 })) + dir.read_write_file(c"pair", new_mutex!(Inner { x: 3, y: 10 })) } fn new(pdev: &platform::Device) -> impl PinInit + '_ { - let debugfs = Dir::new(c_str!("sample_debugfs")); + let debugfs = Dir::new(c"sample_debugfs"); let dev = pdev.as_ref(); try_pin_init! { Self { _compatible <- debugfs.read_only_file( - c_str!("compatible"), + c"compatible", dev.fwnode() .ok_or(ENOENT)? - .property_read::(c_str!("compatible")) + .property_read::(c"compatible") .required_by(dev)?, ), counter <- Self::build_counter(&debugfs), inner <- Self::build_inner(&debugfs), array_blob <- debugfs.read_write_binary_file( - c_str!("array_blob"), + c"array_blob", new_mutex!([0x62, 0x6c, 0x6f, 0x62]), ), vector_blob <- debugfs.read_write_binary_file( - c_str!("vector_blob"), + c"vector_blob", new_mutex!(kernel::kvec!(0x42; SZ_4K)?), ), _debugfs: debugfs, diff --git a/samples/rust/rust_debugfs_scoped.rs b/samples/rust/rust_debugfs_scoped.rs index cb5d3dfdc599..6a575a15a2c2 100644 --- a/samples/rust/rust_debugfs_scoped.rs +++ b/samples/rust/rust_debugfs_scoped.rs @@ -7,7 +7,6 @@ //! track them all. use kernel::{ - c_str, debugfs::{ Dir, Scope, // @@ -89,7 +88,7 @@ fn create_file_write( }; dir.read_write_file(&name, val); } - dir.read_write_binary_file(c_str!("blob"), &dev_data.blob); + dir.read_write_binary_file(c"blob", &dev_data.blob); }, ), GFP_KERNEL, @@ -128,20 +127,16 @@ struct DeviceData { } fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit> + '_ { - base_dir.scope( - ModuleData::init(dyn_dirs), - c_str!("control"), - |data, dir| { - dir.write_only_callback_file(c_str!("create"), data, &create_file_write); - dir.write_only_callback_file(c_str!("remove"), data, &remove_file_write); - }, - ) + base_dir.scope(ModuleData::init(dyn_dirs), c"control", |data, dir| { + dir.write_only_callback_file(c"create", data, &create_file_write); + dir.write_only_callback_file(c"remove", data, &remove_file_write); + }) } impl kernel::Module for RustScopedDebugFs { fn init(_module: &'static kernel::ThisModule) -> Result { - let base_dir = Dir::new(c_str!("rust_scoped_debugfs")); - let dyn_dirs = base_dir.subdir(c_str!("dynamic")); + let base_dir = Dir::new(c"rust_scoped_debugfs"); + let dyn_dirs = base_dir.subdir(c"dynamic"); Ok(Self { _data: KBox::pin_init(init_control(&base_dir, dyn_dirs), GFP_KERNEL)?, }) From 185c81461ff4987d35fdfc4c8da46ae51ee5ada4 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:23:54 +0100 Subject: [PATCH 22/79] samples: rust: pci: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Acked-by: Greg Kroah-Hartman Reviewed-by: Alice Ryhl Reviewed-by: Benno Lossin Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-pci-v1-1-a0397c61bbe4@gmail.com [ Use kernel vertical import style. - Danilo ] Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_pci.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 5823787bea8e..39e644236b24 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -4,7 +4,13 @@ //! //! To make this driver probe, QEMU must be run with `-device pci-testdev`. -use kernel::{c_str, device::Core, devres::Devres, pci, prelude::*, sync::aref::ARef}; +use kernel::{ + device::Core, + devres::Devres, + pci, + prelude::*, + sync::aref::ARef, // +}; struct Regs; @@ -79,7 +85,7 @@ impl pci::Driver for SampleDriver { pdev.set_master(); Ok(try_pin_init!(Self { - bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!("rust_driver_pci")), + bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c"rust_driver_pci"), index: *info, _: { let bar = bar.access(pdev.as_ref())?; From 43e3518582cfc0fc1bb536b62d7f8366ee069ce9 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 23 Dec 2025 08:40:24 +0100 Subject: [PATCH 23/79] samples: rust: faux: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Acked-by: Greg Kroah-Hartman Reviewed-by: Alice Ryhl Reviewed-by: Benno Lossin Signed-off-by: Tamir Duberstein Link: https://patch.msgid.link/20251223-cstr-faux-v1-1-ee0c5cf1be4b@gmail.com [ Use kernel vertical import style. - Danilo ] Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_faux.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/samples/rust/rust_driver_faux.rs b/samples/rust/rust_driver_faux.rs index ecc9fd378cbd..5330b77ea986 100644 --- a/samples/rust/rust_driver_faux.rs +++ b/samples/rust/rust_driver_faux.rs @@ -2,7 +2,11 @@ //! Rust faux device sample. -use kernel::{c_str, faux, prelude::*, Module}; +use kernel::{ + faux, + prelude::*, + Module, // +}; module! { type: SampleModule, @@ -20,7 +24,7 @@ impl Module for SampleModule { fn init(_module: &'static ThisModule) -> Result { pr_info!("Initialising Rust Faux Device Sample\n"); - let reg = faux::Registration::new(c_str!("rust-faux-sample-device"), None)?; + let reg = faux::Registration::new(c"rust-faux-sample-device", None)?; dev_info!(reg.as_ref(), "Hello from faux device!\n"); From 057d44b057755f31a38a3cb040960e8727b93610 Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Fri, 26 Dec 2025 20:17:07 +0000 Subject: [PATCH 24/79] rust: Add soc_device support Allow SoC drivers in Rust to present metadata about their devices to userspace through /sys/devices/socX and other drivers to identify their properties through `soc_device_match`. Signed-off-by: Matthew Maurer Link: https://patch.msgid.link/20251226-soc-bindings-v4-1-2c2fac08f820@google.com Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + rust/bindings/bindings_helper.h | 1 + rust/kernel/lib.rs | 2 + rust/kernel/soc.rs | 135 ++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 rust/kernel/soc.rs diff --git a/MAINTAINERS b/MAINTAINERS index 5b11839cba9d..6cce4538eda0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7700,6 +7700,7 @@ F: rust/kernel/devres.rs F: rust/kernel/driver.rs F: rust/kernel/faux.rs F: rust/kernel/platform.rs +F: rust/kernel/soc.rs F: samples/rust/rust_debugfs.rs F: samples/rust/rust_debugfs_scoped.rs F: samples/rust/rust_driver_platform.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index a067038b4b42..9fdf76ca630e 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -80,6 +80,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index f812cf120042..6d637e2fed1b 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -138,6 +138,8 @@ pub mod security; pub mod seq_file; pub mod sizes; pub mod slice; +#[cfg(CONFIG_SOC_BUS)] +pub mod soc; mod static_assert; #[doc(hidden)] pub mod std_vendor; diff --git a/rust/kernel/soc.rs b/rust/kernel/soc.rs new file mode 100644 index 000000000000..0d6a36c83cb6 --- /dev/null +++ b/rust/kernel/soc.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! SoC Driver Abstraction. +//! +//! C header: [`include/linux/sys_soc.h`](srctree/include/linux/sys_soc.h) + +use crate::{ + bindings, + error, + prelude::*, + str::CString, + types::Opaque, // +}; +use core::ptr::NonNull; + +/// Attributes for a SoC device. +/// +/// These are both exported to userspace under /sys/devices/socX and provided to other drivers to +/// match against via `soc_device_match` (not yet available in Rust) to enable quirks or +/// device-specific support where necessary. +/// +/// All fields are freeform - they have no specific formatting, just defined meanings. +/// For example, the [`machine`](`Attributes::machine`) field could be "DB8500" or +/// "Qualcomm Technologies, Inc. SM8560 HDK", but regardless it should identify a board or product. +pub struct Attributes { + /// Should generally be a board ID or product ID. Examples + /// include DB8500 (ST-Ericsson) or "Qualcomm Technologies, inc. SM8560 HDK". + /// + /// If this field is not populated, the SoC infrastructure will try to populate it from + /// `/model` in the device tree. + pub machine: Option, + /// The broader class this SoC belongs to. Examples include ux500 + /// (for DB8500) or Snapdragon (for SM8650). + /// + /// On chips with ARM firmware supporting SMCCC v1.2+, this may be a JEDEC JEP106 manufacturer + /// identification. + pub family: Option, + /// The manufacturing revision of the part. Frequently this is MAJOR.MINOR, but not always. + pub revision: Option, + /// Serial Number - uniquely identifies a specific SoC. If present, should be unique (buying a + /// replacement part should change it if present). This field cannot be matched on and is + /// solely present to export through /sys. + pub serial_number: Option, + /// SoC ID - identifies a specific SoC kind in question, sometimes more specifically than + /// `machine` if the same SoC is used in multiple products. Some devices use this to specify a + /// SoC name, e.g. "I.MX??", and others just print an ID number (e.g. Tegra and Qualcomm). + /// + /// On chips with ARM firmware supporting SMCCC v1.2+, this may be a JEDEC JEP106 manufacturer + /// identification (the family value) followed by a colon and then a 4-digit ID value. + pub soc_id: Option, +} + +struct BuiltAttributes { + // While `inner` has pointers to `_backing`, it is to the interior of the `CStrings`, not + // `backing` itself, so it does not need to be pinned. + _backing: Attributes, + // `Opaque` makes us `!Unpin`, as the registration holds a pointer to `inner` when used. + inner: Opaque, +} + +fn cstring_to_c(mcs: &Option) -> *const kernel::ffi::c_char { + mcs.as_ref() + .map(|cs| cs.as_char_ptr()) + .unwrap_or(core::ptr::null()) +} + +impl BuiltAttributes { + fn as_mut_ptr(&self) -> *mut bindings::soc_device_attribute { + self.inner.get() + } +} + +impl Attributes { + fn build(self) -> BuiltAttributes { + BuiltAttributes { + inner: Opaque::new(bindings::soc_device_attribute { + machine: cstring_to_c(&self.machine), + family: cstring_to_c(&self.family), + revision: cstring_to_c(&self.revision), + serial_number: cstring_to_c(&self.serial_number), + soc_id: cstring_to_c(&self.soc_id), + data: core::ptr::null(), + custom_attr_group: core::ptr::null(), + }), + _backing: self, + } + } +} + +#[pin_data(PinnedDrop)] +/// Registration handle for your soc_dev. If you let it go out of scope, your soc_dev will be +/// unregistered. +pub struct Registration { + #[pin] + attr: BuiltAttributes, + soc_dev: NonNull, +} + +// SAFETY: We provide no operations through `&Registration`. +unsafe impl Sync for Registration {} + +// SAFETY: All pointers are normal allocations, not thread-specific. +unsafe impl Send for Registration {} + +#[pinned_drop] +impl PinnedDrop for Registration { + fn drop(self: Pin<&mut Self>) { + // SAFETY: Device always contains a live pointer to a soc_device that can be unregistered + unsafe { bindings::soc_device_unregister(self.soc_dev.as_ptr()) } + } +} + +impl Registration { + /// Register a new SoC device + pub fn new(attr: Attributes) -> impl PinInit { + try_pin_init!(Self { + attr: attr.build(), + soc_dev: { + // SAFETY: + // * The struct provided through attr is backed by pinned data next to it, + // so as long as attr lives, the strings pointed to by the struct will too. + // * `attr` is pinned, so the pinned data won't move. + // * If it returns a device, and so others may try to read this data, by + // caller invariant, `attr` won't be released until the device is. + let raw_soc = error::from_err_ptr(unsafe { + bindings::soc_device_register(attr.as_mut_ptr()) + })?; + + NonNull::new(raw_soc).ok_or(EINVAL)? + }, + }? Error) + } +} From d43a12e474351161bb6d7e2a17ab56f591b9302d Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Fri, 26 Dec 2025 20:17:09 +0000 Subject: [PATCH 25/79] rust: Add SoC Driver Sample Shows registration of a SoC device upon receipt of a probe. Signed-off-by: Matthew Maurer Link: https://patch.msgid.link/20251226-soc-bindings-v4-3-2c2fac08f820@google.com Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + samples/rust/Kconfig | 11 ++++++ samples/rust/Makefile | 1 + samples/rust/rust_soc.rs | 81 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 samples/rust/rust_soc.rs diff --git a/MAINTAINERS b/MAINTAINERS index 6cce4538eda0..7120b894b182 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7705,6 +7705,7 @@ F: samples/rust/rust_debugfs.rs F: samples/rust/rust_debugfs_scoped.rs F: samples/rust/rust_driver_platform.rs F: samples/rust/rust_driver_faux.rs +F: samples/rust/rust_soc.rs DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS) M: Nishanth Menon diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 3efa51bfc8ef..c49ab9106345 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -161,6 +161,17 @@ config SAMPLE_RUST_DRIVER_AUXILIARY If unsure, say N. +config SAMPLE_RUST_SOC + tristate "SoC Driver" + select SOC_BUS + help + This option builds the Rust SoC driver sample. + + To compile this as a module, choose M here: + the module will be called rust_soc. + + If unsure, say N. + config SAMPLE_RUST_HOSTPROGS bool "Host programs" help diff --git a/samples/rust/Makefile b/samples/rust/Makefile index f65885d1d62b..6c0aaa58cccc 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) += rust_driver_auxiliary.o obj-$(CONFIG_SAMPLE_RUST_CONFIGFS) += rust_configfs.o +obj-$(CONFIG_SAMPLE_RUST_SOC) += rust_soc.o rust_print-y := rust_print_main.o rust_print_events.o diff --git a/samples/rust/rust_soc.rs b/samples/rust/rust_soc.rs new file mode 100644 index 000000000000..403c1137af77 --- /dev/null +++ b/samples/rust/rust_soc.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust SoC Platform driver sample. + +use kernel::{ + acpi, + device::Core, + of, + platform, + prelude::*, + soc, + str::CString, + sync::aref::ARef, // +}; +use pin_init::pin_init_scope; + +#[pin_data] +struct SampleSocDriver { + pdev: ARef, + #[pin] + _dev_reg: soc::Registration, +} + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [(of::DeviceId::new(c"test,rust-device"), ())] +); + +kernel::acpi_device_table!( + ACPI_TABLE, + MODULE_ACPI_TABLE, + ::IdInfo, + [(acpi::DeviceId::new(c"LNUXBEEF"), ())] +); + +impl platform::Driver for SampleSocDriver { + type IdInfo = (); + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + const ACPI_ID_TABLE: Option> = Some(&ACPI_TABLE); + + fn probe( + pdev: &platform::Device, + _info: Option<&Self::IdInfo>, + ) -> impl PinInit { + let dev = pdev.as_ref(); + + dev_dbg!(dev, "Probe Rust SoC driver sample.\n"); + + let pdev = pdev.into(); + pin_init_scope(move || { + let machine = CString::try_from(c"My cool ACME15 dev board")?; + let family = CString::try_from(c"ACME")?; + let revision = CString::try_from(c"1.2")?; + let serial_number = CString::try_from(c"12345")?; + let soc_id = CString::try_from(c"ACME15")?; + + let attr = soc::Attributes { + machine: Some(machine), + family: Some(family), + revision: Some(revision), + serial_number: Some(serial_number), + soc_id: Some(soc_id), + }; + + Ok(try_pin_init!(SampleSocDriver { + pdev: pdev, + _dev_reg <- soc::Registration::new(attr), + }? Error)) + }) + } +} + +kernel::module_platform_driver! { + type: SampleSocDriver, + name: "rust_soc", + authors: ["Matthew Maurer"], + description: "Rust SoC Driver", + license: "GPL", +} From 31bc0aade4e03a056a6b568571e59d3783c97ffc Mon Sep 17 00:00:00 2001 From: Marko Turk Date: Mon, 5 Jan 2026 22:37:42 +0100 Subject: [PATCH 26/79] rust: io: remove square brackets from pci::Bar reference Remove square brackets since this section is not a part of doc-comment so the reference will not be converted to a link in the generated docs. Suggested-by: Danilo Krummrich Signed-off-by: Marko Turk Link: https://patch.msgid.link/20260105213726.73000-1-mt@markoturk.info Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 98e8b84e68d1..a97eb44a9a87 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -87,7 +87,7 @@ impl IoRaw { /// }; /// use core::ops::Deref; /// -/// // See also [`pci::Bar`] for a real example. +/// // See also `pci::Bar` for a real example. /// struct IoMem(IoRaw); /// /// impl IoMem { From 6506b44e88da265688f786d379987e91b4826bf4 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 5 Jan 2026 15:19:42 +0100 Subject: [PATCH 27/79] rust: auxiliary: use "kernel vertical" style for imports Convert all imports to use "kernel vertical" style. With this, subsequent patches neither introduce unrelated changes nor leave an inconsistent import pattern. While at it, drop unnecessary imports covered by prelude::*. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260105142123.95030-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 21 ++++++++++++++++----- samples/rust/rust_driver_auxiliary.rs | 8 +++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index 56f3c180e8f6..f8273cf165dc 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -5,19 +5,30 @@ //! C header: [`include/linux/auxiliary_bus.h`](srctree/include/linux/auxiliary_bus.h) use crate::{ - bindings, container_of, device, - device_id::{RawDeviceId, RawDeviceIdIndex}, + bindings, + container_of, + device, + device_id::{ + RawDeviceId, + RawDeviceIdIndex, // + }, devres::Devres, driver, - error::{from_result, to_result, Result}, + error::{ + from_result, + to_result, // + }, prelude::*, types::Opaque, - ThisModule, + ThisModule, // }; use core::{ marker::PhantomData, mem::offset_of, - ptr::{addr_of_mut, NonNull}, + ptr::{ + addr_of_mut, + NonNull, // + }, }; /// An adapter for the registration of auxiliary drivers. diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 1e4fb23cfcb0..f148124fe81f 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -6,13 +6,15 @@ use kernel::{ auxiliary, - device::{Bound, Core}, + device::{ + Bound, + Core, // + }, devres::Devres, driver, - error::Error, pci, prelude::*, - InPlaceModule, + InPlaceModule, // }; use core::any::TypeId; From da74aee2ad0dcae8a5544deace7465869890e8f0 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 5 Jan 2026 15:19:43 +0100 Subject: [PATCH 28/79] rust: platform: use "kernel vertical" style for imports Convert all imports to use "kernel vertical" style. With this, subsequent patches neither introduce unrelated changes nor leave an inconsistent import pattern. While at it, drop unnecessary imports covered by prelude::*. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260105142123.95030-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/platform.rs | 31 +++++++++++++++++++++------- samples/rust/rust_driver_platform.rs | 10 ++++++--- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index bddb593cee7b..8836ac46b066 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -5,22 +5,39 @@ //! C header: [`include/linux/platform_device.h`](srctree/include/linux/platform_device.h) use crate::{ - acpi, bindings, container_of, - device::{self, Bound}, + acpi, + bindings, + container_of, + device::{ + self, + Bound, // + }, driver, - error::{from_result, to_result, Result}, - io::{mem::IoRequest, Resource}, - irq::{self, IrqRequest}, + error::{ + from_result, + to_result, // + }, + io::{ + mem::IoRequest, + Resource, // + }, + irq::{ + self, + IrqRequest, // + }, of, prelude::*, types::Opaque, - ThisModule, + ThisModule, // }; use core::{ marker::PhantomData, mem::offset_of, - ptr::{addr_of_mut, NonNull}, + ptr::{ + addr_of_mut, + NonNull, // + }, }; /// An adapter for the registration of platform drivers. diff --git a/samples/rust/rust_driver_platform.rs b/samples/rust/rust_driver_platform.rs index a3044d773176..9537dc38c563 100644 --- a/samples/rust/rust_driver_platform.rs +++ b/samples/rust/rust_driver_platform.rs @@ -66,13 +66,17 @@ use kernel::{ acpi, device::{ self, - property::{FwNodeReferenceArgs, NArgs}, + property::{ + FwNodeReferenceArgs, + NArgs, // + }, Core, }, - of, platform, + of, + platform, prelude::*, str::CString, - sync::aref::ARef, + sync::aref::ARef, // }; struct SampleDriver { From 52563c665b0b0b39f319bee40ecc5e8f25b9050a Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 5 Jan 2026 15:19:44 +0100 Subject: [PATCH 29/79] rust: driver-core: use "kernel vertical" style for imports Convert all imports to use "kernel vertical" style. With this, subsequent patches neither introduce unrelated changes nor leave an inconsistent import pattern. While at it, drop unnecessary imports covered by prelude::*. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260105142123.95030-3-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/device.rs | 14 +++++++++++--- rust/kernel/devres.rs | 25 +++++++++++++++++++------ rust/kernel/driver.rs | 12 ++++++++---- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index a13f6ee24b09..ec9b0945b74c 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -5,12 +5,20 @@ //! C header: [`include/linux/device.h`](srctree/include/linux/device.h) use crate::{ - bindings, fmt, + bindings, + fmt, prelude::*, sync::aref::ARef, - types::{ForeignOwnable, Opaque}, + types::{ + ForeignOwnable, + Opaque, // + }, // +}; +use core::{ + any::TypeId, + marker::PhantomData, + ptr, // }; -use core::{any::TypeId, marker::PhantomData, ptr}; use crate::str::CStrExt as _; diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 835d9c11948e..db02f8b1788d 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -8,13 +8,26 @@ use crate::{ alloc::Flags, bindings, - device::{Bound, Device}, - error::{to_result, Error, Result}, - ffi::c_void, + device::{ + Bound, + Device, // + }, + error::to_result, prelude::*, - revocable::{Revocable, RevocableGuard}, - sync::{aref::ARef, rcu, Completion}, - types::{ForeignOwnable, Opaque, ScopeGuard}, + revocable::{ + Revocable, + RevocableGuard, // + }, + sync::{ + aref::ARef, + rcu, + Completion, // + }, + types::{ + ForeignOwnable, + Opaque, + ScopeGuard, // + }, }; use pin_init::Wrapper; diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs index 9beae2e3d57e..866d5d76ca7e 100644 --- a/rust/kernel/driver.rs +++ b/rust/kernel/driver.rs @@ -90,10 +90,14 @@ //! [`pci::Driver`]: kernel::pci::Driver //! [`platform::Driver`]: kernel::platform::Driver -use crate::error::{Error, Result}; -use crate::{acpi, device, of, str::CStr, try_pin_init, types::Opaque, ThisModule}; -use core::pin::Pin; -use pin_init::{pin_data, pinned_drop, PinInit}; +use crate::{ + acpi, + device, + of, + prelude::*, + types::Opaque, + ThisModule, // +}; /// The [`RegistrationOps`] trait serves as generic interface for subsystems (e.g., PCI, Platform, /// Amba, etc.) to provide the corresponding subsystem specific implementation to register / From d88f27d7f4a1861fe6d548d9a9c203e59489880a Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 5 Jan 2026 15:19:46 +0100 Subject: [PATCH 30/79] rust: faux: use "kernel vertical" style for imports Convert all imports to use "kernel vertical" style. With this, subsequent patches neither introduce unrelated changes nor leave an inconsistent import pattern. While at it, drop unnecessary imports covered by prelude::*. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260105142123.95030-5-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/faux.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/rust/kernel/faux.rs b/rust/kernel/faux.rs index 7fe2dd197e37..43b4974f48cd 100644 --- a/rust/kernel/faux.rs +++ b/rust/kernel/faux.rs @@ -6,8 +6,17 @@ //! //! C header: [`include/linux/device/faux.h`](srctree/include/linux/device/faux.h) -use crate::{bindings, device, error::code::*, prelude::*}; -use core::ptr::{addr_of_mut, null, null_mut, NonNull}; +use crate::{ + bindings, + device, + prelude::*, // +}; +use core::ptr::{ + addr_of_mut, + null, + null_mut, + NonNull, // +}; /// The registration of a faux device. /// From e254b758976f651c47ec902d92306bd49f452ab0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 29 Dec 2025 15:43:25 +0100 Subject: [PATCH 31/79] driver core: make bus_find_device_by_acpi_dev() stub prototype aligned Currently the bus_find_device_by_acpi_dev() stub for !CONFIG_ACPI case takes a const void * parameter instead of const struct acpi_device *. As long as it's a pointer, we may named it as we want to with the help of a forward declaration. Hence move the declaration out of the ifdeffery and use the same prototype in both cases. This adds a bit of an additional type checking at a compilation time. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20251229144325.1252197-1-andriy.shevchenko@linux.intel.com [ Fix minor typo in the commit message. - Danilo ] Signed-off-by: Danilo Krummrich --- include/linux/device/bus.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h index 99b1002b3e31..99c3c83ea520 100644 --- a/include/linux/device/bus.h +++ b/include/linux/device/bus.h @@ -215,9 +215,9 @@ bus_find_next_device(const struct bus_type *bus,struct device *cur) return bus_find_device(bus, cur, NULL, device_match_any); } -#ifdef CONFIG_ACPI struct acpi_device; +#ifdef CONFIG_ACPI /** * bus_find_device_by_acpi_dev : device iterator for locating a particular device * matching the ACPI COMPANION device. @@ -231,7 +231,7 @@ bus_find_device_by_acpi_dev(const struct bus_type *bus, const struct acpi_device } #else static inline struct device * -bus_find_device_by_acpi_dev(const struct bus_type *bus, const void *adev) +bus_find_device_by_acpi_dev(const struct bus_type *bus, const struct acpi_device *adev) { return NULL; } From 601cd264a31d198d5710846c9b53dfa92f1c4268 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sat, 10 Jan 2026 03:48:13 -0800 Subject: [PATCH 32/79] rust: auxiliary: fix remove_callback invariant comment Correct copy-paste errors where remove_callback safety invariants incorrectly referenced probe_callback(). Signed-off-by: Alok Tiwari Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260110114817.2312828-1-alok.a.tiwari@oracle.com Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index f8273cf165dc..d2890a2c543a 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -92,7 +92,7 @@ impl Adapter { // SAFETY: The auxiliary bus only ever calls the probe callback with a valid pointer to a // `struct auxiliary_device`. // - // INVARIANT: `adev` is valid for the duration of `probe_callback()`. + // INVARIANT: `adev` is valid for the duration of `remove_callback()`. let adev = unsafe { &*adev.cast::>() }; // SAFETY: `remove_callback` is only ever called after a successful call to From 585e8a26abfd0c453f01ef78bd637cb7600e9b04 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Sat, 10 Jan 2026 03:51:56 -0800 Subject: [PATCH 33/79] rust: platform: fix remove_callback invariant comment Correct copy-paste errors where remove_callback safety invariants incorrectly referenced probe_callback(). Signed-off-by: Alok Tiwari Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260110115159.2313116-1-alok.a.tiwari@oracle.com Signed-off-by: Danilo Krummrich --- rust/kernel/platform.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 8836ac46b066..bdf89c8293fc 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -103,7 +103,7 @@ impl Adapter { // SAFETY: The platform bus only ever calls the remove callback with a valid pointer to a // `struct platform_device`. // - // INVARIANT: `pdev` is valid for the duration of `probe_callback()`. + // INVARIANT: `pdev` is valid for the duration of `remove_callback()`. let pdev = unsafe { &*pdev.cast::>() }; // SAFETY: `remove_callback` is only ever called after a successful call to From 303db924fe0bb298242694c0c36fa1890cc9cf33 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 13 Jan 2026 13:03:42 +0100 Subject: [PATCH 34/79] MAINTAINERS: update auxiliary bus entry The auxiliary bus is part of the driver-core infrastructure, hence add missing driver-core maintainers to the auxiliary bus entry. Signed-off-by: Danilo Krummrich Acked-by: Rafael J. Wysocki Link: https://patch.msgid.link/20260113120345.4639-1-dakr@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 4ef6015a34ed..54ea68a0883d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4164,6 +4164,8 @@ F: scripts/Makefile.autofdo AUXILIARY BUS DRIVER M: Greg Kroah-Hartman +M: "Rafael J. Wysocki" +M: Danilo Krummrich R: Dave Ertman R: Ira Weiny R: Leon Romanovsky From dc23806a7c47ec5f1293aba407fb69519f976ee0 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Wed, 14 Jan 2026 00:28:43 +0800 Subject: [PATCH 35/79] driver core: enforce device_lock for driver_match_device() Currently, driver_match_device() is called from three sites. One site (__device_attach_driver) holds device_lock(dev), but the other two (bind_store and __driver_attach) do not. This inconsistency means that bus match() callbacks are not guaranteed to be called with the lock held. Fix this by introducing driver_match_device_locked(), which guarantees holding the device lock using a scoped guard. Replace the unlocked calls in bind_store() and __driver_attach() with this new helper. Also add a lock assertion to driver_match_device() to enforce this guarantee. This consistency also fixes a known race condition. The driver_override implementation relies on the device_lock, so the missing lock led to the use-after-free (UAF) reported in Bugzilla for buses using this field. Stress testing the two newly locked paths for 24 hours with CONFIG_PROVE_LOCKING and CONFIG_LOCKDEP enabled showed no UAF recurrence and no lockdep warnings. Cc: stable@vger.kernel.org Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220789 Suggested-by: Qiu-ji Chen Signed-off-by: Gui-Dong Han Fixes: 49b420a13ff9 ("driver core: check bus->match without holding device lock") Reviewed-by: Danilo Krummrich Reviewed-by: Greg Kroah-Hartman Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260113162843.12712-1-hanguidong02@gmail.com Signed-off-by: Danilo Krummrich --- drivers/base/base.h | 9 +++++++++ drivers/base/bus.c | 2 +- drivers/base/dd.c | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index 430cbefbc97f..677320881af1 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -182,9 +182,18 @@ void device_set_deferred_probe_reason(const struct device *dev, struct va_format static inline int driver_match_device(const struct device_driver *drv, struct device *dev) { + device_lock_assert(dev); + return drv->bus->match ? drv->bus->match(dev, drv) : 1; } +static inline int driver_match_device_locked(const struct device_driver *drv, + struct device *dev) +{ + guard(device)(dev); + return driver_match_device(drv, dev); +} + static inline void dev_sync_state(struct device *dev) { if (dev->bus->sync_state) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 9eb7771706f0..331d750465e2 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -263,7 +263,7 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, int err = -ENODEV; dev = bus_find_device_by_name(bus, NULL, buf); - if (dev && driver_match_device(drv, dev)) { + if (dev && driver_match_device_locked(drv, dev)) { err = device_driver_attach(drv, dev); if (!err) { /* success */ diff --git a/drivers/base/dd.c b/drivers/base/dd.c index bea8da5f8a3a..ed3a07624816 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -1180,7 +1180,7 @@ static int __driver_attach(struct device *dev, void *data) * is an error. */ - ret = driver_match_device(drv, dev); + ret = driver_match_device_locked(drv, dev); if (ret == 0) { /* no match */ return 0; From 62eb557580eb2177cf16c3fd2b6efadff297b29a Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 16 Jan 2026 08:02:33 +0000 Subject: [PATCH 36/79] revocable: Revocable resource management Some resources can be removed asynchronously, for example, resources provided by a hot-pluggable device like USB. When holding a reference to such a resource, it's possible for the resource to be removed and its memory freed, leading to use-after-free errors on subsequent access. The "revocable" mechanism addresses this by establishing a weak reference to a resource that might be freed at any time. It allows a resource consumer to safely attempt to access the resource, guaranteeing that the access is valid for the duration of its use, or it fails safely if the resource has already been revoked. The implementation 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_provider and initializes it with a pointer to the resource. - A resource consumer that wants to access the resource allocates a struct revocable which acts as a 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. The REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED() are convenient helpers for doing that. - When the provider needs to remove the resource, it calls revocable_provider_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. Acked-by: Danilo Krummrich Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260116080235.350305-2-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../driver-api/driver-model/index.rst | 1 + .../driver-api/driver-model/revocable.rst | 152 +++++++++++ MAINTAINERS | 7 + drivers/base/Makefile | 2 +- drivers/base/revocable.c | 242 ++++++++++++++++++ include/linux/revocable.h | 69 +++++ 6 files changed, 472 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 4831bdd92e5c..8e1ee21185df 100644 --- a/Documentation/driver-api/driver-model/index.rst +++ b/Documentation/driver-api/driver-model/index.rst @@ -14,6 +14,7 @@ Driver Model overview platform porting + revocable .. only:: subproject and html diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst new file mode 100644 index 000000000000..22a442cc8d7f --- /dev/null +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -0,0 +1,152 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================== +Revocable Resource Management +============================== + +Overview +======== + +.. kernel-doc:: drivers/base/revocable.c + :doc: Overview + +Revocable vs. Devres (devm) +=========================== + +It's important to understand the distinct roles of the Revocable and Devres, +and how they can complement each other. They address different problems in +resource management: + +* **Devres:** Primarily address **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 of a resource managed by the Revocable + mechanism 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 +---------------------- +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_provider + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_provider_alloc + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: devm_revocable_provider_alloc + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_provider_revoke + +For Resource Consumers +---------------------- +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_alloc + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_free + +.. 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 + + void 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; + } + + // At this point, 'res' is guaranteed to be valid until + // this block exits. + do_something_with(res); + + } // revocable_withdraw_access() is automatically called here. + +.. kernel-doc:: include/linux/revocable.h + :identifiers: REVOCABLE_TRY_ACCESS_SCOPED + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + void consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + // Always check if the resource is valid. + if (!res) { + pr_warn("Resource is not available\n"); + return; + } + + // At this point, 'res' is guaranteed to be valid until + // this block exits. + do_something_with(res); + } + + // revocable_withdraw_access() is automatically called here. + } diff --git a/MAINTAINERS b/MAINTAINERS index 54ea68a0883d..f873553d4543 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22368,6 +22368,13 @@ F: include/uapi/linux/rseq.h F: kernel/rseq.c F: tools/testing/selftests/rseq/ +REVOCABLE RESOURCE MANAGEMENT +M: Tzung-Bi Shih +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/base/revocable.c +F: include/linux/revocable.h + RFKILL M: Johannes Berg L: linux-wireless@vger.kernel.org diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 8074a10183dc..bdf854694e39 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -6,7 +6,7 @@ obj-y := 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..f6cece275aac --- /dev/null +++ b/drivers/base/revocable.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 Google LLC + * + * Revocable resource management + */ + +#include +#include +#include +#include +#include + +/** + * DOC: Overview + * + * The "revocable" mechanism is a synchronization primitive designed to manage + * safe access to resources that can be asynchronously removed or invalidated. + * Its primary purpose is to prevent Use-After-Free (UAF) errors when + * interacting with resources whose lifetimes are not guaranteed to outlast + * their consumers. + * + * This is particularly useful in systems where resources can disappear + * unexpectedly, such as those provided by hot-pluggable devices like USB. + * When a consumer holds a reference to such a resource, the underlying device + * might be removed, causing the resource's memory to be freed. Subsequent + * access attempts by the consumer would then lead to UAF errors. + * + * Revocable addresses this by providing a form of "weak reference" and a + * controlled access method. It allows a resource consumer to safely attempt to + * access the resource. The mechanism guarantees that any access granted is + * valid for the duration of its use. If the resource has already been + * revoked (i.e., freed), the access attempt will fail safely, typically by + * returning NULL, instead of causing a crash. + * + * The implementation 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_provider and initializes it with a pointer + * to the resource. + * + * - A resource consumer that wants to access the resource allocates a + * struct revocable which acts as a 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. The + * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED() are + * convenient helpers for doing that. + * + * - When the provider needs to remove the resource, it calls + * revocable_provider_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. + */ + +/** + * struct revocable_provider - 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. + */ +struct revocable_provider { + struct srcu_struct srcu; + void __rcu *res; + struct kref kref; +}; + +/** + * struct revocable - A handle for resource consumer. + * @rp: The pointer of resource provider. + * @idx: The index for the RCU critical section. + */ +struct revocable { + struct revocable_provider *rp; + int idx; +}; + +/** + * revocable_provider_alloc() - Allocate struct revocable_provider. + * @res: The pointer of resource. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable_provider *revocable_provider_alloc(void *res) +{ + struct revocable_provider *rp; + + rp = kzalloc(sizeof(*rp), GFP_KERNEL); + if (!rp) + return NULL; + + init_srcu_struct(&rp->srcu); + rcu_assign_pointer(rp->res, res); + synchronize_srcu(&rp->srcu); + kref_init(&rp->kref); + + return rp; +} +EXPORT_SYMBOL_GPL(revocable_provider_alloc); + +static void revocable_provider_release(struct kref *kref) +{ + struct revocable_provider *rp = container_of(kref, + struct revocable_provider, kref); + + cleanup_srcu_struct(&rp->srcu); + kfree(rp); +} + +/** + * revocable_provider_revoke() - Revoke the managed resource. + * @rp: The pointer of resource provider. + * + * This sets the resource `(struct revocable_provider *)->res` to NULL to + * indicate the resource has gone. + * + * This drops the refcount to the resource provider. If it is the final + * reference, revocable_provider_release() will be called to free the struct. + */ +void revocable_provider_revoke(struct revocable_provider *rp) +{ + rcu_assign_pointer(rp->res, NULL); + synchronize_srcu(&rp->srcu); + kref_put(&rp->kref, revocable_provider_release); +} +EXPORT_SYMBOL_GPL(revocable_provider_revoke); + +static void devm_revocable_provider_revoke(void *data) +{ + struct revocable_provider *rp = data; + + revocable_provider_revoke(rp); +} + +/** + * devm_revocable_provider_alloc() - Dev-managed revocable_provider_alloc(). + * @dev: The device. + * @res: The pointer of resource. + * + * It is convenient to allocate providers via this function if the @res is + * also tied to the lifetime of the @dev. revocable_provider_revoke() will + * be called automatically when the device is unbound. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, + void *res) +{ + struct revocable_provider *rp; + + rp = revocable_provider_alloc(res); + if (!rp) + return NULL; + + if (devm_add_action_or_reset(dev, devm_revocable_provider_revoke, rp)) + return NULL; + + return rp; +} +EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc); + +/** + * revocable_alloc() - Allocate struct revocable. + * @rp: The pointer of resource provider. + * + * This holds a refcount to the resource provider. + * + * Return: The pointer of struct revocable. NULL on errors. + */ +struct revocable *revocable_alloc(struct revocable_provider *rp) +{ + struct revocable *rev; + + rev = kzalloc(sizeof(*rev), GFP_KERNEL); + if (!rev) + return NULL; + + rev->rp = rp; + kref_get(&rp->kref); + + return rev; +} +EXPORT_SYMBOL_GPL(revocable_alloc); + +/** + * revocable_free() - Free struct revocable. + * @rev: The pointer of struct revocable. + * + * This drops a refcount to the resource provider. If it is the final + * reference, revocable_provider_release() will be called to free the struct. + */ +void revocable_free(struct revocable *rev) +{ + struct revocable_provider *rp = rev->rp; + + kref_put(&rp->kref, revocable_provider_release); + kfree(rev); +} +EXPORT_SYMBOL_GPL(revocable_free); + +/** + * revocable_try_access() - Try to access the resource. + * @rev: The pointer of struct revocable. + * + * This tries to de-reference to the resource and enters a RCU critical + * section. + * + * Return: The pointer to the resource. NULL if the resource has gone. + */ +void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu) +{ + struct revocable_provider *rp = rev->rp; + + rev->idx = srcu_read_lock(&rp->srcu); + return srcu_dereference(rp->res, &rp->srcu); +} +EXPORT_SYMBOL_GPL(revocable_try_access); + +/** + * revocable_withdraw_access() - Stop accessing to the resource. + * @rev: The pointer of struct revocable. + * + * Call this function to indicate the resource is no longer used. It exits + * the RCU critical section. + */ +void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu) +{ + struct revocable_provider *rp = rev->rp; + + srcu_read_unlock(&rp->srcu, rev->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..659ba01c58db --- /dev/null +++ b/include/linux/revocable.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2026 Google LLC + */ + +#ifndef __LINUX_REVOCABLE_H +#define __LINUX_REVOCABLE_H + +#include +#include + +struct device; +struct revocable; +struct revocable_provider; + +struct revocable_provider *revocable_provider_alloc(void *res); +void revocable_provider_revoke(struct revocable_provider *rp); +struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, + void *res); + +struct revocable *revocable_alloc(struct revocable_provider *rp); +void revocable_free(struct revocable *rev); +void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); +void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); + +DEFINE_FREE(access_rev, struct revocable *, if (_T) revocable_withdraw_access(_T)) + +/** + * REVOCABLE_TRY_ACCESS_WITH() - A helper for accessing revocable resource + * @_rev: The consumer's ``struct revocable *`` handle. + * @_res: A pointer variable that will be assigned the resource. + * + * The macro simplifies the access-release cycle for consumers, ensuring that + * revocable_withdraw_access() is always 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(). The consumer code **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) \ + struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ + _res = revocable_try_access(_rev) + +#define _REVOCABLE_TRY_ACCESS_SCOPED(_rev, _label, _res) \ + for (struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ + (_res = revocable_try_access(_rev)) || true; ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ + } else + +/** + * REVOCABLE_TRY_ACCESS_SCOPED() - A helper for accessing revocable resource + * @_rev: The consumer's ``struct revocable *`` handle. + * @_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_SCOPED(_rev, _res) \ + _REVOCABLE_TRY_ACCESS_SCOPED(_rev, __UNIQUE_ID(label), _res) + +#endif /* __LINUX_REVOCABLE_H */ From cd7693419bb5abd91ad2f407dab69c480e417a61 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 16 Jan 2026 08:02:34 +0000 Subject: [PATCH 37/79] revocable: Add Kunit test cases 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 provided via the provider. - 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 REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). A way to run the test: $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=y \ revocable_test Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260116080235.350305-3-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + drivers/base/Kconfig | 8 ++ drivers/base/Makefile | 3 + drivers/base/revocable_test.c | 139 ++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 drivers/base/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index f873553d4543..6762bcffb1aa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22373,6 +22373,7 @@ M: Tzung-Bi Shih L: linux-kernel@vger.kernel.org S: Maintained 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 1786d87b29e2..8f7d7b9d81ac 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -250,3 +250,11 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT work on. endmenu + +# Kunit test cases +config REVOCABLE_KUNIT_TEST + tristate "Kunit tests for revocable" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + Kunit tests for the revocable API. diff --git a/drivers/base/Makefile b/drivers/base/Makefile index bdf854694e39..4185aaa9bbb9 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..28d46ce1ba0c --- /dev/null +++ b/drivers/base/revocable_test.c @@ -0,0 +1,139 @@ +// 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 + * provided via the provider. + * + * - 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 + * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). + */ + +#include +#include + +static void revocable_test_basic(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + + rp = revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev = revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + res = revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(rev); + + revocable_free(rev); + revocable_provider_revoke(rp); +} + +static void revocable_test_revocation(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + + rp = revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev = revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + res = revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(rev); + + revocable_provider_revoke(rp); + + res = revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + revocable_withdraw_access(rev); + + revocable_free(rev); +} + +static void revocable_test_try_access_macro(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + + rp = revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev = revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + { + REVOCABLE_TRY_ACCESS_WITH(rev, res); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + } + + revocable_provider_revoke(rp); + + { + REVOCABLE_TRY_ACCESS_WITH(rev, res); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + } + + revocable_free(rev); +} + +static void revocable_test_try_access_macro2(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + bool accessed; + + rp = revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev = revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + accessed = false; + REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + accessed = true; + } + KUNIT_EXPECT_TRUE(test, accessed); + + revocable_provider_revoke(rp); + + accessed = false; + REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + accessed = true; + } + KUNIT_EXPECT_TRUE(test, accessed); + + revocable_free(rev); +} + +static struct kunit_case revocable_test_cases[] = { + KUNIT_CASE(revocable_test_basic), + KUNIT_CASE(revocable_test_revocation), + KUNIT_CASE(revocable_test_try_access_macro), + KUNIT_CASE(revocable_test_try_access_macro2), + {} +}; + +static struct kunit_suite revocable_test_suite = { + .name = "revocable_test", + .test_cases = revocable_test_cases, +}; + +kunit_test_suite(revocable_test_suite); From 9d4502fef00fa7a798d3c0806d4da4466a7ffc6f Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 16 Jan 2026 08:02:35 +0000 Subject: [PATCH 38/79] selftests: revocable: Add kselftest cases Add kselftest cases for the revocable API. The test consists of three parts: - A kernel module (revocable_test.ko) that creates a debugfs interface with `/provider` and `/consumer` files. - A user-space C program (revocable_test) that uses the kselftest harness to interact with the debugfs files. - An orchestrating shell script (test-revocable.sh) that loads the module, runs the C program, and unloads the module. The test cases cover the following scenarios: - Basic: Verifies that a consumer can successfully access the resource provided via the provider. - 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 REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260116080235.350305-4-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + .../selftests/drivers/base/revocable/Makefile | 7 + .../drivers/base/revocable/revocable_test.c | 136 ++++++++++++ .../drivers/base/revocable/test-revocable.sh | 39 ++++ .../base/revocable/test_modules/Makefile | 10 + .../revocable/test_modules/revocable_test.c | 195 ++++++++++++++++++ 7 files changed, 389 insertions(+) create mode 100644 tools/testing/selftests/drivers/base/revocable/Makefile create mode 100644 tools/testing/selftests/drivers/base/revocable/revocable_test.c create mode 100755 tools/testing/selftests/drivers/base/revocable/test-revocable.sh create mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/Makefile create mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index 6762bcffb1aa..6aab211f1630 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22375,6 +22375,7 @@ S: Maintained F: drivers/base/revocable.c F: drivers/base/revocable_test.c F: include/linux/revocable.h +F: tools/testing/selftests/drivers/base/revocable/ RFKILL M: Johannes Berg diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 56e44a98d6a5..11b6515ce3d0 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -17,6 +17,7 @@ TARGETS += damon TARGETS += devices/error_logs TARGETS += devices/probe TARGETS += dmabuf-heaps +TARGETS += drivers/base/revocable TARGETS += drivers/dma-buf TARGETS += drivers/ntsync TARGETS += drivers/s390x/uvdevice diff --git a/tools/testing/selftests/drivers/base/revocable/Makefile b/tools/testing/selftests/drivers/base/revocable/Makefile new file mode 100644 index 000000000000..afa5ca0fa452 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_GEN_MODS_DIR := test_modules +TEST_GEN_PROGS_EXTENDED := revocable_test +TEST_PROGS := test-revocable.sh + +include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/base/revocable/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/revocable_test.c new file mode 100644 index 000000000000..f024164e9273 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/revocable_test.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 Google LLC + * + * A selftest for the revocable API. + * + * The test cases cover the following scenarios: + * + * - Basic: Verifies that a consumer can successfully access the resource + * provided via the provider. + * + * - 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 + * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). + */ + +#include +#include + +#include "../../../kselftest_harness.h" + +#define DEBUGFS_PATH "/sys/kernel/debug/revocable_test" +#define TEST_CMD_RESOURCE_GONE "resource_gone" +#define TEST_DATA "12345678" +#define TEST_MAGIC_OFFSET 0x1234 +#define TEST_MAGIC_OFFSET2 0x5678 + +FIXTURE(revocable_fixture) { + int pfd; + int cfd; +}; + +FIXTURE_SETUP(revocable_fixture) { + int ret; + + self->pfd = open(DEBUGFS_PATH "/provider", O_WRONLY); + ASSERT_NE(-1, self->pfd) + TH_LOG("failed to open provider fd"); + + ret = write(self->pfd, TEST_DATA, strlen(TEST_DATA)); + ASSERT_NE(-1, ret) { + close(self->pfd); + TH_LOG("failed to write test data"); + } + + self->cfd = open(DEBUGFS_PATH "/consumer", O_RDONLY); + ASSERT_NE(-1, self->cfd) + TH_LOG("failed to open consumer fd"); +} + +FIXTURE_TEARDOWN(revocable_fixture) { + close(self->cfd); + close(self->pfd); +} + +/* + * ASSERT_* is only available in TEST or TEST_F block. Use + * macro for the helper. + */ +#define READ_TEST_DATA(_fd, _offset, _data, _msg) \ + do { \ + int ret; \ + \ + ret = lseek(_fd, _offset, SEEK_SET); \ + ASSERT_NE(-1, ret) \ + TH_LOG("failed to lseek"); \ + \ + ret = read(_fd, _data, sizeof(_data) - 1); \ + ASSERT_NE(-1, ret) \ + TH_LOG(_msg); \ + data[ret] = '\0'; \ + } while (0) + +TEST_F(revocable_fixture, basic) { + char data[16]; + + READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); +} + +TEST_F(revocable_fixture, revocation) { + char data[16]; + int ret; + + READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); + + ret = write(self->pfd, TEST_CMD_RESOURCE_GONE, + strlen(TEST_CMD_RESOURCE_GONE)); + ASSERT_NE(-1, ret) + TH_LOG("failed to write resource gone cmd"); + + READ_TEST_DATA(self->cfd, 0, data, + "failed to read test data after resource gone"); + EXPECT_STREQ("(null)", data); +} + +TEST_F(revocable_fixture, try_access_macro) { + char data[16]; + int ret; + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, + "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); + + ret = write(self->pfd, TEST_CMD_RESOURCE_GONE, + strlen(TEST_CMD_RESOURCE_GONE)); + ASSERT_NE(-1, ret) + TH_LOG("failed to write resource gone cmd"); + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, + "failed to read test data after resource gone"); + EXPECT_STREQ("(null)", data); +} + +TEST_F(revocable_fixture, try_access_macro2) { + char data[16]; + int ret; + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET2, data, + "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); + + ret = write(self->pfd, TEST_CMD_RESOURCE_GONE, + strlen(TEST_CMD_RESOURCE_GONE)); + ASSERT_NE(-1, ret) + TH_LOG("failed to write resource gone cmd"); + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET2, data, + "failed to read test data after resource gone"); + EXPECT_STREQ("(null)", data); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/drivers/base/revocable/test-revocable.sh b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh new file mode 100755 index 000000000000..3a34be28001a --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +mod_name="revocable_test" +ksft_fail=1 +ksft_skip=4 + +if [ "$(id -u)" -ne 0 ]; then + echo "$0: Must be run as root" + exit "$ksft_skip" +fi + +if ! which insmod > /dev/null 2>&1; then + echo "$0: Need insmod" + exit "$ksft_skip" +fi + +if ! which rmmod > /dev/null 2>&1; then + echo "$0: Need rmmod" + exit "$ksft_skip" +fi + +insmod test_modules/"${mod_name}".ko + +if [ ! -d /sys/kernel/debug/revocable_test/ ]; then + mount -t debugfs none /sys/kernel/debug/ + + if [ ! -d /sys/kernel/debug/revocable_test/ ]; then + echo "$0: Error mounting debugfs" + exit "$ksft_fail" + fi +fi + +./revocable_test +ret=$? + +rmmod "${mod_name}" + +exit "${ret}" diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile new file mode 100644 index 000000000000..f29e4f909402 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile @@ -0,0 +1,10 @@ +TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST))))) +KDIR ?= /lib/modules/$(shell uname -r)/build + +obj-m += revocable_test.o + +all: + $(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR) + +clean: + $(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR) clean diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c new file mode 100644 index 000000000000..1b0692eb75f3 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 Google LLC + * + * A kernel module for testing the revocable API. + */ + +#include +#include +#include +#include + +#define TEST_CMD_RESOURCE_GONE "resource_gone" +#define TEST_MAGIC_OFFSET 0x1234 +#define TEST_MAGIC_OFFSET2 0x5678 + +static struct dentry *debugfs_dir; + +struct revocable_test_provider_priv { + struct revocable_provider *rp; + struct dentry *dentry; + char res[16]; +}; + +static int revocable_test_consumer_open(struct inode *inode, struct file *filp) +{ + struct revocable *rev; + struct revocable_provider *rp = inode->i_private; + + rev = revocable_alloc(rp); + if (!rev) + return -ENOMEM; + filp->private_data = rev; + + return 0; +} + +static int revocable_test_consumer_release(struct inode *inode, + struct file *filp) +{ + struct revocable *rev = filp->private_data; + + revocable_free(rev); + return 0; +} + +static ssize_t revocable_test_consumer_read(struct file *filp, + char __user *buf, + size_t count, loff_t *offset) +{ + char *res; + char data[16]; + size_t len; + struct revocable *rev = filp->private_data; + + switch (*offset) { + case 0: + res = revocable_try_access(rev); + snprintf(data, sizeof(data), "%s", res ?: "(null)"); + revocable_withdraw_access(rev); + break; + case TEST_MAGIC_OFFSET: + { + REVOCABLE_TRY_ACCESS_WITH(rev, res); + snprintf(data, sizeof(data), "%s", res ?: "(null)"); + } + break; + case TEST_MAGIC_OFFSET2: + REVOCABLE_TRY_ACCESS_SCOPED(rev, res) + snprintf(data, sizeof(data), "%s", res ?: "(null)"); + break; + default: + return 0; + } + + len = min_t(size_t, strlen(data), count); + if (copy_to_user(buf, data, len)) + return -EFAULT; + + *offset = len; + return len; +} + +static const struct file_operations revocable_test_consumer_fops = { + .open = revocable_test_consumer_open, + .release = revocable_test_consumer_release, + .read = revocable_test_consumer_read, + .llseek = default_llseek, +}; + +static int revocable_test_provider_open(struct inode *inode, struct file *filp) +{ + struct revocable_test_provider_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + filp->private_data = priv; + + return 0; +} + +static int revocable_test_provider_release(struct inode *inode, + struct file *filp) +{ + struct revocable_test_provider_priv *priv = filp->private_data; + + debugfs_remove(priv->dentry); + if (priv->rp) + revocable_provider_revoke(priv->rp); + kfree(priv); + + return 0; +} + +static ssize_t revocable_test_provider_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *offset) +{ + size_t copied; + char data[64]; + struct revocable_test_provider_priv *priv = filp->private_data; + + copied = strncpy_from_user(data, buf, sizeof(data)); + if (copied < 0) + return copied; + if (copied == sizeof(data)) + data[sizeof(data) - 1] = '\0'; + + /* + * Note: The test can't just close the FD for signaling the + * resource gone. Subsequent file operations on the opening + * FD of debugfs return -EIO after calling debugfs_remove(). + * See also debugfs_file_get(). + * + * Here is a side command channel for signaling the resource + * gone. + */ + if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) { + revocable_provider_revoke(priv->rp); + priv->rp = NULL; + } else { + if (priv->res[0] != '\0') + return 0; + + strscpy(priv->res, data); + + priv->rp = revocable_provider_alloc(&priv->res); + if (!priv->rp) + return -ENOMEM; + + priv->dentry = debugfs_create_file("consumer", 0400, + debugfs_dir, priv->rp, + &revocable_test_consumer_fops); + if (!priv->dentry) { + revocable_provider_revoke(priv->rp); + return -ENOMEM; + } + } + + return copied; +} + +static const struct file_operations revocable_test_provider_fops = { + .open = revocable_test_provider_open, + .release = revocable_test_provider_release, + .write = revocable_test_provider_write, +}; + +static int __init revocable_test_init(void) +{ + debugfs_dir = debugfs_create_dir("revocable_test", NULL); + if (!debugfs_dir) + return -ENOMEM; + + if (!debugfs_create_file("provider", 0200, debugfs_dir, NULL, + &revocable_test_provider_fops)) { + debugfs_remove_recursive(debugfs_dir); + return -ENOMEM; + } + + return 0; +} + +static void __exit revocable_test_exit(void) +{ + debugfs_remove_recursive(debugfs_dir); +} + +module_init(revocable_test_init); +module_exit(revocable_test_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tzung-Bi Shih "); +MODULE_DESCRIPTION("Revocable Kselftest"); From 99aa03f98ce56804f2e8936f9dabf8c4f117a661 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Fri, 16 Jan 2026 15:07:45 +0000 Subject: [PATCH 39/79] devtmpfs: make 'devtmpfs_context_ops' static The 'devtmpfs_context_ops' object is not exported outside the devtmpfs.c file nor defined anywhere for use outside. Make this static to remove the following sparse warning: drivers/base/devtmpfs.c:88:30: warning: symbol 'devtmpfs_context_ops' was not declared. Should it be static? Signed-off-by: Ben Dooks Link: https://patch.msgid.link/20260116150745.1330145-1-ben.dooks@codethink.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/base/devtmpfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 194b44075ac7..748ad3698107 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -85,7 +85,7 @@ static int devtmpfs_get_tree(struct fs_context *fc) } /* Ops are filled in during init depending on underlying shmem or ramfs type */ -struct fs_context_operations devtmpfs_context_ops = {}; +static struct fs_context_operations devtmpfs_context_ops = {}; /* Call the underlying initialization and set to our ops */ static int devtmpfs_init_fs_context(struct fs_context *fc) From 5f62af9fd20bea5e3b543cf69655c043cea298bb Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 20 Dec 2025 13:59:31 +0100 Subject: [PATCH 40/79] devtmpfs: Replace simple_strtoul with kstrtoint in mount_param Replace simple_strtoul() with the recommended kstrtoint() for parsing the 'devtmpfs.mount=' boot parameter. Unlike simple_strtoul(), which returns an unsigned long, kstrtoint() converts the string directly to int and avoids implicit casting. Check the return value of kstrtoint() and reject invalid values. This adds error handling while preserving behavior for existing values, and removes use of the deprecated simple_strtoul() helper. The current code silently sets 'mount_dev = 0' if parsing fails, instead of leaving the default value (IS_ENABLED(CONFIG_DEVTMPFS_MOUNT)) unchanged. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20251220125930.76836-2-thorsten.blum@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/base/devtmpfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 748ad3698107..b1c4ceb65026 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -56,8 +56,7 @@ static struct req { static int __init mount_param(char *str) { - mount_dev = simple_strtoul(str, NULL, 0); - return 1; + return kstrtoint(str, 0, &mount_dev) == 0; } __setup("devtmpfs.mount=", mount_param); From 7a96ccc82c106b763dd561cb87f9c7261dff4f0d Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Sat, 20 Dec 2025 04:45:34 +0100 Subject: [PATCH 41/79] driver core: attribute_container: change return type to void attribute_container_register() has always returned 0 since its introduction in commit 06ff5a987e ("Add attribute container to generic device model") in the historical Linux tree [1]. Convert the return type to void and update all callers. This removes dead code where callers checked for errors that could never occur. Link: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git [1] Signed-off-by: Daniel Gomez Link: https://patch.msgid.link/20251220-dev-attribute-container-linux-scsi-v1-1-d58fcd03bf21@samsung.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/attribute_container.c | 4 +--- drivers/base/transport_class.c | 8 ++------ drivers/scsi/scsi_transport_spi.c | 2 +- include/linux/attribute_container.h | 2 +- include/linux/transport_class.h | 6 +++--- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/base/attribute_container.c b/drivers/base/attribute_container.c index b6f941a6ab69..72adbacc6554 100644 --- a/drivers/base/attribute_container.c +++ b/drivers/base/attribute_container.c @@ -69,7 +69,7 @@ static DEFINE_MUTEX(attribute_container_mutex); * @cont: The container to register. This must be allocated by the * callee and should also be zeroed by it. */ -int +void attribute_container_register(struct attribute_container *cont) { INIT_LIST_HEAD(&cont->node); @@ -79,8 +79,6 @@ attribute_container_register(struct attribute_container *cont) mutex_lock(&attribute_container_mutex); list_add_tail(&cont->node, &attribute_container_list); mutex_unlock(&attribute_container_mutex); - - return 0; } EXPORT_SYMBOL_GPL(attribute_container_register); diff --git a/drivers/base/transport_class.c b/drivers/base/transport_class.c index 09ee2a1e35bb..4b1e8820e764 100644 --- a/drivers/base/transport_class.c +++ b/drivers/base/transport_class.c @@ -88,17 +88,13 @@ static int anon_transport_dummy_function(struct transport_container *tc, * events. Use prezero and then use DECLARE_ANON_TRANSPORT_CLASS() to * initialise the anon transport class storage. */ -int anon_transport_class_register(struct anon_transport_class *atc) +void anon_transport_class_register(struct anon_transport_class *atc) { - int error; atc->container.class = &atc->tclass.class; attribute_container_set_no_classdevs(&atc->container); - error = attribute_container_register(&atc->container); - if (error) - return error; + attribute_container_register(&atc->container); atc->tclass.setup = anon_transport_dummy_function; atc->tclass.remove = anon_transport_dummy_function; - return 0; } EXPORT_SYMBOL_GPL(anon_transport_class_register); diff --git a/drivers/scsi/scsi_transport_spi.c b/drivers/scsi/scsi_transport_spi.c index fe47850a8258..17a4a0918fc4 100644 --- a/drivers/scsi/scsi_transport_spi.c +++ b/drivers/scsi/scsi_transport_spi.c @@ -1622,7 +1622,7 @@ static __init int spi_transport_init(void) error = transport_class_register(&spi_transport_class); if (error) return error; - error = anon_transport_class_register(&spi_device_class); + anon_transport_class_register(&spi_device_class); return transport_class_register(&spi_host_class); } diff --git a/include/linux/attribute_container.h b/include/linux/attribute_container.h index b3643de9931d..fa6520e192be 100644 --- a/include/linux/attribute_container.h +++ b/include/linux/attribute_container.h @@ -36,7 +36,7 @@ attribute_container_set_no_classdevs(struct attribute_container *atc) atc->flags |= ATTRIBUTE_CONTAINER_NO_CLASSDEVS; } -int attribute_container_register(struct attribute_container *cont); +void attribute_container_register(struct attribute_container *cont); int __must_check attribute_container_unregister(struct attribute_container *cont); void attribute_container_create_device(struct device *dev, int (*fn)(struct attribute_container *, diff --git a/include/linux/transport_class.h b/include/linux/transport_class.h index 2efc271a96fa..9c2e03104461 100644 --- a/include/linux/transport_class.h +++ b/include/linux/transport_class.h @@ -87,9 +87,9 @@ transport_unregister_device(struct device *dev) transport_destroy_device(dev); } -static inline int transport_container_register(struct transport_container *tc) +static inline void transport_container_register(struct transport_container *tc) { - return attribute_container_register(&tc->ac); + attribute_container_register(&tc->ac); } static inline void transport_container_unregister(struct transport_container *tc) @@ -99,7 +99,7 @@ static inline void transport_container_unregister(struct transport_container *tc } int transport_class_register(struct transport_class *); -int anon_transport_class_register(struct anon_transport_class *); +void anon_transport_class_register(struct anon_transport_class *); void transport_class_unregister(struct transport_class *); void anon_transport_class_unregister(struct anon_transport_class *); From 61f5ec54c8a64c8b2d035215294389dd021318c7 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 15 Jan 2026 23:03:31 +0100 Subject: [PATCH 42/79] MAINTAINERS: driver-core: add driver-model documentation Documentation/driver-api/driver-model/ is missing in the driver-core MAINTAINERS entry, hence add it. Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260115220350.6806-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6aab211f1630..3cf5466c3948 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7686,6 +7686,7 @@ M: Danilo Krummrich S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git F: Documentation/core-api/kobject.rst +F: Documentation/driver-api/driver-model/ F: drivers/base/ F: fs/debugfs/ F: fs/sysfs/ From 744905705113a6ab4c38127f18cc0d71594cfaec Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Wed, 3 Dec 2025 09:04:11 +0900 Subject: [PATCH 43/79] rust: debugfs: Use kernel Atomic type in docs example Switch the read_callback_file() documentation example from core::sync::atomic::AtomicU32 to the kernel's Atomic because Rust native atomics are not allowed to use in kernel. Signed-off-by: FUJITA Tomonori Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20251203000411.30434-1-fujita.tomonori@gmail.com [ Use kernel vertical import style. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 513cc5750e63..d7b8014a6474 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -207,20 +207,25 @@ impl Dir { /// # Examples /// /// ``` - /// # use core::sync::atomic::{AtomicU32, Ordering}; - /// # use kernel::debugfs::Dir; - /// # use kernel::prelude::*; + /// # use kernel::{ + /// # debugfs::Dir, + /// # prelude::*, + /// # sync::atomic::{ + /// # Atomic, + /// # Relaxed, + /// # }, + /// # }; /// # let dir = Dir::new(c"foo"); /// let file = KBox::pin_init( /// dir.read_callback_file(c"bar", - /// AtomicU32::new(3), + /// Atomic::::new(3), /// &|val, f| { - /// let out = val.load(Ordering::Relaxed); + /// let out = val.load(Relaxed); /// writeln!(f, "{out:#010x}") /// }), /// GFP_KERNEL)?; /// // Reading "foo/bar" will show "0x00000003". - /// file.store(10, Ordering::Relaxed); + /// file.store(10, Relaxed); /// // Reading "foo/bar" will now show "0x0000000a". /// # Ok::<(), Error>(()) /// ``` From c259cd7ea3c9ad369c473ba2385d82e3432088b1 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 19 Jan 2026 20:51:15 +0100 Subject: [PATCH 44/79] revocable: fix missing module license and description Fix missing MODULE_LICENSE() and MODULE_DESCRIPTION() in the revocable Kunit test module. Reported-by: Mark Brown Closes: https://lore.kernel.org/all/aW6GNvuQVNCUcoy-@sirena.org.uk/ Fixes: cd7693419bb5 ("revocable: Add Kunit test cases") Reviewed-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260119195141.12843-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- drivers/base/revocable_test.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c index 28d46ce1ba0c..873a44082b6c 100644 --- a/drivers/base/revocable_test.c +++ b/drivers/base/revocable_test.c @@ -137,3 +137,6 @@ static struct kunit_suite revocable_test_suite = { }; kunit_test_suite(revocable_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the revocable API"); +MODULE_LICENSE("GPL"); From fdeb3ca3cca827f1a4845b570e4c5dd59fa893de Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Wed, 21 Jan 2026 04:02:04 +0000 Subject: [PATCH 45/79] revocable: Remove redundant synchronize_srcu() call When allocating a revocable provider via revocable_provider_alloc(), there is no revocable consumers (i.e., RCU readers) yet. Remove the redundant synchronize_srcu() call to save cycles. Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260121040204.2699886-1-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/revocable.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c index f6cece275aac..b068e18a847d 100644 --- a/drivers/base/revocable.c +++ b/drivers/base/revocable.c @@ -99,7 +99,6 @@ struct revocable_provider *revocable_provider_alloc(void *res) init_srcu_struct(&rp->srcu); rcu_assign_pointer(rp->res, res); - synchronize_srcu(&rp->srcu); kref_init(&rp->kref); return rp; From 61b76d07d2b46a86ea91267d36449fc78f8a1f6e Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 Jan 2026 11:29:45 +0100 Subject: [PATCH 46/79] driver core: faux: stop using static struct device faux_bus_root should not have been a static struct device, but rather a dynamically created structure so that lockdep and other testing tools do not trip over it (as well as being the right thing overall to do.) Fix this up by making it properly dynamic. Reported-by: Gui-Dong Han Closes: https://lore.kernel.org/lkml/CALbr=LYKJsj6cbrDLA07qioKhWJcRj+gW8=bq5=4ZvpEe2c4Yg@mail.gmail.com/ Reviewed-by: Danilo Krummrich Link: https://patch.msgid.link/2026012145-lapping-countless-ef81@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/base/faux.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/base/faux.c b/drivers/base/faux.c index 21dd02124231..23d725817232 100644 --- a/drivers/base/faux.c +++ b/drivers/base/faux.c @@ -29,9 +29,7 @@ struct faux_object { }; #define to_faux_object(dev) container_of_const(dev, struct faux_object, faux_dev.dev) -static struct device faux_bus_root = { - .init_name = "faux", -}; +static struct device *faux_bus_root; static int faux_match(struct device *dev, const struct device_driver *drv) { @@ -152,7 +150,7 @@ struct faux_device *faux_device_create_with_groups(const char *name, if (parent) dev->parent = parent; else - dev->parent = &faux_bus_root; + dev->parent = faux_bus_root; dev->bus = &faux_bus_type; dev_set_name(dev, "%s", name); device_set_pm_not_required(dev); @@ -236,9 +234,15 @@ int __init faux_bus_init(void) { int ret; - ret = device_register(&faux_bus_root); + faux_bus_root = kzalloc(sizeof(*faux_bus_root), GFP_KERNEL); + if (!faux_bus_root) + return -ENOMEM; + + dev_set_name(faux_bus_root, "faux"); + + ret = device_register(faux_bus_root); if (ret) { - put_device(&faux_bus_root); + put_device(faux_bus_root); return ret; } @@ -256,6 +260,6 @@ error_driver: bus_unregister(&faux_bus_type); error_bus: - device_unregister(&faux_bus_root); + device_unregister(faux_bus_root); return ret; } From ed1ac3c977dd6b119405fa36dd41f7151bd5b4de Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 21 Jan 2026 15:12:01 +0100 Subject: [PATCH 47/79] iommu/arm-smmu-qcom: do not register driver in probe() Commit 0b4eeee2876f ("iommu/arm-smmu-qcom: Register the TBU driver in qcom_smmu_impl_init") intended to also probe the TBU driver when CONFIG_ARM_SMMU_QCOM_DEBUG is disabled, but also moved the corresponding platform_driver_register() call into qcom_smmu_impl_init() which is called from arm_smmu_device_probe(). However, it neither makes sense to register drivers from probe() callbacks of other drivers, nor does the driver core allow registering drivers with a device lock already being held. The latter was revealed by commit dc23806a7c47 ("driver core: enforce device_lock for driver_match_device()") leading to a deadlock condition described in [1]. Additionally, it was noted by Robin that the current approach is potentially racy with async probe [2]. Hence, fix this by registering the qcom_smmu_tbu_driver from module_init(). Unfortunately, due to the vendoring of the driver, this requires an indirection through arm-smmu-impl.c. Reported-by: Mark Brown Closes: https://lore.kernel.org/lkml/7ae38e31-ef31-43ad-9106-7c76ea0e8596@sirena.org.uk/ Link: https://lore.kernel.org/lkml/DFU7CEPUSG9A.1KKGVW4HIPMSH@kernel.org/ [1] Link: https://lore.kernel.org/lkml/0c0d3707-9ea5-44f9-88a1-a65c62e3df8d@arm.com/ [2] Fixes: dc23806a7c47 ("driver core: enforce device_lock for driver_match_device()") Fixes: 0b4eeee2876f ("iommu/arm-smmu-qcom: Register the TBU driver in qcom_smmu_impl_init") Acked-by: Robin Murphy Tested-by: Bjorn Andersson Reviewed-by: Bjorn Andersson Acked-by: Konrad Dybcio Reviewed-by: Greg Kroah-Hartman Tested-by: Ioana Ciornei #LX2160ARDB Tested-by: Wang Jiayue Reviewed-by: Wang Jiayue Tested-by: Mark Brown Acked-by: Joerg Roedel Link: https://patch.msgid.link/20260121141215.29658-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- drivers/iommu/arm/arm-smmu/arm-smmu-impl.c | 14 +++++++++++++ drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 14 +++++++++---- drivers/iommu/arm/arm-smmu/arm-smmu.c | 24 +++++++++++++++++++++- drivers/iommu/arm/arm-smmu/arm-smmu.h | 5 +++++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c b/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c index db9b9a8e139c..4565a58bb213 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-impl.c @@ -228,3 +228,17 @@ struct arm_smmu_device *arm_smmu_impl_init(struct arm_smmu_device *smmu) return smmu; } + +int __init arm_smmu_impl_module_init(void) +{ + if (IS_ENABLED(CONFIG_ARM_SMMU_QCOM)) + return qcom_smmu_module_init(); + + return 0; +} + +void __exit arm_smmu_impl_module_exit(void) +{ + if (IS_ENABLED(CONFIG_ARM_SMMU_QCOM)) + qcom_smmu_module_exit(); +} diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index 573085349df3..22906d2c9a2d 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -774,10 +774,6 @@ struct arm_smmu_device *qcom_smmu_impl_init(struct arm_smmu_device *smmu) { const struct device_node *np = smmu->dev->of_node; const struct of_device_id *match; - static u8 tbu_registered; - - if (!tbu_registered++) - platform_driver_register(&qcom_smmu_tbu_driver); #ifdef CONFIG_ACPI if (np == NULL) { @@ -802,3 +798,13 @@ struct arm_smmu_device *qcom_smmu_impl_init(struct arm_smmu_device *smmu) return smmu; } + +int __init qcom_smmu_module_init(void) +{ + return platform_driver_register(&qcom_smmu_tbu_driver); +} + +void __exit qcom_smmu_module_exit(void) +{ + platform_driver_unregister(&qcom_smmu_tbu_driver); +} diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c index 5e690cf85ec9..1e218fbea35a 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c @@ -2365,7 +2365,29 @@ static struct platform_driver arm_smmu_driver = { .remove = arm_smmu_device_remove, .shutdown = arm_smmu_device_shutdown, }; -module_platform_driver(arm_smmu_driver); + +static int __init arm_smmu_init(void) +{ + int ret; + + ret = platform_driver_register(&arm_smmu_driver); + if (ret) + return ret; + + ret = arm_smmu_impl_module_init(); + if (ret) + platform_driver_unregister(&arm_smmu_driver); + + return ret; +} +module_init(arm_smmu_init); + +static void __exit arm_smmu_exit(void) +{ + arm_smmu_impl_module_exit(); + platform_driver_unregister(&arm_smmu_driver); +} +module_exit(arm_smmu_exit); MODULE_DESCRIPTION("IOMMU API for ARM architected SMMU implementations"); MODULE_AUTHOR("Will Deacon "); diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.h b/drivers/iommu/arm/arm-smmu/arm-smmu.h index 2dbf3243b5ad..26d2e33cd328 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.h +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.h @@ -540,6 +540,11 @@ struct arm_smmu_device *arm_smmu_impl_init(struct arm_smmu_device *smmu); struct arm_smmu_device *nvidia_smmu_impl_init(struct arm_smmu_device *smmu); struct arm_smmu_device *qcom_smmu_impl_init(struct arm_smmu_device *smmu); +int __init arm_smmu_impl_module_init(void); +void __exit arm_smmu_impl_module_exit(void); +int __init qcom_smmu_module_init(void); +void __exit qcom_smmu_module_exit(void); + void arm_smmu_write_context_bank(struct arm_smmu_device *smmu, int idx); int arm_mmu500_reset(struct arm_smmu_device *smmu); From 08a55792245a7bd395c947ff88b08b6abdd56f93 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 19 Jan 2026 17:27:57 +0100 Subject: [PATCH 48/79] driver-core: move devres_for_each_res() to base.h devres_for_each_res() is only used by .../firmware_loader/main.c, which already includes base.h. The usage of devres_for_each_res() by code outside of driver-core is questionable, hence move it to base.h. Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260119162920.77189-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- drivers/base/base.h | 4 ++++ include/linux/device/devres.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index 430cbefbc97f..60ee4d466b29 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -213,6 +213,10 @@ static inline void device_set_driver(struct device *dev, const struct device_dri WRITE_ONCE(dev->driver, (struct device_driver *)drv); } +void devres_for_each_res(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data, + void (*fn)(struct device *, void *, void *), + void *data); int devres_release_all(struct device *dev); void device_block_probing(void); void device_unblock_probing(void); diff --git a/include/linux/device/devres.h b/include/linux/device/devres.h index 9c1e3d643d69..14ab9159bdda 100644 --- a/include/linux/device/devres.h +++ b/include/linux/device/devres.h @@ -26,10 +26,6 @@ __devres_alloc_node(dr_release_t release, size_t size, gfp_t gfp, int nid, const #define devres_alloc_node(release, size, gfp, nid) \ __devres_alloc_node(release, size, gfp, nid, #release) -void devres_for_each_res(struct device *dev, dr_release_t release, - dr_match_t match, void *match_data, - void (*fn)(struct device *, void *, void *), - void *data); void devres_free(void *res); void devres_add(struct device *dev, void *res); void *devres_find(struct device *dev, dr_release_t release, dr_match_t match, void *match_data); From 428ad969c304bf94e11288dbfb4033baaa873a9f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 20 Jan 2026 13:38:52 +0100 Subject: [PATCH 49/79] MAINTAINERS: Add driver-core mailing list Add the driver-core mailing list for all entries maintained under the driver-core tree. If existent, replace linux-kernel and rust-for-linux list entries, as they're automatically picked up by scripts/get_maintainer.pl. Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260120123925.28267-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- MAINTAINERS | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3cf5466c3948..1f15d4a5b9a5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4169,6 +4169,7 @@ M: Danilo Krummrich R: Dave Ertman R: Ira Weiny R: Leon Romanovsky +L: driver-core@lists.linux.dev S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git F: Documentation/driver-api/auxiliary_bus.rst @@ -7218,7 +7219,7 @@ DEVICE I/O & IRQ [RUST] M: Danilo Krummrich M: Alice Ryhl M: Daniel Almeida -L: rust-for-linux@vger.kernel.org +L: driver-core@lists.linux.dev S: Supported W: https://rust-for-linux.com B: https://github.com/Rust-for-Linux/linux/issues @@ -7469,7 +7470,7 @@ R: Abdiel Janulgue R: Daniel Almeida R: Robin Murphy R: Andreas Hindborg -L: rust-for-linux@vger.kernel.org +L: driver-core@lists.linux.dev S: Supported W: https://rust-for-linux.com T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git @@ -7683,6 +7684,7 @@ DRIVER CORE, KOBJECTS, DEBUGFS AND SYSFS M: Greg Kroah-Hartman M: "Rafael J. Wysocki" M: Danilo Krummrich +L: driver-core@lists.linux.dev S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git F: Documentation/core-api/kobject.rst @@ -9826,7 +9828,7 @@ FIRMWARE LOADER (request_firmware) M: Luis Chamberlain M: Russ Weight M: Danilo Krummrich -L: linux-kernel@vger.kernel.org +L: driver-core@lists.linux.dev S: Maintained F: Documentation/firmware_class/ F: drivers/base/firmware_loader/ @@ -13944,6 +13946,7 @@ F: tools/testing/selftests/kvm/x86/ KERNFS M: Greg Kroah-Hartman M: Tejun Heo +L: driver-core@lists.linux.dev S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git F: fs/kernfs/ From 7d3825bfb54239c49284c5770a4d421daf5470ba Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 20 Jan 2026 13:38:53 +0100 Subject: [PATCH 50/79] MAINTAINERS: Add missing T: entry for FIRMWARE LOADER The firmware loader is maintained through the driver-core tree, hence add a corresponding T: entry. Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260120123925.28267-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 1f15d4a5b9a5..7faf04cc4278 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9830,6 +9830,7 @@ M: Russ Weight M: Danilo Krummrich 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: Documentation/firmware_class/ F: drivers/base/firmware_loader/ F: rust/kernel/firmware.rs From 7043698aee6b6f61415ebb49b7e95fdfb9373a77 Mon Sep 17 00:00:00 2001 From: Zhi Wang Date: Wed, 21 Jan 2026 22:22:07 +0200 Subject: [PATCH 51/79] rust: devres: style for imports Convert all imports in the devres to use "kernel vertical" style. Cc: Gary Guo Cc: Miguel Ojeda Reviewed-by: Gary Guo Signed-off-by: Zhi Wang Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260121202212.4438-2-zhiw@nvidia.com Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index db02f8b1788d..43089511bf76 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -254,8 +254,12 @@ impl Devres { /// # Examples /// /// ```no_run - /// # #![cfg(CONFIG_PCI)] - /// # use kernel::{device::Core, devres::Devres, pci}; + /// #![cfg(CONFIG_PCI)] + /// use kernel::{ + /// device::Core, + /// devres::Devres, + /// pci, // + /// }; /// /// fn from_core(dev: &pci::Device, devres: Devres>) -> Result { /// let bar = devres.access(dev.as_ref())?; @@ -358,7 +362,13 @@ where /// # Examples /// /// ```no_run -/// use kernel::{device::{Bound, Device}, devres}; +/// use kernel::{ +/// device::{ +/// Bound, +/// Device, // +/// }, +/// devres, // +/// }; /// /// /// Registration of e.g. a class device, IRQ, etc. /// struct Registration; From 121d87b28e1d9061d3aaa156c43a627d3cb5e620 Mon Sep 17 00:00:00 2001 From: Zhi Wang Date: Wed, 21 Jan 2026 22:22:08 +0200 Subject: [PATCH 52/79] rust: io: separate generic I/O helpers from MMIO implementation The previous Io type combined both the generic I/O access helpers and MMIO implementation details in a single struct. This coupling prevented reusing the I/O helpers for other backends, such as PCI configuration space. Establish a clean separation between the I/O interface and concrete backends by separating generic I/O helpers from MMIO implementation. Introduce a new trait hierarchy to handle different access capabilities: - IoCapable: A marker trait indicating that a backend supports I/O operations of a certain type (u8, u16, u32, or u64). - Io trait: Defines fallible (try_read8, try_write8, etc.) and infallibile (read8, write8, etc.) I/O methods with runtime bounds checking and compile-time bounds checking. - IoKnownSize trait: The marker trait for types support infallible I/O methods. Move the MMIO-specific logic into a dedicated Mmio type that implements the Io traits. Rename IoRaw to MmioRaw and update consumers to use the new types. Cc: Alexandre Courbot Cc: Alice Ryhl Cc: Bjorn Helgaas Cc: Gary Guo Cc: Danilo Krummrich Cc: John Hubbard Signed-off-by: Zhi Wang Reviewed-by: Alice Ryhl Reviewed-by: Alexandre Courbot Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260121202212.4438-3-zhiw@nvidia.com [ Add #[expect(unused)] to define_{read,write}!(). - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/tyr/regs.rs | 1 + drivers/gpu/nova-core/gsp/sequencer.rs | 5 +- drivers/gpu/nova-core/regs/macros.rs | 90 +++--- drivers/gpu/nova-core/vbios.rs | 1 + drivers/pwm/pwm_th1520.rs | 5 +- rust/kernel/devres.rs | 19 +- rust/kernel/io.rs | 408 ++++++++++++++++++++----- rust/kernel/io/mem.rs | 16 +- rust/kernel/io/poll.rs | 16 +- rust/kernel/pci/io.rs | 12 +- samples/rust/rust_driver_pci.rs | 1 + 11 files changed, 439 insertions(+), 135 deletions(-) diff --git a/drivers/gpu/drm/tyr/regs.rs b/drivers/gpu/drm/tyr/regs.rs index f46933aaa221..d3a541cb37c6 100644 --- a/drivers/gpu/drm/tyr/regs.rs +++ b/drivers/gpu/drm/tyr/regs.rs @@ -11,6 +11,7 @@ use kernel::bits::bit_u32; use kernel::device::Bound; use kernel::device::Device; use kernel::devres::Devres; +use kernel::io::Io; use kernel::prelude::*; use crate::driver::IoMem; diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs index 2d0369c49092..862cf7f27143 100644 --- a/drivers/gpu/nova-core/gsp/sequencer.rs +++ b/drivers/gpu/nova-core/gsp/sequencer.rs @@ -12,7 +12,10 @@ use core::{ use kernel::{ device, - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + Io, // + }, prelude::*, time::{ delay::fsleep, diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs index fd1a815fa57d..ed624be1f39b 100644 --- a/drivers/gpu/nova-core/regs/macros.rs +++ b/drivers/gpu/nova-core/regs/macros.rs @@ -369,16 +369,18 @@ macro_rules! register { /// Read the register from its address in `io`. #[inline(always)] - pub(crate) fn read(io: &T) -> Self where - T: ::core::ops::Deref>, + pub(crate) fn read(io: &T) -> Self where + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, { Self(io.read32($offset)) } /// Write the value contained in `self` to the register address in `io`. #[inline(always)] - pub(crate) fn write(self, io: &T) where - T: ::core::ops::Deref>, + pub(crate) fn write(self, io: &T) where + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, { io.write32(self.0, $offset) } @@ -386,11 +388,12 @@ macro_rules! register { /// Read the register from its address in `io` and run `f` on its value to obtain a new /// value to write back. #[inline(always)] - pub(crate) fn update( + pub(crate) fn update( io: &T, f: F, ) where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, F: ::core::ops::FnOnce(Self) -> Self, { let reg = f(Self::read(io)); @@ -408,12 +411,13 @@ macro_rules! register { /// Read the register from `io`, using the base address provided by `base` and adding /// the register's offset to it. #[inline(always)] - pub(crate) fn read( + pub(crate) fn read( io: &T, #[allow(unused_variables)] base: &B, ) -> Self where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, { const OFFSET: usize = $name::OFFSET; @@ -428,13 +432,14 @@ macro_rules! register { /// Write the value contained in `self` to `io`, using the base address provided by /// `base` and adding the register's offset to it. #[inline(always)] - pub(crate) fn write( + pub(crate) fn write( self, io: &T, #[allow(unused_variables)] base: &B, ) where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, { const OFFSET: usize = $name::OFFSET; @@ -449,12 +454,13 @@ macro_rules! register { /// the register's offset to it, then run `f` on its value to obtain a new value to /// write back. #[inline(always)] - pub(crate) fn update( + pub(crate) fn update( io: &T, base: &B, f: F, ) where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, F: ::core::ops::FnOnce(Self) -> Self, { @@ -474,11 +480,12 @@ macro_rules! register { /// Read the array register at index `idx` from its address in `io`. #[inline(always)] - pub(crate) fn read( + pub(crate) fn read( io: &T, idx: usize, ) -> Self where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, { build_assert!(idx < Self::SIZE); @@ -490,12 +497,13 @@ macro_rules! register { /// Write the value contained in `self` to the array register with index `idx` in `io`. #[inline(always)] - pub(crate) fn write( + pub(crate) fn write( self, io: &T, idx: usize ) where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, { build_assert!(idx < Self::SIZE); @@ -507,12 +515,13 @@ macro_rules! register { /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a /// new value to write back. #[inline(always)] - pub(crate) fn update( + pub(crate) fn update( io: &T, idx: usize, f: F, ) where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, F: ::core::ops::FnOnce(Self) -> Self, { let reg = f(Self::read(io, idx)); @@ -524,11 +533,12 @@ macro_rules! register { /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the /// access was out-of-bounds. #[inline(always)] - pub(crate) fn try_read( + pub(crate) fn try_read( io: &T, idx: usize, ) -> ::kernel::error::Result where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, { if idx < Self::SIZE { Ok(Self::read(io, idx)) @@ -542,12 +552,13 @@ macro_rules! register { /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the /// access was out-of-bounds. #[inline(always)] - pub(crate) fn try_write( + pub(crate) fn try_write( self, io: &T, idx: usize, ) -> ::kernel::error::Result where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, { if idx < Self::SIZE { Ok(self.write(io, idx)) @@ -562,12 +573,13 @@ macro_rules! register { /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the /// access was out-of-bounds. #[inline(always)] - pub(crate) fn try_update( + pub(crate) fn try_update( io: &T, idx: usize, f: F, ) -> ::kernel::error::Result where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, F: ::core::ops::FnOnce(Self) -> Self, { if idx < Self::SIZE { @@ -593,13 +605,14 @@ macro_rules! register { /// Read the array register at index `idx` from `io`, using the base address provided /// by `base` and adding the register's offset to it. #[inline(always)] - pub(crate) fn read( + pub(crate) fn read( io: &T, #[allow(unused_variables)] base: &B, idx: usize, ) -> Self where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, { build_assert!(idx < Self::SIZE); @@ -614,14 +627,15 @@ macro_rules! register { /// Write the value contained in `self` to `io`, using the base address provided by /// `base` and adding the offset of array register `idx` to it. #[inline(always)] - pub(crate) fn write( + pub(crate) fn write( self, io: &T, #[allow(unused_variables)] base: &B, idx: usize ) where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, { build_assert!(idx < Self::SIZE); @@ -636,13 +650,14 @@ macro_rules! register { /// by `base` and adding the register's offset to it, then run `f` on its value to /// obtain a new value to write back. #[inline(always)] - pub(crate) fn update( + pub(crate) fn update( io: &T, base: &B, idx: usize, f: F, ) where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, F: ::core::ops::FnOnce(Self) -> Self, { @@ -656,12 +671,13 @@ macro_rules! register { /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the /// access was out-of-bounds. #[inline(always)] - pub(crate) fn try_read( + pub(crate) fn try_read( io: &T, base: &B, idx: usize, ) -> ::kernel::error::Result where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, { if idx < Self::SIZE { @@ -677,13 +693,14 @@ macro_rules! register { /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the /// access was out-of-bounds. #[inline(always)] - pub(crate) fn try_write( + pub(crate) fn try_write( self, io: &T, base: &B, idx: usize, ) -> ::kernel::error::Result where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, { if idx < Self::SIZE { @@ -700,13 +717,14 @@ macro_rules! register { /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the /// access was out-of-bounds. #[inline(always)] - pub(crate) fn try_update( + pub(crate) fn try_update( io: &T, base: &B, idx: usize, f: F, ) -> ::kernel::error::Result where - T: ::core::ops::Deref>, + T: ::core::ops::Deref, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable, B: crate::regs::macros::RegisterBase<$base>, F: ::core::ops::FnOnce(Self) -> Self, { diff --git a/drivers/gpu/nova-core/vbios.rs b/drivers/gpu/nova-core/vbios.rs index abf423560ff4..fe33b519e4d8 100644 --- a/drivers/gpu/nova-core/vbios.rs +++ b/drivers/gpu/nova-core/vbios.rs @@ -6,6 +6,7 @@ use core::convert::TryFrom; use kernel::{ device, + io::Io, prelude::*, ptr::{ Alignable, diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs index e3b7e77356fc..616ca398b2c5 100644 --- a/drivers/pwm/pwm_th1520.rs +++ b/drivers/pwm/pwm_th1520.rs @@ -26,7 +26,10 @@ use kernel::{ clk::Clk, device::{Bound, Core, Device}, devres, - io::mem::IoMem, + io::{ + mem::IoMem, + Io, // + }, of, platform, prelude::*, pwm, time, diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 43089511bf76..cdc49677022a 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -74,14 +74,17 @@ struct Inner { /// devres::Devres, /// io::{ /// Io, -/// IoRaw, -/// PhysAddr, +/// IoKnownSize, +/// Mmio, +/// MmioRaw, +/// PhysAddr, // /// }, +/// prelude::*, /// }; /// use core::ops::Deref; /// /// // See also [`pci::Bar`] for a real example. -/// struct IoMem(IoRaw); +/// struct IoMem(MmioRaw); /// /// impl IoMem { /// /// # Safety @@ -96,7 +99,7 @@ struct Inner { /// return Err(ENOMEM); /// } /// -/// Ok(IoMem(IoRaw::new(addr as usize, SIZE)?)) +/// Ok(IoMem(MmioRaw::new(addr as usize, SIZE)?)) /// } /// } /// @@ -108,11 +111,11 @@ struct Inner { /// } /// /// impl Deref for IoMem { -/// type Target = Io; +/// type Target = Mmio; /// /// fn deref(&self) -> &Self::Target { /// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`. -/// unsafe { Io::from_raw(&self.0) } +/// unsafe { Mmio::from_raw(&self.0) } /// } /// } /// # fn no_run(dev: &Device) -> Result<(), Error> { @@ -258,6 +261,10 @@ impl Devres { /// use kernel::{ /// device::Core, /// devres::Devres, + /// io::{ + /// Io, + /// IoKnownSize, // + /// }, /// pci, // /// }; /// diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index a97eb44a9a87..53179eb839b1 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -32,16 +32,16 @@ pub type ResourceSize = bindings::resource_size_t; /// By itself, the existence of an instance of this structure does not provide any guarantees that /// the represented MMIO region does exist or is properly mapped. /// -/// Instead, the bus specific MMIO implementation must convert this raw representation into an `Io` -/// instance providing the actual memory accessors. Only by the conversion into an `Io` structure -/// any guarantees are given. -pub struct IoRaw { +/// Instead, the bus specific MMIO implementation must convert this raw representation into an +/// `Mmio` instance providing the actual memory accessors. Only by the conversion into an `Mmio` +/// structure any guarantees are given. +pub struct MmioRaw { addr: usize, maxsize: usize, } -impl IoRaw { - /// Returns a new `IoRaw` instance on success, an error otherwise. +impl MmioRaw { + /// Returns a new `MmioRaw` instance on success, an error otherwise. pub fn new(addr: usize, maxsize: usize) -> Result { if maxsize < SIZE { return Err(EINVAL); @@ -81,14 +81,16 @@ impl IoRaw { /// ffi::c_void, /// io::{ /// Io, -/// IoRaw, +/// IoKnownSize, +/// Mmio, +/// MmioRaw, /// PhysAddr, /// }, /// }; /// use core::ops::Deref; /// /// // See also `pci::Bar` for a real example. -/// struct IoMem(IoRaw); +/// struct IoMem(MmioRaw); /// /// impl IoMem { /// /// # Safety @@ -103,7 +105,7 @@ impl IoRaw { /// return Err(ENOMEM); /// } /// -/// Ok(IoMem(IoRaw::new(addr as usize, SIZE)?)) +/// Ok(IoMem(MmioRaw::new(addr as usize, SIZE)?)) /// } /// } /// @@ -115,11 +117,11 @@ impl IoRaw { /// } /// /// impl Deref for IoMem { -/// type Target = Io; +/// type Target = Mmio; /// /// fn deref(&self) -> &Self::Target { /// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`. -/// unsafe { Io::from_raw(&self.0) } +/// unsafe { Mmio::from_raw(&self.0) } /// } /// } /// @@ -133,29 +135,31 @@ impl IoRaw { /// # } /// ``` #[repr(transparent)] -pub struct Io(IoRaw); +pub struct Mmio(MmioRaw); macro_rules! define_read { - ($(#[$attr:meta])* $name:ident, $try_name:ident, $c_fn:ident -> $type_name:ty) => { + (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $c_fn:ident -> $type_name:ty) => { /// Read IO data from a given offset known at compile time. /// /// Bound checks are performed on compile time, hence if the offset is not known at compile /// time, the build will fail. $(#[$attr])* #[inline] - pub fn $name(&self, offset: usize) -> $type_name { + $vis fn $name(&self, offset: usize) -> $type_name { let addr = self.io_addr_assert::<$type_name>(offset); // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. unsafe { bindings::$c_fn(addr as *const c_void) } } + }; + (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $c_fn:ident -> $type_name:ty) => { /// Read IO data from a given offset. /// /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is /// out of bounds. $(#[$attr])* - pub fn $try_name(&self, offset: usize) -> Result<$type_name> { + $vis fn $try_name(&self, offset: usize) -> Result<$type_name> { let addr = self.io_addr::<$type_name>(offset)?; // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. @@ -163,74 +167,97 @@ macro_rules! define_read { } }; } +#[expect(unused)] +pub(crate) use define_read; macro_rules! define_write { - ($(#[$attr:meta])* $name:ident, $try_name:ident, $c_fn:ident <- $type_name:ty) => { + (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $c_fn:ident <- $type_name:ty) => { /// Write IO data from a given offset known at compile time. /// /// Bound checks are performed on compile time, hence if the offset is not known at compile /// time, the build will fail. $(#[$attr])* #[inline] - pub fn $name(&self, value: $type_name, offset: usize) { + $vis fn $name(&self, value: $type_name, offset: usize) { let addr = self.io_addr_assert::<$type_name>(offset); // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. unsafe { bindings::$c_fn(value, addr as *mut c_void) } } + }; + (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $c_fn:ident <- $type_name:ty) => { /// Write IO data from a given offset. /// /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is /// out of bounds. $(#[$attr])* - pub fn $try_name(&self, value: $type_name, offset: usize) -> Result { + $vis fn $try_name(&self, value: $type_name, offset: usize) -> Result { let addr = self.io_addr::<$type_name>(offset)?; // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. - unsafe { bindings::$c_fn(value, addr as *mut c_void) } + unsafe { bindings::$c_fn(value, addr as *mut c_void) }; Ok(()) } }; } +#[expect(unused)] +pub(crate) use define_write; -impl Io { - /// Converts an `IoRaw` into an `Io` instance, providing the accessors to the MMIO mapping. - /// - /// # Safety - /// - /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size - /// `maxsize`. - pub unsafe fn from_raw(raw: &IoRaw) -> &Self { - // SAFETY: `Io` is a transparent wrapper around `IoRaw`. - unsafe { &*core::ptr::from_ref(raw).cast() } +/// Checks whether an access of type `U` at the given `offset` +/// is valid within this region. +#[inline] +const fn offset_valid(offset: usize, size: usize) -> bool { + let type_size = core::mem::size_of::(); + if let Some(end) = offset.checked_add(type_size) { + end <= size && offset % type_size == 0 + } else { + false } +} + +/// Marker trait indicating that an I/O backend supports operations of a certain type. +/// +/// Different I/O backends can implement this trait to expose only the operations they support. +/// +/// For example, a PCI configuration space may implement `IoCapable`, `IoCapable`, +/// and `IoCapable`, but not `IoCapable`, while an MMIO region on a 64-bit +/// system might implement all four. +pub trait IoCapable {} + +/// Types implementing this trait (e.g. MMIO BARs or PCI config regions) +/// can perform I/O operations on regions of memory. +/// +/// This is an abstract representation to be implemented by arbitrary I/O +/// backends (e.g. MMIO, PCI config space, etc.). +/// +/// The [`Io`] trait provides: +/// - Base address and size information +/// - Helper methods for offset validation and address calculation +/// - Fallible (runtime checked) accessors for different data widths +/// +/// Which I/O methods are available depends on which [`IoCapable`] traits +/// are implemented for the type. +/// +/// # Examples +/// +/// For MMIO regions, all widths (u8, u16, u32, and u64 on 64-bit systems) are typically +/// supported. For PCI configuration space, u8, u16, and u32 are supported but u64 is not. +pub trait Io { + /// Minimum usable size of this region. + const MIN_SIZE: usize; /// Returns the base address of this mapping. - #[inline] - pub fn addr(&self) -> usize { - self.0.addr() - } + fn addr(&self) -> usize; /// Returns the maximum size of this mapping. - #[inline] - pub fn maxsize(&self) -> usize { - self.0.maxsize() - } - - #[inline] - const fn offset_valid(offset: usize, size: usize) -> bool { - let type_size = core::mem::size_of::(); - if let Some(end) = offset.checked_add(type_size) { - end <= size && offset % type_size == 0 - } else { - false - } - } + fn maxsize(&self) -> usize; + /// Returns the absolute I/O address for a given `offset`, + /// performing runtime bound checks. #[inline] fn io_addr(&self, offset: usize) -> Result { - if !Self::offset_valid::(offset, self.maxsize()) { + if !offset_valid::(offset, self.maxsize()) { return Err(EINVAL); } @@ -239,50 +266,285 @@ impl Io { self.addr().checked_add(offset).ok_or(EINVAL) } + /// Returns the absolute I/O address for a given `offset`, + /// performing compile-time bound checks. #[inline] fn io_addr_assert(&self, offset: usize) -> usize { - build_assert!(Self::offset_valid::(offset, SIZE)); + build_assert!(offset_valid::(offset, Self::MIN_SIZE)); self.addr() + offset } - define_read!(read8, try_read8, readb -> u8); - define_read!(read16, try_read16, readw -> u16); - define_read!(read32, try_read32, readl -> u32); + /// Fallible 8-bit read with runtime bounds check. + #[inline(always)] + fn try_read8(&self, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 8-bit read") + } + + /// Fallible 16-bit read with runtime bounds check. + #[inline(always)] + fn try_read16(&self, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 16-bit read") + } + + /// Fallible 32-bit read with runtime bounds check. + #[inline(always)] + fn try_read32(&self, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 32-bit read") + } + + /// Fallible 64-bit read with runtime bounds check. + #[inline(always)] + fn try_read64(&self, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 64-bit read") + } + + /// Fallible 8-bit write with runtime bounds check. + #[inline(always)] + fn try_write8(&self, _value: u8, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 8-bit write") + } + + /// Fallible 16-bit write with runtime bounds check. + #[inline(always)] + fn try_write16(&self, _value: u16, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 16-bit write") + } + + /// Fallible 32-bit write with runtime bounds check. + #[inline(always)] + fn try_write32(&self, _value: u32, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 32-bit write") + } + + /// Fallible 64-bit write with runtime bounds check. + #[inline(always)] + fn try_write64(&self, _value: u64, _offset: usize) -> Result + where + Self: IoCapable, + { + build_error!("Backend does not support fallible 64-bit write") + } + + /// Infallible 8-bit read with compile-time bounds check. + #[inline(always)] + fn read8(&self, _offset: usize) -> u8 + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 8-bit read") + } + + /// Infallible 16-bit read with compile-time bounds check. + #[inline(always)] + fn read16(&self, _offset: usize) -> u16 + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 16-bit read") + } + + /// Infallible 32-bit read with compile-time bounds check. + #[inline(always)] + fn read32(&self, _offset: usize) -> u32 + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 32-bit read") + } + + /// Infallible 64-bit read with compile-time bounds check. + #[inline(always)] + fn read64(&self, _offset: usize) -> u64 + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 64-bit read") + } + + /// Infallible 8-bit write with compile-time bounds check. + #[inline(always)] + fn write8(&self, _value: u8, _offset: usize) + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 8-bit write") + } + + /// Infallible 16-bit write with compile-time bounds check. + #[inline(always)] + fn write16(&self, _value: u16, _offset: usize) + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 16-bit write") + } + + /// Infallible 32-bit write with compile-time bounds check. + #[inline(always)] + fn write32(&self, _value: u32, _offset: usize) + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 32-bit write") + } + + /// Infallible 64-bit write with compile-time bounds check. + #[inline(always)] + fn write64(&self, _value: u64, _offset: usize) + where + Self: IoKnownSize + IoCapable, + { + build_error!("Backend does not support infallible 64-bit write") + } +} + +/// Marker trait for types with a known size at compile time. +/// +/// This trait is implemented by I/O backends that have a compile-time known size, +/// enabling the use of infallible I/O accessors with compile-time bounds checking. +/// +/// Types implementing this trait can use the infallible methods in [`Io`] trait +/// (e.g., `read8`, `write32`), which require `Self: IoKnownSize` bound. +pub trait IoKnownSize: Io {} + +// MMIO regions support 8, 16, and 32-bit accesses. +impl IoCapable for Mmio {} +impl IoCapable for Mmio {} +impl IoCapable for Mmio {} + +// MMIO regions on 64-bit systems also support 64-bit accesses. +#[cfg(CONFIG_64BIT)] +impl IoCapable for Mmio {} + +impl Io for Mmio { + const MIN_SIZE: usize = SIZE; + + /// Returns the base address of this mapping. + #[inline] + fn addr(&self) -> usize { + self.0.addr() + } + + /// Returns the maximum size of this mapping. + #[inline] + fn maxsize(&self) -> usize { + self.0.maxsize() + } + + define_read!(fallible, try_read8, readb -> u8); + define_read!(fallible, try_read16, readw -> u16); + define_read!(fallible, try_read32, readl -> u32); define_read!( + fallible, #[cfg(CONFIG_64BIT)] - read64, try_read64, readq -> u64 ); - define_read!(read8_relaxed, try_read8_relaxed, readb_relaxed -> u8); - define_read!(read16_relaxed, try_read16_relaxed, readw_relaxed -> u16); - define_read!(read32_relaxed, try_read32_relaxed, readl_relaxed -> u32); - define_read!( - #[cfg(CONFIG_64BIT)] - read64_relaxed, - try_read64_relaxed, - readq_relaxed -> u64 - ); - - define_write!(write8, try_write8, writeb <- u8); - define_write!(write16, try_write16, writew <- u16); - define_write!(write32, try_write32, writel <- u32); + define_write!(fallible, try_write8, writeb <- u8); + define_write!(fallible, try_write16, writew <- u16); + define_write!(fallible, try_write32, writel <- u32); define_write!( + fallible, #[cfg(CONFIG_64BIT)] - write64, try_write64, writeq <- u64 ); - define_write!(write8_relaxed, try_write8_relaxed, writeb_relaxed <- u8); - define_write!(write16_relaxed, try_write16_relaxed, writew_relaxed <- u16); - define_write!(write32_relaxed, try_write32_relaxed, writel_relaxed <- u32); - define_write!( + define_read!(infallible, read8, readb -> u8); + define_read!(infallible, read16, readw -> u16); + define_read!(infallible, read32, readl -> u32); + define_read!( + infallible, #[cfg(CONFIG_64BIT)] - write64_relaxed, - try_write64_relaxed, + read64, + readq -> u64 + ); + + define_write!(infallible, write8, writeb <- u8); + define_write!(infallible, write16, writew <- u16); + define_write!(infallible, write32, writel <- u32); + define_write!( + infallible, + #[cfg(CONFIG_64BIT)] + write64, + writeq <- u64 + ); +} + +impl IoKnownSize for Mmio {} + +impl Mmio { + /// Converts an `MmioRaw` into an `Mmio` instance, providing the accessors to the MMIO mapping. + /// + /// # Safety + /// + /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size + /// `maxsize`. + pub unsafe fn from_raw(raw: &MmioRaw) -> &Self { + // SAFETY: `Mmio` is a transparent wrapper around `MmioRaw`. + unsafe { &*core::ptr::from_ref(raw).cast() } + } + + define_read!(infallible, pub read8_relaxed, readb_relaxed -> u8); + define_read!(infallible, pub read16_relaxed, readw_relaxed -> u16); + define_read!(infallible, pub read32_relaxed, readl_relaxed -> u32); + define_read!( + infallible, + #[cfg(CONFIG_64BIT)] + pub read64_relaxed, + readq_relaxed -> u64 + ); + + define_read!(fallible, pub try_read8_relaxed, readb_relaxed -> u8); + define_read!(fallible, pub try_read16_relaxed, readw_relaxed -> u16); + define_read!(fallible, pub try_read32_relaxed, readl_relaxed -> u32); + define_read!( + fallible, + #[cfg(CONFIG_64BIT)] + pub try_read64_relaxed, + readq_relaxed -> u64 + ); + + define_write!(infallible, pub write8_relaxed, writeb_relaxed <- u8); + define_write!(infallible, pub write16_relaxed, writew_relaxed <- u16); + define_write!(infallible, pub write32_relaxed, writel_relaxed <- u32); + define_write!( + infallible, + #[cfg(CONFIG_64BIT)] + pub write64_relaxed, + writeq_relaxed <- u64 + ); + + define_write!(fallible, pub try_write8_relaxed, writeb_relaxed <- u8); + define_write!(fallible, pub try_write16_relaxed, writew_relaxed <- u16); + define_write!(fallible, pub try_write32_relaxed, writel_relaxed <- u32); + define_write!( + fallible, + #[cfg(CONFIG_64BIT)] + pub try_write64_relaxed, writeq_relaxed <- u64 ); } diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index e4878c131c6d..620022cff401 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -16,8 +16,8 @@ use crate::{ Region, Resource, // }, - Io, - IoRaw, // + Mmio, + MmioRaw, // }, prelude::*, }; @@ -212,7 +212,7 @@ impl ExclusiveIoMem { } impl Deref for ExclusiveIoMem { - type Target = Io; + type Target = Mmio; fn deref(&self) -> &Self::Target { &self.iomem @@ -226,10 +226,10 @@ impl Deref for ExclusiveIoMem { /// /// # Invariants /// -/// [`IoMem`] always holds an [`IoRaw`] instance that holds a valid pointer to the +/// [`IoMem`] always holds an [`MmioRaw`] instance that holds a valid pointer to the /// start of the I/O memory mapped region. pub struct IoMem { - io: IoRaw, + io: MmioRaw, } impl IoMem { @@ -264,7 +264,7 @@ impl IoMem { return Err(ENOMEM); } - let io = IoRaw::new(addr as usize, size)?; + let io = MmioRaw::new(addr as usize, size)?; let io = IoMem { io }; Ok(io) @@ -287,10 +287,10 @@ impl Drop for IoMem { } impl Deref for IoMem { - type Target = Io; + type Target = Mmio; fn deref(&self) -> &Self::Target { // SAFETY: Safe as by the invariant of `IoMem`. - unsafe { Io::from_raw(&self.io) } + unsafe { Mmio::from_raw(&self.io) } } } diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs index b1a2570364f4..75d1b3e8596c 100644 --- a/rust/kernel/io/poll.rs +++ b/rust/kernel/io/poll.rs @@ -45,12 +45,16 @@ use crate::{ /// # Examples /// /// ```no_run -/// use kernel::io::{Io, poll::read_poll_timeout}; +/// use kernel::io::{ +/// Io, +/// Mmio, +/// poll::read_poll_timeout, // +/// }; /// use kernel::time::Delta; /// /// const HW_READY: u16 = 0x01; /// -/// fn wait_for_hardware(io: &Io) -> Result { +/// fn wait_for_hardware(io: &Mmio) -> Result { /// read_poll_timeout( /// // The `op` closure reads the value of a specific status register. /// || io.try_read16(0x1000), @@ -128,12 +132,16 @@ where /// # Examples /// /// ```no_run -/// use kernel::io::{poll::read_poll_timeout_atomic, Io}; +/// use kernel::io::{ +/// Io, +/// Mmio, +/// poll::read_poll_timeout_atomic, // +/// }; /// use kernel::time::Delta; /// /// const HW_READY: u16 = 0x01; /// -/// fn wait_for_hardware(io: &Io) -> Result { +/// fn wait_for_hardware(io: &Mmio) -> Result { /// read_poll_timeout_atomic( /// // The `op` closure reads the value of a specific status register. /// || io.try_read16(0x1000), diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 70e3854e7d8d..e3377397666e 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -8,8 +8,8 @@ use crate::{ device, devres::Devres, io::{ - Io, - IoRaw, // + Mmio, + MmioRaw, // }, prelude::*, sync::aref::ARef, // @@ -27,7 +27,7 @@ use core::ops::Deref; /// memory mapped PCI BAR and its size. pub struct Bar { pdev: ARef, - io: IoRaw, + io: MmioRaw, num: i32, } @@ -63,7 +63,7 @@ impl Bar { return Err(ENOMEM); } - let io = match IoRaw::new(ioptr, len as usize) { + let io = match MmioRaw::new(ioptr, len as usize) { Ok(io) => io, Err(err) => { // SAFETY: @@ -117,11 +117,11 @@ impl Drop for Bar { } impl Deref for Bar { - type Target = Io; + type Target = Mmio; fn deref(&self) -> &Self::Target { // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped. - unsafe { Io::from_raw(&self.io) } + unsafe { Mmio::from_raw(&self.io) } } } diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index ef04c6401e6a..38c949efce38 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -7,6 +7,7 @@ use kernel::{ device::Core, devres::Devres, + io::Io, pci, prelude::*, sync::aref::ARef, // From 5981d03c27a14df1c03e10570eeb1ab26e9709f7 Mon Sep 17 00:00:00 2001 From: Zhi Wang Date: Wed, 21 Jan 2026 22:22:09 +0200 Subject: [PATCH 53/79] rust: io: factor out MMIO read/write macros Refactor the existing MMIO accessors to use common call macros instead of inlining the bindings calls in each `define_{read,write}!` expansion. This factoring separates the common offset/bounds checks from the low-level call pattern, making it easier to add additional I/O accessor families. No functional change intended. Cc: Alexandre Courbot Signed-off-by: Zhi Wang Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260121202212.4438-4-zhiw@nvidia.com Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 147 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 45 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 53179eb839b1..87719e58548a 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -137,8 +137,65 @@ impl MmioRaw { #[repr(transparent)] pub struct Mmio(MmioRaw); +/// Internal helper macros used to invoke C MMIO read functions. +/// +/// This macro is intended to be used by higher-level MMIO access macros (define_read) and provides +/// a unified expansion for infallible vs. fallible read semantics. It emits a direct call into the +/// corresponding C helper and performs the required cast to the Rust return type. +/// +/// # Parameters +/// +/// * `$c_fn` – The C function performing the MMIO read. +/// * `$self` – The I/O backend object. +/// * `$ty` – The type of the value to be read. +/// * `$addr` – The MMIO address to read. +/// +/// This macro does not perform any validation; all invariants must be upheld by the higher-level +/// abstraction invoking it. +macro_rules! call_mmio_read { + (infallible, $c_fn:ident, $self:ident, $type:ty, $addr:expr) => { + // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. + unsafe { bindings::$c_fn($addr as *const c_void) as $type } + }; + + (fallible, $c_fn:ident, $self:ident, $type:ty, $addr:expr) => {{ + // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. + Ok(unsafe { bindings::$c_fn($addr as *const c_void) as $type }) + }}; +} + +/// Internal helper macros used to invoke C MMIO write functions. +/// +/// This macro is intended to be used by higher-level MMIO access macros (define_write) and provides +/// a unified expansion for infallible vs. fallible write semantics. It emits a direct call into the +/// corresponding C helper and performs the required cast to the Rust return type. +/// +/// # Parameters +/// +/// * `$c_fn` – The C function performing the MMIO write. +/// * `$self` – The I/O backend object. +/// * `$ty` – The type of the written value. +/// * `$addr` – The MMIO address to write. +/// * `$value` – The value to write. +/// +/// This macro does not perform any validation; all invariants must be upheld by the higher-level +/// abstraction invoking it. +macro_rules! call_mmio_write { + (infallible, $c_fn:ident, $self:ident, $ty:ty, $addr:expr, $value:expr) => { + // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. + unsafe { bindings::$c_fn($value, $addr as *mut c_void) } + }; + + (fallible, $c_fn:ident, $self:ident, $ty:ty, $addr:expr, $value:expr) => {{ + // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. + unsafe { bindings::$c_fn($value, $addr as *mut c_void) }; + Ok(()) + }}; +} + macro_rules! define_read { - (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $c_fn:ident -> $type_name:ty) => { + (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $call_macro:ident($c_fn:ident) -> + $type_name:ty) => { /// Read IO data from a given offset known at compile time. /// /// Bound checks are performed on compile time, hence if the offset is not known at compile @@ -148,12 +205,13 @@ macro_rules! define_read { $vis fn $name(&self, offset: usize) -> $type_name { let addr = self.io_addr_assert::<$type_name>(offset); - // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. - unsafe { bindings::$c_fn(addr as *const c_void) } + // SAFETY: By the type invariant `addr` is a valid address for IO operations. + $call_macro!(infallible, $c_fn, self, $type_name, addr) } }; - (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $c_fn:ident -> $type_name:ty) => { + (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $call_macro:ident($c_fn:ident) -> + $type_name:ty) => { /// Read IO data from a given offset. /// /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is @@ -162,8 +220,8 @@ macro_rules! define_read { $vis fn $try_name(&self, offset: usize) -> Result<$type_name> { let addr = self.io_addr::<$type_name>(offset)?; - // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. - Ok(unsafe { bindings::$c_fn(addr as *const c_void) }) + // SAFETY: By the type invariant `addr` is a valid address for IO operations. + $call_macro!(fallible, $c_fn, self, $type_name, addr) } }; } @@ -171,7 +229,8 @@ macro_rules! define_read { pub(crate) use define_read; macro_rules! define_write { - (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $c_fn:ident <- $type_name:ty) => { + (infallible, $(#[$attr:meta])* $vis:vis $name:ident, $call_macro:ident($c_fn:ident) <- + $type_name:ty) => { /// Write IO data from a given offset known at compile time. /// /// Bound checks are performed on compile time, hence if the offset is not known at compile @@ -181,12 +240,12 @@ macro_rules! define_write { $vis fn $name(&self, value: $type_name, offset: usize) { let addr = self.io_addr_assert::<$type_name>(offset); - // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. - unsafe { bindings::$c_fn(value, addr as *mut c_void) } + $call_macro!(infallible, $c_fn, self, $type_name, addr, value); } }; - (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $c_fn:ident <- $type_name:ty) => { + (fallible, $(#[$attr:meta])* $vis:vis $try_name:ident, $call_macro:ident($c_fn:ident) <- + $type_name:ty) => { /// Write IO data from a given offset. /// /// Bound checks are performed on runtime, it fails if the offset (plus the type size) is @@ -195,9 +254,7 @@ macro_rules! define_write { $vis fn $try_name(&self, value: $type_name, offset: usize) -> Result { let addr = self.io_addr::<$type_name>(offset)?; - // SAFETY: By the type invariant `addr` is a valid address for MMIO operations. - unsafe { bindings::$c_fn(value, addr as *mut c_void) }; - Ok(()) + $call_macro!(fallible, $c_fn, self, $type_name, addr, value) } }; } @@ -453,44 +510,44 @@ impl Io for Mmio { self.0.maxsize() } - define_read!(fallible, try_read8, readb -> u8); - define_read!(fallible, try_read16, readw -> u16); - define_read!(fallible, try_read32, readl -> u32); + define_read!(fallible, try_read8, call_mmio_read(readb) -> u8); + define_read!(fallible, try_read16, call_mmio_read(readw) -> u16); + define_read!(fallible, try_read32, call_mmio_read(readl) -> u32); define_read!( fallible, #[cfg(CONFIG_64BIT)] try_read64, - readq -> u64 + call_mmio_read(readq) -> u64 ); - define_write!(fallible, try_write8, writeb <- u8); - define_write!(fallible, try_write16, writew <- u16); - define_write!(fallible, try_write32, writel <- u32); + define_write!(fallible, try_write8, call_mmio_write(writeb) <- u8); + define_write!(fallible, try_write16, call_mmio_write(writew) <- u16); + define_write!(fallible, try_write32, call_mmio_write(writel) <- u32); define_write!( fallible, #[cfg(CONFIG_64BIT)] try_write64, - writeq <- u64 + call_mmio_write(writeq) <- u64 ); - define_read!(infallible, read8, readb -> u8); - define_read!(infallible, read16, readw -> u16); - define_read!(infallible, read32, readl -> u32); + define_read!(infallible, read8, call_mmio_read(readb) -> u8); + define_read!(infallible, read16, call_mmio_read(readw) -> u16); + define_read!(infallible, read32, call_mmio_read(readl) -> u32); define_read!( infallible, #[cfg(CONFIG_64BIT)] read64, - readq -> u64 + call_mmio_read(readq) -> u64 ); - define_write!(infallible, write8, writeb <- u8); - define_write!(infallible, write16, writew <- u16); - define_write!(infallible, write32, writel <- u32); + define_write!(infallible, write8, call_mmio_write(writeb) <- u8); + define_write!(infallible, write16, call_mmio_write(writew) <- u16); + define_write!(infallible, write32, call_mmio_write(writel) <- u32); define_write!( infallible, #[cfg(CONFIG_64BIT)] write64, - writeq <- u64 + call_mmio_write(writeq) <- u64 ); } @@ -508,43 +565,43 @@ impl Mmio { unsafe { &*core::ptr::from_ref(raw).cast() } } - define_read!(infallible, pub read8_relaxed, readb_relaxed -> u8); - define_read!(infallible, pub read16_relaxed, readw_relaxed -> u16); - define_read!(infallible, pub read32_relaxed, readl_relaxed -> u32); + define_read!(infallible, pub read8_relaxed, call_mmio_read(readb_relaxed) -> u8); + define_read!(infallible, pub read16_relaxed, call_mmio_read(readw_relaxed) -> u16); + define_read!(infallible, pub read32_relaxed, call_mmio_read(readl_relaxed) -> u32); define_read!( infallible, #[cfg(CONFIG_64BIT)] pub read64_relaxed, - readq_relaxed -> u64 + call_mmio_read(readq_relaxed) -> u64 ); - define_read!(fallible, pub try_read8_relaxed, readb_relaxed -> u8); - define_read!(fallible, pub try_read16_relaxed, readw_relaxed -> u16); - define_read!(fallible, pub try_read32_relaxed, readl_relaxed -> u32); + define_read!(fallible, pub try_read8_relaxed, call_mmio_read(readb_relaxed) -> u8); + define_read!(fallible, pub try_read16_relaxed, call_mmio_read(readw_relaxed) -> u16); + define_read!(fallible, pub try_read32_relaxed, call_mmio_read(readl_relaxed) -> u32); define_read!( fallible, #[cfg(CONFIG_64BIT)] pub try_read64_relaxed, - readq_relaxed -> u64 + call_mmio_read(readq_relaxed) -> u64 ); - define_write!(infallible, pub write8_relaxed, writeb_relaxed <- u8); - define_write!(infallible, pub write16_relaxed, writew_relaxed <- u16); - define_write!(infallible, pub write32_relaxed, writel_relaxed <- u32); + define_write!(infallible, pub write8_relaxed, call_mmio_write(writeb_relaxed) <- u8); + define_write!(infallible, pub write16_relaxed, call_mmio_write(writew_relaxed) <- u16); + define_write!(infallible, pub write32_relaxed, call_mmio_write(writel_relaxed) <- u32); define_write!( infallible, #[cfg(CONFIG_64BIT)] pub write64_relaxed, - writeq_relaxed <- u64 + call_mmio_write(writeq_relaxed) <- u64 ); - define_write!(fallible, pub try_write8_relaxed, writeb_relaxed <- u8); - define_write!(fallible, pub try_write16_relaxed, writew_relaxed <- u16); - define_write!(fallible, pub try_write32_relaxed, writel_relaxed <- u32); + define_write!(fallible, pub try_write8_relaxed, call_mmio_write(writeb_relaxed) <- u8); + define_write!(fallible, pub try_write16_relaxed, call_mmio_write(writew_relaxed) <- u16); + define_write!(fallible, pub try_write32_relaxed, call_mmio_write(writel_relaxed) <- u32); define_write!( fallible, #[cfg(CONFIG_64BIT)] pub try_write64_relaxed, - writeq_relaxed <- u64 + call_mmio_write(writeq_relaxed) <- u64 ); } From 4dc0bacb1d3c4722cbd002c4aab6bd458d30d869 Mon Sep 17 00:00:00 2001 From: Zhi Wang Date: Wed, 21 Jan 2026 22:22:10 +0200 Subject: [PATCH 54/79] rust: pci: add config space read/write support Drivers might need to access PCI config space for querying capability structures and access the registers inside the structures. For Rust drivers need to access PCI config space, the Rust PCI abstraction needs to support it in a way that upholds Rust's safety principles. Introduce a `ConfigSpace` wrapper in Rust PCI abstraction to provide safe accessors for PCI config space. The new type implements the `Io` trait and `IoCapable` for u8, u16, and u32 to share offset validation and bound-checking logic with other I/O backends. The `ConfigSpace` type uses marker types (`Normal` and `Extended`) to represent configuration space sizes at the type level. Cc: Alexandre Courbot Cc: Danilo Krummrich Cc: Gary Guo Cc: Joel Fernandes Signed-off-by: Zhi Wang Reviewed-by: Gary Guo Link: https://lore.kernel.org/all/DFV4IJDQC2J6.1Q91JOAL6CJSG@kernel.org/ [1] Link: https://patch.msgid.link/20260121202212.4438-5-zhiw@nvidia.com [ Applied the diff from [1], considering subsequent comment; remove #[expect(unused)] from define_{read,write}!(). - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 2 - rust/kernel/pci.rs | 8 +- rust/kernel/pci/io.rs | 198 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 204 insertions(+), 4 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 87719e58548a..fcd1b156a14a 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -225,7 +225,6 @@ macro_rules! define_read { } }; } -#[expect(unused)] pub(crate) use define_read; macro_rules! define_write { @@ -258,7 +257,6 @@ macro_rules! define_write { } }; } -#[expect(unused)] pub(crate) use define_write; /// Checks whether an access of type `U` at the given `offset` diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 82e128431f08..1d1a253e5d5d 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -40,7 +40,13 @@ pub use self::id::{ ClassMask, Vendor, // }; -pub use self::io::Bar; +pub use self::io::{ + Bar, + ConfigSpaceKind, + ConfigSpaceSize, + Extended, + Normal, // +}; pub use self::irq::{ IrqType, IrqTypes, diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index e3377397666e..026e7a3b69bd 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -8,13 +8,174 @@ use crate::{ device, devres::Devres, io::{ + define_read, + define_write, + Io, + IoCapable, + IoKnownSize, Mmio, MmioRaw, // }, prelude::*, sync::aref::ARef, // }; -use core::ops::Deref; +use core::{ + marker::PhantomData, + ops::Deref, // +}; + +/// Represents the size of a PCI configuration space. +/// +/// PCI devices can have either a *normal* (legacy) configuration space of 256 bytes, +/// or an *extended* configuration space of 4096 bytes as defined in the PCI Express +/// specification. +#[repr(usize)] +#[derive(Eq, PartialEq)] +pub enum ConfigSpaceSize { + /// 256-byte legacy PCI configuration space. + Normal = 256, + + /// 4096-byte PCIe extended configuration space. + Extended = 4096, +} + +impl ConfigSpaceSize { + /// Get the raw value of this enum. + #[inline(always)] + pub const fn into_raw(self) -> usize { + // CAST: PCI configuration space size is at most 4096 bytes, so the value always fits + // within `usize` without truncation or sign change. + self as usize + } +} + +/// Marker type for normal (256-byte) PCI configuration space. +pub struct Normal; + +/// Marker type for extended (4096-byte) PCIe configuration space. +pub struct Extended; + +/// Trait for PCI configuration space size markers. +/// +/// This trait is implemented by [`Normal`] and [`Extended`] to provide +/// compile-time knowledge of the configuration space size. +pub trait ConfigSpaceKind { + /// The size of this configuration space in bytes. + const SIZE: usize; +} + +impl ConfigSpaceKind for Normal { + const SIZE: usize = 256; +} + +impl ConfigSpaceKind for Extended { + const SIZE: usize = 4096; +} + +/// The PCI configuration space of a device. +/// +/// Provides typed read and write accessors for configuration registers +/// using the standard `pci_read_config_*` and `pci_write_config_*` helpers. +/// +/// The generic parameter `S` indicates the maximum size of the configuration space. +/// Use [`Normal`] for 256-byte legacy configuration space or [`Extended`] for +/// 4096-byte PCIe extended configuration space (default). +pub struct ConfigSpace<'a, S: ConfigSpaceKind = Extended> { + pub(crate) pdev: &'a Device, + _marker: PhantomData, +} + +/// Internal helper macros used to invoke C PCI configuration space read functions. +/// +/// This macro is intended to be used by higher-level PCI configuration space access macros +/// (define_read) and provides a unified expansion for infallible vs. fallible read semantics. It +/// emits a direct call into the corresponding C helper and performs the required cast to the Rust +/// return type. +/// +/// # Parameters +/// +/// * `$c_fn` – The C function performing the PCI configuration space write. +/// * `$self` – The I/O backend object. +/// * `$ty` – The type of the value to read. +/// * `$addr` – The PCI configuration space offset to read. +/// +/// This macro does not perform any validation; all invariants must be upheld by the higher-level +/// abstraction invoking it. +macro_rules! call_config_read { + (infallible, $c_fn:ident, $self:ident, $ty:ty, $addr:expr) => {{ + let mut val: $ty = 0; + // SAFETY: By the type invariant `$self.pdev` is a valid address. + // CAST: The offset is cast to `i32` because the C functions expect a 32-bit signed offset + // parameter. PCI configuration space size is at most 4096 bytes, so the value always fits + // within `i32` without truncation or sign change. + // Return value from C function is ignored in infallible accessors. + let _ret = unsafe { bindings::$c_fn($self.pdev.as_raw(), $addr as i32, &mut val) }; + val + }}; +} + +/// Internal helper macros used to invoke C PCI configuration space write functions. +/// +/// This macro is intended to be used by higher-level PCI configuration space access macros +/// (define_write) and provides a unified expansion for infallible vs. fallible read semantics. It +/// emits a direct call into the corresponding C helper and performs the required cast to the Rust +/// return type. +/// +/// # Parameters +/// +/// * `$c_fn` – The C function performing the PCI configuration space write. +/// * `$self` – The I/O backend object. +/// * `$ty` – The type of the written value. +/// * `$addr` – The configuration space offset to write. +/// * `$value` – The value to write. +/// +/// This macro does not perform any validation; all invariants must be upheld by the higher-level +/// abstraction invoking it. +macro_rules! call_config_write { + (infallible, $c_fn:ident, $self:ident, $ty:ty, $addr:expr, $value:expr) => { + // SAFETY: By the type invariant `$self.pdev` is a valid address. + // CAST: The offset is cast to `i32` because the C functions expect a 32-bit signed offset + // parameter. PCI configuration space size is at most 4096 bytes, so the value always fits + // within `i32` without truncation or sign change. + // Return value from C function is ignored in infallible accessors. + let _ret = unsafe { bindings::$c_fn($self.pdev.as_raw(), $addr as i32, $value) }; + }; +} + +// PCI configuration space supports 8, 16, and 32-bit accesses. +impl<'a, S: ConfigSpaceKind> IoCapable for ConfigSpace<'a, S> {} +impl<'a, S: ConfigSpaceKind> IoCapable for ConfigSpace<'a, S> {} +impl<'a, S: ConfigSpaceKind> IoCapable for ConfigSpace<'a, S> {} + +impl<'a, S: ConfigSpaceKind> Io for ConfigSpace<'a, S> { + const MIN_SIZE: usize = S::SIZE; + + /// Returns the base address of the I/O region. It is always 0 for configuration space. + #[inline] + fn addr(&self) -> usize { + 0 + } + + /// Returns the maximum size of the configuration space. + #[inline] + fn maxsize(&self) -> usize { + self.pdev.cfg_size().into_raw() + } + + // PCI configuration space does not support fallible operations. + // The default implementations from the Io trait are not used. + + define_read!(infallible, read8, call_config_read(pci_read_config_byte) -> u8); + define_read!(infallible, read16, call_config_read(pci_read_config_word) -> u16); + define_read!(infallible, read32, call_config_read(pci_read_config_dword) -> u32); + + define_write!(infallible, write8, call_config_write(pci_write_config_byte) <- u8); + define_write!(infallible, write16, call_config_write(pci_write_config_word) <- u16); + define_write!(infallible, write32, call_config_write(pci_write_config_dword) <- u32); +} + +/// Marker trait indicating ConfigSpace has a known size at compile time. +impl<'a, S: ConfigSpaceKind> IoKnownSize for ConfigSpace<'a, S> {} /// A PCI BAR to perform I/O-Operations on. /// @@ -144,4 +305,39 @@ impl Device { ) -> impl PinInit, Error> + 'a { self.iomap_region_sized::<0>(bar, name) } + + /// Returns the size of configuration space. + pub fn cfg_size(&self) -> ConfigSpaceSize { + // SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`. + let size = unsafe { (*self.as_raw()).cfg_size }; + match size { + 256 => ConfigSpaceSize::Normal, + 4096 => ConfigSpaceSize::Extended, + _ => { + // PANIC: The PCI subsystem only ever reports the configuration space size as either + // `ConfigSpaceSize::Normal` or `ConfigSpaceSize::Extended`. + unreachable!(); + } + } + } + + /// Return an initialized normal (256-byte) config space object. + pub fn config_space<'a>(&'a self) -> ConfigSpace<'a, Normal> { + ConfigSpace { + pdev: self, + _marker: PhantomData, + } + } + + /// Return an initialized extended (4096-byte) config space object. + pub fn config_space_extended<'a>(&'a self) -> Result> { + if self.cfg_size() != ConfigSpaceSize::Extended { + return Err(EINVAL); + } + + Ok(ConfigSpace { + pdev: self, + _marker: PhantomData, + }) + } } From e62e48adf76cc4432c6d749a940da8649f8cb5a7 Mon Sep 17 00:00:00 2001 From: Zhi Wang Date: Wed, 21 Jan 2026 22:22:11 +0200 Subject: [PATCH 55/79] sample: rust: pci: add tests for config space routines Add tests exercising the PCI configuration space helpers. Suggested-by: Danilo Krummrich Signed-off-by: Zhi Wang Reviewed-by: Alexandre Courbot Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260121202212.4438-6-zhiw@nvidia.com Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_pci.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 38c949efce38..8eea79e858a2 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -5,6 +5,7 @@ //! To make this driver probe, QEMU must be run with `-device pci-testdev`. use kernel::{ + device::Bound, device::Core, devres::Devres, io::Io, @@ -65,6 +66,30 @@ impl SampleDriver { Ok(bar.read32(Regs::COUNT)) } + + fn config_space(pdev: &pci::Device) { + let config = pdev.config_space(); + + // TODO: use the register!() macro for defining PCI configuration space registers once it + // has been move out of nova-core. + dev_info!( + pdev.as_ref(), + "pci-testdev config space read8 rev ID: {:x}\n", + config.read8(0x8) + ); + + dev_info!( + pdev.as_ref(), + "pci-testdev config space read16 vendor ID: {:x}\n", + config.read16(0) + ); + + dev_info!( + pdev.as_ref(), + "pci-testdev config space read32 BAR 0: {:x}\n", + config.read32(0x10) + ); + } } impl pci::Driver for SampleDriver { @@ -96,6 +121,7 @@ impl pci::Driver for SampleDriver { "pci-testdev data-match count: {}\n", Self::testdev(info, bar)? ); + Self::config_space(pdev); }, pdev: pdev.into(), })) From a38cd1fea98990e20021823cea251e6cb088eeab Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 23 Jan 2026 17:58:38 +0000 Subject: [PATCH 56/79] rust: device: support `dev_printk` on all devices Currently, `dev_*` only works on the core `Device`, but not on any other bus or class device objects. This causes a pattern of `dev_info!(pdev.as_ref())` which is not ideal. This adds support of using these devices directly with `dev_*` macros, by adding `AsRef` call inside the macro. To make sure we can still use just `kernel::device::Device`, as `AsRef` implementation is added for it; this is typical for types that is designed to use with `AsRef` anyway, for example, `str` implements `AsRef` and `Path` implements `AsRef`. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260123175854.176735-1-gary@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/device.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index cb2348934676..a9cbed2e204c 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -599,6 +599,13 @@ impl DeviceContext for Core {} impl DeviceContext for CoreInternal {} impl DeviceContext for Normal {} +impl AsRef> for Device { + #[inline] + fn as_ref(&self) -> &Device { + self + } +} + /// Convert device references to bus device references. /// /// Bus devices can implement this trait to allow abstractions to provide the bus device in @@ -718,7 +725,7 @@ macro_rules! impl_device_context_into_aref { macro_rules! dev_printk { ($method:ident, $dev:expr, $($f:tt)*) => { { - ($dev).$method($crate::prelude::fmt!($($f)*)); + $crate::device::Device::$method($dev.as_ref(), $crate::prelude::fmt!($($f)*)) } } } From 600de1c008b2302b56d69ff27d12a9d8d14892ac Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 23 Jan 2026 17:58:39 +0000 Subject: [PATCH 57/79] rust: pci: remove redundant `.as_ref()` for `dev_*` print This is now handled by the macro itself. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260123175854.176735-2-gary@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 2 +- rust/kernel/pci/id.rs | 2 +- samples/rust/rust_driver_pci.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 1d1a253e5d5d..cd46ac12812c 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -348,7 +348,7 @@ impl Device { /// // Get an instance of `Vendor`. /// let vendor = pdev.vendor_id(); /// dev_info!( - /// pdev.as_ref(), + /// pdev, /// "Device: Vendor={}, Device=0x{:x}\n", /// vendor, /// pdev.device_id() diff --git a/rust/kernel/pci/id.rs b/rust/kernel/pci/id.rs index c09125946d9e..e2d9e8804347 100644 --- a/rust/kernel/pci/id.rs +++ b/rust/kernel/pci/id.rs @@ -22,7 +22,7 @@ use crate::{ /// fn probe_device(pdev: &pci::Device) -> Result { /// let pci_class = pdev.pci_class(); /// dev_info!( -/// pdev.as_ref(), +/// pdev, /// "Detected PCI class: {}\n", /// pci_class /// ); diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 8eea79e858a2..4dfb8a6a4707 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -101,7 +101,7 @@ impl pci::Driver for SampleDriver { pin_init::pin_init_scope(move || { let vendor = pdev.vendor_id(); dev_dbg!( - pdev.as_ref(), + pdev, "Probe Rust PCI driver sample (PCI ID: {}, 0x{:x}).\n", vendor, pdev.device_id() @@ -117,7 +117,7 @@ impl pci::Driver for SampleDriver { let bar = bar.access(pdev.as_ref())?; dev_info!( - pdev.as_ref(), + pdev, "pci-testdev data-match count: {}\n", Self::testdev(info, bar)? ); @@ -139,7 +139,7 @@ impl pci::Driver for SampleDriver { #[pinned_drop] impl PinnedDrop for SampleDriver { fn drop(self: Pin<&mut Self>) { - dev_dbg!(self.pdev.as_ref(), "Remove Rust PCI driver sample.\n"); + dev_dbg!(self.pdev, "Remove Rust PCI driver sample.\n"); } } From 3be458a5a7ed57cb874474aee7929daed0d5d3aa Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 23 Jan 2026 17:58:40 +0000 Subject: [PATCH 58/79] rust: samples: driver-core: remove redundant `.as_ref()` for `dev_*` print This is now handled by the macro itself. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260123175854.176735-3-gary@kernel.org Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_auxiliary.rs | 2 +- samples/rust/rust_driver_faux.rs | 2 +- samples/rust/rust_driver_platform.rs | 2 +- samples/rust/rust_soc.rs | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index f148124fe81f..c20961f16835 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -39,7 +39,7 @@ impl auxiliary::Driver for AuxiliaryDriver { fn probe(adev: &auxiliary::Device, _info: &Self::IdInfo) -> impl PinInit { dev_info!( - adev.as_ref(), + adev, "Probing auxiliary driver for auxiliary device with id={}\n", adev.id() ); diff --git a/samples/rust/rust_driver_faux.rs b/samples/rust/rust_driver_faux.rs index 5330b77ea986..99876c8e3743 100644 --- a/samples/rust/rust_driver_faux.rs +++ b/samples/rust/rust_driver_faux.rs @@ -26,7 +26,7 @@ impl Module for SampleModule { let reg = faux::Registration::new(c"rust-faux-sample-device", None)?; - dev_info!(reg.as_ref(), "Hello from faux device!\n"); + dev_info!(reg, "Hello from faux device!\n"); Ok(Self { _reg: reg }) } diff --git a/samples/rust/rust_driver_platform.rs b/samples/rust/rust_driver_platform.rs index 9537dc38c563..f2229d176fb9 100644 --- a/samples/rust/rust_driver_platform.rs +++ b/samples/rust/rust_driver_platform.rs @@ -180,7 +180,7 @@ impl SampleDriver { impl Drop for SampleDriver { fn drop(&mut self) { - dev_dbg!(self.pdev.as_ref(), "Remove Rust Platform driver sample.\n"); + dev_dbg!(self.pdev, "Remove Rust Platform driver sample.\n"); } } diff --git a/samples/rust/rust_soc.rs b/samples/rust/rust_soc.rs index 403c1137af77..8079c1c48416 100644 --- a/samples/rust/rust_soc.rs +++ b/samples/rust/rust_soc.rs @@ -44,9 +44,7 @@ impl platform::Driver for SampleSocDriver { pdev: &platform::Device, _info: Option<&Self::IdInfo>, ) -> impl PinInit { - let dev = pdev.as_ref(); - - dev_dbg!(dev, "Probe Rust SoC driver sample.\n"); + dev_dbg!(pdev, "Probe Rust SoC driver sample.\n"); let pdev = pdev.into(); pin_init_scope(move || { From f8ed7a49d40aea7769d80e05a3b7b14594cb3aef Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 23 Jan 2026 17:58:41 +0000 Subject: [PATCH 59/79] rust: samples: dma: remove redundant `.as_ref()` for `dev_*` print This is now handled by the macro itself. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260123175854.176735-4-gary@kernel.org [ Fix up code formatting. - Danilo ] Signed-off-by: Danilo Krummrich --- samples/rust/rust_dma.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index f53bce2a73e3..9c45851c876e 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -57,7 +57,7 @@ impl pci::Driver for DmaSampleDriver { fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> impl PinInit { pin_init::pin_init_scope(move || { - dev_info!(pdev.as_ref(), "Probe DMA test driver.\n"); + dev_info!(pdev, "Probe DMA test driver.\n"); let mask = DmaMask::new::<64>(); @@ -88,9 +88,7 @@ impl pci::Driver for DmaSampleDriver { #[pinned_drop] impl PinnedDrop for DmaSampleDriver { fn drop(self: Pin<&mut Self>) { - let dev = self.pdev.as_ref(); - - dev_info!(dev, "Unload DMA test driver.\n"); + dev_info!(self.pdev, "Unload DMA test driver.\n"); for (i, value) in TEST_VALUES.into_iter().enumerate() { let val0 = kernel::dma_read!(self.ca[i].h); @@ -107,7 +105,12 @@ impl PinnedDrop for DmaSampleDriver { } for (i, entry) in self.sgt.iter().enumerate() { - dev_info!(dev, "Entry[{}]: DMA address: {:#x}", i, entry.dma_address()); + dev_info!( + self.pdev, + "Entry[{}]: DMA address: {:#x}", + i, + entry.dma_address(), + ); } } } From ae3bf7612220ac8a8020a285142e0d918543a408 Mon Sep 17 00:00:00 2001 From: Ke Sun Date: Tue, 20 Jan 2026 16:38:20 +0800 Subject: [PATCH 60/79] rust: debugfs: use pin_init::zeroed() for file_operations Replace unsafe core::mem::zeroed() with pin_init::zeroed() for file_operations initialization in all debugfs file operation implementations. Suggested-by: Benno Lossin Signed-off-by: Ke Sun Link: https://github.com/Rust-for-Linux/linux/issues/1189 Link: https://patch.msgid.link/20260120083824.477339-5-sunke@kylinos.cn Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs/file_ops.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs index ad19360540ba..f15908f71c4a 100644 --- a/rust/kernel/debugfs/file_ops.rs +++ b/rust/kernel/debugfs/file_ops.rs @@ -135,8 +135,7 @@ impl ReadFile for T { llseek: Some(bindings::seq_lseek), release: Some(bindings::single_release), open: Some(writer_open::), - // SAFETY: `file_operations` supports zeroes in all fields. - ..unsafe { core::mem::zeroed() } + ..pin_init::zeroed() }; // SAFETY: `operations` is all stock `seq_file` implementations except for `writer_open`. // `open`'s only requirement beyond what is provided to all open functions is that the @@ -188,8 +187,7 @@ impl ReadWriteFile for T { write: Some(write::), llseek: Some(bindings::seq_lseek), release: Some(bindings::single_release), - // SAFETY: `file_operations` supports zeroes in all fields. - ..unsafe { core::mem::zeroed() } + ..pin_init::zeroed() }; // SAFETY: `operations` is all stock `seq_file` implementations except for `writer_open` // and `write`. @@ -244,8 +242,7 @@ impl WriteFile for T { open: Some(write_only_open), write: Some(write_only_write::), llseek: Some(bindings::noop_llseek), - // SAFETY: `file_operations` supports zeroes in all fields. - ..unsafe { core::mem::zeroed() } + ..pin_init::zeroed() }; // SAFETY: // * `write_only_open` populates the file private data with the inode private data @@ -297,8 +294,7 @@ impl BinaryReadFile for T { read: Some(blob_read::), llseek: Some(bindings::default_llseek), open: Some(bindings::simple_open), - // SAFETY: `file_operations` supports zeroes in all fields. - ..unsafe { core::mem::zeroed() } + ..pin_init::zeroed() }; // SAFETY: @@ -352,8 +348,7 @@ impl BinaryWriteFile for T { write: Some(blob_write::), llseek: Some(bindings::default_llseek), open: Some(bindings::simple_open), - // SAFETY: `file_operations` supports zeroes in all fields. - ..unsafe { core::mem::zeroed() } + ..pin_init::zeroed() }; // SAFETY: @@ -378,8 +373,7 @@ impl BinaryReadWriteFile for T { write: Some(blob_write::), llseek: Some(bindings::default_llseek), open: Some(bindings::simple_open), - // SAFETY: `file_operations` supports zeroes in all fields. - ..unsafe { core::mem::zeroed() } + ..pin_init::zeroed() }; // SAFETY: From 1cab0874875a1c37f71edf1e1e3029b1cf31d81e Mon Sep 17 00:00:00 2001 From: Atharv Dubey Date: Sat, 29 Nov 2025 18:17:06 +0530 Subject: [PATCH 61/79] rust: auxiliary: use `pin_init::zeroed()` for device ID Replace the previous `unsafe { core::mem::zeroed() }` initialization for `bindings::auxillary_device_id` with `pin_init::zeroed()`. This removes the explicit unsafe block and uses the safer pinned zero-initialization helper. Suggested-by: Benno Lossin Signed-off-by: Atharv Dubey Link: https://github.com/Rust-for-Linux/linux/issues/1189 Link: https://patch.msgid.link/20251129124706.26263-1-atharvd440@gmail.com Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index d2890a2c543a..c0d34f53a399 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -121,12 +121,7 @@ impl DeviceId { let name = name.to_bytes_with_nul(); let modname = modname.to_bytes_with_nul(); - // TODO: Replace with `bindings::auxiliary_device_id::default()` once stabilized for - // `const`. - // - // SAFETY: FFI type is valid to be zero-initialized. - let mut id: bindings::auxiliary_device_id = unsafe { core::mem::zeroed() }; - + let mut id: bindings::auxiliary_device_id = pin_init::zeroed(); let mut i = 0; while i < modname.len() { id.name[i] = modname[i]; From 7c60d964fbb100103730db9560f5594c1c4fc844 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 20 Jan 2026 18:11:09 +0000 Subject: [PATCH 62/79] gpu: tyr: remove redundant `.as_ref()` for `dev_*` print This is now handled by the macro itself. Acked-by: Alice Ryhl Reviewed-by: Daniel Almeida Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260120181152.3640314-4-gary@kernel.org Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/tyr/driver.rs | 2 +- drivers/gpu/drm/tyr/gpu.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index 0389c558c036..dc75df5e769c 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -147,7 +147,7 @@ impl platform::Driver for TyrDriver { // We need this to be dev_info!() because dev_dbg!() does not work at // all in Rust for now, and we need to see whether probe succeeded. - dev_info!(pdev.as_ref(), "Tyr initialized correctly.\n"); + dev_info!(pdev, "Tyr initialized correctly.\n"); Ok(driver) } } diff --git a/drivers/gpu/drm/tyr/gpu.rs b/drivers/gpu/drm/tyr/gpu.rs index 6c582910dd5d..5fa864a90dec 100644 --- a/drivers/gpu/drm/tyr/gpu.rs +++ b/drivers/gpu/drm/tyr/gpu.rs @@ -113,7 +113,7 @@ impl GpuInfo { }; dev_info!( - pdev.as_ref(), + pdev, "mali-{} id 0x{:x} major 0x{:x} minor 0x{:x} status 0x{:x}", model_name, self.gpu_id >> 16, @@ -123,7 +123,7 @@ impl GpuInfo { ); dev_info!( - pdev.as_ref(), + pdev, "Features: L2:{:#x} Tiler:{:#x} Mem:{:#x} MMU:{:#x} AS:{:#x}", self.l2_features, self.tiler_features, @@ -133,7 +133,7 @@ impl GpuInfo { ); dev_info!( - pdev.as_ref(), + pdev, "shader_present=0x{:016x} l2_present=0x{:016x} tiler_present=0x{:016x}", self.shader_present, self.l2_present, From c71257394bc9c59ea727803f6e55e83fe63db74e Mon Sep 17 00:00:00 2001 From: Beata Michalska Date: Wed, 28 Jan 2026 14:53:20 +0100 Subject: [PATCH 63/79] rust: dma: allow drivers to tune max segment size Make dma_set_max_seg_size() available to Rust so drivers can perform standard DMA setup steps. Signed-off-by: Beata Michalska Acked-by: Robin Murphy Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260128135320.689046-1-beata.michalska@arm.com Signed-off-by: Danilo Krummrich --- rust/helpers/dma.c | 6 ++++++ rust/kernel/dma.rs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/rust/helpers/dma.c b/rust/helpers/dma.c index e7defeecda71..20232ac64850 100644 --- a/rust/helpers/dma.c +++ b/rust/helpers/dma.c @@ -43,3 +43,9 @@ size_t rust_helper_dma_max_mapping_size(struct device *dev) { return dma_max_mapping_size(dev); } + +__rust_helper void rust_helper_dma_set_max_seg_size(struct device *dev, + unsigned int size) +{ + dma_set_max_seg_size(dev, size); +} diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index acc65b1e0f24..909d56fd5118 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -85,6 +85,23 @@ pub trait Device: AsRef> { bindings::dma_set_mask_and_coherent(self.as_ref().as_raw(), mask.value()) }) } + + /// Set the maximum size of a single DMA segment the device may request. + /// + /// This method is usually called once from `probe()` as soon as the device capabilities are + /// known. + /// + /// # Safety + /// + /// This method must not be called concurrently with any DMA allocation or mapping primitives, + /// such as [`CoherentAllocation::alloc_attrs`]. + unsafe fn dma_set_max_seg_size(&self, size: u32) { + // SAFETY: + // - By the type invariant of `device::Device`, `self.as_ref().as_raw()` is valid. + // - The safety requirement of this function guarantees that there are no concurrent calls + // to DMA allocation and mapping primitives using this parameter. + unsafe { bindings::dma_set_max_seg_size(self.as_ref().as_raw(), size) } + } } /// A DMA mask that holds a bitmask with the lowest `n` bits set. From 72bfbe50ffbf36937c77b39e19143aabdb69b080 Mon Sep 17 00:00:00 2001 From: Zijing Zhang Date: Sat, 31 Jan 2026 16:42:17 +0000 Subject: [PATCH 64/79] rust: pci: re-export ConfigSpace Re-export ConfigSpace, such that users can refer to the type as kernel::pci::ConfigSpace, rather than kernel::pci::io::ConfigSpace. Fixes: 4dc0bacb1d3c ("rust: pci: add config space read/write support") Reported-by: Gary Guo Closes: https://lore.kernel.org/rust-for-linux/DG2D5ONS18FE.TC7K3O8V8SU1@garyguo.net/ Reviewed-by: Alexandre Courbot Signed-off-by: Zijing Zhang Link: https://patch.msgid.link/995384df9224283fab185b5e06f519506fff1873.1769877524.git.zijing.zhang@ry.rs [ Slightly reworded commit message. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index f347c2f7c3a6..af74ddff6114 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -42,6 +42,7 @@ pub use self::id::{ }; pub use self::io::{ Bar, + ConfigSpace, ConfigSpaceKind, ConfigSpaceSize, Extended, From 726c262060252e13d5805f9acc382fb9f081ba07 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Fri, 30 Jan 2026 22:32:46 +0900 Subject: [PATCH 65/79] rust: io: move MIN_SIZE and io_addr_assert to IoKnownSize `MIN_SIZE` and `io_addr_assert` are only ever used for IO types which implement `IoKnownSize` and do not make sense for types that don't. It looks like they should have been there since the beginning, so move them while the code is still fresh. Also update `IoKnownSize`'s documentation since it is not just a marker trait anymore. Fixes: 121d87b28e1d ("rust: io: separate generic I/O helpers from MMIO implementation") Signed-off-by: Alexandre Courbot Link: https://patch.msgid.link/20260130-io-min-size-v1-1-65a546e3104d@nvidia.com [ Fix typo in commit message. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 36 ++++++++++++++++++------------------ rust/kernel/pci/io.rs | 7 +++---- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 056a3ec71647..c1cca7b438c3 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -301,9 +301,6 @@ pub trait IoCapable {} /// For MMIO regions, all widths (u8, u16, u32, and u64 on 64-bit systems) are typically /// supported. For PCI configuration space, u8, u16, and u32 are supported but u64 is not. pub trait Io { - /// Minimum usable size of this region. - const MIN_SIZE: usize; - /// Returns the base address of this mapping. fn addr(&self) -> usize; @@ -323,16 +320,6 @@ pub trait Io { self.addr().checked_add(offset).ok_or(EINVAL) } - /// Returns the absolute I/O address for a given `offset`, - /// performing compile-time bound checks. - // Always inline to optimize out error path of `build_assert`. - #[inline(always)] - fn io_addr_assert(&self, offset: usize) -> usize { - build_assert!(offset_valid::(offset, Self::MIN_SIZE)); - - self.addr() + offset - } - /// Fallible 8-bit read with runtime bounds check. #[inline(always)] fn try_read8(&self, _offset: usize) -> Result @@ -478,14 +465,27 @@ pub trait Io { } } -/// Marker trait for types with a known size at compile time. +/// Trait for types with a known size at compile time. /// /// This trait is implemented by I/O backends that have a compile-time known size, /// enabling the use of infallible I/O accessors with compile-time bounds checking. /// /// Types implementing this trait can use the infallible methods in [`Io`] trait /// (e.g., `read8`, `write32`), which require `Self: IoKnownSize` bound. -pub trait IoKnownSize: Io {} +pub trait IoKnownSize: Io { + /// Minimum usable size of this region. + const MIN_SIZE: usize; + + /// Returns the absolute I/O address for a given `offset`, + /// performing compile-time bound checks. + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] + fn io_addr_assert(&self, offset: usize) -> usize { + build_assert!(offset_valid::(offset, Self::MIN_SIZE)); + + self.addr() + offset + } +} // MMIO regions support 8, 16, and 32-bit accesses. impl IoCapable for Mmio {} @@ -497,8 +497,6 @@ impl IoCapable for Mmio {} impl IoCapable for Mmio {} impl Io for Mmio { - const MIN_SIZE: usize = SIZE; - /// Returns the base address of this mapping. #[inline] fn addr(&self) -> usize { @@ -552,7 +550,9 @@ impl Io for Mmio { ); } -impl IoKnownSize for Mmio {} +impl IoKnownSize for Mmio { + const MIN_SIZE: usize = SIZE; +} impl Mmio { /// Converts an `MmioRaw` into an `Mmio` instance, providing the accessors to the MMIO mapping. diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 026e7a3b69bd..6ca4cf75594c 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -148,8 +148,6 @@ impl<'a, S: ConfigSpaceKind> IoCapable for ConfigSpace<'a, S> {} impl<'a, S: ConfigSpaceKind> IoCapable for ConfigSpace<'a, S> {} impl<'a, S: ConfigSpaceKind> Io for ConfigSpace<'a, S> { - const MIN_SIZE: usize = S::SIZE; - /// Returns the base address of the I/O region. It is always 0 for configuration space. #[inline] fn addr(&self) -> usize { @@ -174,8 +172,9 @@ impl<'a, S: ConfigSpaceKind> Io for ConfigSpace<'a, S> { define_write!(infallible, write32, call_config_write(pci_write_config_dword) <- u32); } -/// Marker trait indicating ConfigSpace has a known size at compile time. -impl<'a, S: ConfigSpaceKind> IoKnownSize for ConfigSpace<'a, S> {} +impl<'a, S: ConfigSpaceKind> IoKnownSize for ConfigSpace<'a, S> { + const MIN_SIZE: usize = S::SIZE; +} /// A PCI BAR to perform I/O-Operations on. /// From 289b14592cefe95f7d0ef334873c12b96ce3824f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Sat, 31 Jan 2026 02:42:07 +0100 Subject: [PATCH 66/79] driver core: fix inverted "locked" suffix of driver_match_device() In the current implementation driver_match_device() expects the device lock to be held, while driver_match_device_locked() acquires the device lock. By convention it should be the other way around, hence swap the name of both functions. Fixes: dc23806a7c47 ("driver core: enforce device_lock for driver_match_device()") Reviewed-by: Greg Kroah-Hartman Reviewed-by: Gui-Dong Han Link: https://patch.msgid.link/20260131014211.12841-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- drivers/base/base.h | 10 +++++----- drivers/base/bus.c | 2 +- drivers/base/dd.c | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index 5bc1439d3498..8c2175820da9 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -179,19 +179,19 @@ void device_release_driver_internal(struct device *dev, const struct device_driv void driver_detach(const struct device_driver *drv); void driver_deferred_probe_del(struct device *dev); void device_set_deferred_probe_reason(const struct device *dev, struct va_format *vaf); -static inline int driver_match_device(const struct device_driver *drv, - struct device *dev) +static inline int driver_match_device_locked(const struct device_driver *drv, + struct device *dev) { device_lock_assert(dev); return drv->bus->match ? drv->bus->match(dev, drv) : 1; } -static inline int driver_match_device_locked(const struct device_driver *drv, - struct device *dev) +static inline int driver_match_device(const struct device_driver *drv, + struct device *dev) { guard(device)(dev); - return driver_match_device(drv, dev); + return driver_match_device_locked(drv, dev); } static inline void dev_sync_state(struct device *dev) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 331d750465e2..9eb7771706f0 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -263,7 +263,7 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, int err = -ENODEV; dev = bus_find_device_by_name(bus, NULL, buf); - if (dev && driver_match_device_locked(drv, dev)) { + if (dev && driver_match_device(drv, dev)) { err = device_driver_attach(drv, dev); if (!err) { /* success */ diff --git a/drivers/base/dd.c b/drivers/base/dd.c index ed3a07624816..0354f209529c 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -928,7 +928,7 @@ static int __device_attach_driver(struct device_driver *drv, void *_data) bool async_allowed; int ret; - ret = driver_match_device(drv, dev); + ret = driver_match_device_locked(drv, dev); if (ret == 0) { /* no match */ return 0; @@ -1180,7 +1180,7 @@ static int __driver_attach(struct device *dev, void *data) * is an error. */ - ret = driver_match_device_locked(drv, dev); + ret = driver_match_device(drv, dev); if (ret == 0) { /* no match */ return 0; From 4d7dc4d1a62dbb22b1178dddeeb7a22d0272df77 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 29 Jan 2026 14:37:30 +0000 Subject: [PATCH 67/79] revocable: Fix races in revocable_alloc() using RCU There are two race conditions when allocating a revocable instance: 1. After a struct revocable_provider is revoked, the caller might still hold a dangling pointer to it. A subsequent call to revocable_alloc() can trigger a use-after-free. 2. If revocable_provider_release() runs concurrently with revocable_alloc(), the memory of struct revocable_provider can be accessed during or after kfree(). To fix these: - Manage the lifetime of struct revocable_provider using RCU. Annotate pointers to it with __rcu and use kfree_rcu() for deallocation. - Update revocable_alloc() to safely acquire a reference using RCU primitives. - Update revocable_provider_revoke() to take a double pointer (`**rp`). It atomically NULLs out the caller's pointer before starting revocation. This prevents the caller from holding a dangling pointer. - Drop devm_revocable_provider_alloc(). The devm-managed model cannot support the required double-pointer semantic for safe pointer nulling. Reported-by: Johan Hovold Closes: https://lore.kernel.org/all/aXdy-b3GOJkzGqYo@hovoldconsulting.com/ Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260129143733.45618-2-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../driver-api/driver-model/revocable.rst | 3 - drivers/base/revocable.c | 97 ++++++++++--------- drivers/base/revocable_test.c | 20 ++-- include/linux/revocable.h | 8 +- .../revocable/test_modules/revocable_test.c | 19 ++-- 5 files changed, 74 insertions(+), 73 deletions(-) diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst index 22a442cc8d7f..ab7c0af5fbb6 100644 --- a/Documentation/driver-api/driver-model/revocable.rst +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -76,9 +76,6 @@ For Resource Providers .. kernel-doc:: drivers/base/revocable.c :identifiers: revocable_provider_alloc -.. kernel-doc:: drivers/base/revocable.c - :identifiers: devm_revocable_provider_alloc - .. kernel-doc:: drivers/base/revocable.c :identifiers: revocable_provider_revoke diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c index b068e18a847d..1bcd1cf54764 100644 --- a/drivers/base/revocable.c +++ b/drivers/base/revocable.c @@ -64,11 +64,13 @@ * @srcu: The SRCU to protect the resource. * @res: The pointer of resource. It can point to anything. * @kref: The refcount for this handle. + * @rcu: The RCU to protect pointer to itself. */ struct revocable_provider { struct srcu_struct srcu; void __rcu *res; struct kref kref; + struct rcu_head rcu; }; /** @@ -88,8 +90,9 @@ struct revocable { * This holds an initial refcount to the struct. * * Return: The pointer of struct revocable_provider. NULL on errors. + * It enforces the caller handles the returned pointer in RCU ways. */ -struct revocable_provider *revocable_provider_alloc(void *res) +struct revocable_provider __rcu *revocable_provider_alloc(void *res) { struct revocable_provider *rp; @@ -98,10 +101,10 @@ struct revocable_provider *revocable_provider_alloc(void *res) return NULL; init_srcu_struct(&rp->srcu); - rcu_assign_pointer(rp->res, res); + RCU_INIT_POINTER(rp->res, res); kref_init(&rp->kref); - return rp; + return (struct revocable_provider __rcu *)rp; } EXPORT_SYMBOL_GPL(revocable_provider_alloc); @@ -111,82 +114,80 @@ static void revocable_provider_release(struct kref *kref) struct revocable_provider, kref); cleanup_srcu_struct(&rp->srcu); - kfree(rp); + kfree_rcu(rp, rcu); } /** * revocable_provider_revoke() - Revoke the managed resource. - * @rp: The pointer of resource provider. + * @rp_ptr: The pointer of pointer of resource provider. * * This sets the resource `(struct revocable_provider *)->res` to NULL to * indicate the resource has gone. * * This drops the refcount to the resource provider. If it is the final * reference, revocable_provider_release() will be called to free the struct. + * + * It enforces the caller to pass a pointer of pointer of resource provider so + * that it sets \*rp_ptr to NULL to prevent from keeping a dangling pointer. */ -void revocable_provider_revoke(struct revocable_provider *rp) +void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) { + struct revocable_provider *rp; + + rp = rcu_replace_pointer(*rp_ptr, NULL, 1); + if (!rp) + return; + rcu_assign_pointer(rp->res, NULL); synchronize_srcu(&rp->srcu); kref_put(&rp->kref, revocable_provider_release); } EXPORT_SYMBOL_GPL(revocable_provider_revoke); -static void devm_revocable_provider_revoke(void *data) -{ - struct revocable_provider *rp = data; - - revocable_provider_revoke(rp); -} - -/** - * devm_revocable_provider_alloc() - Dev-managed revocable_provider_alloc(). - * @dev: The device. - * @res: The pointer of resource. - * - * It is convenient to allocate providers via this function if the @res is - * also tied to the lifetime of the @dev. revocable_provider_revoke() will - * be called automatically when the device is unbound. - * - * This holds an initial refcount to the struct. - * - * Return: The pointer of struct revocable_provider. NULL on errors. - */ -struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, - void *res) -{ - struct revocable_provider *rp; - - rp = revocable_provider_alloc(res); - if (!rp) - return NULL; - - if (devm_add_action_or_reset(dev, devm_revocable_provider_revoke, rp)) - return NULL; - - return rp; -} -EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc); - /** * revocable_alloc() - Allocate struct revocable. - * @rp: The pointer of resource provider. + * @_rp: The pointer of resource provider. * * This holds a refcount to the resource provider. * * Return: The pointer of struct revocable. NULL on errors. */ -struct revocable *revocable_alloc(struct revocable_provider *rp) +struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp) { + struct revocable_provider *rp; struct revocable *rev; - rev = kzalloc(sizeof(*rev), GFP_KERNEL); - if (!rev) + if (!_rp) return NULL; - rev->rp = rp; - kref_get(&rp->kref); + /* + * Enter a read-side critical section. + * + * This prevents kfree_rcu() from freeing the struct revocable_provider + * memory, for the duration of this scope. + */ + scoped_guard(rcu) { + rp = rcu_dereference(_rp); + if (!rp) + /* The revocable provider has been revoked. */ + return NULL; + if (!kref_get_unless_zero(&rp->kref)) + /* + * The revocable provider is releasing (i.e., + * revocable_provider_release() has been called). + */ + return NULL; + } + /* At this point, `rp` is safe to access as holding a kref of it */ + + rev = kzalloc(sizeof(*rev), GFP_KERNEL); + if (!rev) { + kref_put(&rp->kref, revocable_provider_release); + return NULL; + } + + rev->rp = rp; return rev; } EXPORT_SYMBOL_GPL(revocable_alloc); diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c index 873a44082b6c..1622aae92fd3 100644 --- a/drivers/base/revocable_test.c +++ b/drivers/base/revocable_test.c @@ -21,7 +21,7 @@ static void revocable_test_basic(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; @@ -36,12 +36,13 @@ static void revocable_test_basic(struct kunit *test) revocable_withdraw_access(rev); revocable_free(rev); - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); } static void revocable_test_revocation(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; @@ -55,7 +56,8 @@ static void revocable_test_revocation(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, res, real_res); revocable_withdraw_access(rev); - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); res = revocable_try_access(rev); KUNIT_EXPECT_PTR_EQ(test, res, NULL); @@ -66,7 +68,7 @@ static void revocable_test_revocation(struct kunit *test) static void revocable_test_try_access_macro(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; @@ -81,7 +83,8 @@ static void revocable_test_try_access_macro(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, res, real_res); } - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); { REVOCABLE_TRY_ACCESS_WITH(rev, res); @@ -93,7 +96,7 @@ static void revocable_test_try_access_macro(struct kunit *test) static void revocable_test_try_access_macro2(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; bool accessed; @@ -111,7 +114,8 @@ static void revocable_test_try_access_macro2(struct kunit *test) } KUNIT_EXPECT_TRUE(test, accessed); - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); accessed = false; REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { diff --git a/include/linux/revocable.h b/include/linux/revocable.h index 659ba01c58db..d5da3584adbe 100644 --- a/include/linux/revocable.h +++ b/include/linux/revocable.h @@ -13,12 +13,10 @@ struct device; struct revocable; struct revocable_provider; -struct revocable_provider *revocable_provider_alloc(void *res); -void revocable_provider_revoke(struct revocable_provider *rp); -struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, - void *res); +struct revocable_provider __rcu *revocable_provider_alloc(void *res); +void revocable_provider_revoke(struct revocable_provider __rcu **rp); -struct revocable *revocable_alloc(struct revocable_provider *rp); +struct revocable *revocable_alloc(struct revocable_provider __rcu *rp); void revocable_free(struct revocable *rev); void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c index 1b0692eb75f3..ae6c67e65f3d 100644 --- a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c @@ -17,7 +17,7 @@ static struct dentry *debugfs_dir; struct revocable_test_provider_priv { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct dentry *dentry; char res[16]; }; @@ -25,7 +25,7 @@ struct revocable_test_provider_priv { static int revocable_test_consumer_open(struct inode *inode, struct file *filp) { struct revocable *rev; - struct revocable_provider *rp = inode->i_private; + struct revocable_provider __rcu *rp = inode->i_private; rev = revocable_alloc(rp); if (!rev) @@ -106,8 +106,8 @@ static int revocable_test_provider_release(struct inode *inode, struct revocable_test_provider_priv *priv = filp->private_data; debugfs_remove(priv->dentry); - if (priv->rp) - revocable_provider_revoke(priv->rp); + if (unrcu_pointer(priv->rp)) + revocable_provider_revoke(&priv->rp); kfree(priv); return 0; @@ -137,8 +137,8 @@ static ssize_t revocable_test_provider_write(struct file *filp, * gone. */ if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) { - revocable_provider_revoke(priv->rp); - priv->rp = NULL; + revocable_provider_revoke(&priv->rp); + rcu_assign_pointer(priv->rp, NULL); } else { if (priv->res[0] != '\0') return 0; @@ -146,14 +146,15 @@ static ssize_t revocable_test_provider_write(struct file *filp, strscpy(priv->res, data); priv->rp = revocable_provider_alloc(&priv->res); - if (!priv->rp) + if (!unrcu_pointer(priv->rp)) return -ENOMEM; priv->dentry = debugfs_create_file("consumer", 0400, - debugfs_dir, priv->rp, + debugfs_dir, + unrcu_pointer(priv->rp), &revocable_test_consumer_fops); if (!priv->dentry) { - revocable_provider_revoke(priv->rp); + revocable_provider_revoke(&priv->rp); return -ENOMEM; } } From a243f7fb11fe67c59c5df079384b123e58edb814 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 29 Jan 2026 14:37:31 +0000 Subject: [PATCH 68/79] revocable: Add KUnit test for provider lifetime races Add a test to verify that revocable_alloc() correctly handles race conditions where the provider is being released. The test covers three scenarios: 1. Allocating from a NULL provider. 2. Allocating from a provider that has been detached (pointer is NULL). 3. Allocating from a provider that is in the process of destruction (refcount is 0), simulating a race between revocable_alloc() and revocable_provider_release(). A way to run the test: $ ./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 --raw_output=all \ revocable_test Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260129143733.45618-3-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/revocable_test.c | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c index 1622aae92fd3..7fc4d6a3dff6 100644 --- a/drivers/base/revocable_test.c +++ b/drivers/base/revocable_test.c @@ -14,9 +14,13 @@ * * - Try Access Macro: Same as "Revocation" but uses the * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). + * + * - Provider Use-after-free: Verifies revocable_alloc() correctly handles + * race conditions where the provider is being released. */ #include +#include #include static void revocable_test_basic(struct kunit *test) @@ -127,11 +131,48 @@ static void revocable_test_try_access_macro2(struct kunit *test) revocable_free(rev); } +static void revocable_test_provider_use_after_free(struct kunit *test) +{ + struct revocable_provider __rcu *rp; + struct revocable_provider *old_rp; + void *real_res = (void *)0x12345678; + struct revocable *rev; + + rp = revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev = revocable_alloc(NULL); + KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + + /* Simulate the provider has been freed. */ + old_rp = rcu_replace_pointer(rp, NULL, 1); + rev = revocable_alloc(rp); + KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + rcu_replace_pointer(rp, old_rp, 1); + + struct { + struct srcu_struct srcu; + void __rcu *res; + struct kref kref; + struct rcu_head rcu; + } *rp_internal = (void *)rp; + + /* Simulate the provider is releasing. */ + refcount_set(&rp_internal->kref.refcount, 0); + rev = revocable_alloc(rp); + KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + refcount_set(&rp_internal->kref.refcount, 1); + + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); +} + static struct kunit_case revocable_test_cases[] = { KUNIT_CASE(revocable_test_basic), KUNIT_CASE(revocable_test_revocation), KUNIT_CASE(revocable_test_try_access_macro), KUNIT_CASE(revocable_test_try_access_macro2), + KUNIT_CASE(revocable_test_provider_use_after_free), {} }; From 377563ce0653031de8d530e8b2f590d13349e29c Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 29 Jan 2026 14:37:32 +0000 Subject: [PATCH 69/79] revocable: fix SRCU index corruption by requiring caller-provided storage The struct revocable handle stores the SRCU read-side index (idx) for the duration of a resource access. If multiple threads share the same struct revocable instance, they race on writing to the idx field, corrupting the SRCU state and potentially causing unsafe unlocks. Refactor the API to replace revocable_alloc()/revocable_free() with revocable_init()/revocable_deinit(). This change requires the caller to provide the storage for struct revocable. By moving storage ownership to the caller, the API ensures that concurrent users maintain their own private idx storage, eliminating the race condition. Reported-by: Johan Hovold Closes: https://lore.kernel.org/all/20260124170535.11756-4-johan@kernel.org/ Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260129143733.45618-4-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../driver-api/driver-model/revocable.rst | 14 ++-- drivers/base/revocable.c | 41 ++++------- drivers/base/revocable_test.c | 69 +++++++++---------- include/linux/revocable.h | 54 ++++++++++----- .../revocable/test_modules/revocable_test.c | 37 ++++------ 5 files changed, 102 insertions(+), 113 deletions(-) diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst index ab7c0af5fbb6..350e7faeccdc 100644 --- a/Documentation/driver-api/driver-model/revocable.rst +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -81,14 +81,14 @@ For Resource Providers For Resource Consumers ---------------------- -.. kernel-doc:: drivers/base/revocable.c +.. kernel-doc:: include/linux/revocable.h :identifiers: revocable .. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_alloc + :identifiers: revocable_init .. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_free + :identifiers: revocable_deinit .. kernel-doc:: drivers/base/revocable.c :identifiers: revocable_try_access @@ -104,11 +104,11 @@ Example Usage .. code-block:: c - void consumer_use_resource(struct revocable *rev) + void consumer_use_resource(struct revocable_provider *rp) { struct foo_resource *res; - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); // Always check if the resource is valid. if (!res) { pr_warn("Resource is not available\n"); @@ -129,11 +129,11 @@ Example Usage .. code-block:: c - void consumer_use_resource(struct revocable *rev) + void consumer_use_resource(struct revocable_provider *rp) { struct foo_resource *res; - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { // Always check if the resource is valid. if (!res) { pr_warn("Resource is not available\n"); diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c index 1bcd1cf54764..8532ca6a371c 100644 --- a/drivers/base/revocable.c +++ b/drivers/base/revocable.c @@ -73,16 +73,6 @@ struct revocable_provider { struct rcu_head rcu; }; -/** - * struct revocable - A handle for resource consumer. - * @rp: The pointer of resource provider. - * @idx: The index for the RCU critical section. - */ -struct revocable { - struct revocable_provider *rp; - int idx; -}; - /** * revocable_provider_alloc() - Allocate struct revocable_provider. * @res: The pointer of resource. @@ -145,20 +135,20 @@ void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) EXPORT_SYMBOL_GPL(revocable_provider_revoke); /** - * revocable_alloc() - Allocate struct revocable. + * revocable_init() - Initialize struct revocable. * @_rp: The pointer of resource provider. + * @rev: The pointer of resource consumer. * * This holds a refcount to the resource provider. * - * Return: The pointer of struct revocable. NULL on errors. + * Return: 0 on success, -errno otherwise. */ -struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp) +int revocable_init(struct revocable_provider __rcu *_rp, struct revocable *rev) { struct revocable_provider *rp; - struct revocable *rev; if (!_rp) - return NULL; + return -ENODEV; /* * Enter a read-side critical section. @@ -170,43 +160,36 @@ struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp) rp = rcu_dereference(_rp); if (!rp) /* The revocable provider has been revoked. */ - return NULL; + return -ENODEV; if (!kref_get_unless_zero(&rp->kref)) /* * The revocable provider is releasing (i.e., * revocable_provider_release() has been called). */ - return NULL; + return -ENODEV; } /* At this point, `rp` is safe to access as holding a kref of it */ - rev = kzalloc(sizeof(*rev), GFP_KERNEL); - if (!rev) { - kref_put(&rp->kref, revocable_provider_release); - return NULL; - } - rev->rp = rp; - return rev; + return 0; } -EXPORT_SYMBOL_GPL(revocable_alloc); +EXPORT_SYMBOL_GPL(revocable_init); /** - * revocable_free() - Free struct revocable. + * revocable_deinit() - Deinitialize struct revocable. * @rev: The pointer of struct revocable. * * This drops a refcount to the resource provider. If it is the final * reference, revocable_provider_release() will be called to free the struct. */ -void revocable_free(struct revocable *rev) +void revocable_deinit(struct revocable *rev) { struct revocable_provider *rp = rev->rp; kref_put(&rp->kref, revocable_provider_release); - kfree(rev); } -EXPORT_SYMBOL_GPL(revocable_free); +EXPORT_SYMBOL_GPL(revocable_deinit); /** * revocable_try_access() - Try to access the resource. diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c index 7fc4d6a3dff6..a2818ec01298 100644 --- a/drivers/base/revocable_test.c +++ b/drivers/base/revocable_test.c @@ -15,7 +15,7 @@ * - Try Access Macro: Same as "Revocation" but uses the * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). * - * - Provider Use-after-free: Verifies revocable_alloc() correctly handles + * - Provider Use-after-free: Verifies revocable_init() correctly handles * race conditions where the provider is being released. */ @@ -26,20 +26,21 @@ static void revocable_test_basic(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; + struct revocable rev; void *real_res = (void *)0x12345678, *res; + int ret; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + ret = revocable_init(rp, &rev); + KUNIT_ASSERT_EQ(test, ret, 0); - res = revocable_try_access(rev); + res = revocable_try_access(&rev); KUNIT_EXPECT_PTR_EQ(test, res, real_res); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); - revocable_free(rev); + revocable_deinit(&rev); revocable_provider_revoke(&rp); KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); } @@ -47,43 +48,40 @@ static void revocable_test_basic(struct kunit *test) static void revocable_test_revocation(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; + struct revocable rev; void *real_res = (void *)0x12345678, *res; + int ret; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + ret = revocable_init(rp, &rev); + KUNIT_ASSERT_EQ(test, ret, 0); - res = revocable_try_access(rev); + res = revocable_try_access(&rev); KUNIT_EXPECT_PTR_EQ(test, res, real_res); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); revocable_provider_revoke(&rp); KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); - res = revocable_try_access(rev); + res = revocable_try_access(&rev); KUNIT_EXPECT_PTR_EQ(test, res, NULL); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); - revocable_free(rev); + revocable_deinit(&rev); } static void revocable_test_try_access_macro(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; void *real_res = (void *)0x12345678, *res; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); - { - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); KUNIT_EXPECT_PTR_EQ(test, res, real_res); } @@ -91,28 +89,22 @@ static void revocable_test_try_access_macro(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); { - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); KUNIT_EXPECT_PTR_EQ(test, res, NULL); } - - revocable_free(rev); } static void revocable_test_try_access_macro2(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; void *real_res = (void *)0x12345678, *res; bool accessed; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); - accessed = false; - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { KUNIT_EXPECT_PTR_EQ(test, res, real_res); accessed = true; } @@ -122,32 +114,31 @@ static void revocable_test_try_access_macro2(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); accessed = false; - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { KUNIT_EXPECT_PTR_EQ(test, res, NULL); accessed = true; } KUNIT_EXPECT_TRUE(test, accessed); - - revocable_free(rev); } static void revocable_test_provider_use_after_free(struct kunit *test) { struct revocable_provider __rcu *rp; struct revocable_provider *old_rp; + struct revocable rev; void *real_res = (void *)0x12345678; - struct revocable *rev; + int ret; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(NULL); - KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + ret = revocable_init(NULL, &rev); + KUNIT_EXPECT_NE(test, ret, 0); /* Simulate the provider has been freed. */ old_rp = rcu_replace_pointer(rp, NULL, 1); - rev = revocable_alloc(rp); - KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + ret = revocable_init(rp, &rev); + KUNIT_EXPECT_NE(test, ret, 0); rcu_replace_pointer(rp, old_rp, 1); struct { @@ -159,12 +150,14 @@ static void revocable_test_provider_use_after_free(struct kunit *test) /* Simulate the provider is releasing. */ refcount_set(&rp_internal->kref.refcount, 0); - rev = revocable_alloc(rp); - KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + ret = revocable_init(rp, &rev); + KUNIT_EXPECT_NE(test, ret, 0); refcount_set(&rp_internal->kref.refcount, 1); revocable_provider_revoke(&rp); KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); + ret = revocable_init(rp, &rev); + KUNIT_EXPECT_NE(test, ret, 0); } static struct kunit_case revocable_test_cases[] = { diff --git a/include/linux/revocable.h b/include/linux/revocable.h index d5da3584adbe..e3d6d2c953a3 100644 --- a/include/linux/revocable.h +++ b/include/linux/revocable.h @@ -10,27 +10,46 @@ #include struct device; -struct revocable; struct revocable_provider; +/** + * struct revocable - A handle for resource consumer. + * @rp: The pointer of resource provider. + * @idx: The index for the RCU critical section. + */ +struct revocable { + struct revocable_provider *rp; + int idx; +}; + struct revocable_provider __rcu *revocable_provider_alloc(void *res); void revocable_provider_revoke(struct revocable_provider __rcu **rp); -struct revocable *revocable_alloc(struct revocable_provider __rcu *rp); -void revocable_free(struct revocable *rev); +int revocable_init(struct revocable_provider __rcu *rp, struct revocable *rev); +void revocable_deinit(struct revocable *rev); void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); -DEFINE_FREE(access_rev, struct revocable *, if (_T) revocable_withdraw_access(_T)) +DEFINE_FREE(access_rev, struct revocable *, { + if ((_T)->idx != -1) + revocable_withdraw_access(_T); + if ((_T)->rp) + revocable_deinit(_T); +}) + +#define _REVOCABLE_TRY_ACCESS_WITH(_rp, _rev, _res) \ + struct revocable _rev = {.rp = NULL, .idx = -1}; \ + struct revocable *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ + _res = revocable_init(_rp, &_rev) ? NULL : revocable_try_access(&_rev) /** * REVOCABLE_TRY_ACCESS_WITH() - A helper for accessing revocable resource - * @_rev: The consumer's ``struct revocable *`` handle. + * @_rp: The provider's ``struct revocable_provider *`` handle. * @_res: A pointer variable that will be assigned the resource. * * The macro simplifies the access-release cycle for consumers, ensuring that - * revocable_withdraw_access() is always called, even in the case of an early - * exit. + * corresponding revocable_withdraw_access() and revocable_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(). The consumer code **must** check if @@ -41,13 +60,15 @@ DEFINE_FREE(access_rev, struct revocable *, if (_T) revocable_withdraw_access(_T * 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) \ - struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ - _res = revocable_try_access(_rev) +#define REVOCABLE_TRY_ACCESS_WITH(_rp, _res) \ + _REVOCABLE_TRY_ACCESS_WITH(_rp, __UNIQUE_ID(name), _res) -#define _REVOCABLE_TRY_ACCESS_SCOPED(_rev, _label, _res) \ - for (struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ - (_res = revocable_try_access(_rev)) || true; ({ goto _label; })) \ +#define _REVOCABLE_TRY_ACCESS_SCOPED(_rp, _rev, _label, _res) \ + for (struct revocable _rev = {.rp = NULL, .idx = -1}, \ + *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ + (_res = revocable_init(_rp, &_rev) ? NULL : \ + revocable_try_access(&_rev)) || true; \ + ({ goto _label; })) \ if (0) { \ _label: \ break; \ @@ -55,13 +76,14 @@ _label: \ /** * REVOCABLE_TRY_ACCESS_SCOPED() - A helper for accessing revocable resource - * @_rev: The consumer's ``struct revocable *`` handle. + * @_rp: The provider's ``struct revocable_provider *`` handle. * @_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_SCOPED(_rev, _res) \ - _REVOCABLE_TRY_ACCESS_SCOPED(_rev, __UNIQUE_ID(label), _res) +#define REVOCABLE_TRY_ACCESS_SCOPED(_rp, _res) \ + _REVOCABLE_TRY_ACCESS_SCOPED(_rp, __UNIQUE_ID(name), \ + __UNIQUE_ID(label), _res) #endif /* __LINUX_REVOCABLE_H */ diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c index ae6c67e65f3d..a560ceda7318 100644 --- a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c @@ -24,23 +24,7 @@ struct revocable_test_provider_priv { static int revocable_test_consumer_open(struct inode *inode, struct file *filp) { - struct revocable *rev; - struct revocable_provider __rcu *rp = inode->i_private; - - rev = revocable_alloc(rp); - if (!rev) - return -ENOMEM; - filp->private_data = rev; - - return 0; -} - -static int revocable_test_consumer_release(struct inode *inode, - struct file *filp) -{ - struct revocable *rev = filp->private_data; - - revocable_free(rev); + filp->private_data = inode->i_private; return 0; } @@ -48,25 +32,33 @@ static ssize_t revocable_test_consumer_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { + int ret; char *res; char data[16]; size_t len; - struct revocable *rev = filp->private_data; + struct revocable rev; + struct revocable_provider __rcu *rp = filp->private_data; switch (*offset) { case 0: - res = revocable_try_access(rev); + ret = revocable_init(rp, &rev); + if (ret) { + snprintf(data, sizeof(data), "%s", "(null)"); + break; + } + res = revocable_try_access(&rev); snprintf(data, sizeof(data), "%s", res ?: "(null)"); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); + revocable_deinit(&rev); break; case TEST_MAGIC_OFFSET: { - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); snprintf(data, sizeof(data), "%s", res ?: "(null)"); } break; case TEST_MAGIC_OFFSET2: - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) snprintf(data, sizeof(data), "%s", res ?: "(null)"); break; default: @@ -83,7 +75,6 @@ static ssize_t revocable_test_consumer_read(struct file *filp, static const struct file_operations revocable_test_consumer_fops = { .open = revocable_test_consumer_open, - .release = revocable_test_consumer_release, .read = revocable_test_consumer_read, .llseek = default_llseek, }; From 988357628c2c84e891f5992c4f5cfb31fb3bd27b Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 29 Jan 2026 14:37:33 +0000 Subject: [PATCH 70/79] revocable: Add KUnit test for concurrent access Add a test case to verify correct synchronization between concurrent readers and a revocation. The test setup involves: 1. Consumer 1 enters the critical section (SRCU read lock) and verifies access to the resource. 2. Provider attempts to revoke the resource. This should block until Consumer 1 releases the lock. 3. Consumer 2 attempts to enter the critical section while revocation is pending. It should see the resource as revoked (NULL). 4. Consumer 1 exits, allowing the revocation to complete. This ensures that the SRCU mechanism correctly enforces grace periods and that new readers are properly prevented from accessing the resource once revocation has begun. A way to run the test: $ ./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 --raw_output=all \ revocable_test Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260129143733.45618-5-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/revocable_test.c | 104 ++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c index a2818ec01298..27f5d7d96f4b 100644 --- a/drivers/base/revocable_test.c +++ b/drivers/base/revocable_test.c @@ -17,9 +17,14 @@ * * - Provider Use-after-free: Verifies revocable_init() correctly handles * race conditions where the provider is being released. + * + * - Concurrent Access: Verifies multiple threads can access the resource. */ #include +#include +#include +#include #include #include @@ -160,12 +165,111 @@ static void revocable_test_provider_use_after_free(struct kunit *test) KUNIT_EXPECT_NE(test, ret, 0); } +struct test_concurrent_access_context { + struct kunit *test; + struct revocable_provider __rcu *rp; + struct revocable rev; + struct completion started, enter, exit; + struct task_struct *thread; + 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_provider_revoke(&ctx->rp); + KUNIT_EXPECT_PTR_EQ(ctx->test, unrcu_pointer(ctx->rp), NULL); + + 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->rev); + KUNIT_EXPECT_PTR_EQ(ctx->test, res, ctx->expected_res); + + wait_for_completion(&ctx->exit); + revocable_withdraw_access(&ctx->rev); + + return 0; +} + +static void revocable_test_concurrent_access(struct kunit *test) +{ + struct revocable_provider __rcu *rp; + void *real_res = (void *)0x12345678; + struct test_concurrent_access_context *ctx; + int ret, i; + + rp = revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + 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); + init_completion(&ctx[i].exit); + + if (i == 0) { + ctx[i].rp = rp; + ctx[i].thread = kthread_run( + test_concurrent_access_provider, ctx + i, + "revocable_provider_%d", i); + } else { + ret = revocable_init(rp, &ctx[i].rev); + KUNIT_ASSERT_EQ(test, ret, 0); + + ctx[i].thread = kthread_run( + test_concurrent_access_consumer, ctx + i, + "revocable_consumer_%d", i); + } + KUNIT_ASSERT_FALSE(test, IS_ERR(ctx[i].thread)); + + wait_for_completion(&ctx[i].started); + } + ctx[1].expected_res = real_res; + ctx[2].expected_res = NULL; + + /* consumer1 enters read-side critical section */ + complete(&ctx[1].enter); + msleep(100); + /* provider0 revokes the resource */ + complete(&ctx[0].enter); + msleep(100); + /* consumer2 enters read-side critical section */ + complete(&ctx[2].enter); + msleep(100); + + /* consumer{1,2} exit read-side critical section */ + complete(&ctx[1].exit); + complete(&ctx[2].exit); + + for (i = 0; i < 3; ++i) + kthread_stop(ctx[i].thread); + for (i = 1; i < 3; ++i) + revocable_deinit(&ctx[i].rev); +} + static struct kunit_case revocable_test_cases[] = { KUNIT_CASE(revocable_test_basic), KUNIT_CASE(revocable_test_revocation), KUNIT_CASE(revocable_test_try_access_macro), KUNIT_CASE(revocable_test_try_access_macro2), KUNIT_CASE(revocable_test_provider_use_after_free), + KUNIT_CASE(revocable_test_concurrent_access), {} }; From c5048ddee936ca5ce0aeb79172ce512130779d31 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 3 Feb 2026 13:30:37 +0100 Subject: [PATCH 71/79] driver core: disable revocable code from build The revocable code is still under active discussion, and there is no in-kernel users of it. So disable it from the build for now so that no one suffers from it being present in the tree, yet leave it in the source tree so that others can easily test it by reverting this commit and building off of it for future releases. Acked-by: Danilo Krummrich Link: https://patch.msgid.link/2026020307-rimmed-dreamy-5a67@gregkh Reviewed-by: Tzung-Bi Shih Signed-off-by: Greg Kroah-Hartman --- drivers/base/Kconfig | 2 +- drivers/base/Makefile | 2 +- tools/testing/selftests/Makefile | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 8f7d7b9d81ac..9f318b98144d 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -254,7 +254,7 @@ endmenu # Kunit test cases config REVOCABLE_KUNIT_TEST tristate "Kunit tests for revocable" if !KUNIT_ALL_TESTS - depends on KUNIT + depends on KUNIT && BROKEN default KUNIT_ALL_TESTS help Kunit tests for the revocable API. diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 4185aaa9bbb9..4c6607616a73 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 revocable.o + swnode.o faux.o obj-$(CONFIG_AUXILIARY_BUS) += auxiliary.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-y += power/ diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 11b6515ce3d0..56e44a98d6a5 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -17,7 +17,6 @@ TARGETS += damon TARGETS += devices/error_logs TARGETS += devices/probe TARGETS += dmabuf-heaps -TARGETS += drivers/base/revocable TARGETS += drivers/dma-buf TARGETS += drivers/ntsync TARGETS += drivers/s390x/uvdevice From d79ff2d612022548b09c3427c69263bedf7f16df Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 5 Feb 2026 14:09:42 +0100 Subject: [PATCH 72/79] sysfs: remove exports of sysfs_*change_owner() Both sysfs_change_owner() and sysfs_file_change_owner() are exported to modules, but there are no in-kernel module users, so remove the exports so that crazy out-of-tree drivers don't get the impression that it is safe to call these functions at all. Reviewed-by: Rafael J. Wysocki (Intel) Reviewed-by: Danilo Krummrich Reported-by: Lee Jones Reviewed-by: Lee Jones Link: https://patch.msgid.link/2026020541-energize-graduate-981a@gregkh Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 3825e780cc58..a8176c875f55 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -689,7 +689,6 @@ int sysfs_file_change_owner(struct kobject *kobj, const char *name, kuid_t kuid, return error; } -EXPORT_SYMBOL_GPL(sysfs_file_change_owner); /** * sysfs_change_owner - change owner of the given object. @@ -736,7 +735,6 @@ int sysfs_change_owner(struct kobject *kobj, kuid_t kuid, kgid_t kgid) return 0; } -EXPORT_SYMBOL_GPL(sysfs_change_owner); /** * sysfs_emit - scnprintf equivalent, aware of PAGE_SIZE buffer. From f55ae0bfa00e446ea751d09f468daeafc303e03f Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 5 Feb 2026 14:09:43 +0100 Subject: [PATCH 73/79] driver core: remove device_change_owner() export The function, device_change_owner() is exported for modules to use, but there are no in-kernel users of it, so remove the export to prevent out-of-tree code from thinking this is a safe function to call. Reviewed-by: Danilo Krummrich Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/2026020543-molar-childcare-af20@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/base/core.c b/drivers/base/core.c index 40de2f51a1b1..f599a1384eec 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -4781,7 +4781,6 @@ out: put_device(dev); return error; } -EXPORT_SYMBOL_GPL(device_change_owner); /** * device_shutdown - call ->shutdown() on each device to shutdown. From 379a5aad4e8ce7bd0b1600c03ae0c9a28f66a183 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 4 Feb 2026 15:28:47 +0100 Subject: [PATCH 74/79] Revert "selftests: revocable: Add kselftest cases" This reverts commit 9d4502fef00fa7a798d3c0806d4da4466a7ffc6f. The new revocable functionality is fundamentally broken and at a minimum needs to be redesigned. Drop the revocable selftests to allow the implementation to be reverted. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260204142849.22055-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 - .../selftests/drivers/base/revocable/Makefile | 7 - .../drivers/base/revocable/revocable_test.c | 136 ------------- .../drivers/base/revocable/test-revocable.sh | 39 ---- .../base/revocable/test_modules/Makefile | 10 - .../revocable/test_modules/revocable_test.c | 187 ------------------ 6 files changed, 380 deletions(-) delete mode 100644 tools/testing/selftests/drivers/base/revocable/Makefile delete mode 100644 tools/testing/selftests/drivers/base/revocable/revocable_test.c delete mode 100755 tools/testing/selftests/drivers/base/revocable/test-revocable.sh delete mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/Makefile delete mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index b277baee5eb6..7759e9103070 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22392,7 +22392,6 @@ S: Maintained F: drivers/base/revocable.c F: drivers/base/revocable_test.c F: include/linux/revocable.h -F: tools/testing/selftests/drivers/base/revocable/ RFKILL M: Johannes Berg diff --git a/tools/testing/selftests/drivers/base/revocable/Makefile b/tools/testing/selftests/drivers/base/revocable/Makefile deleted file mode 100644 index afa5ca0fa452..000000000000 --- a/tools/testing/selftests/drivers/base/revocable/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 - -TEST_GEN_MODS_DIR := test_modules -TEST_GEN_PROGS_EXTENDED := revocable_test -TEST_PROGS := test-revocable.sh - -include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/base/revocable/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/revocable_test.c deleted file mode 100644 index f024164e9273..000000000000 --- a/tools/testing/selftests/drivers/base/revocable/revocable_test.c +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright 2026 Google LLC - * - * A selftest for the revocable API. - * - * The test cases cover the following scenarios: - * - * - Basic: Verifies that a consumer can successfully access the resource - * provided via the provider. - * - * - 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 - * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). - */ - -#include -#include - -#include "../../../kselftest_harness.h" - -#define DEBUGFS_PATH "/sys/kernel/debug/revocable_test" -#define TEST_CMD_RESOURCE_GONE "resource_gone" -#define TEST_DATA "12345678" -#define TEST_MAGIC_OFFSET 0x1234 -#define TEST_MAGIC_OFFSET2 0x5678 - -FIXTURE(revocable_fixture) { - int pfd; - int cfd; -}; - -FIXTURE_SETUP(revocable_fixture) { - int ret; - - self->pfd = open(DEBUGFS_PATH "/provider", O_WRONLY); - ASSERT_NE(-1, self->pfd) - TH_LOG("failed to open provider fd"); - - ret = write(self->pfd, TEST_DATA, strlen(TEST_DATA)); - ASSERT_NE(-1, ret) { - close(self->pfd); - TH_LOG("failed to write test data"); - } - - self->cfd = open(DEBUGFS_PATH "/consumer", O_RDONLY); - ASSERT_NE(-1, self->cfd) - TH_LOG("failed to open consumer fd"); -} - -FIXTURE_TEARDOWN(revocable_fixture) { - close(self->cfd); - close(self->pfd); -} - -/* - * ASSERT_* is only available in TEST or TEST_F block. Use - * macro for the helper. - */ -#define READ_TEST_DATA(_fd, _offset, _data, _msg) \ - do { \ - int ret; \ - \ - ret = lseek(_fd, _offset, SEEK_SET); \ - ASSERT_NE(-1, ret) \ - TH_LOG("failed to lseek"); \ - \ - ret = read(_fd, _data, sizeof(_data) - 1); \ - ASSERT_NE(-1, ret) \ - TH_LOG(_msg); \ - data[ret] = '\0'; \ - } while (0) - -TEST_F(revocable_fixture, basic) { - char data[16]; - - READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); - EXPECT_STREQ(TEST_DATA, data); -} - -TEST_F(revocable_fixture, revocation) { - char data[16]; - int ret; - - READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); - EXPECT_STREQ(TEST_DATA, data); - - ret = write(self->pfd, TEST_CMD_RESOURCE_GONE, - strlen(TEST_CMD_RESOURCE_GONE)); - ASSERT_NE(-1, ret) - TH_LOG("failed to write resource gone cmd"); - - READ_TEST_DATA(self->cfd, 0, data, - "failed to read test data after resource gone"); - EXPECT_STREQ("(null)", data); -} - -TEST_F(revocable_fixture, try_access_macro) { - char data[16]; - int ret; - - READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, - "failed to read test data"); - EXPECT_STREQ(TEST_DATA, data); - - ret = write(self->pfd, TEST_CMD_RESOURCE_GONE, - strlen(TEST_CMD_RESOURCE_GONE)); - ASSERT_NE(-1, ret) - TH_LOG("failed to write resource gone cmd"); - - READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, - "failed to read test data after resource gone"); - EXPECT_STREQ("(null)", data); -} - -TEST_F(revocable_fixture, try_access_macro2) { - char data[16]; - int ret; - - READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET2, data, - "failed to read test data"); - EXPECT_STREQ(TEST_DATA, data); - - ret = write(self->pfd, TEST_CMD_RESOURCE_GONE, - strlen(TEST_CMD_RESOURCE_GONE)); - ASSERT_NE(-1, ret) - TH_LOG("failed to write resource gone cmd"); - - READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET2, data, - "failed to read test data after resource gone"); - EXPECT_STREQ("(null)", data); -} - -TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/drivers/base/revocable/test-revocable.sh b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh deleted file mode 100755 index 3a34be28001a..000000000000 --- a/tools/testing/selftests/drivers/base/revocable/test-revocable.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 - -mod_name="revocable_test" -ksft_fail=1 -ksft_skip=4 - -if [ "$(id -u)" -ne 0 ]; then - echo "$0: Must be run as root" - exit "$ksft_skip" -fi - -if ! which insmod > /dev/null 2>&1; then - echo "$0: Need insmod" - exit "$ksft_skip" -fi - -if ! which rmmod > /dev/null 2>&1; then - echo "$0: Need rmmod" - exit "$ksft_skip" -fi - -insmod test_modules/"${mod_name}".ko - -if [ ! -d /sys/kernel/debug/revocable_test/ ]; then - mount -t debugfs none /sys/kernel/debug/ - - if [ ! -d /sys/kernel/debug/revocable_test/ ]; then - echo "$0: Error mounting debugfs" - exit "$ksft_fail" - fi -fi - -./revocable_test -ret=$? - -rmmod "${mod_name}" - -exit "${ret}" diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile deleted file mode 100644 index f29e4f909402..000000000000 --- a/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST))))) -KDIR ?= /lib/modules/$(shell uname -r)/build - -obj-m += revocable_test.o - -all: - $(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR) - -clean: - $(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR) clean diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c deleted file mode 100644 index a560ceda7318..000000000000 --- a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright 2026 Google LLC - * - * A kernel module for testing the revocable API. - */ - -#include -#include -#include -#include - -#define TEST_CMD_RESOURCE_GONE "resource_gone" -#define TEST_MAGIC_OFFSET 0x1234 -#define TEST_MAGIC_OFFSET2 0x5678 - -static struct dentry *debugfs_dir; - -struct revocable_test_provider_priv { - struct revocable_provider __rcu *rp; - struct dentry *dentry; - char res[16]; -}; - -static int revocable_test_consumer_open(struct inode *inode, struct file *filp) -{ - filp->private_data = inode->i_private; - return 0; -} - -static ssize_t revocable_test_consumer_read(struct file *filp, - char __user *buf, - size_t count, loff_t *offset) -{ - int ret; - char *res; - char data[16]; - size_t len; - struct revocable rev; - struct revocable_provider __rcu *rp = filp->private_data; - - switch (*offset) { - case 0: - ret = revocable_init(rp, &rev); - if (ret) { - snprintf(data, sizeof(data), "%s", "(null)"); - break; - } - res = revocable_try_access(&rev); - snprintf(data, sizeof(data), "%s", res ?: "(null)"); - revocable_withdraw_access(&rev); - revocable_deinit(&rev); - break; - case TEST_MAGIC_OFFSET: - { - REVOCABLE_TRY_ACCESS_WITH(rp, res); - snprintf(data, sizeof(data), "%s", res ?: "(null)"); - } - break; - case TEST_MAGIC_OFFSET2: - REVOCABLE_TRY_ACCESS_SCOPED(rp, res) - snprintf(data, sizeof(data), "%s", res ?: "(null)"); - break; - default: - return 0; - } - - len = min_t(size_t, strlen(data), count); - if (copy_to_user(buf, data, len)) - return -EFAULT; - - *offset = len; - return len; -} - -static const struct file_operations revocable_test_consumer_fops = { - .open = revocable_test_consumer_open, - .read = revocable_test_consumer_read, - .llseek = default_llseek, -}; - -static int revocable_test_provider_open(struct inode *inode, struct file *filp) -{ - struct revocable_test_provider_priv *priv; - - priv = kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - filp->private_data = priv; - - return 0; -} - -static int revocable_test_provider_release(struct inode *inode, - struct file *filp) -{ - struct revocable_test_provider_priv *priv = filp->private_data; - - debugfs_remove(priv->dentry); - if (unrcu_pointer(priv->rp)) - revocable_provider_revoke(&priv->rp); - kfree(priv); - - return 0; -} - -static ssize_t revocable_test_provider_write(struct file *filp, - const char __user *buf, - size_t count, loff_t *offset) -{ - size_t copied; - char data[64]; - struct revocable_test_provider_priv *priv = filp->private_data; - - copied = strncpy_from_user(data, buf, sizeof(data)); - if (copied < 0) - return copied; - if (copied == sizeof(data)) - data[sizeof(data) - 1] = '\0'; - - /* - * Note: The test can't just close the FD for signaling the - * resource gone. Subsequent file operations on the opening - * FD of debugfs return -EIO after calling debugfs_remove(). - * See also debugfs_file_get(). - * - * Here is a side command channel for signaling the resource - * gone. - */ - if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) { - revocable_provider_revoke(&priv->rp); - rcu_assign_pointer(priv->rp, NULL); - } else { - if (priv->res[0] != '\0') - return 0; - - strscpy(priv->res, data); - - priv->rp = revocable_provider_alloc(&priv->res); - if (!unrcu_pointer(priv->rp)) - return -ENOMEM; - - priv->dentry = debugfs_create_file("consumer", 0400, - debugfs_dir, - unrcu_pointer(priv->rp), - &revocable_test_consumer_fops); - if (!priv->dentry) { - revocable_provider_revoke(&priv->rp); - return -ENOMEM; - } - } - - return copied; -} - -static const struct file_operations revocable_test_provider_fops = { - .open = revocable_test_provider_open, - .release = revocable_test_provider_release, - .write = revocable_test_provider_write, -}; - -static int __init revocable_test_init(void) -{ - debugfs_dir = debugfs_create_dir("revocable_test", NULL); - if (!debugfs_dir) - return -ENOMEM; - - if (!debugfs_create_file("provider", 0200, debugfs_dir, NULL, - &revocable_test_provider_fops)) { - debugfs_remove_recursive(debugfs_dir); - return -ENOMEM; - } - - return 0; -} - -static void __exit revocable_test_exit(void) -{ - debugfs_remove_recursive(debugfs_dir); -} - -module_init(revocable_test_init); -module_exit(revocable_test_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Tzung-Bi Shih "); -MODULE_DESCRIPTION("Revocable Kselftest"); From 7149ce34dd48886b3f69153c7f5533dd3fd5f47e Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 4 Feb 2026 15:28:48 +0100 Subject: [PATCH 75/79] Revert "revocable: Add Kunit test cases" This reverts commit cd7693419bb5abd91ad2f407dab69c480e417a61. The new revocable functionality is fundamentally broken and at a minimum needs to be redesigned. Drop the revocable Kunit tests to allow the implementation to be reverted. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260204142849.22055-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 - drivers/base/Kconfig | 8 - drivers/base/Makefile | 3 - drivers/base/revocable_test.c | 284 ---------------------------------- 4 files changed, 296 deletions(-) delete mode 100644 drivers/base/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index 7759e9103070..93c07c645c68 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22390,7 +22390,6 @@ M: Tzung-Bi Shih L: linux-kernel@vger.kernel.org S: Maintained 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 9f318b98144d..1786d87b29e2 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -250,11 +250,3 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT work on. endmenu - -# Kunit test cases -config REVOCABLE_KUNIT_TEST - tristate "Kunit tests for revocable" if !KUNIT_ALL_TESTS - depends on KUNIT && BROKEN - default KUNIT_ALL_TESTS - help - Kunit tests for the revocable API. diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 4c6607616a73..8074a10183dc 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -35,6 +35,3 @@ 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 deleted file mode 100644 index 27f5d7d96f4b..000000000000 --- a/drivers/base/revocable_test.c +++ /dev/null @@ -1,284 +0,0 @@ -// 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 - * provided via the provider. - * - * - 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 - * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). - * - * - Provider Use-after-free: Verifies revocable_init() correctly handles - * race conditions where the provider is being released. - * - * - Concurrent Access: Verifies multiple threads can access the resource. - */ - -#include -#include -#include -#include -#include -#include - -static void revocable_test_basic(struct kunit *test) -{ - struct revocable_provider __rcu *rp; - struct revocable rev; - void *real_res = (void *)0x12345678, *res; - int ret; - - rp = revocable_provider_alloc(real_res); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - - ret = revocable_init(rp, &rev); - KUNIT_ASSERT_EQ(test, ret, 0); - - res = revocable_try_access(&rev); - KUNIT_EXPECT_PTR_EQ(test, res, real_res); - revocable_withdraw_access(&rev); - - revocable_deinit(&rev); - revocable_provider_revoke(&rp); - KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); -} - -static void revocable_test_revocation(struct kunit *test) -{ - struct revocable_provider __rcu *rp; - struct revocable rev; - void *real_res = (void *)0x12345678, *res; - int ret; - - rp = revocable_provider_alloc(real_res); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - - ret = revocable_init(rp, &rev); - KUNIT_ASSERT_EQ(test, ret, 0); - - res = revocable_try_access(&rev); - KUNIT_EXPECT_PTR_EQ(test, res, real_res); - revocable_withdraw_access(&rev); - - revocable_provider_revoke(&rp); - KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); - - res = revocable_try_access(&rev); - KUNIT_EXPECT_PTR_EQ(test, res, NULL); - revocable_withdraw_access(&rev); - - revocable_deinit(&rev); -} - -static void revocable_test_try_access_macro(struct kunit *test) -{ - struct revocable_provider __rcu *rp; - void *real_res = (void *)0x12345678, *res; - - rp = revocable_provider_alloc(real_res); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - - { - REVOCABLE_TRY_ACCESS_WITH(rp, res); - KUNIT_EXPECT_PTR_EQ(test, res, real_res); - } - - revocable_provider_revoke(&rp); - KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); - - { - REVOCABLE_TRY_ACCESS_WITH(rp, res); - KUNIT_EXPECT_PTR_EQ(test, res, NULL); - } -} - -static void revocable_test_try_access_macro2(struct kunit *test) -{ - struct revocable_provider __rcu *rp; - void *real_res = (void *)0x12345678, *res; - bool accessed; - - rp = revocable_provider_alloc(real_res); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - - accessed = false; - REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { - KUNIT_EXPECT_PTR_EQ(test, res, real_res); - accessed = true; - } - KUNIT_EXPECT_TRUE(test, accessed); - - revocable_provider_revoke(&rp); - KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); - - accessed = false; - REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { - KUNIT_EXPECT_PTR_EQ(test, res, NULL); - accessed = true; - } - KUNIT_EXPECT_TRUE(test, accessed); -} - -static void revocable_test_provider_use_after_free(struct kunit *test) -{ - struct revocable_provider __rcu *rp; - struct revocable_provider *old_rp; - struct revocable rev; - void *real_res = (void *)0x12345678; - int ret; - - rp = revocable_provider_alloc(real_res); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - - ret = revocable_init(NULL, &rev); - KUNIT_EXPECT_NE(test, ret, 0); - - /* Simulate the provider has been freed. */ - old_rp = rcu_replace_pointer(rp, NULL, 1); - ret = revocable_init(rp, &rev); - KUNIT_EXPECT_NE(test, ret, 0); - rcu_replace_pointer(rp, old_rp, 1); - - struct { - struct srcu_struct srcu; - void __rcu *res; - struct kref kref; - struct rcu_head rcu; - } *rp_internal = (void *)rp; - - /* Simulate the provider is releasing. */ - refcount_set(&rp_internal->kref.refcount, 0); - ret = revocable_init(rp, &rev); - KUNIT_EXPECT_NE(test, ret, 0); - refcount_set(&rp_internal->kref.refcount, 1); - - revocable_provider_revoke(&rp); - KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); - ret = revocable_init(rp, &rev); - KUNIT_EXPECT_NE(test, ret, 0); -} - -struct test_concurrent_access_context { - struct kunit *test; - struct revocable_provider __rcu *rp; - struct revocable rev; - struct completion started, enter, exit; - struct task_struct *thread; - 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_provider_revoke(&ctx->rp); - KUNIT_EXPECT_PTR_EQ(ctx->test, unrcu_pointer(ctx->rp), NULL); - - 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->rev); - KUNIT_EXPECT_PTR_EQ(ctx->test, res, ctx->expected_res); - - wait_for_completion(&ctx->exit); - revocable_withdraw_access(&ctx->rev); - - return 0; -} - -static void revocable_test_concurrent_access(struct kunit *test) -{ - struct revocable_provider __rcu *rp; - void *real_res = (void *)0x12345678; - struct test_concurrent_access_context *ctx; - int ret, i; - - rp = revocable_provider_alloc(real_res); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - - 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); - init_completion(&ctx[i].exit); - - if (i == 0) { - ctx[i].rp = rp; - ctx[i].thread = kthread_run( - test_concurrent_access_provider, ctx + i, - "revocable_provider_%d", i); - } else { - ret = revocable_init(rp, &ctx[i].rev); - KUNIT_ASSERT_EQ(test, ret, 0); - - ctx[i].thread = kthread_run( - test_concurrent_access_consumer, ctx + i, - "revocable_consumer_%d", i); - } - KUNIT_ASSERT_FALSE(test, IS_ERR(ctx[i].thread)); - - wait_for_completion(&ctx[i].started); - } - ctx[1].expected_res = real_res; - ctx[2].expected_res = NULL; - - /* consumer1 enters read-side critical section */ - complete(&ctx[1].enter); - msleep(100); - /* provider0 revokes the resource */ - complete(&ctx[0].enter); - msleep(100); - /* consumer2 enters read-side critical section */ - complete(&ctx[2].enter); - msleep(100); - - /* consumer{1,2} exit read-side critical section */ - complete(&ctx[1].exit); - complete(&ctx[2].exit); - - for (i = 0; i < 3; ++i) - kthread_stop(ctx[i].thread); - for (i = 1; i < 3; ++i) - revocable_deinit(&ctx[i].rev); -} - -static struct kunit_case revocable_test_cases[] = { - KUNIT_CASE(revocable_test_basic), - KUNIT_CASE(revocable_test_revocation), - KUNIT_CASE(revocable_test_try_access_macro), - KUNIT_CASE(revocable_test_try_access_macro2), - KUNIT_CASE(revocable_test_provider_use_after_free), - 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_DESCRIPTION("KUnit tests for the revocable API"); -MODULE_LICENSE("GPL"); From 21bab791346e5b7902a04709231c0642ff6d69bc Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 4 Feb 2026 15:28:49 +0100 Subject: [PATCH 76/79] Revert "revocable: Revocable resource management" This reverts commit 62eb557580eb2177cf16c3fd2b6efadff297b29a. The revocable implementation uses two separate abstractions, struct revocable_provider and struct revocable, in order to store the SRCU read lock index which must be passed unaltered to srcu_read_unlock() in the same context when a resource is no longer needed. With the merged revocable API, multiple threads could however share the same struct revocable and therefore potentially overwrite the SRCU index of another thread which can cause the SRCU synchronisation in revocable_provider_revoke() to never complete. [1] An example revocable conversion of the gpiolib code also turned out to be fundamentally flawed and could lead to use-after-free. [2] An attempt to address both issues was quickly put together and merged, but revocable is still fundamentally broken. [3] Specifically, the latest design relies on RCU for storing a pointer to the revocable provider, but since the resource can be shared by value (e.g. as in the now reverted selftests) this does not work at all and can also lead to use-after-free: static void revocable_provider_release(struct kref *kref) { struct revocable_provider *rp = container_of(kref, struct revocable_provider, kref); cleanup_srcu_struct(&rp->srcu); kfree_rcu(rp, rcu); } void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) { struct revocable_provider *rp; rp = rcu_replace_pointer(*rp_ptr, NULL, 1); ... kref_put(&rp->kref, revocable_provider_release); } int revocable_init(struct revocable_provider __rcu *_rp, struct revocable *rev) { struct revocable_provider *rp; ... scoped_guard(rcu) { rp = rcu_dereference(_rp); if (!rp) return -ENODEV; if (!kref_get_unless_zero(&rp->kref)) return -ENODEV; } ... } producer: priv->rp = revocable_provider_alloc(&priv->res); // pass priv->rp by value to consumer revocable_provider_revoke(&priv->rp); consumer: struct revocable_provider __rcu *rp = filp->private_data; struct revocable *rev; revocable_init(rp, &rev); as _rp would still be non-NULL in revocable_init() regardless of whether the producer has revoked the resource and set its pointer to NULL. Essentially revocable still relies on having a pointer to reference counted driver data which holds the revocable provider, which makes all the RCU protection unnecessary along with most of the current revocable design and implementation. As the above shows, and as has been pointed out repeatedly elsewhere, these kind of issues are not something that should be addressed incrementally. [4] Revert the revocable implementation until a redesign has been proposed and evaluated properly. Link: https://lore.kernel.org/all/20260124170535.11756-4-johan@kernel.org/ [1] Link: https://lore.kernel.org/all/aXT45B6vLf9R3Pbf@hovoldconsulting.com/ [2] Link: https://lore.kernel.org/all/20260129143733.45618-1-tzungbi@kernel.org/ [3] Link: https://lore.kernel.org/all/aXobzoeooJqxMkEj@hovoldconsulting.com/ [4] Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260204142849.22055-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../driver-api/driver-model/index.rst | 1 - .../driver-api/driver-model/revocable.rst | 149 ------------ MAINTAINERS | 7 - drivers/base/revocable.c | 225 ------------------ include/linux/revocable.h | 89 ------- 5 files changed, 471 deletions(-) delete mode 100644 Documentation/driver-api/driver-model/revocable.rst delete mode 100644 drivers/base/revocable.c delete 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 8e1ee21185df..4831bdd92e5c 100644 --- a/Documentation/driver-api/driver-model/index.rst +++ b/Documentation/driver-api/driver-model/index.rst @@ -14,7 +14,6 @@ Driver Model overview platform porting - revocable .. only:: subproject and html diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst deleted file mode 100644 index 350e7faeccdc..000000000000 --- a/Documentation/driver-api/driver-model/revocable.rst +++ /dev/null @@ -1,149 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -============================== -Revocable Resource Management -============================== - -Overview -======== - -.. kernel-doc:: drivers/base/revocable.c - :doc: Overview - -Revocable vs. Devres (devm) -=========================== - -It's important to understand the distinct roles of the Revocable and Devres, -and how they can complement each other. They address different problems in -resource management: - -* **Devres:** Primarily address **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 of a resource managed by the Revocable - mechanism 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 ----------------------- -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_provider - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_provider_alloc - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_provider_revoke - -For Resource Consumers ----------------------- -.. kernel-doc:: include/linux/revocable.h - :identifiers: revocable - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_init - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_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 - - void consumer_use_resource(struct revocable_provider *rp) - { - struct foo_resource *res; - - REVOCABLE_TRY_ACCESS_WITH(rp, res); - // Always check if the resource is valid. - if (!res) { - pr_warn("Resource is not available\n"); - return; - } - - // At this point, 'res' is guaranteed to be valid until - // this block exits. - do_something_with(res); - - } // revocable_withdraw_access() is automatically called here. - -.. kernel-doc:: include/linux/revocable.h - :identifiers: REVOCABLE_TRY_ACCESS_SCOPED - -Example Usage -~~~~~~~~~~~~~ - -.. code-block:: c - - void consumer_use_resource(struct revocable_provider *rp) - { - struct foo_resource *res; - - REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { - // Always check if the resource is valid. - if (!res) { - pr_warn("Resource is not available\n"); - return; - } - - // At this point, 'res' is guaranteed to be valid until - // this block exits. - do_something_with(res); - } - - // revocable_withdraw_access() is automatically called here. - } diff --git a/MAINTAINERS b/MAINTAINERS index 93c07c645c68..1c5ceccc9928 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22385,13 +22385,6 @@ F: include/uapi/linux/rseq.h F: kernel/rseq.c F: tools/testing/selftests/rseq/ -REVOCABLE RESOURCE MANAGEMENT -M: Tzung-Bi Shih -L: linux-kernel@vger.kernel.org -S: Maintained -F: drivers/base/revocable.c -F: include/linux/revocable.h - RFKILL M: Johannes Berg L: linux-wireless@vger.kernel.org diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c deleted file mode 100644 index 8532ca6a371c..000000000000 --- a/drivers/base/revocable.c +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright 2026 Google LLC - * - * Revocable resource management - */ - -#include -#include -#include -#include -#include - -/** - * DOC: Overview - * - * The "revocable" mechanism is a synchronization primitive designed to manage - * safe access to resources that can be asynchronously removed or invalidated. - * Its primary purpose is to prevent Use-After-Free (UAF) errors when - * interacting with resources whose lifetimes are not guaranteed to outlast - * their consumers. - * - * This is particularly useful in systems where resources can disappear - * unexpectedly, such as those provided by hot-pluggable devices like USB. - * When a consumer holds a reference to such a resource, the underlying device - * might be removed, causing the resource's memory to be freed. Subsequent - * access attempts by the consumer would then lead to UAF errors. - * - * Revocable addresses this by providing a form of "weak reference" and a - * controlled access method. It allows a resource consumer to safely attempt to - * access the resource. The mechanism guarantees that any access granted is - * valid for the duration of its use. If the resource has already been - * revoked (i.e., freed), the access attempt will fail safely, typically by - * returning NULL, instead of causing a crash. - * - * The implementation 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_provider and initializes it with a pointer - * to the resource. - * - * - A resource consumer that wants to access the resource allocates a - * struct revocable which acts as a 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. The - * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED() are - * convenient helpers for doing that. - * - * - When the provider needs to remove the resource, it calls - * revocable_provider_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. - */ - -/** - * struct revocable_provider - 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. - * @rcu: The RCU to protect pointer to itself. - */ -struct revocable_provider { - struct srcu_struct srcu; - void __rcu *res; - struct kref kref; - struct rcu_head rcu; -}; - -/** - * revocable_provider_alloc() - Allocate struct revocable_provider. - * @res: The pointer of resource. - * - * This holds an initial refcount to the struct. - * - * Return: The pointer of struct revocable_provider. NULL on errors. - * It enforces the caller handles the returned pointer in RCU ways. - */ -struct revocable_provider __rcu *revocable_provider_alloc(void *res) -{ - struct revocable_provider *rp; - - rp = kzalloc(sizeof(*rp), GFP_KERNEL); - if (!rp) - return NULL; - - init_srcu_struct(&rp->srcu); - RCU_INIT_POINTER(rp->res, res); - kref_init(&rp->kref); - - return (struct revocable_provider __rcu *)rp; -} -EXPORT_SYMBOL_GPL(revocable_provider_alloc); - -static void revocable_provider_release(struct kref *kref) -{ - struct revocable_provider *rp = container_of(kref, - struct revocable_provider, kref); - - cleanup_srcu_struct(&rp->srcu); - kfree_rcu(rp, rcu); -} - -/** - * revocable_provider_revoke() - Revoke the managed resource. - * @rp_ptr: The pointer of pointer of resource provider. - * - * This sets the resource `(struct revocable_provider *)->res` to NULL to - * indicate the resource has gone. - * - * This drops the refcount to the resource provider. If it is the final - * reference, revocable_provider_release() will be called to free the struct. - * - * It enforces the caller to pass a pointer of pointer of resource provider so - * that it sets \*rp_ptr to NULL to prevent from keeping a dangling pointer. - */ -void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) -{ - struct revocable_provider *rp; - - rp = rcu_replace_pointer(*rp_ptr, NULL, 1); - if (!rp) - return; - - rcu_assign_pointer(rp->res, NULL); - synchronize_srcu(&rp->srcu); - kref_put(&rp->kref, revocable_provider_release); -} -EXPORT_SYMBOL_GPL(revocable_provider_revoke); - -/** - * revocable_init() - Initialize struct revocable. - * @_rp: The pointer of resource provider. - * @rev: The pointer of resource consumer. - * - * This holds a refcount to the resource provider. - * - * Return: 0 on success, -errno otherwise. - */ -int revocable_init(struct revocable_provider __rcu *_rp, struct revocable *rev) -{ - struct revocable_provider *rp; - - if (!_rp) - return -ENODEV; - - /* - * Enter a read-side critical section. - * - * This prevents kfree_rcu() from freeing the struct revocable_provider - * memory, for the duration of this scope. - */ - scoped_guard(rcu) { - rp = rcu_dereference(_rp); - if (!rp) - /* The revocable provider has been revoked. */ - return -ENODEV; - - if (!kref_get_unless_zero(&rp->kref)) - /* - * The revocable provider is releasing (i.e., - * revocable_provider_release() has been called). - */ - return -ENODEV; - } - /* At this point, `rp` is safe to access as holding a kref of it */ - - rev->rp = rp; - return 0; -} -EXPORT_SYMBOL_GPL(revocable_init); - -/** - * revocable_deinit() - Deinitialize struct revocable. - * @rev: The pointer of struct revocable. - * - * This drops a refcount to the resource provider. If it is the final - * reference, revocable_provider_release() will be called to free the struct. - */ -void revocable_deinit(struct revocable *rev) -{ - struct revocable_provider *rp = rev->rp; - - kref_put(&rp->kref, revocable_provider_release); -} -EXPORT_SYMBOL_GPL(revocable_deinit); - -/** - * revocable_try_access() - Try to access the resource. - * @rev: The pointer of struct revocable. - * - * This tries to de-reference to the resource and enters a RCU critical - * section. - * - * Return: The pointer to the resource. NULL if the resource has gone. - */ -void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu) -{ - struct revocable_provider *rp = rev->rp; - - rev->idx = srcu_read_lock(&rp->srcu); - return srcu_dereference(rp->res, &rp->srcu); -} -EXPORT_SYMBOL_GPL(revocable_try_access); - -/** - * revocable_withdraw_access() - Stop accessing to the resource. - * @rev: The pointer of struct revocable. - * - * Call this function to indicate the resource is no longer used. It exits - * the RCU critical section. - */ -void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu) -{ - struct revocable_provider *rp = rev->rp; - - srcu_read_unlock(&rp->srcu, rev->idx); -} -EXPORT_SYMBOL_GPL(revocable_withdraw_access); diff --git a/include/linux/revocable.h b/include/linux/revocable.h deleted file mode 100644 index e3d6d2c953a3..000000000000 --- a/include/linux/revocable.h +++ /dev/null @@ -1,89 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright 2026 Google LLC - */ - -#ifndef __LINUX_REVOCABLE_H -#define __LINUX_REVOCABLE_H - -#include -#include - -struct device; -struct revocable_provider; - -/** - * struct revocable - A handle for resource consumer. - * @rp: The pointer of resource provider. - * @idx: The index for the RCU critical section. - */ -struct revocable { - struct revocable_provider *rp; - int idx; -}; - -struct revocable_provider __rcu *revocable_provider_alloc(void *res); -void revocable_provider_revoke(struct revocable_provider __rcu **rp); - -int revocable_init(struct revocable_provider __rcu *rp, struct revocable *rev); -void revocable_deinit(struct revocable *rev); -void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); -void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); - -DEFINE_FREE(access_rev, struct revocable *, { - if ((_T)->idx != -1) - revocable_withdraw_access(_T); - if ((_T)->rp) - revocable_deinit(_T); -}) - -#define _REVOCABLE_TRY_ACCESS_WITH(_rp, _rev, _res) \ - struct revocable _rev = {.rp = NULL, .idx = -1}; \ - struct revocable *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ - _res = revocable_init(_rp, &_rev) ? NULL : revocable_try_access(&_rev) - -/** - * REVOCABLE_TRY_ACCESS_WITH() - A helper for accessing revocable resource - * @_rp: The provider's ``struct revocable_provider *`` handle. - * @_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_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(). The consumer code **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(_rp, _res) \ - _REVOCABLE_TRY_ACCESS_WITH(_rp, __UNIQUE_ID(name), _res) - -#define _REVOCABLE_TRY_ACCESS_SCOPED(_rp, _rev, _label, _res) \ - for (struct revocable _rev = {.rp = NULL, .idx = -1}, \ - *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ - (_res = revocable_init(_rp, &_rev) ? NULL : \ - revocable_try_access(&_rev)) || true; \ - ({ goto _label; })) \ - if (0) { \ -_label: \ - break; \ - } else - -/** - * REVOCABLE_TRY_ACCESS_SCOPED() - A helper for accessing revocable resource - * @_rp: The provider's ``struct revocable_provider *`` handle. - * @_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_SCOPED(_rp, _res) \ - _REVOCABLE_TRY_ACCESS_SCOPED(_rp, __UNIQUE_ID(name), \ - __UNIQUE_ID(label), _res) - -#endif /* __LINUX_REVOCABLE_H */ From 408e4f9408ec5e4dfec4a3adc8b411b046f85a8e Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Mon, 2 Feb 2026 07:40:01 +0100 Subject: [PATCH 77/79] samples: rust: pci: Remove some additional `.as_ref()` for `dev_*` print The commit 600de1c008b2 ("rust: pci: remove redundant `.as_ref()` for `dev_*` print") removed `.as_ref()` for `dev_*` prints. Nearly at the same time the commit e62e48adf76c ("sample: rust: pci: add tests for config space routines") was merged. Which missed this removal, then. Signed-off-by: Dirk Behme Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260202064001.176787-1-dirk.behme@de.bosch.com Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_pci.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 4dfb8a6a4707..d3d4a7931deb 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -73,19 +73,19 @@ impl SampleDriver { // TODO: use the register!() macro for defining PCI configuration space registers once it // has been move out of nova-core. dev_info!( - pdev.as_ref(), + pdev, "pci-testdev config space read8 rev ID: {:x}\n", config.read8(0x8) ); dev_info!( - pdev.as_ref(), + pdev, "pci-testdev config space read16 vendor ID: {:x}\n", config.read16(0) ); dev_info!( - pdev.as_ref(), + pdev, "pci-testdev config space read32 BAR 0: {:x}\n", config.read32(0x10) ); From 66fb10bc5c25c83ab268be0390863dd4299d063c Mon Sep 17 00:00:00 2001 From: Dirk Behme Date: Mon, 26 Jan 2026 08:17:38 +0100 Subject: [PATCH 78/79] rust: dma: add missing __rust_helper annotations The commit d8932355f8c5 ("rust: dma: add helpers for architectures without CONFIG_HAS_DMA") missed adding the __rust_helper annotations. Add them. Reported-by: Gary Guo Closes: https://lore.kernel.org/rust-for-linux/DFW4F5OSDO7A.TBUOX6RCN8G7@garyguo.net/ Signed-off-by: Dirk Behme Reviewed-by: Gary Guo Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260126071738.1670967-1-dirk.behme@de.bosch.com [ Fix minor checkpatch.pl warning. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/helpers/dma.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/helpers/dma.c b/rust/helpers/dma.c index 20232ac64850..9fbeb507b08c 100644 --- a/rust/helpers/dma.c +++ b/rust/helpers/dma.c @@ -23,23 +23,23 @@ __rust_helper int rust_helper_dma_set_mask_and_coherent(struct device *dev, return dma_set_mask_and_coherent(dev, mask); } -int rust_helper_dma_set_mask(struct device *dev, u64 mask) +__rust_helper int rust_helper_dma_set_mask(struct device *dev, u64 mask) { return dma_set_mask(dev, mask); } -int rust_helper_dma_set_coherent_mask(struct device *dev, u64 mask) +__rust_helper int rust_helper_dma_set_coherent_mask(struct device *dev, u64 mask) { return dma_set_coherent_mask(dev, mask); } -int rust_helper_dma_map_sgtable(struct device *dev, struct sg_table *sgt, - enum dma_data_direction dir, unsigned long attrs) +__rust_helper int rust_helper_dma_map_sgtable(struct device *dev, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs) { return dma_map_sgtable(dev, sgt, dir, attrs); } -size_t rust_helper_dma_max_mapping_size(struct device *dev) +__rust_helper size_t rust_helper_dma_max_mapping_size(struct device *dev) { return dma_max_mapping_size(dev); } From ba268514ea14b44570030e8ed2aef92a38679e85 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 5 Feb 2026 23:25:15 +0100 Subject: [PATCH 79/79] rust: devres: fix race condition due to nesting Commit f5d3ef25d238 ("rust: devres: get rid of Devres' inner Arc") did attempt to optimize away the internal reference count of Devres. However, without an internal reference count, we can't support cases where Devres is indirectly nested, resulting into a deadlock. Such indirect nesting easily happens in the following way: A registration object (which is guarded by devres) hold a reference count of an object that holds a device resource guarded by devres itself. For instance a drm::Registration holds a reference of a drm::Device. The drm::Device itself holds a device resource in its private data. When the drm::Registration is dropped by devres, and it happens that it did hold the last reference count of the drm::Device, it also drops the device resource, which is guarded by devres itself. Thus, resulting into a deadlock in the Devres destructor of the device resource, as in the following backtrace. sysrq: Show Blocked State task:rmmod state:D stack:0 pid:1331 tgid:1331 ppid:1330 task_flags:0x400100 flags:0x00000010 Call trace: __switch_to+0x190/0x294 (T) __schedule+0x878/0xf10 schedule+0x4c/0xcc schedule_timeout+0x44/0x118 wait_for_common+0xc0/0x18c wait_for_completion+0x18/0x24 _RINvNtCs4gKlGRWyJ5S_4core3ptr13drop_in_placeINtNtNtCsgzhNYVB7wSz_6kernel4sync3arc3ArcINtNtBN_6devres6DevresmEEECsRdyc7Hyps3_15rust_driver_pci+0x68/0xe8 [rust_driver_pci] _RINvNvNtCsgzhNYVB7wSz_6kernel6devres16register_foreign8callbackINtNtCs4gKlGRWyJ5S_4core3pin3PinINtNtNtB6_5alloc4kbox3BoxINtNtNtB6_4sync3arc3ArcINtB4_6DevresmEENtNtB1A_9allocator7KmallocEEECsRdyc7Hyps3_15rust_driver_pci+0x34/0xc8 [rust_driver_pci] devm_action_release+0x14/0x20 devres_release_all+0xb8/0x118 device_release_driver_internal+0x1c4/0x28c driver_detach+0x94/0xd4 bus_remove_driver+0xdc/0x11c driver_unregister+0x34/0x58 pci_unregister_driver+0x20/0x80 __arm64_sys_delete_module+0x1d8/0x254 invoke_syscall+0x40/0xcc el0_svc_common+0x8c/0xd8 do_el0_svc+0x1c/0x28 el0_svc+0x54/0x1d4 el0t_64_sync_handler+0x84/0x12c el0t_64_sync+0x198/0x19c In order to fix this, re-introduce the internal reference count. Reported-by: Boris Brezillon Closes: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/.E2.9C.94.20Deadlock.20caused.20by.20nested.20Devres/with/571242651 Reported-by: Markus Probst Closes: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/.E2.9C.94.20Devres.20inside.20Devres.20stuck.20on.20cleanup/with/571239721 Reported-by: Alice Ryhl Closes: https://gitlab.freedesktop.org/panfrost/linux/-/merge_requests/56#note_3282757 Fixes: f5d3ef25d238 ("rust: devres: get rid of Devres' inner Arc") Reviewed-by: Greg Kroah-Hartman Reviewed-by: Alice Ryhl Tested-by: Boris Brezillon Link: https://patch.msgid.link/20260205222529.91465-1-dakr@kernel.org [ Call clone() prior to devm_add_action(). - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 151 ++++++++++++------------------------------ 1 file changed, 41 insertions(+), 110 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index cdc49677022a..6afe196be42c 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -21,30 +21,11 @@ use crate::{ sync::{ aref::ARef, rcu, - Completion, // - }, - types::{ - ForeignOwnable, - Opaque, - ScopeGuard, // + Arc, // }, + types::ForeignOwnable, }; -use pin_init::Wrapper; - -/// [`Devres`] inner data accessed from [`Devres::callback`]. -#[pin_data] -struct Inner { - #[pin] - data: Revocable, - /// Tracks whether [`Devres::callback`] has been completed. - #[pin] - devm: Completion, - /// Tracks whether revoking [`Self::data`] has been completed. - #[pin] - revoke: Completion, -} - /// This abstraction is meant to be used by subsystems to containerize [`Device`] bound resources to /// manage their lifetime. /// @@ -121,18 +102,13 @@ struct Inner { /// # fn no_run(dev: &Device) -> Result<(), Error> { /// // SAFETY: Invalid usage for example purposes. /// let iomem = unsafe { IoMem::<{ core::mem::size_of::() }>::new(0xBAAAAAAD)? }; -/// let devres = KBox::pin_init(Devres::new(dev, iomem), GFP_KERNEL)?; +/// let devres = Devres::new(dev, iomem)?; /// /// let res = devres.try_access().ok_or(ENXIO)?; /// res.write8(0x42, 0x0); /// # Ok(()) /// # } /// ``` -/// -/// # Invariants -/// -/// `Self::inner` is guaranteed to be initialized and is always accessed read-only. -#[pin_data(PinnedDrop)] pub struct Devres { dev: ARef, /// Pointer to [`Self::devres_callback`]. @@ -140,14 +116,7 @@ pub struct Devres { /// Has to be stored, since Rust does not guarantee to always return the same address for a /// function. However, the C API uses the address as a key. callback: unsafe extern "C" fn(*mut c_void), - /// Contains all the fields shared with [`Self::callback`]. - // TODO: Replace with `UnsafePinned`, once available. - // - // Subsequently, the `drop_in_place()` in `Devres::drop` and `Devres::new` as well as the - // explicit `Send` and `Sync' impls can be removed. - #[pin] - inner: Opaque>, - _add_action: (), + data: Arc>, } impl Devres { @@ -155,74 +124,48 @@ impl Devres { /// /// The `data` encapsulated within the returned `Devres` instance' `data` will be /// (revoked)[`Revocable`] once the device is detached. - pub fn new<'a, E>( - dev: &'a Device, - data: impl PinInit + 'a, - ) -> impl PinInit + 'a + pub fn new(dev: &Device, data: impl PinInit) -> Result where - T: 'a, Error: From, { - try_pin_init!(&this in Self { + let callback = Self::devres_callback; + let data = Arc::pin_init(Revocable::new(data), GFP_KERNEL)?; + let devres_data = data.clone(); + + // SAFETY: + // - `dev.as_raw()` is a pointer to a valid bound device. + // - `data` is guaranteed to be a valid for the duration of the lifetime of `Self`. + // - `devm_add_action()` is guaranteed not to call `callback` for the entire lifetime of + // `dev`. + to_result(unsafe { + bindings::devm_add_action( + dev.as_raw(), + Some(callback), + Arc::as_ptr(&data).cast_mut().cast(), + ) + })?; + + // `devm_add_action()` was successful and has consumed the reference count. + core::mem::forget(devres_data); + + Ok(Self { dev: dev.into(), - callback: Self::devres_callback, - // INVARIANT: `inner` is properly initialized. - inner <- Opaque::pin_init(try_pin_init!(Inner { - devm <- Completion::new(), - revoke <- Completion::new(), - data <- Revocable::new(data), - })), - // TODO: Replace with "initializer code blocks" [1] once available. - // - // [1] https://github.com/Rust-for-Linux/pin-init/pull/69 - _add_action: { - // SAFETY: `this` is a valid pointer to uninitialized memory. - let inner = unsafe { &raw mut (*this.as_ptr()).inner }; - - // SAFETY: - // - `dev.as_raw()` is a pointer to a valid bound device. - // - `inner` is guaranteed to be a valid for the duration of the lifetime of `Self`. - // - `devm_add_action()` is guaranteed not to call `callback` until `this` has been - // properly initialized, because we require `dev` (i.e. the *bound* device) to - // live at least as long as the returned `impl PinInit`. - to_result(unsafe { - bindings::devm_add_action(dev.as_raw(), Some(*callback), inner.cast()) - }).inspect_err(|_| { - let inner = Opaque::cast_into(inner); - - // SAFETY: `inner` is a valid pointer to an `Inner` and valid for both reads - // and writes. - unsafe { core::ptr::drop_in_place(inner) }; - })?; - }, + callback, + data, }) } - fn inner(&self) -> &Inner { - // SAFETY: By the type invairants of `Self`, `inner` is properly initialized and always - // accessed read-only. - unsafe { &*self.inner.get() } - } - fn data(&self) -> &Revocable { - &self.inner().data + &self.data } #[allow(clippy::missing_safety_doc)] unsafe extern "C" fn devres_callback(ptr: *mut kernel::ffi::c_void) { - // SAFETY: In `Self::new` we've passed a valid pointer to `Inner` to `devm_add_action()`, - // hence `ptr` must be a valid pointer to `Inner`. - let inner = unsafe { &*ptr.cast::>() }; + // SAFETY: In `Self::new` we've passed a valid pointer of `Revocable` to + // `devm_add_action()`, hence `ptr` must be a valid pointer to `Revocable`. + let data = unsafe { Arc::from_raw(ptr.cast::>()) }; - // Ensure that `inner` can't be used anymore after we signal completion of this callback. - let inner = ScopeGuard::new_with_data(inner, |inner| inner.devm.complete_all()); - - if !inner.data.revoke() { - // If `revoke()` returns false, it means that `Devres::drop` already started revoking - // `data` for us. Hence we have to wait until `Devres::drop` signals that it - // completed revoking `data`. - inner.revoke.wait_for_completion(); - } + data.revoke(); } fn remove_action(&self) -> bool { @@ -234,7 +177,7 @@ impl Devres { bindings::devm_remove_action_nowarn( self.dev.as_raw(), Some(self.callback), - core::ptr::from_ref(self.inner()).cast_mut().cast(), + core::ptr::from_ref(self.data()).cast_mut().cast(), ) } == 0) } @@ -313,31 +256,19 @@ unsafe impl Send for Devres {} // SAFETY: `Devres` can be shared with any task, if `T: Sync`. unsafe impl Sync for Devres {} -#[pinned_drop] -impl PinnedDrop for Devres { - fn drop(self: Pin<&mut Self>) { +impl Drop for Devres { + fn drop(&mut self) { // SAFETY: When `drop` runs, it is guaranteed that nobody is accessing the revocable data // anymore, hence it is safe not to wait for the grace period to finish. if unsafe { self.data().revoke_nosync() } { // We revoked `self.data` before the devres action did, hence try to remove it. - if !self.remove_action() { - // We could not remove the devres action, which means that it now runs concurrently, - // hence signal that `self.data` has been revoked by us successfully. - self.inner().revoke.complete_all(); - - // Wait for `Self::devres_callback` to be done using this object. - self.inner().devm.wait_for_completion(); + if self.remove_action() { + // SAFETY: In `Self::new` we have taken an additional reference count of `self.data` + // for `devm_add_action()`. Since `remove_action()` was successful, we have to drop + // this additional reference count. + drop(unsafe { Arc::from_raw(Arc::as_ptr(&self.data)) }); } - } else { - // `Self::devres_callback` revokes `self.data` for us, hence wait for it to be done - // using this object. - self.inner().devm.wait_for_completion(); } - - // INVARIANT: At this point it is guaranteed that `inner` can't be accessed any more. - // - // SAFETY: `inner` is valid for dropping. - unsafe { core::ptr::drop_in_place(self.inner.get()) }; } }