In preparation for ioremap support, add a Rust abstraction for struct
resource.
A future commit will introduce the Rust API to ioremap a resource from a
platform device. The current abstraction, therefore, adds only the
minimum API needed to get that done.
Co-developed-by: Fiona Behrens <me@kloenk.dev>
Signed-off-by: Fiona Behrens <me@kloenk.dev>
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
rust/bindings/bindings_helper.h | 1 +
rust/helpers/io.c | 36 +++++++
rust/kernel/io.rs | 4 +
rust/kernel/io/resource.rs | 204 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 245 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 7e8f2285064797d5bbac5583990ff823b76c6bdc..5f795e60e889b9fc887013743c81b1cf92a52adb 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -53,6 +53,7 @@
#include <linux/file.h>
#include <linux/firmware.h>
#include <linux/fs.h>
+#include <linux/ioport.h>
#include <linux/jiffies.h>
#include <linux/jump_label.h>
#include <linux/mdio.h>
diff --git a/rust/helpers/io.c b/rust/helpers/io.c
index 15ea187c5466256effd07efe6f6995a1dd95a967..404776cf6717c8570c7600a24712ce6e4623d3c6 100644
--- a/rust/helpers/io.c
+++ b/rust/helpers/io.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/io.h>
+#include <linux/ioport.h>
void __iomem *rust_helper_ioremap(phys_addr_t offset, size_t size)
{
@@ -99,3 +100,38 @@ 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)
+{
+ return resource_size(res);
+}
+
+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)
+{
+ release_mem_region(start, n);
+}
+
+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)
+{
+ return request_muxed_region(start, n, name);
+}
+
+void rust_helper_release_region(resource_size_t start, resource_size_t n)
+{
+ release_region(start, n);
+}
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 72d80a6f131e3e826ecd9d2c3bcf54e89aa60cc3..7b70d5b5477e57d6d0f24bcd26bd8b0071721bc0 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -7,6 +7,10 @@
use crate::error::{code::EINVAL, Result};
use crate::{bindings, build_assert};
+pub mod resource;
+
+pub use resource::Resource;
+
/// Raw representation of an MMIO region.
///
/// By itself, the existence of an instance of this structure does not provide any guarantees that
diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b20b2df3d65dfc8c14881c440ea901da0273b1bb
--- /dev/null
+++ b/rust/kernel/io/resource.rs
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for [system
+//! resources](https://docs.kernel.org/core-api/kernel-api.html#resources-management).
+//!
+//! C header: [`include/linux/ioport.h`](srctree/include/linux/ioport.h)
+
+use core::ops::Deref;
+use core::ptr::NonNull;
+
+use crate::prelude::*;
+use crate::str::CStr;
+use crate::types::Opaque;
+
+/// Resource Size type.
+///
+/// This is a type alias to either `u32` or `u64` depending on the config option
+/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures.
+pub type ResourceSize = bindings::phys_addr_t;
+
+/// A region allocated from a parent [`Resource`].
+///
+/// # Invariants
+///
+/// - `self.0` points to a valid `bindings::resource` that was obtained through
+/// `bindings::__request_region`.
+pub struct Region(NonNull<bindings::resource>);
+
+impl Deref for Region {
+ type Target = Resource;
+
+ fn deref(&self) -> &Self::Target {
+ // SAFETY: Safe as per the invariant of `Region`.
+ unsafe { Resource::from_raw(self.0.as_ptr()) }
+ }
+}
+
+impl Drop for Region {
+ fn drop(&mut self) {
+ let (flags, start, size) = {
+ let res = &**self;
+ (res.flags(), res.start(), res.size())
+ };
+
+ let release_fn = if flags.contains(Flags::IORESOURCE_MEM) {
+ bindings::release_mem_region
+ } else {
+ bindings::release_region
+ };
+
+ // SAFETY: Safe as per the invariant of `Region`.
+ unsafe { release_fn(start, size) };
+ }
+}
+
+// SAFETY: `Region` only holds a pointer to a C `struct resource`, which is safe to be used from
+// any thread.
+unsafe impl Send for Region {}
+
+// SAFETY: `Region` only holds a pointer to a C `struct resource`, references to which are
+// safe to be used from any thread.
+unsafe impl Sync for Region {}
+
+/// A resource abstraction.
+///
+/// # Invariants
+///
+/// [`Resource`] is a transparent wrapper around a valid `bindings::resource`.
+#[repr(transparent)]
+pub struct Resource(Opaque<bindings::resource>);
+
+impl Resource {
+ /// Creates a reference to a [`Resource`] from a valid pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that for the duration of 'a, the pointer will
+ /// point at a valid `bindings::resource`.
+ ///
+ /// The caller must also ensure that the [`Resource`] is only accessed via the
+ /// returned reference for the duration of 'a.
+ pub(crate) const unsafe fn from_raw<'a>(ptr: *mut bindings::resource) -> &'a Self {
+ // SAFETY: Self is a transparent wrapper around `Opaque<bindings::resource>`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Requests a resource region.
+ ///
+ /// Exclusive access will be given and the region will be marked as busy.
+ /// Further calls to [`Self::request_region`] will return [`None`] if
+ /// the region, or a part of it, is already in use.
+ pub fn request_region(
+ &self,
+ start: ResourceSize,
+ size: ResourceSize,
+ name: &'static CStr,
+ flags: Flags,
+ ) -> Option<Region> {
+ // SAFETY:
+ // - Safe as per the invariant of `Resource`.
+ // - `__request_region` will store a reference to the name, but that is
+ // safe as the name is 'static.
+ let region = unsafe {
+ bindings::__request_region(
+ self.0.get(),
+ start,
+ size,
+ name.as_char_ptr(),
+ flags.0 as c_int,
+ )
+ };
+
+ Some(Region(NonNull::new(region)?))
+ }
+
+ /// Returns the size of the resource.
+ pub fn size(&self) -> ResourceSize {
+ let inner = self.0.get();
+ // SAFETY: safe as per the invariants of `Resource`.
+ unsafe { bindings::resource_size(inner) }
+ }
+
+ /// Returns the start address of the resource.
+ pub fn start(&self) -> u64 {
+ let inner = self.0.get();
+ // SAFETY: safe as per the invariants of `Resource`.
+ unsafe { (*inner).start }
+ }
+
+ /// Returns the name of the resource.
+ pub fn name(&self) -> &'static CStr {
+ let inner = self.0.get();
+ // SAFETY: safe as per the invariants of `Resource`
+ unsafe { CStr::from_char_ptr((*inner).name) }
+ }
+
+ /// Returns the flags associated with the resource.
+ pub fn flags(&self) -> Flags {
+ let inner = self.0.get();
+ // SAFETY: safe as per the invariants of `Resource`
+ let flags = unsafe { (*inner).flags };
+
+ Flags(flags)
+ }
+}
+
+// SAFETY: `Resource` only holds a pointer to a C `struct resource`, which is
+// safe to be used from any thread.
+unsafe impl Send for Resource {}
+
+// SAFETY: `Resource` only holds a pointer to a C `struct resource`, references
+// to which are safe to be used from any thread.
+unsafe impl Sync for Resource {}
+
+/// Resource flags as stored in the C `struct resource::flags` field.
+///
+/// They can be combined with the operators `|`, `&`, and `!`.
+///
+/// Values can be used from the [`flags`] module.
+#[derive(Clone, Copy, PartialEq)]
+pub struct Flags(c_ulong);
+
+impl Flags {
+ /// Check whether `flags` is contained in `self`.
+ pub fn contains(self, flags: Flags) -> bool {
+ (self & flags) == flags
+ }
+}
+
+impl core::ops::BitOr for Flags {
+ type Output = Self;
+ fn bitor(self, rhs: Self) -> Self::Output {
+ Self(self.0 | rhs.0)
+ }
+}
+
+impl core::ops::BitAnd for Flags {
+ type Output = Self;
+ fn bitand(self, rhs: Self) -> Self::Output {
+ Self(self.0 & rhs.0)
+ }
+}
+
+impl core::ops::Not for Flags {
+ type Output = Self;
+ fn not(self) -> Self::Output {
+ Self(!self.0)
+ }
+}
+
+impl Flags {
+ /// PCI/ISA I/O ports.
+ pub const IORESOURCE_IO: Flags = Flags(bindings::IORESOURCE_IO as c_ulong);
+
+ /// Resource is software muxed.
+ pub const IORESOURCE_MUXED: Flags = Flags(bindings::IORESOURCE_MUXED as c_ulong);
+
+ /// Resource represents a memory region.
+ pub const IORESOURCE_MEM: Flags = Flags(bindings::IORESOURCE_MEM as c_ulong);
+
+ /// Resource represents a memory region that must be ioremaped using `ioremap_np`.
+ pub const IORESOURCE_MEM_NONPOSTED: Flags =
+ Flags(bindings::IORESOURCE_MEM_NONPOSTED as c_ulong);
+}
--
2.50.0
On Fri, Jul 11, 2025 at 07:32:27PM -0300, Daniel Almeida wrote: > In preparation for ioremap support, add a Rust abstraction for struct > resource. > > A future commit will introduce the Rust API to ioremap a resource from a > platform device. The current abstraction, therefore, adds only the > minimum API needed to get that done. > > Co-developed-by: Fiona Behrens <me@kloenk.dev> > Signed-off-by: Fiona Behrens <me@kloenk.dev> > Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com> Some nits below, but overall LGTM. With those fixed: Reviewed-by: Alice Ryhl <aliceryhl@google.com> > + /// Requests a resource region. > + /// > + /// Exclusive access will be given and the region will be marked as busy. > + /// Further calls to [`Self::request_region`] will return [`None`] if > + /// the region, or a part of it, is already in use. > + pub fn request_region( > + &self, > + start: ResourceSize, > + size: ResourceSize, > + name: &'static CStr, > + flags: Flags, > + ) -> Option<Region> { > + // SAFETY: > + // - Safe as per the invariant of `Resource`. > + // - `__request_region` will store a reference to the name, but that is > + // safe as the name is 'static. > + let region = unsafe { > + bindings::__request_region( > + self.0.get(), > + start, > + size, > + name.as_char_ptr(), > + flags.0 as c_int, > + ) > + }; > + > + Some(Region(NonNull::new(region)?)) > + } > + > + /// Returns the size of the resource. > + pub fn size(&self) -> ResourceSize { > + let inner = self.0.get(); > + // SAFETY: safe as per the invariants of `Resource`. > + unsafe { bindings::resource_size(inner) } > + } > + > + /// Returns the start address of the resource. > + pub fn start(&self) -> u64 { This should be a ResourceSize, right? You use the ResourceSize type for the `start` when calling `request_region`. Or ... should we have another PhysAddr typedef that we can use here? > + let inner = self.0.get(); > + // SAFETY: safe as per the invariants of `Resource`. > + unsafe { (*inner).start } > + } > + > + /// Returns the name of the resource. > + pub fn name(&self) -> &'static CStr { > + let inner = self.0.get(); > + // SAFETY: safe as per the invariants of `Resource` > + unsafe { CStr::from_char_ptr((*inner).name) } This is 'static? I would like this safety comment to explicitly say that the string always lives forever no matter what resource you call this on. Alice
Hi Alice, > >> + let inner = self.0.get(); >> + // SAFETY: safe as per the invariants of `Resource`. >> + unsafe { (*inner).start } >> + } >> + >> + /// Returns the name of the resource. >> + pub fn name(&self) -> &'static CStr { >> + let inner = self.0.get(); >> + // SAFETY: safe as per the invariants of `Resource` >> + unsafe { CStr::from_char_ptr((*inner).name) } > > This is 'static? I would like this safety comment to explicitly say that > the string always lives forever no matter what resource you call this > on. > > Alice > Actually, we have a bit of a problem here. First, there appears to be no guarantee that a `Resource` has a valid name. In fact: #define DEFINE_RES_NAMED(_start, _size, _name, _flags) \ DEFINE_RES_NAMED_DESC(_start, _size, _name, _flags, IORES_DESC_NONE) #define DEFINE_RES(_start, _size, _flags) \ DEFINE_RES_NAMED(_start, _size, NULL, _flags) #define DEFINE_RES_IO_NAMED(_start, _size, _name) \ DEFINE_RES_NAMED((_start), (_size), (_name), IORESOURCE_IO) #define DEFINE_RES_IO(_start, _size) \ DEFINE_RES_IO_NAMED((_start), (_size), NULL) The non _NAMED version of these macros will assign a NULL pointer, so we can't derive a CStr from that at all. On top of that, although some call sites do use static names, i.e.: struct resource ioport_resource = { .name = "PCI IO", .start = 0, .end = IO_SPACE_LIMIT, .flags = IORESOURCE_IO, }; EXPORT_SYMBOL(ioport_resource); struct resource iomem_resource = { .name = "PCI mem", .start = 0, .end = -1, .flags = IORESOURCE_MEM, }; EXPORT_SYMBOL(iomem_resource); static struct resource busn_resource = { .name = "PCI busn", .start = 0, .end = 255, .flags = IORESOURCE_BUS, }; Some appear to use other, smaller lifetimes, like the one below: struct pnp_resource *pnp_add_resource(struct pnp_dev *dev, struct resource *res) { struct pnp_resource *pnp_res; pnp_res = pnp_new_resource(dev); if (!pnp_res) { dev_err(&dev->dev, "can't add resource %pR\n", res); return NULL; } pnp_res->res = *res; pnp_res->res.name = dev->name; I guess the easiest solution is to drop 'static in order to account for the above, and change the signature to return Option<&CStr> instead. We can also change Region to own the name, and pass name by value here: pub fn request_region( &self, start: ResourceSize, size: ResourceSize, name: &'static CStr <------ Thoughts? — Daniel
On Wed Jul 16, 2025 at 6:52 PM CEST, Daniel Almeida wrote: > I guess the easiest solution is to drop 'static in order to account for the > above, and change the signature to return Option<&CStr> instead. I think you have to do both, this... > We can also change Region to own the name, and pass name by value here: ...and this. Otherwise you'd need to ensure that the Resource you create the Region from out-lives the Region, which I think we can't. > pub fn request_region( > &self, > start: ResourceSize, > size: ResourceSize, > name: &'static CStr <------ > > > Thoughts? > > — Daniel
On Wed, Jul 16, 2025 at 6:53 PM Daniel Almeida <daniel.almeida@collabora.com> wrote: > > Hi Alice, > > > > >> + let inner = self.0.get(); > >> + // SAFETY: safe as per the invariants of `Resource`. > >> + unsafe { (*inner).start } > >> + } > >> + > >> + /// Returns the name of the resource. > >> + pub fn name(&self) -> &'static CStr { > >> + let inner = self.0.get(); > >> + // SAFETY: safe as per the invariants of `Resource` > >> + unsafe { CStr::from_char_ptr((*inner).name) } > > > > This is 'static? I would like this safety comment to explicitly say that > > the string always lives forever no matter what resource you call this > > on. > > > > Alice > > > > Actually, we have a bit of a problem here. > > First, there appears to be no guarantee that a `Resource` has a valid name. > > In fact: > > #define DEFINE_RES_NAMED(_start, _size, _name, _flags) \ > DEFINE_RES_NAMED_DESC(_start, _size, _name, _flags, IORES_DESC_NONE) > #define DEFINE_RES(_start, _size, _flags) \ > DEFINE_RES_NAMED(_start, _size, NULL, _flags) > > #define DEFINE_RES_IO_NAMED(_start, _size, _name) \ > DEFINE_RES_NAMED((_start), (_size), (_name), IORESOURCE_IO) > #define DEFINE_RES_IO(_start, _size) \ > DEFINE_RES_IO_NAMED((_start), (_size), NULL) > > The non _NAMED version of these macros will assign a NULL pointer, so we can't > derive a CStr from that at all. > > On top of that, although some call sites do use static names, i.e.: > > struct resource ioport_resource = { > .name = "PCI IO", > .start = 0, > .end = IO_SPACE_LIMIT, > .flags = IORESOURCE_IO, > }; > EXPORT_SYMBOL(ioport_resource); > > struct resource iomem_resource = { > .name = "PCI mem", > .start = 0, > .end = -1, > .flags = IORESOURCE_MEM, > }; > EXPORT_SYMBOL(iomem_resource); > > static struct resource busn_resource = { > .name = "PCI busn", > .start = 0, > .end = 255, > .flags = IORESOURCE_BUS, > }; > > Some appear to use other, smaller lifetimes, like the one below: > > struct pnp_resource *pnp_add_resource(struct pnp_dev *dev, > struct resource *res) > { > struct pnp_resource *pnp_res; > > pnp_res = pnp_new_resource(dev); > if (!pnp_res) { > dev_err(&dev->dev, "can't add resource %pR\n", res); > return NULL; > } > > pnp_res->res = *res; > pnp_res->res.name = dev->name; > > > I guess the easiest solution is to drop 'static in order to account for the > above, and change the signature to return Option<&CStr> instead. Using Option<&CStr> sounds good to me.
© 2016 - 2025 Red Hat, Inc.