`register!` gives every register a location with the `ReadWrite` access mode, so any register can be read, written and updated, including the ones the hardware defines as read-only or write-only.
Add the `#[ro]` and `#[wo]` attributes, which make the macro use the `ReadOnly` or `WriteOnly` mode instead: in the register's `OffsetLoc` for fixed registers, and as `RegisterArray::Access` for arrays. Reading a write-only register, or writing or updating a read-only one, then does not compile. An alias does not inherit the mode of the register it aliases. Add an example to the `register!` documentation. The tyr driver uses the attributes in a following patch. Signed-off-by: Daniel Almeida <[email protected]> --- rust/kernel/io/register.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++ rust/macros/io/register.rs | 35 ++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index 9218dc66017b..5c0188947879 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -147,6 +147,13 @@ impl<Base: ?Sized, T> OffsetLoc<Base, T> { pub const fn new(offset: usize) -> Self { Self(offset, PhantomData) } +} + +impl<Base: ?Sized, T, A> OffsetLoc<Base, T, A> { + #[inline] + pub const fn with_access(offset: usize) -> Self { + Self(offset, PhantomData) + } #[inline] pub const fn const_offset(self) -> usize { @@ -596,6 +603,52 @@ pub const fn element_alias_offset<Base: ?Sized, Alias: RegisterArray<Base = Base /// io_project!(io, build: CPU0).write_reg(CPU_CTL::zeroed()); /// # } /// ``` +/// +/// ## Access modes +/// +/// Registers are read-write unless declared with the `#[ro]` (read-only) or `#[wo]` (write-only) +/// attribute. Reading a write-only register, or writing or updating a read-only one, does not +/// compile: +/// +/// ```no_run +/// use kernel::io::{ +/// register, +/// Io, +/// Region, +/// }; +/// # use kernel::io::Mmio; +/// +/// register! { +/// base: Region<0x1000>; +/// +/// #[ro] +/// pub R1(u32) @ 0x0 { +/// 31:0 value; +/// } +/// +/// #[wo] +/// pub R2(u32) @ 0x4 { +/// 31:0 value; +/// } +/// +/// #[ro] +/// pub R3: u8 @ 0x8; +/// } +/// +/// # fn test(io: Mmio<'_, Region<0x1000>>) { +/// let r1 = io.read(R1); +/// let r3 = io.read(R3); +/// io.write_reg(R2::zeroed()); +/// +/// // Neither of these compiles. +/// // io.write_reg(r1); +/// // io.read(R2); +/// # } +/// ``` +/// +/// The mode applies to accesses through the register's location only. An alias is read-write +/// unless declared `#[ro]` or `#[wo]` itself, and the register's offset can still be accessed +/// directly through the I/O region. #[macro_export] macro_rules! register { ($($tt:tt)*) => { diff --git a/rust/macros/io/register.rs b/rust/macros/io/register.rs index 2d92c7f41447..171a4c1f96b4 100644 --- a/rust/macros/io/register.rs +++ b/rust/macros/io/register.rs @@ -63,6 +63,7 @@ struct Reg { attrs: Vec<Attribute>, vis: Visibility, name: Ident, + access: Type, unique: bool, ty: Type, array: Option<RegArrayDef>, @@ -72,7 +73,26 @@ struct Reg { impl Parse for Reg { fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> { - let attrs = input.call(Attribute::parse_outer)?; + let mut attrs = input.call(Attribute::parse_outer)?; + let modes = attrs + .extract_if(.., |attr| { + attr.path().is_ident("ro") || attr.path().is_ident("wo") + }) + .collect::<Vec<_>>(); + if let Some(attr) = modes.get(1) { + Err(Error::new_spanned( + attr, + "only one of `#[ro]` and `#[wo]` can be specified", + ))? + } + let access = match modes.first() { + None => parse_quote!(::kernel::io::access::ReadWrite), + Some(attr) if attr.path().is_ident("ro") => { + parse_quote!(::kernel::io::access::ReadOnly) + } + Some(_) => parse_quote!(::kernel::io::access::WriteOnly), + }; + let vis = input.parse()?; let name = input.parse()?; @@ -161,6 +181,7 @@ fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> { attrs, vis, name, + access, unique, ty, array, @@ -205,6 +226,7 @@ pub(crate) fn register(def: RegDef) -> Result<TokenStream> { attrs, vis, name, + access, unique, ty, array, @@ -220,7 +242,7 @@ pub(crate) fn register(def: RegDef) -> Result<TokenStream> { RegOffset::Fixed { offset } => quote!(#offset), RegOffset::Alias { alias } => { quote_spanned!(alias.span().resolved_at(span) => - ::kernel::io::register::OffsetLoc::<#base, _>::const_offset(#alias) + ::kernel::io::register::OffsetLoc::<#base, _, _>::const_offset(#alias) ) } RegOffset::ElementAlias { alias, idx } => { @@ -246,15 +268,16 @@ pub(crate) fn register(def: RegDef) -> Result<TokenStream> { if unique { outputs.extend(quote!( impl ::kernel::io::register::FixedIoLoc<#base> for #ty { - type Location = ::kernel::io::register::OffsetLoc<#base, #ty>; + type Location = ::kernel::io::register::OffsetLoc<#base, #ty, #access>; const LOCATION: Self::Location = #name; } )) } outputs.extend(quote_spanned!(span => - #(#attrs)* #vis const #name: ::kernel::io::register::OffsetLoc<#base, #ty> = - ::kernel::io::register::OffsetLoc::new(#offset); + #(#attrs)* #vis const #name: + ::kernel::io::register::OffsetLoc<#base, #ty, #access> = + ::kernel::io::register::OffsetLoc::with_access(#offset); )); } @@ -283,7 +306,7 @@ impl ::kernel::io::register::Array for #name {} impl ::kernel::io::register::RegisterArray for #name { type Base = #base; - type Access = ::kernel::io::access::ReadWrite; + type Access = #access; const OFFSET: usize = #offset; const SIZE: usize = #size; const STRIDE: usize = #stride; -- 2.56.0
