rust: io: perform conversions using AsRepr

For types that are layout-compatible with an I/O capable type, we would
want the ability to use them directly for I/O operations. E.g.

    bitfield! {
        pub struct Foo(u32) {
            ...
        }
    }

    #[repr(C)]
    struct Bar {
        foo: Foo,
    }

    let mmio: Mmio<'_, Bar> = ...;
    io_read!(mmio, .foo)

Currently this feature is available from `register!()` macro but not
otherwise available with `io_read!`, `io_write!`. Support this by
performing conversions to I/O primitives via the `AsRepr`/`AsReprMut`
trait.

This makes the `IoLoc::IoType` and `Register::Storage` redundant; thus
remove them; also convert register methods to use the `read_val` and
`write_val` instead.

Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: Gary Guo <gary@garyguo.net>
Tested-by: Alexandre Courbot <acourbot@nvidia.com>
Link: https://patch.msgid.link/20260901-typed_register-v4-4-5552b1d59525@garyguo.net
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
Gary Guo 2026-09-01 17:50:28 +01:00 committed by Danilo Krummrich
parent ef605a433d
commit ff067ace5f
4 changed files with 78 additions and 76 deletions

View File

@ -346,6 +346,15 @@ fn from(val: $storage) -> $name {
Self::from_raw(val)
}
}
// SAFETY: `$name` is transparent over `$storage` and `$storage` has no interior mutability.
unsafe impl $crate::mem::AsRepr for $name {
// Normalize `$storage` to the canonical repr type in case it is signed.
type Repr = <$storage as $crate::mem::AsRepr>::Repr;
}
// SAFETY: `$name` is transparent over `$storage`.
unsafe impl $crate::mem::AsReprMut for $name {}
};
// Definitions requiring knowledge of individual fields: private and public field accessors,

View File

