Skip to content
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

Add Mstatus helpers to allow setting fields in Mstatus #207

Merged
merged 8 commits into from
May 12, 2024

Conversation

jsgf
Copy link
Contributor

@jsgf jsgf commented May 7, 2024

Without needing to touch the CSR. This allows multiple changes in a single register write.

@jsgf jsgf requested a review from a team as a code owner May 7, 2024 00:52
@jsgf jsgf force-pushed the mstatus-field-set branch 2 times, most recently from f766976 to a10e637 Compare May 7, 2024 01:20
@romancardenas
Copy link
Contributor

I find it confusing having methods that modify MStatus that do not modify the mstatus register, but a "copy". Alternatively, I like the idea of register builders (see #98). However, the PR looks stale...

Would you consider adapting the builders approach for this PR?

@jsgf
Copy link
Contributor Author

jsgf commented May 7, 2024

I think there's a few things to pick apart here:

  • a type representing possible values of mstatus
  • the actual mstatus csr
  • updating the csr with a new value

The third one is what adds the complications, because of the special atomic bit set/clear operations in a single csr instruction. These are very useful for all the single-bit fields in mstatus so we wouldn't want to do without them. But in the general case - either updating a multi-bit field or updating multiple fields at once - requires a full read/modify/write.

So as I see it, the current "operate directly on mstatus" methods are the special case exception, and the common case is:

let mut mstatus = mstatus::read();
mstatus = mstatus.set_X().set_Y();
mstatus::write(mstatus);

Would you consider adapting the builders approach for this PR?

I'm not sure how that would really be different? As I said, my use-case here is to read mstatus, make some changes, then put it back, whereas a builder would imply building new Mstatus values from scratch.

But I agree with you about the potential for confusion. I should definitely add a clarifying line in the doc comment to say it updates the Mstatus value but not the csr, and maybe rename them to update_?

@jsgf
Copy link
Contributor Author

jsgf commented May 7, 2024

I added a commit to:

  • add clarifying comment
  • rename the methods from set_ -> update_ to make them more distinct from the mstatus::set_ functions.

@jsgf jsgf force-pushed the mstatus-field-set branch 2 times, most recently from 37581f1 to 79d85c6 Compare May 7, 2024 15:33
@jsgf
Copy link
Contributor Author

jsgf commented May 7, 2024

Oh I just noticed there's no write_as for updating a csr from a type. And mstatus doesn't have write_as_usize...

@jsgf
Copy link
Contributor Author

jsgf commented May 7, 2024

Hm, need to do more testing.

@jsgf
Copy link
Contributor Author

jsgf commented May 7, 2024

Ugh, shift vs & precedence. Looks like this is correct now.

/// Note this updates the [`Mstatus`] value, but does not affect the mstatus
/// CSR itself. See [`set_sie`]/[`clear_sie`] to directly update the CSR.
#[inline]
pub fn update_sie(&self, sie: bool) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat of a bikeshed, but methods that set a bit in a bitfield without clearing the rest of the bitfield typically use the modify_* convention, e.g. in pac crates generated with svd2rust.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cortex-m, they use the set_* convention. I would align with them

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with set but the concern was it would be confusing vs the functions which act directly on the csr. Also there's a semantic difference as the csr functions use parameterleas set/clear for single bit fields whereas the update/modify/... on Mstatus takes a parameter.

@romancardenas
Copy link
Contributor

Whenever I have doubts about new features, I take a look at how cortex-m works. For instance, the FPSCR. They do have x methods to get bit fields and set_x methods to set bit fields in the read value. Next, they provide a write function that takes the register structure and writes its value in the register using inline assembly. This looks very similar to your proposal @jsgf

Let me take some time to review your changes, I'll try to provide a review ASAP.

@@ -72,6 +72,15 @@ impl From<bool> for Endianness {
}

impl Mstatus {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have macros for structs like Mstatus, as I expect a lot of duplicates with this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could have a pub(crate) bit module somewhere with bitwise functions for usize. In this way, we avoid having methods in every register, and all registers use the same function. I'm not sure if this would help us optimize the binary size or if the compiler is smart enough to collapse all these methods (don't think so).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I saw a lot of opportunity to collapse some repeated code.

Copy link
Contributor

@romancardenas romancardenas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's follow the set_* approach as in cortex-m. Also, maybe it is a good moment to add a utility module with bit-wise functions that can be shared among all the registers (it feels like we will need it in the future)

/// Note this updates the [`Mstatus`] value, but does not affect the mstatus
/// CSR itself. See [`set_sie`]/[`clear_sie`] to directly update the CSR.
#[inline]
pub fn update_sie(&self, sie: bool) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cortex-m, they use the set_* convention. I would align with them

riscv/src/register/mstatus.rs Outdated Show resolved Hide resolved
/// Machine Interrupt Enable
#[inline]
pub fn mie(&self) -> bool {
self.bits & (1 << 3) != 0
}

/// Update Machine Interrupt Enable
///
/// Note this updates the [`Mstatus`] value, but does not affect the mstatus
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Note this updates the [`Mstatus`] value, but does not affect the mstatus
/// Note this updates the [`Mstatus`] value read previously, but does not affect the mstatus

@@ -72,6 +72,15 @@ impl From<bool> for Endianness {
}

impl Mstatus {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could have a pub(crate) bit module somewhere with bitwise functions for usize. In this way, we avoid having methods in every register, and all registers use the same function. I'm not sure if this would help us optimize the binary size or if the compiler is smart enough to collapse all these methods (don't think so).

@jsgf
Copy link
Contributor Author

jsgf commented May 8, 2024

@romancardenas Yeah, I think a good general structure for any register is to define a type to abstract all the bitfields, read/write operations in terms of that type, and operations on the type to get/set each field. And as an optimization, specific atomic set operations directly on the register if it makes a difference (as it does for riscv single bit fields, and multi-bit in some situations). But it's not really worth having those direct-access functions for anything that's going to require a full read/modify/write pattern anyway.

mstatus is just where I've run into this for my current project, but this pattern applies to pretty much everything in riscv::registers.

For registers which only contain one logical field - eg the pmp_addr registers - you could say that it's probably not necessary to define a new type for it. But that 2 bit shift keeps biting me, so I'd probably still go for it...

jsgf added 6 commits May 8, 2024 12:39
Without needing to touch the CSR. This allows multiple changes in a
single register write.
And add a clarifying doc comment that this is an operation on Mstatus
values, not on the CSR itself.
This adds a macro to generate `write(value:T)` function for writing a
structured value to a CSR.
Allow `Mstatus` values to be directly written back to the CSR.
Copy link
Contributor

@romancardenas romancardenas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hard work! Have a look to make comments, especially the one regarding the setter approach.

@@ -36,6 +36,7 @@
#![allow(clippy::missing_safety_doc)]

pub mod asm;
pub(crate) mod bits;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can leave it as this, but now I don't find any reason why we should hide it from dependents. Maybe PACs find them handy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd hold off on that until we're confident it's a stable public api.

Comment on lines 76 to 89
/// Helper to insert a bitfield into Mstatus
#[inline]
fn bf_insert(&self, bit: usize, width: usize, val: usize) -> Self {
Self {
bits: bf_insert(self.bits, bit, width, val),
}
}

/// Helper to extract a bitfield from Mstatus
#[inline]
fn bf_extract(&self, bit: usize, width: usize) -> usize {
bf_extract(self.bits, bit, width)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Helper to insert a bitfield into Mstatus
#[inline]
fn bf_insert(&self, bit: usize, width: usize, val: usize) -> Self {
Self {
bits: bf_insert(self.bits, bit, width, val),
}
}
/// Helper to extract a bitfield from Mstatus
#[inline]
fn bf_extract(&self, bit: usize, width: usize) -> usize {
bf_extract(self.bits, bit, width)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally prefer using the new utility functions instead of filling all the registers with new methods. But it is not a hard feeling, if you think this approach has advantages, please write them so we can choose the best option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mostly to save on repeating Self { bits: ... } but if we're going with the &mut self approach there's no benefit.

@@ -81,46 +96,106 @@ impl Mstatus {
/// Supervisor Interrupt Enable
#[inline]
pub fn sie(&self) -> bool {
self.bits & (1 << 1) != 0
self.bf_extract(1, 1) != 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.bf_extract(1, 1) != 0
bf_extract(self.bits, 1, 1) != 0

/// update the CSR.
#[inline]
pub fn set_sie(&self, sie: bool) -> Self {
self.bf_insert(1, 1, sie as usize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.bf_insert(1, 1, sie as usize)
self.bits = bf_insert(self.bits, 1, 1, sie as usize);

/// affect the mstatus CSR itself. See [`set_sie`]/[`clear_sie`] to directly
/// update the CSR.
#[inline]
pub fn set_sie(&self, sie: bool) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn set_sie(&self, sie: bool) -> Self {
pub fn set_sie(&mut self, sie: bool) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From cortex-m:

#[inline]
pub fn set_n(&mut self, n: bool) {
    let mask = 1 << 31;
    match n {
        true => self.bits |= mask,
        false => self.bits &= !mask,
    }
}

So let's stick with the classic setter approach with mutable reference and no return value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about returning &mut Self so you can chain them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your idea!

@jsgf
Copy link
Contributor Author

jsgf commented May 9, 2024

BTW there's a lot of repetition here and in other modules - what do you think about a larger scale macro to generate all of this more automatically? It would make it easier to have all the different registers be consistent in form.

(I'm not a huge fan of big macros, but this crate already has a fair amount of macro-driven codegen and I think it tips the cost/benefit scales the right way for this.)

(In a separate PR of course.)

Copy link
Contributor

@romancardenas romancardenas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

found a 🐛

}

/// Supervisor Previous Privilege Mode
#[inline]
pub fn spp(&self) -> SPP {
match self.bits & (1 << 8) != 0 {
match bf_extract(self.bits, 7, 1) != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
match bf_extract(self.bits, 7, 1) != 0 {
match bf_extract(self.bits, 8, 1) != 0 {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh oops. Definitely need a macro-based mechanism that can derive all this from a single source of truth.

@romancardenas
Copy link
Contributor

I agree with you. We could explore using macros to "ease" the new changes to all the registers

Also remove local bf_* helpers
Copy link
Contributor

@romancardenas romancardenas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

facebook-github-bot pushed a commit to facebook/sapling that referenced this pull request May 10, 2024
Summary:
Bring in:
- rust-embedded/riscv#206 - Eq for Permission
- rust-embedded/riscv#207 - Mstatus update operations

Details of 207 are still under discussion so this will probably need another freshen (and go back to pulling from the riscv repo once its landed).

Reviewed By: dtolnay

Differential Revision: D57136236

fbshipit-source-id: 09e5365d3fd499c4dccae7d2ada333bf369fac47
facebook-github-bot pushed a commit to facebookexperimental/rust-shed that referenced this pull request May 10, 2024
Summary:
Bring in:
- rust-embedded/riscv#206 - Eq for Permission
- rust-embedded/riscv#207 - Mstatus update operations

Details of 207 are still under discussion so this will probably need another freshen (and go back to pulling from the riscv repo once its landed).

Reviewed By: dtolnay

Differential Revision: D57136236

fbshipit-source-id: 09e5365d3fd499c4dccae7d2ada333bf369fac47
@romancardenas romancardenas added this pull request to the merge queue May 12, 2024
Merged via the queue into rust-embedded:master with commit e9f43ae May 12, 2024
95 checks passed
@jsgf jsgf deleted the mstatus-field-set branch May 12, 2024 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants