Question on 'System Control Block' implementation in the cortex-m crate

Hi - I'm fairly new to embedded rust.

Happen to pick up a random project (i.e. write a minimal bootloader) to get some hands on experience. So far, I've managed to build the 'bootloader' part of my project and everything seems to works.... except, I'm having a hard time figuring out why (it works).

After spending a day on this, I believe I'm missing something obvious and could use some help here.

  1. Its my understanding that the 'cortex-m' crate contains definitions and implementations for all of the 'arm-cortex-m' core-peripherals.
  2. So, I'm using the system control block structure defined in cortex_m::peripheral::SCB to set vtor or vector table offset register. (please see attached image)
  3. While it works, I cant seem to figure out why. SCB has no other properties other than a phantomdata marker and vtor is defined in scb.rs i.e. cortex_m::peripheral::scb::RegisterBlock.
  4. My question is how am I able to access vtor directly via SCB i.e. how are SCB and RegisterBlock connected.

I know this sounds like a I'm missing something really obvious but I haven't been able to figure this out. Appreciate it if someone can clear this up for me.

Another question.-

  1. My version of a minimal bootlader used core::mem::transmute which the compiler tells me is incredibly unsafe. So, is the a better(cleaner) way of performing a jump from the bootloader to the app?

In case its needed - My target is an nrf52840 but for now I've only tested it with qemu.

As I understand it, the magic happens in the implementation of Deref for SCB.

Essentially this means that if you have a &SCB reference, you can use it where you need a reference to an scb::RegisterBlock, and it will always give you a RegisterBlock at address 0xE000_ED04.

I assumed that was the case until I tried to access 'vtor' without a reference (i.e. by taking ownership). Attached a screenshot.

let x = cortex_m::peripheral::SCB{_marker: PhantomData};

x has access to vtor although it isn't a reference as seen in the dropdown menu suggestions of 'rls'.

Also, doesnt the Deref trait only work with the * operator (and not &) i.e. rust substitutes the * operator with a call to the deref method?

*(x.deref())

No, in addition to *, there are also situations where a Deref implementation is implicitly dereferenced: Treating Smart Pointers Like Regular References with the Deref Trait - The Rust Programming Language

Also: Deref in std::ops - Rust

I'm not sure which specific rules are leading to the result you're seeing, but I assume the . automatically creates a reference, which is then automatically coerced. Even if I'm wrong about the details, I'm positive that the Deref implementation is what makes this work.

I believe you can think of it as follows:

The compiler is trying to find a way to assign types which permits your program to compile.

x.vtor would be OK if x was a RegisterBlock.

So x.vtor would also be OK if x was a reference to a RegisterBlock, because of automatic dereferencing for field accesses.

So x.vtor would also be OK if x was a reference to an SCB, because of the Deref implementation and implicit deref coercion.

And x.vtor is also OK if x is an SCB, because of automatic referencing when calling Deref::deref().

I guess the first 2 cases make sense, the 3rd one too (sort of) but the last case is quite confusing.

x.vtor is also OK if x is an SCB , because of automatic referencing when calling Deref::deref()

What are we de-refencing here ?

That one is automatic referencing, not automatic dereferencing.

See Where’s the -> Operator?

Thank you @mjw and @hannobraun for taking the time out to help me with this. Appreciate it.

Here's what I found: I re-factored the code and dropped it in 'rust-playground'.

Couple of points:

  1. Accessing RegisterBlock fields with a reference & to SCB (i.e. &SCB.vtor) automatically calls SCB's deref method.
  2. A simple SCB reference does not make an implicit deref call.
  3. The same holds true for direct access i.e. we can access RegisterBlock fields without referencing by using SCB.vtor. However there is something odd with this method of access.

Problem: When I try to use (like say print) the result of SCB.vtor, it results in a

  • Segmentation fault (on rust playground)
  • Or an exit code: 0xc0000005, STATUS_ACCESS_VIOLATION (on VScode)

Any idea why this is the case?

use core::ops::Deref;
use core::marker::PhantomData;

fn address<T>(r: *const T) -> usize {
    r as usize
}

#[derive(Debug)]
pub struct RegisterBlock {
    pub icsr: u32,  // Interrupt Control and State
    pub vtor: u32,  // Vector Table Offset (not present on Cortex-M0 variants)
}

#[derive(Debug)]
pub struct SCB {
    _marker: PhantomData<*const ()>,
}

impl SCB {
    /// Returns a pointer to the register block
    #[inline(always)]
    pub fn ptr() -> *const RegisterBlock {
        0xE000_ED04 as *const _
    }
}

impl Deref for SCB {
    type Target = RegisterBlock;

    #[inline(always)]
    fn deref(&self) -> &Self::Target {
        unsafe { 
        println!("deref called");
        &*Self::ptr() }
    }
}

fn main() {
    let x = SCB { _marker: PhantomData }; 
    assert_eq!(address(& x.icsr), 0xE000_ED04);  // deref called auotmatically when accessing icsr via &SCB 
    assert_eq!(address(& x.vtor), 0xE000_ED08);  // deref called automatically when accessing vtor via &SCB
    println!("  {:X}", address(& x.icsr));       // deref called automatically when printing the value of icsr via &SCB
    println!("  {:X}", address(& x.vtor));       // deref called automatically when printing the value of vtor via &SCB
    println!("  {:?}", &x);                      // **deref NOT called** when you simply take a reference to SCB
    
    let _y = x.icsr;                             // deref called automatically when accessing icsr via 'SCB'
    // println!("{:#?}", y);  //not sure why this isn't working. uncommenting this line gives me a 'seg fault'  
    let _z = x.vtor;                             // deref called automatically when accessing vtor via 'SCB' 
    // println!("{:#?}", z)   //not sure why this isn't working. uncommenting this line gives me a 'seg fault'
    }

Standard Error

   Compiling playground v0.0.1 (/playground)
    Finished release [optimized] target(s) in 1.67s
     Running `target/release/playground`

Standard Output

deref called
deref called
deref called
  E000ED04
deref called
  E000ED08
  SCB { _marker: PhantomData }
deref called
deref called

rust-playground isn't running on a Cortex M device, so it doesn't have a system control block at 0xE000_ED04.

So actually accessing the RegisterBlock fields reads from an address in the middle of nowhere, with a segmentation fault being the most likely result.

2 Likes

Yep, I guess that clears it up. Completely forgot I was working on rust-playground

Just an observation: I think this deref thing is pretty confusing and convoluted. To the layman, this feels like you can declare one struct and have it magically reach into another (struct's) fields.

But thanks a lot!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.