[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <DFUDN05YDH4W.1VXNXWWZST0C6@garyguo.net>
Date: Wed, 21 Jan 2026 15:36:42 +0000
From: "Gary Guo" <gary@...yguo.net>
To: "Zhi Wang" <zhiw@...dia.com>, <rust-for-linux@...r.kernel.org>,
<linux-pci@...r.kernel.org>, <linux-kernel@...r.kernel.org>
Cc: <dakr@...nel.org>, <aliceryhl@...gle.com>, <bhelgaas@...gle.com>,
<kwilczynski@...nel.org>, <ojeda@...nel.org>, <alex.gaynor@...il.com>,
<boqun.feng@...il.com>, <gary@...yguo.net>, <bjorn3_gh@...tonmail.com>,
<lossin@...nel.org>, <a.hindborg@...nel.org>, <tmgross@...ch.edu>,
<markus.probst@...teo.de>, <helgaas@...nel.org>, <cjia@...dia.com>,
<smitra@...dia.com>, <ankita@...dia.com>, <aniketa@...dia.com>,
<kwankhede@...dia.com>, <targupta@...dia.com>, <acourbot@...dia.com>,
<joelagnelf@...dia.com>, <jhubbard@...dia.com>, <zhiwang@...nel.org>,
<daniel.almeida@...labora.com>
Subject: Re: [PATCH v11 4/5] rust: pci: add config space read/write support
On Wed Jan 21, 2026 at 2:23 PM GMT, Zhi Wang wrote:
> 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<T>` for u8, u16, and u32 to share offset validation and
> bound-checking logic with other I/O backends.
>
> Cc: Alexandre Courbot <acourbot@...dia.com>
> Cc: Danilo Krummrich <dakr@...nel.org>
> Cc: Joel Fernandes <joelagnelf@...dia.com>
> Signed-off-by: Zhi Wang <zhiw@...dia.com>
> ---
> rust/kernel/pci/io.rs | 161 ++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 161 insertions(+)
>
> diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs
> index e3377397666e..e9c540fe80ac 100644
> --- a/rust/kernel/pci/io.rs
> +++ b/rust/kernel/pci/io.rs
> @@ -8,6 +8,11 @@
> device,
> devres::Devres,
> io::{
> + define_read,
> + define_write,
> + Io,
> + IoCapable,
> + IoKnownSize,
> Mmio,
> MmioRaw, //
> },
> @@ -16,6 +21,134 @@
> };
> use core::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)]
> +pub(super) 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(super) const fn as_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
> + }
> +}
> +
> +/// 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 const parameter `SIZE` can be used to indicate the
> +/// maximum size of the configuration space (e.g. `ConfigSpaceSize::Normal`
> +/// or `ConfigSpaceSize::Extended`).
> +pub struct ConfigSpace<'a, const SIZE: usize = { ConfigSpaceSize::Extended as usize }> {
This is quite long to write. Given that it'll either be normal or extended, can
we just have two marker types instead? So you have
ConfigSpace<Normal> and ConfigSpace<Extended>
Best,
Gary
> + pub(crate) pdev: &'a Device<device::Bound>,
> +}
> +
> +/// 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, const SIZE: usize> IoCapable<u8> for ConfigSpace<'a, SIZE> {}
> +impl<'a, const SIZE: usize> IoCapable<u16> for ConfigSpace<'a, SIZE> {}
> +impl<'a, const SIZE: usize> IoCapable<u32> for ConfigSpace<'a, SIZE> {}
> +
> +impl<'a, const SIZE: usize> Io for ConfigSpace<'a, SIZE> {
> + const MIN_SIZE: usize = 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().map_or(0, |v| v as usize)
> + }
> +
> + // PCI configuration space does not support fallible operations.
> + // The default implementations from the Io trait are not used.
> +}
> +
> +/// Implement IoKnownSize for ConfigSpace with compile-time size.
> +impl<'a, const SIZE: usize> IoKnownSize for ConfigSpace<'a, SIZE> {
> + 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);
> +}
> +
> /// A PCI BAR to perform I/O-Operations on.
> ///
> /// I/O backend assumes that the device is little-endian and will automatically
> @@ -144,4 +277,32 @@ pub fn iomap_region<'a>(
> ) -> impl PinInit<Devres<Bar>, Error> + 'a {
> self.iomap_region_sized::<0>(bar, name)
> }
> +
> + /// Returns the size of configuration space.
> + fn cfg_size(&self) -> Result<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 => Ok(ConfigSpaceSize::Normal),
> + 4096 => Ok(ConfigSpaceSize::Extended),
> + _ => {
> + debug_assert!(false);
> + Err(EINVAL)
> + }
> + }
> + }
> +
> + /// Return an initialized config space object.
> + pub fn config_space<'a>(
> + &'a self,
> + ) -> Result<ConfigSpace<'a, { ConfigSpaceSize::Normal.as_raw() }>> {
> + Ok(ConfigSpace { pdev: self })
> + }
> +
> + /// Return an initialized config space object.
> + pub fn config_space_extended<'a>(
> + &'a self,
> + ) -> Result<ConfigSpace<'a, { ConfigSpaceSize::Extended.as_raw() }>> {
> + Ok(ConfigSpace { pdev: self })
> + }
> }
Powered by blists - more mailing lists