Skip to content

Safe API to enable the ITM #82

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

Open
japaric opened this issue Feb 23, 2018 · 16 comments
Open

Safe API to enable the ITM #82

japaric opened this issue Feb 23, 2018 · 16 comments

Comments

@japaric
Copy link
Member

japaric commented Feb 23, 2018

The ITM can easily be enabled using OpenOCD but that requires having a debugger connected to the MCU. ITM can be used for logging or as a communication channel in a production application; in that case the MCU must take care of initializing the ITM. Right now enabling the ITM requires unsafe code; this is what it looks like:

    unsafe {
        // enable TPIU and ITM
        cp.DCB.demcr.modify(|r| r | (1 << 24));

        // prescaler
        let swo_freq = 2_000_000;
        cp.TPIU.acpr.write((clocks.sysclk().0 / swo_freq) - 1);

        // SWO NRZ
        cp.TPIU.sppr.write(2);

        cp.TPIU.ffcr.modify(|r| r & !(1 << 1));

        // STM32 specific: enable tracing in the DBGMCU_CR register
        const DBGMCU_CR: *mut u32 = 0xe0042004 as *mut u32;
        let r = ptr::read_volatile(DBGMCU_CR);
        ptr::write_volatile(DBGMCU_CR, r | (1 << 5));

        // unlock the ITM
        cp.ITM.lar.write(0xC5ACCE55);

        cp.ITM.tcr.write(
            (0b000001 << 16) | // TraceBusID
            (1 << 3) | // enable SWO output
            (1 << 0), // enable the ITM
        );

        // enable stimulus port 0
        cp.ITM.ter[0].write(1);
    }

We should have a safe API to enable the ITM. The API could be an ITM::enable(&mut self, prescaler: u32) method to globally enable the ITM and an ITM::{enable,disable}_port(&mut self, i: u8) to enable / disable an individual port.

@therealprof
Copy link
Contributor

Hm, ITM is incredibly powerful and the code above is a rather specific configuration. Maybe we should talk to ITM experts like @mubes to get an opinion about a useful API. BTW: Check out his blog about some of his incredible ITM work: http://shadetail.com/blog/ . It would be outstanding to have good ITM support in Rust.

@mubes
Copy link

mubes commented Feb 24, 2018

Hi guys, the magic of gitter informed me about this conversation. Bearing in mind that I have very little context here my first reaction is to ask why you want to switch ITM stimulus ports on and off from program code? The normal use case is to spit everything out over the various ports in your code and then use the debugger to switch them on and off as needed....after all, you don't really need them if there's no debugger (actually, there's no intrinsic need for a debugger since ITM output comes via different routes, but it's certainly the normal use case).

If you look at orbuculum you will find gdb macros that do the needed on/off switching..the idea is that you can decide what debug output you actually want at any point in the debug cycle. I remember reading somewhere that Arm claim that the low overhead means you can leave the calls in production code with the ITM switched off....I'm not sure I agree with that, but you can see the intent.