@ -11,6 +11,10 @@
use crate::{
bindings,
mem::{
AsRepr,
AsReprMut, //
},
prelude::*,
ptr::{
Alignment,
@ -226,6 +230,17 @@ fn io_view<'a, IO: Io<'a>, U>(
Ok(unsafe { IO::Backend::project_view(view, projected_ptr) })
}
/// Returns the primitive view of a I/O view.
#[inline]
fn io_view_as_repr<'a, IO: Io<'a, Target = T>, T: AsRepr>(
this: IO,
) -> <IO::Backend as IoBackend>::View<'a, T::Repr> {
let view = this.as_view();
// SAFETY: `AsRepr` guarantees layout compatibility.
unsafe { IO::Backend::project_view(view, IO::Backend::as_ptr(view).cast::<T::Repr>()) }
}
/// I/O backends.
///
/// This is an abstract representation to be implemented by arbitrary I/O
@ -353,15 +368,12 @@ fn copy_write<T: IntoBytes>(view: Self::View<'_, T>, value: T) {
///
/// - The valid `Base` to operate on. For most registers, this should be [`Region`].
/// - The offset to access (returned by [`IoLoc::offset`]),
/// - The width of the access (determined by [`IoLoc::IoType`]),
/// - The type `T` in which the raw data is returned or provided.
/// - The type `T` in which the data is returned or provided.
///
/// `T` and `IoLoc::IoType` may differ: for instance, a typed register has `T` = the register type
/// with its bitfields, and `IoType` = its backing primitive (e.g. `u32`).
/// `T` is not necessarily the type for underlying I/O operation. Methods that take `IoLoc` have `T:
/// AsRepr` bound and the `<T as AsRepr>::Repr` type would be used to perform I/O and converted to
/// `T` instead.
pub trait IoLoc<Base: ?Sized, T> {
/// Size ([`u8`], [`u16`], etc) of the I/O performed on the returned [`offset`](IoLoc::offset).
type IoType: Into<T> + From<T>;
/// Consumes `self` and returns the offset of this location.
fn offset(self) -> usize;
}
@ -372,8 +384,6 @@ macro_rules! impl_usize_ioloc {
($($ty:ty),*) => {
$(
impl<const SIZE: usize> IoLoc<Region<SIZE>, $ty> for usize {
type IoType = $ty;
#[inline(always)]
fn offset(self) -> usize {
self
@ -498,10 +508,10 @@ fn try_cast<U>(self) -> Result<<Self::Backend as IoBackend>::View<'a, U>>
#[inline]
fn read_val(self) -> Self::Target
where
Self::Backend: IoCapable<Self::Target>,
Self::Target: Sized,
Self::Target: AsReprMut,
Self::Backend: IoCapable<<Self::Target as AsRepr>::Repr>,
{
Self::Backend::io_read(self.as_view())
Self::Target::from_repr(Self::Backend::io_read(io_view_as_repr(self)))
}
/// Write a value to I/O.
@ -520,10 +530,10 @@ fn read_val(self) -> Self::Target
#[inline]
fn write_val(self, value: Self::Target)
where
Self::Backend: IoCapable<Self::Target>,
Self::Target: Sized,
Self::Target: AsRepr,
Self::Backend: IoCapable<<Self::Target as AsRepr>::Repr>,
{
Self::Backend::io_write(self.as_view(), value)
Self::Backend::io_write(io_view_as_repr(self), Self::Target::into_repr(value))
}
/// Copy-read from I/O memory.
@ -645,7 +655,7 @@ fn copy_to_slice(self, data: &mut [u8])
#[inline(always)]
fn try_read8(self, offset: usize) -> Result<u8>
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
usize: IoLoc<Self::Target, u8>,
Self::Backend: IoCapable<u8>,
{
self.try_read(offset)
@ -655,7 +665,7 @@ fn try_read8(self, offset: usize) -> Result<u8>
#[inline(always)]
fn try_read16(self, offset: usize) -> Result<u16>
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
usize: IoLoc<Self::Target, u16>,
Self::Backend: IoCapable<u16>,
{
self.try_read(offset)
@ -665,7 +675,7 @@ fn try_read16(self, offset: usize) -> Result<u16>
#[inline(always)]
fn try_read32(self, offset: usize) -> Result<u32>
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
usize: IoLoc<Self::Target, u32>,
Self::Backend: IoCapable<u32>,
{
self.try_read(offset)
@ -675,7 +685,7 @@ fn try_read32(self, offset: usize) -> Result<u32>
#[inline(always)]
fn try_read64(self, offset: usize) -> Result<u64>
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
usize: IoLoc<Self::Target, u64>,
Self::Backend: IoCapable<u64>,
{
self.try_read(offset)
@ -685,7 +695,7 @@ fn try_read64(self, offset: usize) -> Result<u64>
#[inline(always)]
fn try_write8(self, value: u8, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
usize: IoLoc<Self::Target, u8>,
Self::Backend: IoCapable<u8>,
{
self.try_write(offset, value)
@ -695,7 +705,7 @@ fn try_write8(self, value: u8, offset: usize) -> Result
#[inline(always)]
fn try_write16(self, value: u16, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
usize: IoLoc<Self::Target, u16>,
Self::Backend: IoCapable<u16>,
{
self.try_write(offset, value)
@ -705,7 +715,7 @@ fn try_write16(self, value: u16, offset: usize) -> Result
#[inline(always)]
fn try_write32(self, value: u32, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
usize: IoLoc<Self::Target, u32>,
Self::Backend: IoCapable<u32>,
{
self.try_write(offset, value)
@ -715,7 +725,7 @@ fn try_write32(self, value: u32, offset: usize) -> Result
#[inline(always)]
fn try_write64(self, value: u64, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
usize: IoLoc<Self::Target, u64>,
Self::Backend: IoCapable<u64>,
{
self.try_write(offset, value)
@ -727,7 +737,7 @@ fn try_write64(self, value: u64, offset: usize) -> Result
#[inline(always)]
fn read8(self, offset: usize) -> u8
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
usize: IoLoc<Self::Target, u8>,
Self::Backend: IoCapable<u8>,
{
self.read(offset)
@ -739,7 +749,7 @@ fn read8(self, offset: usize) -> u8
#[inline(always)]
fn read16(self, offset: usize) -> u16
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
usize: IoLoc<Self::Target, u16>,
Self::Backend: IoCapable<u16>,
{
self.read(offset)
@ -751,7 +761,7 @@ fn read16(self, offset: usize) -> u16
#[inline(always)]
fn read32(self, offset: usize) -> u32
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
usize: IoLoc<Self::Target, u32>,
Self::Backend: IoCapable<u32>,
{
self.read(offset)
@ -763,7 +773,7 @@ fn read32(self, offset: usize) -> u32
#[inline(always)]
fn read64(self, offset: usize) -> u64
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
usize: IoLoc<Self::Target, u64>,
Self::Backend: IoCapable<u64>,
{
self.read(offset)
@ -775,7 +785,7 @@ fn read64(self, offset: usize) -> u64
#[inline(always)]
fn write8(self, value: u8, offset: usize)
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
usize: IoLoc<Self::Target, u8>,
Self::Backend: IoCapable<u8>,
{
self.write(offset, value)
@ -787,7 +797,7 @@ fn write8(self, value: u8, offset: usize)
#[inline(always)]
fn write16(self, value: u16, offset: usize)
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
usize: IoLoc<Self::Target, u16>,
Self::Backend: IoCapable<u16>,
{
self.write(offset, value)
@ -799,7 +809,7 @@ fn write16(self, value: u16, offset: usize)
#[inline(always)]
fn write32(self, value: u32, offset: usize)
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
usize: IoLoc<Self::Target, u32>,
Self::Backend: IoCapable<u32>,
{
self.write(offset, value)
@ -811,7 +821,7 @@ fn write32(self, value: u32, offset: usize)
#[inline(always)]
fn write64(self, value: u64, offset: usize)
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
usize: IoLoc<Self::Target, u64>,
Self::Backend: IoCapable<u64>,
{
self.write(offset, value)
@ -843,11 +853,12 @@ fn write64(self, value: u64, offset: usize)
#[inline(always)]
fn try_read<T, L>(self, location: L) -> Result<T>
where
T: AsReprMut,
L: IoLoc<Self::Target, T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
{
let view = io_view::<Self, L::IoType>(self, location.offset())?;
Ok(Self::Backend::io_read(view).into())
let view = io_view::<Self, T>(self, location.offset())?;
Ok(view.read_val())
}
/// Generic fallible write with runtime bounds check.
@ -876,12 +887,12 @@ fn try_read<T, L>(self, location: L) -> Result<T>
#[inline(always)]
fn try_write<T, L>(self, location: L, value: T) -> Result
where
T: AsRepr,
L: IoLoc<Self::Target, T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
{
let view = io_view::<Self, L::IoType>(self, location.offset())?;
let io_value = value.into();
Self::Backend::io_write(view, io_value);
let view = io_view::<Self, T>(self, location.offset())?;
view.write_val(value);
Ok(())
}
@ -920,9 +931,10 @@ fn try_write<T, L>(self, location: L, value: T) -> Result
#[inline(always)]
fn try_write_reg<T, L, V>(self, value: V) -> Result
where
T: AsRepr,
L: IoLoc<Self::Target, T>,
V: LocatedRegister<Self::Target, Location = L, Value = T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
{
let (location, value) = value.into_io_op();
@ -954,16 +966,13 @@ fn try_write_reg<T, L, V>(self, value: V) -> Result
#[inline(always)]
fn try_update<T, L, F>(self, location: L, f: F) -> Result
where
T: AsReprMut,
L: IoLoc<Self::Target, T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
F: FnOnce(T) -> T,
{
let view = io_view::<Self, L::IoType>(self, location.offset())?;
let value: T = Self::Backend::io_read(view).into();
let io_value = f(value).into();
Self::Backend::io_write(view, io_value);
let view = io_view::<Self, T>(self, location.offset())?;
view.write_val(f(view.read_val()));
Ok(())
}
@ -991,11 +1000,12 @@ fn try_update<T, L, F>(self, location: L, f: F) -> Result
#[inline(always)]
fn read<T, L>(self, location: L) -> T
where
T: AsReprMut,
L: IoLoc<Self::Target, T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
{
let view = io_view_assert::<Self, L::IoType>(self, location.offset());
Self::Backend::io_read(view).into()
let view = io_view_assert::<Self, T>(self, location.offset());
view.read_val()
}
/// Generic infallible write with compile-time bounds check.
@ -1022,12 +1032,12 @@ fn read<T, L>(self, location: L) -> T
#[inline(always)]
fn write<T, L>(self, location: L, value: T)
where
T: AsRepr,
L: IoLoc<Self::Target, T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
{
let view = io_view_assert::<Self, L::IoType>(self, location.offset());
let io_value = value.into();
Self::Backend::io_write(view, io_value);
let view = io_view_assert::<Self, T>(self, location.offset());
view.write_val(value)
}
/// Generic infallible write of a fully-located register value.
@ -1064,9 +1074,10 @@ fn write<T, L>(self, location: L, value: T)
#[inline(always)]
fn write_reg<T, L, V>(self, value: V)
where
T: AsRepr,
L: IoLoc<Self::Target, T>,
V: LocatedRegister<Self::Target, Location = L, Value = T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
{
let (location, value) = value.into_io_op();
@ -1098,14 +1109,13 @@ fn write_reg<T, L, V>(self, value: V)
#[inline(always)]
fn update<T, L, F>(self, location: L, f: F)
where
T: AsReprMut,
L: IoLoc<Self::Target, T>,
Self::Backend: IoCapable<L::IoType>,
Self::Backend: IoCapable<<T as AsRepr>::Repr>,
F: FnOnce(T) -> T,
{
let view = io_view_assert::<Self, L::IoType>(self, location.offset());
let value: T = Self::Backend::io_read(view).into();
let io_value = f(value).into();
Self::Backend::io_write(view, io_value);
let view = io_view_assert::<Self, T>(self, location.offset());
view.write_val(f(view.read_val()));
}
}

View File

@ -117,9 +117,6 @@
/// Trait implemented by all registers.
pub trait Register: Sized {
/// Backing primitive type of the register.
type Storage: Into<Self> + From<Self>;
/// Start offset of the register.
///
/// The interpretation of this offset depends on the type of the register.
@ -135,8 +132,6 @@ impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for ()
where
T: FixedRegister,
{
type IoType = T::Storage;
#[inline(always)]
fn offset(self) -> usize {
T::OFFSET
@ -149,8 +144,6 @@ impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for T
where
T: FixedRegister,
{
type IoType = T::Storage;
#[inline(always)]
fn offset(self) -> usize {
T::OFFSET
@ -174,8 +167,6 @@ impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for FixedRegisterLoc<T>
where
T: FixedRegister,
{
type IoType = T::Storage;
#[inline(always)]
fn offset(self) -> usize {
T::OFFSET
@ -246,8 +237,6 @@ impl<const SIZE: usize, T, B> IoLoc<Region<SIZE>, T> for RelativeRegisterLoc<T,
T: RelativeRegister,
B: RegisterBase<T::BaseFamily> + ?Sized,
{
type IoType = T::Storage;
#[inline(always)]
fn offset(self) -> usize {
RelativeRegisterLoc::offset(self)
@ -289,8 +278,6 @@ impl<const SIZE: usize, T> IoLoc<Region<SIZE>, T> for RegisterArrayLoc<T>
where
T: RegisterArray,
{
type IoType = T::Storage;
#[inline(always)]
fn offset(self) -> usize {
T::OFFSET + self.0 * T::STRIDE
@ -377,8 +364,6 @@ impl<const SIZE: usize, T, B> IoLoc<Region<SIZE>, T> for RelativeRegisterArrayLo
T: RelativeRegisterArray,
B: RegisterBase<T::BaseFamily> + ?Sized,
{
type IoType = T::Storage;
#[inline(always)]
fn offset(self) -> usize {
self.0.offset() + self.1 * T::STRIDE

View File

@ -213,8 +213,6 @@ pub(crate) fn register(def: RegDef) -> Result<TokenStream> {
);
impl ::kernel::io::register::Register for #name {
type Storage = #storage;
const OFFSET: usize = #offset;
}
));