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

Modeling of AFL/AFH registers presents a bit of a footgun #206

Open
cbiffle opened this issue Jun 28, 2023 · 5 comments
Open

Modeling of AFL/AFH registers presents a bit of a footgun #206

cbiffle opened this issue Jun 28, 2023 · 5 comments

Comments

@cbiffle
Copy link
Contributor

cbiffle commented Jun 28, 2023

I've now ported a nontrivial firmware application over to stm32-metapac and I'm delighted with the results. It has enabled me to extend my G0 prototype to support C011 with very few code changes. So, thank you for all the hard work there.

I ran into something interesting during the porting process. First, I kept getting zero-byte binaries, but this was my own mistake. (In case anyone's reading this with that problem: make sure you've specified the rt and pac features and included the -Tlink.x linker arg in your target triple. And not, say, in a different but unrelated target triple you copied from another project. Learn from my mistakes.)

Then I started getting very small binaries -- like 5 kiB instead of the ~18 kiB I was expecting. This is almost always a sign that rustc has decided that some code has become an unconditional panic or UB and eliminated the rest of the program.

After spending some time hunting and reading the machine code, the problem turned out to be my use of the AFRH register in the GPIO block. Previously, using the traditional PAC, I had

    p.GPIOA.afrh.write(|w| {
        w.afsel9().af1(); // USART1_TX
        w.afsel10().af1(); // USART1_RX
        w
    });

During the port to the metapac, I rewrote this code to

    device::GPIOA.afr(1).write(|w| {
        w.set_afr(9, 1); // USART1_TX
        w.set_afr(10, 1); // USART1_RX
    });

(This reflects the discovery that the registers described in the reference manual as AFRL / AFRH are modeled in the metapac as a two-element array -- which I'm not totally convinced about, but I can see the advantages for HALs!)

This code was compiling into an unconditional program-ending panic. Perhaps you can see the problem already. :-) If, like me, it isn't obvious, the answer is: the AFR field arrays in each of the two AFRx registers are treated as 8-element arrays. This means to set (say) AFRH9, you need to set_afr(1, 1) instead of set_afr(9, 1).

Given that the fields in the AFRH register are numbered from 8 in the reference manual and all other SDKs, this seems like it's going to bite other people in the future. I've altered my code to use set_afr(9 - 8, 1) to try and remind me what's going on in the future.

I wonder if you can think of a way to address? Since the setters already contain an index-checking assert, I suppose they could insist that the accesses start at 8 and subtract 8, but... even though that would have fixed my bug, it seems really weird and inconsistent.

Allowing the use of constants for the indices maybe? Passing AFR8 which is secretly equivalent to 0 would make the code easier to read in terms of the datasheet. However, since the registers are modeled as an array, the types of AFRL and AFRH must match -- meaning that this would open the risk of using AFR8 as an index to AFRL, which would be bad.

Anyway, as you can probably infer from my questions, I don't see a super obvious fix here -- but I wanted to raise it before it surprises other people.

@cbiffle
Copy link
Contributor Author

cbiffle commented Jun 28, 2023

(Side note: in cases like this I really wish there were a way to have rustc turn certain guaranteed-at-runtime panics into compile errors! Like a semi-static assert. My indices to these arrays will almost always be constants, and so the assert will either be optimized out, or will end the program, but either way the compile will succeed.)

@Dirbaio
Copy link
Member

Dirbaio commented Jun 28, 2023

This reflects the discovery that the registers described in the reference manual as AFRL / AFRH are modeled in the metapac as a two-element array -- which I'm not totally convinced about, but I can see the advantages for HALs!

yeah, that's exactly the reason why it is like this :)

stm32-rs PACs don't have this issue because they simply don't arrayify AFRx/AFSELx. It has two downsides:

  • It forces using horrible macros in the HAL.
  • It bloats compile times (of PAC and HAL) a lot.

I don't see any easy way to fix it... The yaml matches how the registers are: 2 AFRx regs with 8 fields each.

@cbiffle
Copy link
Contributor Author

cbiffle commented Jun 28, 2023

I don't use HALs, but even in non-HAL code, the stm32-rs approach to the GPIO registers in general forces horrible code duplication or macros. So I definitely sympathize with wanting to avoid that.

I also don't see an easy fix, and I agree that the YAML matches reality. I am concerned about the indices in the top register not matching what the user might expect though.

One approach that I do not recommend would be wrapping the afrl/afrh register pair in some kind of abstraction that makes it look like one register. Many of my applications depend on being able to reason about when, exactly, the alternate function switches, for correctness and to avoid glitching pins -- the current API allows this by making writes to each register explicit. Presenting them as a pair would open the question of which are written, in what order, and with how many cycles between. (I don't mean to imply that I think you're considering this option, but that doesn't mean someone else won't consider it!)

Edit: I think perhaps the best "fix" here is an expansion of the rustdoc on these registers, to explain the potential error and why it exists. I can have a look at doing that.

@cbiffle
Copy link
Contributor Author

cbiffle commented Jun 28, 2023

.......so if you're trying to keep this language-independent...where do I put a rustdoc block containing a Rust code example to attach to the AFR registers? The YAML file isn't right because it won't make sense to C or Python or Cobol programmers.

@Dirbaio
Copy link
Member

Dirbaio commented Jun 28, 2023

yeah I dont' think adding a description_rust: "..." is worth the trouble 😓. The warning you added in #207 seems a good compromise

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

No branches or pull requests

2 participants