(Not sure gitter will be bright enough to keep me informed of follow ups, so if I don't respond can you poke me please Daniel?).

Dave

@japaric
Copy link
Member Author

japaric commented Mar 10, 2018

@therealprof

Hm, ITM is incredibly powerful and the code above is a rather specific configuration.

We have to start somewhere though. Having to use unsafe code to get logging, the most basic ITM feature, working is kind of ... sad. We could design the API so that adding more functionality, like tracing interrupt entry/exit times, can be added later in a backwards compatible fashion. Though I don't mind making a new minor release if it's to add more ITM functionality.

@mubes

why you want to switch ITM stimulus ports on and off from program code?

Because ITM can work without a debugger and it's a nice and fast way to log data. I have used it to wirelessly (2 Mbps UART to Bluetooth adapter) log sensor data and program state from a mobile robot -- I certainly don't want to attach a debugger, or a USB cable, to a moving robot just to enable logging.

@ruabmbua
Copy link

ruabmbua commented Jun 12, 2018

Hey!
Arm beginner here, could the ITM be the reason, why my board does not want to boot up, when no debugger is connected(openocd not running)?

I use the cortex-m crate, and the iprint! macro, so maybe it blocks in while !port.is_fifo_ready() {}? If that is the case, it would be a nice addition, to mention it in your various tutorials.

By the way,
your tutorials are great @japaric , thanks for your efforts!

@nickray
Copy link

nickray commented Mar 16, 2019

I too would love to use ITM for logging (without debugger attached) instead of using an USART. What needs to happen to move this issue forward? If need be behind some "unproven" feature.

One question: What happens to such code if read out protection level 2 is activated (on ST chips)? It would be good if doing so would not cause the application to hard fault if there is embedded ITM logging code.

@korken89
Copy link
Contributor

I think this should be given some priority soon.

@cs2dsb
Copy link

cs2dsb commented Jan 30, 2020

I'm not sure this is possible - I've been poking about and it seems that you need DEBUGEN in DHCSR to be set for any DBGMCU registers to work but DEBUGEN cannot be set by the core. Here's a quick test that indicates you can't write DBGMCU:

let x = 0x00000027;
while core::ptr::read_volatile(0xE0042004 as *mut u32) != x {
  core::ptr::write_volatile(0xE0042004 as *mut u32, x);            
}
usr_led.on();

With the debugger attached the led lights, without, it doesn't.

Section 10.2.1 of the Cortex-M3 technical reference manual (page 211 in the latest version) states:

C_DEBUGEN - Enables debug. This can only be written by AHB-AP and not by the core. It
is ignored when written by the core, which cannot set or clear it.

The solution offered by the stm32 forum says you need to check this flag (although that may not even work on M0 - see the note in the code) before attempting to use ITM.

@mubes
Copy link

mubes commented Jan 30, 2020 via email

@cs2dsb
Copy link

cs2dsb commented Jan 30, 2020

@mubes In my case, I have my UART dongle connected to SWO and GND and a separate header for programming so I was listening for ITM packets but if I reset the device without OpenOCD connected it wouldn't work.

It would be nice to get a crate that is able to detect if ITM is configured and available and sets up the log crates logger instance to either something that sends logs over ITM or a stubbed out nop logger. I've implemented a logger for ITM here which works fine IF ITM is enabled. The checks I added to detect if the stim port was enabled turned out to not be sufficient to detect if is_fifo_ready will block or not. Based off the note in the code it might be non trivial to support this across multiple device families.

Do you know of a reliable method to detect if the fifo will block that works across all the cortex_m range (since DHCSR.DEBUGEN is impl specific)? The same detection method could be used in this crate in ITM::enable and have it return an error if ITM can't be properly enabled.

@mubes
Copy link

mubes commented Jan 31, 2020 via email

@cs2dsb
Copy link

cs2dsb commented Jan 31, 2020

A fly in the ointment is if you are using DWT for delays which a bunch of the hal implementations do f1/i2c for example the TRCENA bit has to be set for DWT to run.

So if you've got code enabling TRCENA for DWT and enabling ITM (this issue), the three checks listed in swo-starting-the-steroids still doesn't tell you if ITM is ready because all of those bits are settable by the core.

I think there needs to be an additional check on some register that the core can't update for you to be sure something is listening.

Regarding your last point about having to check for something listening, is this true for all variants of tracing? I've got mine configured for SWO and the micro keeps running fine if I unplug the STLINK and/or the UART. Plugging the UART back in without the STLINK logging over SWO starts working again.

@mubes
Copy link

mubes commented Feb 2, 2020 via email

@tib888
Copy link

tib888 commented Apr 14, 2020

I just finished a two day investigation: why can't I use the ITM debugging.
On a BluePill I used A15 and B4 pins. I thought B3 is then still fine for ITM.
When after two days I finally noticed that 'disable_jtag' word in the line below:
let (pa15, pb3, pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4);
which I think conflict with the ITM. So please take care of that when designing safe API!

@adamgreig
Copy link
Member

The disable_jtag() function on the F1xx HAL writes 0b010 to SWJ_CFG to disable JTAG-DP and leave SW-DP enabled, which according to the reference manual should leave PB3 available for SWO use (I haven't checked on hardware):

image

In your case did you confirm that removing disable_jtag() causes SWO to work, and adding it back breaks SWO? Are you using PB3 for anything else?

@tib888
Copy link

tib888 commented Apr 14, 2020

Well, I have double checked what has happened, but the final conclusion is missing: now everything works. However during the investigation the problem appeared many times:
when iprintln!(stim, ) was called, the CPU went into an infinite loop, and the watchdog restarted it.
I tried to connect this with 'disable_jtag' or with 'panic_halt / panic_itm' or with the usage of the A15 / B4 pins, but no correlation found.
Now everything works, except I do not see the ITM output, probably due to my COM5 port (?).
If you have other tip, let me know.

@diondokter
Copy link
Contributor

Even if turning the ITM on with code is not very sensible, I think it'd be good to have an API for this. With it you should be able to set the SWO speed and the used encoding.

adamgreig pushed a commit that referenced this issue Jan 12, 2022
90: turn macros into attributes r=adamgreig a=japaric

This is a PoC implementation of RFC #82. Assuming that we are OK with this implementation we should
add some compile fail tests (e.g. using a function with the wrong signature as the entry point)
before landing this.

Look at the diff of the examples to get an overview of the changes.

cc @rust-embedded/cortex-m

Co-authored-by: Jorge Aparicio <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants