-
Notifications
You must be signed in to change notification settings - Fork 747
tock_registers::peripheral!
: Sound and testable register definitions
#4001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Awesome! Very excited to see some progress here. Can you briefly compare to prior approaches, namely your #3405 PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some thoughts from looking through the README
// Padding's length may also be inferred by the macro by specifying _ as | ||
// the data type: | ||
0x008 => _: _, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this include arbitrarily long gaps? If the gap is 100 bytes, will the system automatically make an array of padding?
Do you need to have 0x008 => _: _,
at all? What would happen if you did:
0x006 => byte0: u8 {Read, Write},
// gap here
0x00C => byte1: u8 {Read, Write},
Presumably that would be a compile-time failure?
But this code would work?
0x006 => byte0: u8 {Read, Write},
// HUGE gap here
0x007 => _: _,
0x100 => byte1: u8 {Read, Write},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I was originally planning to have users specify the size of the padding (syntax A):
0x0 => ctrl: u8 { Write },
0x1 => _: u8, // One byte of padding
0x2 => status: u16 { Read },
however, when I was working on the README, I realized the current design doesn't require the user to specify the size:
(0x0 => ctrl: WriteOnly<u8>),
(0x1 => _reserved),
(0x2 => status: ReadOnly<u16>),
I want to migrate as many uses away from register_structs!
as possible (because it is unsound), so I want to make the new syntax no less powerful than register_structs!
(apart from being MMIO-only, which was a necessary concession to keep the complexity reasonable). I originally tweaked the syntax to optionally allow the size to be inferred, which would've l
8000
ooked like (syntax B):
0x0 => ctrl: u8 { Write },
0x1 => _: _, // Inferred padding (length will be 0x2 - 0x1)
0x2 => status: u16 { Read },
However I ultimately decided to simplify it and remove the length entirely, making the current syntax (syntax C):
0x0 => ctrl: u8 { Write },
0x1 => _, // Inferred padding (length will be 0x2 - 0x1)
0x2 => status: u16 { Read },
However, I apparently missed a couple cases of syntax B in README when I made that change. I've updated the README and peripheral!
's docs to show syntax C, which is my current design. Sorry about the confusion, hopefully that clears some things up!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Syntax C is nice. I'm a big fan.
And I believe it mostly clears up my original confusion of whether gaps can be arbitrarily long. When you just have inferred padding, it feels like it could be any size. When you have an explicit inferred type there, it feels like it's constrained to an actual existing type size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like syntax C, with future expansion to allow 0x1 => _reserved_with_this_name: [u8; 16],
to allow both:
- A custom name for reserved fields.
- An expected datatype for a reserved field to make its size explicit.
```rust | ||
trait Registers { | ||
type a<'s>: tock_registers::Register<DataType = u8> + | ||
tock_registers::Read<LongName = ()> + | ||
tock_registers::Write<LongName = ()> where Self: 's; | ||
fn a(&self) -> Self::a<'_> { ... } | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you show an example with the bitfield parameter too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an example later, under the "Example: Using registers and bitfields" heading. Maybe I should move the "Register Trait Bounds" heading to the end, so "Example: Using registers and bitfields" would come right after "Defining bitfields"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a macro expansion for the bitfield parameter under that heading.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I misunderstood Branden's request. I added an register with a bitfield parameter and its trait expansion to the "Register Trait Bounds" section.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super excited for this; it looks like a nice set of ergonomic improvements on top of the safety fixes, and is designed nicely for minimum churn with extant code.
One general question, should we use Fake
or Mock
consistently through the documents and types here? I have vanishingly little experience with real-world unit testing, but I thought Mock
was the more standard term?
Sorry, something went wrong.
All reactions
(0x100 => @END), | ||
// If you have several adjacent registers, you can specify an array | ||
// register: | ||
0x00C => array: [u32; 4] { Read, Write }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0x00C => array: [u32; 4] { Read, Write }, | |
0x00C => gpioPins: [u32; 4] { Read, Write }, |
Maybe something like this for the example? When I first parsed this, I thought array
was a keyword for the macro indicating an array of regsiters.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I would like to specify a specific bitfield type, would I write:
gpioPins: [u32(Gpio::Bank); 4] { Read, Write },
ED48
Sorry, something went wrong.
All reactions
-
👍 1 reaction -
👀 1 reaction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a request to allow registers to be specified by type alias, i.e.:
peripheral! {
GpioPort {
0x0 => pins: GpioPins(GpioPin::Register) { Read, Write },
}
}
type GpioPins = [u8; 16];
That requires the traits to infer the fact the register is an array from the type itself. As a result, peripheral!
treats the type as an opaque value (a syn::Type
) and does not try to determine whether a register is an array or scalar. That also rules out the [u32(Gpio::Bank); 4]
syntax.
@kupiakos FYI
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking more like this syntax:
peripheral! {
GpioPort {
0x0 => pins: [Gpio::Bank; 4] { Read, Write },
}
}
Is there a specific reason to provide the primitive type backing a bitfield inside of peripheral!
? The bitfield register type should map to exactly one primitive type. I could see some value in documentation like the explicit offsets.
That requires the traits to infer the fact the register is an array from the type itself.
Yes, but not via any specialization.
Another alternative syntax could be:
peripheral! {
GpioPort {
0x0 => pins: [u32; 4](GpioPin::Register) { Read, Write },
}
}
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking more like this syntax:
peripheral! { GpioPort { 0x0 => pins: [Gpio::Bank; 4] { Read, Write }, } }Is there a specific reason to provide the primitive type backing a bitfield inside of
peripheral!
? The bitfield register type should map to exactly one primitive type. I could see some value in documentation like the explicit offsets.
I would love for users to only have to specify the bitfield type and have the macro/registers infer it. However, currently bitfield types implement the RegisterLongName
trait, which doesn't indicate the underlying primitive type that it applies to. I would love to change the trait to:
pub trait RegisterLongName {
// The underlying data type of this register (the u8/u16/u32/etc
// passed over the MMIO bus).
type Primitive: UIntLike;
}
However, many (most?) registers use the following RegisterLongName
impl:
impl RegisterLongName for () {}
regardless of the primitive type. So adding Primitive
would be a massively breaking change.
Instead, we could add a new trait that contains both the bitfield implementation and primitive, but that would result in sizable changes to the register_bitfield!
infrastructure. I have two reasons for not implementing those changes:
- I haven't used register bitfields enough to be confident implementing a v2 of it.
- I'm on a deadline to implement these changes which does not support rewriting
register_bitfield!
.
Another alternative syntax could be:
peripheral! { GpioPort { 0x0 => pins: [u32; 4](GpioPin::Register) { Read, Write }, } }
This is the syntax I envisioned using; I added an example to the README. Definitely changeable though.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured out how to make peripheral!
work without requiring the user to specify both the value type and the bitfield type and also without totally rewriting the bitfield stuff. This leads to the following syntax:
use tock_registers::{Aliased, peripheral, register_bitfields};
register_bitfields![u32,
Control [ ... ]
Pin [ ... ]
Status [ ... ]
Trigger [ ... ]
];
peripheral! {
Foo {
0x0 => control: Control::Register { Write },
// Array registers can have bitfields as well. The bitfield applies to
// each register in the array.
0x4 => pins: [Pin::Register; 4] { Read, Write },
// Registers can have different bitfields for read operations than for
// write operations. In that case, specify the type as
// Aliased<read bitfield, write bitfield>
0x8 => aliased: Aliased<Status::Register, Trigger::Register> { Read, Write },
// Array registers can a
1E80
lso be aliased:
0xc => motors: [Aliased<Status::Register, Trigger::Register>; 4] { Read, Write },
}
}
The value type (u32
for all the registers here) is taken from the register_bitfields!
invocation (transmitted through some new traits).
I personally prefer this; I always thought that specifying both the value type and the bitfield is redundant. However, this was a pretty big change to how the macro works, and making it involved deleting a bunch of complex code. Therefore I pushed it as a separate commit, so I can roll it back if you don't like the changes. I'm a little unsure of the change because I do not understand why the RegisterLongName and value type were specified separately in the first place.
I can't really make further progress on this PR until I know which way we're going on this issue (the design diverged pretty hard), so lets discuss this at this Friday's core WG call and reach a consensus on which way to go.
Oh, and feel free to register your opinion on this latest change by using thumbs up and thumbs down on this comment.
Sorry, something went wrong.
All reactions
-
👍 4 reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the reason to specify them separately in the old (well... current) syntax is because the width of the field determines the offset of the next field. While, in this new syntax, that's unnecessary since the offset of the next field is expressed explicitly. So this seems striclty better
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By "old" syntax, are you referring to the syntax that predates offset tests? i.e.:
#[repr(C)]
struct Registers {
a: ReadWrite<u32, Ctrl::Register>,
b: ReadWrite<u8, Status::Register>,
}
If so that makes sense to me!
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My opinions:
- The new syntax is strictly an improvement.
- It would be less confusing as a user to have the
::Register
type be a#[repr(transparent)]
struct over the data type with the expected size, rather than with another ZST and associated type - especially whenu32
andu8
, placed in the same location, are the actual primitive types being read.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In today's core WG call, we decided to move forward with this new syntax. I will squash the two commits in this PR together and move forward with the new design.
Sorry, something went wrong.
All reactions
/// This argument is used to support unusual chip designs (mainly some LiteX | ||
/// designs). Because the bus adapter can change the size of registers (e.g. a | ||
/// register with a data type of `u8` may take up more than 1 byte in the MMIO | ||
/// address space), `peripheral!` allows you to specify that an offset is to be | ||
/// inferred: | ||
/// ``` | ||
/// tock_registers::peripheral! { | ||
/// #[allow_bus_adapter] | ||
/// Foo { | ||
/// // The first register is still at offset 0. | ||
/// 0x0 => a: u8 { Read }, | ||
/// | ||
/// // This register has an inferred offset, which may depend on the bus | ||
/// // adapter passed to RealFoo. | ||
/// _ => b: u32 { Write }, | ||
/// } | ||
/// } | ||
/// ``` | ||
/// Note that a padding field cannot be followed by a field with an inferred | ||
/// offset. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to preface this question with, 'this is bananas, and I hate that LiteX has created this headache'—
How is endianness expressed in the context of a bus adapter? i.e. if you have nominal u8
's being pushed into u32
padding, does the u8
sit at the top or bottom of the resulting u32
(and what controls this)?
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just pushed my first revision of the BusAdapter trait: https://github.com/jrvanwhy/tock/blob/testable-registers-2/libraries/tock-register-interface/src/bus_adapter.rs
tock-registers doesn't see the individual memory accesses required to access a register value, the BusAdapter handles that. So endianess is the BusAdapter's responsibility.
Sorry, something went wrong.
All reactions
/// // This register is only present if the bounce feature is enabled. | ||
/// #[cfg(feature = "bounce")] | ||
/// 0x1 => bounce: u8 { Read, Write }, | ||
/// | ||
/// // Often, you will need to place a register with an inferred offset | ||
/// // (or padding) after a register with a `cfg` attribute, because the | ||
/// // offset may change depending on the crate's enabled features. | ||
/// _ => next_register: u8 {}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scares me a bit. I think I'd prefer forcing people to be more explicit, i.e. include the complimentary cfg(not
with a fixed size padding.
More generally, I think we should restrict inferred offsets to only work with allow_bus_adapter
. It can be a really rough bug to track down when memory map definitions are subtlety off. cfg
is risky as-is. I think requiring an explicit address for post-cfg stuff will help minimize the chance of hidden errors by providing more opportunity for structure validation.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upon more reflection, I'm starting to lean against allowing inferred offsets in any context. I think this is an area where explicit beats implicit. Inferred padding length is a reasonable ergonomic thing, but for any 'real' / accessible thing, I think we should require explicit placement / definition.
Sorry, something went wrong.
All reactions
-
👍 1 reaction -
👎 1 reaction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because the offset may change depending on the crate's enabled features.
Does this mean there is hardware where, say, the Control register is at address 0x8 but then on another variant of the chip that supports bounce the Control register would be at address 0xC? That's pretty scary.
It seems like this implicit placement would have to cascade to all future registers in the peripheral, so you could just use _
as the address for all of them? That seems like something we don't want.
Sorry, something went wrong.
All reactions
/// | ||
/// // This register has an inferred offset, which may depend on the bus | ||
/// // adapter passed to RealFoo. | ||
/// _ => b: u32 { Write }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more I think about this, the more complex it starts to feel to me. Does this mean that for something which is nominally
Foo {
0x0 => zero: u8
0x1 => one: u16
0x3 => three: u8
0x4 => four: u32
0x8 => eight: u8
}
on a "byte-aligned bus" the struct would be unchanged, but otherwise
// halfword-aligned bus becomes
Foo {
0x0 => zero: u8
0x1 => _
0x2 => one: u16
0x4 => three: u8
0x5 => _
0x6 => four: u32
0xa => eight: u8
}
// word-aligned bus becomes
Foo {
0x0 => zero: u8
0x1 => _
0x2 => _
0x3 => _
0x4 => one: u16
0x6 => _
0x7 => _
0x8 => three: u8
0x9 => _
0xa => _
0xb => _
0xc => four: u32
}
What happens if a struct like this has padding??
// byte-aligned bus
Nominal {
0x0 => zero: u8
0x1 => _: u8
0x2 => two: u16
}
// half-word does... this?
ExpandExplicitPadding {
0x0 => zero: u8
0x1 => _ // from bus expansion
0x2 => _: u8 // the originally defined padding
0x3 => _ // from bus expansion
0x4 => two: u16
}
// or this?
BusUsesExtantPadding {
0x0 => zero: u8
0x1 => _: u8 // originally defined padding meets bus expansion needs
0x2 => two: u16
}
// but then a word-aligned bus would need to add padding no matter what?
I don't know what's better here, but the more I think about things, the more "inferred offset" scares me.
One, kind of verbose, concrete suggestion might be something like:
/// tock_registers::peripheral! {
/// #[allow_bus_adapter(u8,u32)]
/// Foo {
/// 0x0,0x0 => a: u8 { Read },
/// 0x1,0x4 => b: u8 { Read },
where the allow_bus_adapter
explicitly states which alignments are allowed, and then the register placement definitions explicitly show how things are laid out, based on the underlying alignment?
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From tock-registers' perspective, the padding in LiteX registers is BusAdapters' problem, and does not show up in anything peripheral!
does. peripheral!
just sees that a register's size may differ from its data type's size.
My understanding is that LiteX registers do not have padding, @lschuermann to confirm.
To make your suggestion match my understanding, I think we could do:
use litex_registers::{C8B32, C32B32};
tock_registers::peripheral! {
#[allow_bus_adapter(C8B32, C32B32)]
Foo {
[0x0, 0x0] => a: u16 { Read },
[0x8, 0x4] => b: u8 { Read },
}
}
But I would have to implement it to be sure.
Sorry, something went wrong.
All reactions
5bce296
to
e84f756
Compare
Ultimately, #3405 was closed because it was unsound (apparently I closed it without saying why). It relied on deriving pointers to the MMIO registers from references to zero-sized-types, which is unsound under most (all?) of the pointer provenance models being considered for Rust. Similarities:
Differences:
|
All reactions
Sorry, something went wrong.
I've been using the terms the way they are used internally at Google; those definitions line up with the definitions of "Fake", "Stubs", and "Mocks" under the "From Martin Fowler about Mock and Stub" at https://stackoverflow.com/a/346440.
The rest of this comment explains the different meanings in my own words: Suppose, for example, that you have a function that depends on an RNG: trait Rng {
fn get_random(&mut self) -> u32;
}
// Returns true with 50% probability.
fn please_test_me<R: Rng>(rng: &R) -> bool {
rng.get_random() % 2 != 0
} The real implementation of A test using stubs would look like: struct StubEvenRng;
impl Rng for StubEvenRng {
fn get_random(&mut self) -> u32 {
4
}
}
struct StubOddRng;
impl Rng for StubOddRng {
fn get_random(&mut self) -> u32 {
7
}
}
#[test]
fn unit_test() {
assert!(!please_test_me(&mut StubEvenRng));
assert!(please_test_me(&mut StubOddRng));
} The stub is incredibly simple to write, but not very useful. E.g. you can't use it to thoroughly test a method that calls the RNG twice. A mock is too long to write here (and would generally be autogenerated by a library), but using it would look like: #[test]
fn unit_test() {
let mut mock = MockRng::new();
// Tell mock to return 4 then return 7
mock.on_call_return(4);
mock.on_call_return(7);
assert!(!please_test_me(&mut mock));
assert!(please_test_me(&mut mock));
} You can use a mock to test a function that calls the RNG multiple times, because you can program it to make any sequence of outputs. A fake is generally handwritten and doesn't require programming from within the unit test: #[derive(Default)]
struct FakeRng {
// Our pseudo-rng algorithm is to return incrementing integers
counter: u32,
}
impl Rng for FakeRng {
fn get_random(&mut self) -> u32 {
self.counter += 1;
self.counter
}
}
#[test]
fn unit_test() {
let mut fake = FakeRng::default();
assert!(please_test_me(&mut fake));
assert!(!please_test_me(&mut fake));
} The fake is more work to write, but is a much more versatile tool. In many cases, you can write a single fake of something, then use it for many unit tests. If you wrote a fake version of every peripheral a Tock kernel needs, you could spin the entire kernel up on the host system using all fake hardware! That's not practical with mocks/stubs, and is a use case I want to support. |
All reactions
-
👍 2 reactions
Sorry, something went wrong.
e84f756
to
de186d4
Compare
On the core WG call, someone asked for a document describing how code using
|
All reactions
Sorry, something went wrong.
|
||
register_structs! { | ||
```rust | ||
tock_registers::peripheral! { | ||
Registers { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rustdoc is supported in this position as well, correct (as documented by peripheral!
)? Consider adding a /// Documentation for Registers
or similar line here
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is. I added lines like that in both README and peripheral!
to make it clear that doc comments are supported on both peripheral definitions and fields.
Sorry, something went wrong.
All reactions
/// // triggers a memory-unsafe operation). | ||
/// 0x2 => c: u8 { UnsafeRead }, | ||
/// | ||
/// // Any combination of readable, writable, safe, and unsafe is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about 0x2 => c: u8 { UnsafeRead, Read }
. What happens?
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, my wording was extremely unclear. That results in a compile error -- I support at most one read-type operation and at most one write-type operation. I reworded that example to make it clear what is and is not supported.
Sorry, something went wrong.
All reactions
/// ``` | ||
/// Padding fields are ommitted from the generated trait. | ||
/// | ||
/// # Doc comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider either:
- Removing this section and place the rustdoc comments inside the starting example (as it should be immediately apparent and obvious to users), or
- Combining with the
cfg
attributes section below, as doc comment support is an extension of supporting attributes in general.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a bit of both. Added doc comments inside the starting example, then combined this with the cfg
attribute section. I used the new combined section to document where the doc comments end up.
Sorry, something went wrong.
All reactions
/// instead of their safe equivalents. | ||
/// | ||
/// # `cfg` attributes | ||
/// Peripheral declarations can have `cfg` attributes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a set of attribute-position proc macros that yield cfg
attributes - e.g. #[cfg_device]
and #[cfg_host]
both yield some long #[cfg]
. Would they work in this position?
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine if they can't work here, as we have simple workarounds - but the lack of support for custom attributes should be documented if so
Sorry, something went wrong.
All reactions
de186d4
to
4f56791
Compare
I had a sentence buried in
|
All reactions
Sorry, something went wrong.
Does this work? peripheral! {
Registers {
0x000 => cr: u8(Control::Register) { Read, Write },
0x001 => _,
0x008 => word: u32 { Read, Write },
0x00C => _,
0x010 => array: [u32; 4] { Read, Write },
}
} As in, can there be multiple I think you have a bug in your example (should be read_field()):
I like this: // You can no longer index into arrays using []. Instead, call
// .get(), which will return an Option<ArrayElement<_>>, and
// use that. Alternatively, call .iter() to get an
// ArrayIterator<_> and use that instead.
self.registers.array().get(2).unwrap().set(3); I'm concerned about this:
I assume that we can use the same default peripherals trick we do now, and most boards won't have to worry about these details, but board authors who want to specify exactly what is included can do so. If not, then I'm worried this is a lot of complexity to make board authors grapple with. |
All reactions
Sorry, something went wrong.
I don't see why not, and also I actually think these might be more appropriate to define in chip crates anyway |
All reactions
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not do an in-depth review yet, but this looks good to me generally.
I'd try to keep changes to the API surface exposed by the Read
(ex Readable
) and Write
(ex Writeable
) to a minimum. That includes re-introducing a ReadWrite
trait that's auto-derived on a Read + Write
field. I'd be happy to make these changes if you like.
I think this is also (deliberately?) missing Nevermind, it seems like the proposal does work for that, sorry!Aliased
... How do we feel about that? It is at least somewhat common to have an aliased register in, e.g., UART peripherals as a TX sink / RX source.
Sorry, something went wrong.
All reactions
/// A register that can be read, but which is not memory-safe to read. | ||
pub trait UnsafeRead: Register { | ||
/// # Safety | ||
/// Reading this register has hardware-specific safety requirements which | ||
/// the caller must comply with. | ||
unsafe fn read(self) -> <Self::DataType as DataType>::Value | ||
where | ||
Self::DataType: ScalarDataType; | ||
|
||
/// Read from an unsafe array register. Instead of using `read_at_unchecked` | ||
/// directly, callers are encouraged to call `get()` to get an | ||
/// `ArrayElement` pointing at the register, then invoke `read` on that. | ||
/// | ||
/// # Safety | ||
/// `index` must be less than `Self::LEN`. | ||
/// Reading this register has hardware-specific safety requirements which | ||
/// the caller must comply with. | ||
unsafe fn read_at_unchecked(self, index: usize) -> <Self::DataType as DataType>::Value | ||
where | ||
Self::DataType: ArrayDataType; | ||
} | ||
|
||
/// A regsiter that can be written, but which is not memory-safe to write. | ||
pub trait UnsafeWrite: Register { | ||
/// # Safety | ||
/// Writing this register has hardware-specific safety requirements which | ||
/// the caller must comply with. | ||
unsafe fn write(self, value: <Self::DataType as DataType>::Value) | ||
where | ||
Self::DataType: ScalarDataType; | ||
|
||
/// Write to an unsafe array register. Instead of using `write_at_unchecked` | ||
/// directly, callers are encouraged to call `get()` to get an | ||
/// `ArrayElement` pointing at the register, then invoke `read` on that. | ||
/// | ||
/// # Safety | ||
/// `index` must be less than `Self::LEN`. | ||
/// Writing this register has hardware-specific safety requirements which | ||
/// the caller must comply with. | ||
unsafe fn write_at_unchecked(self, index: usize, value: <Self::DataType as DataType>::Value) | ||
where | ||
Self::DataType: ArrayDataType; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer if we, for this particular PR, remove the content (i.e. methods) contained in these traits. I'm not strongly opposed to introducing the notion of "safety" into the register interface itself, but it's a can of worms that I think we should be carefully discussing in a follow-up PR. We do have some good arguments as to why our current approach of "unsafe construction, safe usage" as discussed in #3244 and #2593 is at least sufficient, if not preferable.
In short, with this PR merged we'd still be able to specify that a register is UnsafeRead
or UnsafeWrite
, but those registers would -- for the time being -- not expose any useful interfaces.
We should then reason about when exactly a register must be marked unsafe, which operations it should support, and how those operations are implemented. In particular, we may want an UnsafeRead
or UnsafeWrite
to not be traits with first-class access methods (such as the current read
/ write
), but simply have an unsafe access()
method that then returns a reference to something that implements Read
or Write
, and as such having the same API surface as the safe counterparts.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requiring that all safe usage of all of a peripheral's register is correct at the point of the peripheral's construction results in facile load-bearing unsafe
blocks and should be avoided. Consider that future edits to safe code will rarely check that unsafe
preconditions are being upheld, so load-bearing safe code should be kept as local as possible.
we may want an UnsafeRead or UnsafeWrite to not be traits with first-class access methods... simply have an unsafe access() method that then returns a reference to something that implements...
This is morally equivalent to unsafe { &*ptr }
; it's not in any way mutually exclusive with locally-unsafe
reads/writes. I would also recommend people prefer the locally-unsafe
read/writes as the unsafe
block requires local reasoning about state.
A similar option with a similar caveat would be to have the trait method to retrieve the register type from the peripheral type be made unsafe
for unsafe registers. That would block this PR on changing the safety of the register access and I'd suggest a syntax more like { unsafe Read, unsafe Write }
.
One advantage of that is that clippy::missing_safety_doc
can be made to enforce that # Safety
doc strings are present for register fields that contain UnsafeRead
or UnsafeWrite
, instead of having a generic # Safety 🤷🏻
section in the trait.
However, a disadvantage is it's no longer clear how to allow for registers that are unsafe to write + safe to read or vice versa, along with the safety caveat.
Overall I'd still prefer some way for peripheral macro users to document safety conditions that will be raised at the point where the UB may be exercised.
Sorry, something went wrong.
All reactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I'm opposed to anything you've written. It's just that I'd like to defer having exactly this discussion to a follow-up PR.
Whether we want unsafe methods that read/write directly, or ones that yield another ephemeral reference that can then be used safely seems like a mostly stylistic question. While I don't disagree with the unsafe { &*ptr }
argument above, it would be nice if we could avoid duplication of Read
and UnsafeRead
trait implementations, and it can also be nice to be able to "lease out" a safe reference to something that's generally UnsafeRead
or UnsafeWrite
when the unsafe
code can assert that the respective operations are safe temporarily, for the lifetime of the handed-out reference.
Still, those are discussions that arguably we ought to have in a follow-up PR. This is complex enough already without diving in to these interface discussions. They're also orthogonal -- this PR is quite nice as it changes little about the Read
/Write
(ex Readable
/Writeable
) interfaces. In line with that, let's defer introduction of the UnsafeRead
etc. traits to a follow-up PR.
Sorry, something went wrong.
All reactions
092979d
to
47a2705
Compare
This PR introduces `peripheral!` to `tock_registers`. `peripheral!` is a successor to `register_structs!` that makes several improvements: 1. It is sound. The types generated and used by `peripheral!` do not use `&` references into MMIO memory -- instead, only raw pointers are used. This avoids the unsoundness described in unsafe-code-guidelines issue 411. 2. It supports driver unit tests. Instead of only emitting a `struct`, it emits a trait representing the hardware's capabilities, which can be used by unit tests to fake or stub out hardware behavior. Of course, it also emits a struct that implements the trait by interacting with the real hardware. 3. It supports unsafe operations, as described in tock issue 3244. 4. It supports conditionally-compiled registers, as described in tock issue 3257. There is one major drawback relative to `register_structs!`: `peripheral!` is only designed to support MMIO registers. This was done to keep the implementation complexity manageable. For a basic introduction of its usage, see `libraries/tock-register-interface/README.md`, and for detailed documentation, see `libraries/tock-register-interface/src/peripheral.rs`.
With this change, the user can just specify a register bitfield type, rather than having to specify both the primitive type and the long name. This cuts down on duplication. This also simplifies how aliased registers are specified, making the user manually write out the `Aliased<>` type.
47a2705
to
6dd05b3
Compare
No action required from this comment, just linking an updated reference. Upstream had a brief conversation around this today ( rust-embedded/wg#387 ), and consensus / consolidated around the outcome that designs should only use raw pointers for MMIO accesses [which this update already does]. There is a new issue in the embedded wg tracking their progress to that end: rust-embedded/wg#791, which I'm posting here in case their work is a useful reference at any point. |
All reactions
-
👍 1 reaction
Sorry, something went wrong.
Something x86 includes is port-mapped I/O. For example, from the recent PR (#4385):
The existing Tock registers interface does support this, but I believe this new system could in a very similar fashion to the I don't think Port-Mapped I/O is going to be especially common in Architectures Tock supports, but I see it as a validation of this implementation direction, and I wanted to leave a note here about it. |
All reactions
Sorry, something went wrong.
alevy
brghena
ppannuto
bradjc
721F
alexandruradovici
kupiakos
lschuermann
At least 1 approving review is required to merge this pull request.
Successfully merging this pull request may close these issues.
This PR introduces
peripheral!
totock_registers
.peripheral!
is a successor toregister_structs!
that makes several improvements:peripheral!
do not use&
references into MMIO memory -- instead, only raw pointers are used. This avoids the unsoundness described in Can we have VolatileCell rust-lang/unsafe-code-guidelines#411.struct
, it emits a trait representing the hardware's capabilities, which can be used by unit tests to fake or stub out hardware behavior. Of course, it also emits a struct that implements the trait by interacting with the real hardware.WriteOnly
,ReadWrite
, andReadOnly
(?) variants for tock-register-interface? #3244.There is one major drawback relative to
register_structs!
:peripheral!
is only designed to support MMIO registers. This was done to keep the implementation complexity manageable.For a basic introduction of its usage, see
libraries/tock-register-interface/README.md
, and for detailed documentation, seelibraries/tock-register-interface/src/peripheral.rs
.This PR is only partially completed. At the moment, I suggest you review:
peripheral!
.Also, note that I plan to continue force-pushing to this branch, so this PR's history doesn't become ridiculously long.