cortex-m
cortex-m copied to clipboard
Safe API to enable the ITM
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.
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.
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
@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.
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!
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.
I think this should be given some priority soon.
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.
There is no point in sending stuff out of the ITM if there's nothing connected to hear it :-)
There is a valid argument that a debugger isn't strictly needed for that purpose (after all, SWO is independent of debug) but it's the logic behind how things work in the arm world....basically, check if the debug is switched on before sending anything out of it.
Dave
On Thu, 30 Jan 2020, 18:10 cs2dsb, [email protected] wrote:
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 https://community.st.com/s/question/0D50X00009XkaEn/itmsendchar-blocks-when-no-debugger-attached says you need to check this flag (although that may not even work on M0 - see the note in the code https://github.com/rust-embedded/cortex-m/blob/22d47dd75e9fb5004e0192666123d28f0a418310/src/peripheral/dcb.rs#L45) before attempting to use ITM.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-embedded/cortex-m/issues/82?email_source=notifications&email_token=ABJTBD56DTV2AJE5HLEBTODRAMJZJA5CNFSM4ESE5KHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKL664Q#issuecomment-580382578, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABJTBD7DNUNBLOLX5YT3M6DRAMJZJANCNFSM4ESE5KHA .
@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.
You can use DEMCA and the TRCENA bit to see if the ITM is being listened to. That is a 'standard' register;
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/CEGHJDCF.html
...and also;
http://shadetail.com/blog/swo-starting-the-steroids/
It is essential you know that something is listening to you because the flow control mechanism will effectively lock you up unless you put timeout mechanisms in there.
Regards
DAVE
On 30/01/2020 20:28, cs2dsb wrote:
@mubes https://github.com/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 https://github.com/cs2dsb/itm_logger.rs/blob/master/src/logger.rs 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 https://github.com/rust-embedded/cortex-m/blob/22d47dd75e9fb5004e0192666123d28f0a418310/src/peripheral/dcb.rs#L45 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.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-embedded/cortex-m/issues/82?email_source=notifications&email_token=ABJTBDZ62DCXN7TJAN3WUUDRAM2AJA5CNFSM4ESE5KHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKMOJKQ#issuecomment-580445354, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABJTBD4CKAC3MAA73ULTP43RAM2AJANCNFSM4ESE5KHA.
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.
This raises a number of issues;
- the interlock, which is effectively a software flow control mechanism, only tells you that the hardware has picked up your message and transmitted it....it can't say anything about if something at the other end got it (there's no ack mechanism on this, Swo is unidirectional). The interlock is only there to ensure that software channel messages are spat out at an appropriate rate. Thus, other messages such as PC sampling etc don't have that mechanism...nothing cares if there's anything at the other end or not
- there's no flow control on hardware messages. It's the old tree falling in the forest and noone there to hear it thing.
- I confess to having not looked at the code you linked (I'm working from my phone over the weekend) but if these HALs are using CYCCNT for delays that makes me very uncomfortable. Firstly, it means the debug circuits are always on, which is a power drain. It means we're generslly in busy loops, which isn't nice, and it also means that when the clock speed of the CPU changes (e.g. for power saving) the indexing factor will be wrong, or you've got implementation details of the clock spread across your hal. I may have misinterpreted what's been done here so I'll try to look at it later and follow up.
Regards
Dave
On Fri, 31 Jan 2020, 12:03 cs2dsb, [email protected] wrote:
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 https://github.com/stm32-rs/stm32f1xx-hal/blob/master/src/i2c.rs 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 http://shadetail.com/blog/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.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-embedded/cortex-m/issues/82?email_source=notifications&email_token=ABJTBD2OE2DVOT6RZ7ZDGTLRAQHPRA5CNFSM4ESE5KHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKOOEYQ#issuecomment-580706914, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABJTBD5G4G2FWQJUL2TK6LLRAQHPRANCNFSM4ESE5KHA .
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!
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):

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?
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.
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.