mirror of https://github.com/torvalds/linux.git
381 lines
13 KiB
Rust
381 lines
13 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
//! Macro to define register layout and accessors.
|
|
//!
|
|
//! A single register typically includes several fields, which are accessed through a combination
|
|
//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
|
|
//! not all possible field values are necessarily valid.
|
|
//!
|
|
//! The macro in this module allow to define, using an intruitive and readable syntax, a dedicated
|
|
//! type for each register with its own field accessors that can return an error is a field's value
|
|
//! is invalid.
|
|
|
|
/// Defines a dedicated type for a register with an absolute offset, alongside with getter and
|
|
/// setter methods for its fields and methods to read and write it from an `Io` region.
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```no_run
|
|
/// register!(BOOT_0 @ 0x00000100, "Basic revision information about the GPU" {
|
|
/// 3:0 minor_revision as u8, "Minor revision of the chip";
|
|
/// 7:4 major_revision as u8, "Major revision of the chip";
|
|
/// 28:20 chipset as u32 ?=> Chipset, "Chipset model";
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
|
|
/// region. It is composed of 3 fields, for instance `minor_revision` is made of the 4 less
|
|
/// significant bits of the register. Each field can be accessed and modified using accessor
|
|
/// methods:
|
|
///
|
|
/// ```no_run
|
|
/// // Read from the register's defined offset (0x100).
|
|
/// let boot0 = BOOT_0::read(&bar);
|
|
/// pr_info!("chip revision: {}.{}", boot0.major_revision(), boot0.minor_revision());
|
|
///
|
|
/// // `Chipset::try_from` will be called with the value of the field and returns an error if the
|
|
/// // value is invalid.
|
|
/// let chipset = boot0.chipset()?;
|
|
///
|
|
/// // Update some fields and write the value back.
|
|
/// boot0.set_major_revision(3).set_minor_revision(10).write(&bar);
|
|
///
|
|
/// // Or just read and update the register in a single step:
|
|
/// BOOT_0::alter(&bar, |r| r.set_major_revision(3).set_minor_revision(10));
|
|
/// ```
|
|
///
|
|
/// Fields can be defined as follows:
|
|
///
|
|
/// - `as <type>` simply returns the field value casted as the requested integer type, typically
|
|
/// `u32`, `u16`, `u8` or `bool`. Note that `bool` fields must have a range of 1 bit.
|
|
/// - `as <type> => <into_type>` calls `<into_type>`'s `From::<<type>>` implementation and returns
|
|
/// the result.
|
|
/// - `as <type> ?=> <try_into_type>` calls `<try_into_type>`'s `TryFrom::<<type>>` implementation
|
|
/// and returns the result. This is useful on fields for which not all values are value.
|
|
///
|
|
/// The documentation strings are optional. If present, they will be added to the type's
|
|
/// definition, or the field getter and setter methods they are attached to.
|
|
///
|
|
/// Putting a `+` before the address of the register makes it relative to a base: the `read` and
|
|
/// `write` methods take a `base` argument that is added to the specified address before access,
|
|
/// and `try_read` and `try_write` methods are also created, allowing access with offsets unknown
|
|
/// at compile-time:
|
|
///
|
|
/// ```no_run
|
|
/// register!(CPU_CTL @ +0x0000010, "CPU core control" {
|
|
/// 0:0 start as bool, "Start the CPU core";
|
|
/// });
|
|
///
|
|
/// // Flip the `start` switch for the CPU core which base address is at `CPU_BASE`.
|
|
/// let cpuctl = CPU_CTL::read(&bar, CPU_BASE);
|
|
/// pr_info!("CPU CTL: {:#x}", cpuctl);
|
|
/// cpuctl.set_start(true).write(&bar, CPU_BASE);
|
|
/// ```
|
|
macro_rules! register {
|
|
// Creates a register at a fixed offset of the MMIO space.
|
|
(
|
|
$name:ident @ $offset:literal $(, $comment:literal)? {
|
|
$($fields:tt)*
|
|
}
|
|
) => {
|
|
register!(@common $name $(, $comment)?);
|
|
register!(@field_accessors $name { $($fields)* });
|
|
register!(@io $name @ $offset);
|
|
};
|
|
|
|
// Creates a register at a relative offset from a base address.
|
|
(
|
|
$name:ident @ + $offset:literal $(, $comment:literal)? {
|
|
$($fields:tt)*
|
|
}
|
|
) => {
|
|
register!(@common $name $(, $comment)?);
|
|
register!(@field_accessors $name { $($fields)* });
|
|
register!(@io$name @ + $offset);
|
|
};
|
|
|
|
// Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`, `BitOr`,
|
|
// and conversion to regular `u32`).
|
|
(@common $name:ident $(, $comment:literal)?) => {
|
|
$(
|
|
#[doc=$comment]
|
|
)?
|
|
#[repr(transparent)]
|
|
#[derive(Clone, Copy, Default)]
|
|
pub(crate) struct $name(u32);
|
|
|
|
// TODO: display the raw hex value, then the value of all the fields. This requires
|
|
// matching the fields, which will complexify the syntax considerably...
|
|
impl ::core::fmt::Debug for $name {
|
|
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
|
|
f.debug_tuple(stringify!($name))
|
|
.field(&format_args!("0x{0:x}", &self.0))
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl core::ops::BitOr for $name {
|
|
type Output = Self;
|
|
|
|
fn bitor(self, rhs: Self) -> Self::Output {
|
|
Self(self.0 | rhs.0)
|
|
}
|
|
}
|
|
|
|
impl ::core::convert::From<$name> for u32 {
|
|
fn from(reg: $name) -> u32 {
|
|
reg.0
|
|
}
|
|
}
|
|
};
|
|
|
|
// Defines all the field getter/methods methods for `$name`.
|
|
(
|
|
@field_accessors $name:ident {
|
|
$($hi:tt:$lo:tt $field:ident as $type:tt
|
|
$(?=> $try_into_type:ty)?
|
|
$(=> $into_type:ty)?
|
|
$(, $comment:literal)?
|
|
;
|
|
)*
|
|
}
|
|
) => {
|
|
$(
|
|
register!(@check_field_bounds $hi:$lo $field as $type);
|
|
)*
|
|
|
|
#[allow(dead_code)]
|
|
impl $name {
|
|
$(
|
|
register!(@field_accessor $name $hi:$lo $field as $type
|
|
$(?=> $try_into_type)?
|
|
$(=> $into_type)?
|
|
$(, $comment)?
|
|
;
|
|
);
|
|
)*
|
|
}
|
|
};
|
|
|
|
// Boolean fields must have `$hi == $lo`.
|
|
(@check_field_bounds $hi:tt:$lo:tt $field:ident as bool) => {
|
|
#[allow(clippy::eq_op)]
|
|
const _: () = {
|
|
kernel::build_assert!(
|
|
$hi == $lo,
|
|
concat!("boolean field `", stringify!($field), "` covers more than one bit")
|
|
);
|
|
};
|
|
};
|
|
|
|
// Non-boolean fields must have `$hi >= $lo`.
|
|
(@check_field_bounds $hi:tt:$lo:tt $field:ident as $type:tt) => {
|
|
#[allow(clippy::eq_op)]
|
|
const _: () = {
|
|
kernel::build_assert!(
|
|
$hi >= $lo,
|
|
concat!("field `", stringify!($field), "`'s MSB is smaller than its LSB")
|
|
);
|
|
};
|
|
};
|
|
|
|
// Catches fields defined as `bool` and convert them into a boolean value.
|
|
(
|
|
@field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool => $into_type:ty
|
|
$(, $comment:literal)?;
|
|
) => {
|
|
register!(
|
|
@leaf_accessor $name $hi:$lo $field as bool
|
|
{ |f| <$into_type>::from(if f != 0 { true } else { false }) }
|
|
$into_type => $into_type $(, $comment)?;
|
|
);
|
|
};
|
|
|
|
// Shortcut for fields defined as `bool` without the `=>` syntax.
|
|
(
|
|
@field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool $(, $comment:literal)?;
|
|
) => {
|
|
register!(@field_accessor $name $hi:$lo $field as bool => bool $(, $comment)?;);
|
|
};
|
|
|
|
// Catches the `?=>` syntax for non-boolean fields.
|
|
(
|
|
@field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt ?=> $try_into_type:ty
|
|
$(, $comment:literal)?;
|
|
) => {
|
|
register!(@leaf_accessor $name $hi:$lo $field as $type
|
|
{ |f| <$try_into_type>::try_from(f as $type) } $try_into_type =>
|
|
::core::result::Result<
|
|
$try_into_type,
|
|
<$try_into_type as ::core::convert::TryFrom<$type>>::Error
|
|
>
|
|
$(, $comment)?;);
|
|
};
|
|
|
|
// Catches the `=>` syntax for non-boolean fields.
|
|
(
|
|
@field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt => $into_type:ty
|
|
$(, $comment:literal)?;
|
|
) => {
|
|
register!(@leaf_accessor $name $hi:$lo $field as $type
|
|
{ |f| <$into_type>::from(f as $type) } $into_type => $into_type $(, $comment)?;);
|
|
};
|
|
|
|
// Shortcut for fields defined as non-`bool` without the `=>` or `?=>` syntax.
|
|
(
|
|
@field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt
|
|
$(, $comment:literal)?;
|
|
) => {
|
|
register!(@field_accessor $name $hi:$lo $field as $type => $type $(, $comment)?;);
|
|
};
|
|
|
|
// Generates the accessor methods for a single field.
|
|
(
|
|
@leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:ty
|
|
{ $process:expr } $to_type:ty => $res_type:ty $(, $comment:literal)?;
|
|
) => {
|
|
kernel::macros::paste!(
|
|
const [<$field:upper>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi;
|
|
const [<$field:upper _MASK>]: u32 = ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1);
|
|
const [<$field:upper _SHIFT>]: u32 = Self::[<$field:upper _MASK>].trailing_zeros();
|
|
);
|
|
|
|
$(
|
|
#[doc="Returns the value of this field:"]
|
|
#[doc=$comment]
|
|
)?
|
|
#[inline]
|
|
pub(crate) fn $field(self) -> $res_type {
|
|
kernel::macros::paste!(
|
|
const MASK: u32 = $name::[<$field:upper _MASK>];
|
|
const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
|
|
);
|
|
let field = ((self.0 & MASK) >> SHIFT);
|
|
|
|
$process(field)
|
|
}
|
|
|
|
kernel::macros::paste!(
|
|
$(
|
|
#[doc="Sets the value of this field:"]
|
|
#[doc=$comment]
|
|
)?
|
|
#[inline]
|
|
pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self {
|
|
const MASK: u32 = $name::[<$field:upper _MASK>];
|
|
const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
|
|
let value = ((value as u32) << SHIFT) & MASK;
|
|
self.0 = (self.0 & !MASK) | value;
|
|
|
|
self
|
|
}
|
|
);
|
|
};
|
|
|
|
// Creates the IO accessors for a fixed offset register.
|
|
(@io $name:ident @ $offset:literal) => {
|
|
#[allow(dead_code)]
|
|
impl $name {
|
|
#[inline]
|
|
pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
{
|
|
Self(io.read32($offset))
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn write<const SIZE: usize, T>(self, io: &T) where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
{
|
|
io.write32(self.0, $offset)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn alter<const SIZE: usize, T, F>(
|
|
io: &T,
|
|
f: F,
|
|
) where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
F: ::core::ops::FnOnce(Self) -> Self,
|
|
{
|
|
let reg = f(Self::read(io));
|
|
reg.write(io);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create the IO accessors for a relative offset register.
|
|
(@io $name:ident @ + $offset:literal) => {
|
|
#[allow(dead_code)]
|
|
impl $name {
|
|
#[inline]
|
|
pub(crate) fn read<const SIZE: usize, T>(
|
|
io: &T,
|
|
base: usize,
|
|
) -> Self where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
{
|
|
Self(io.read32(base + $offset))
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn write<const SIZE: usize, T>(
|
|
self,
|
|
io: &T,
|
|
base: usize,
|
|
) where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
{
|
|
io.write32(self.0, base + $offset)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn alter<const SIZE: usize, T, F>(
|
|
io: &T,
|
|
base: usize,
|
|
f: F,
|
|
) where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
F: ::core::ops::FnOnce(Self) -> Self,
|
|
{
|
|
let reg = f(Self::read(io, base));
|
|
reg.write(io, base);
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn try_read<const SIZE: usize, T>(
|
|
io: &T,
|
|
base: usize,
|
|
) -> ::kernel::error::Result<Self> where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
{
|
|
io.try_read32(base + $offset).map(Self)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn try_write<const SIZE: usize, T>(
|
|
self,
|
|
io: &T,
|
|
base: usize,
|
|
) -> ::kernel::error::Result<()> where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
{
|
|
io.try_write32(self.0, base + $offset)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn try_alter<const SIZE: usize, T, F>(
|
|
io: &T,
|
|
base: usize,
|
|
f: F,
|
|
) -> ::kernel::error::Result<()> where
|
|
T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
|
|
F: ::core::ops::FnOnce(Self) -> Self,
|
|
{
|
|
let reg = f(Self::try_read(io, base)?);
|
|
reg.try_write(io, base)
|
|
}
|
|
}
|
|
};
|
|
}
|