Hi! I use STM32F417 with cortex-M4 core in my project. I found that a timer interrupt occures just after 2 instruction after disabled through peripherial register. How iis it possible? Assembler code (Interrupt disable regiter - is memory mapped peripherial register)
0x080016B4 49C0 LDR r1,[pc,#768] ; @0x080019B8 0x080016B6 8008 STRH r0,[r1,#0x00] ;DISABLE INTERRUPT IN IER REGISTER 146: preemtp_disabled++; 147: //TIM4->DIER |= TIM_DIER_UIE; 0x080016B8 48BF LDR r0,[pc,#764] ; @0x080019BC 0x080016BC 6800 LDR r0,[r0,#0x00] 0x080016BE 1C40 ADDS r0,r0,#1 ; <<<THIS IS A RETURT ADDRESS FROM INTERRUPT
May be it's a pipilene prefetching and reordering? Pipeline prefetsches 3 instructions, reorder them and advance pc counter, just in this time an interrupt occures. I was stopped at breakpoint in interrupt handler and interrupts has been disabled yet, and pc in exception frame points to 0x080016BE. I try to insert ISB instruction after clear interrupt disable bit, and after that cant reproduce this behavior any more. May be it works for me, or an error has not reproduced yet.. Thanks.
Ок, Seems I found a solution in this article https://www.keil.com/support/docs/3928.htm
Sometimes vendors incorporate an additional external, system-level write buffer in their Cortex-M3 and Cortex-M4 designs for better performance. But unfortunately, the core is not aware of this external write buffer and cannot access it's status. .... Using the DSB instruction or __dsb(0) intrinsic before exiting will force a wait for the internal write buffer to empty, but that instruction cannot test the status of an optional system-level write buffer if there happens to be one. To make sure the peripheral interrupt register gets set properly, just perform another memory write
First of all i tried to add DSB and 6 NOPs after disable interrupt, but return address of isr sometimes points out of nops tail:
inline void disable_preemption(void) { TIM4->DIER &= ~(TIM_DIER_UIE); __DSB(); //convay reload neaded, or inetrrup may be after we disabled it __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); preemtp_disabled++;
0x080015D6 8008 STRH r0,[r1,#0x00] <<<<<DISABLE INTERRUPTS 146: __DSB(); //convay reload neaded, or inetrrup may be after we disabled it 0x080015D8 F3BF8F4F DSB.W 147: __NOP(); 0x080015DC BF00 NOP 148: __NOP(); 0x080015DE BF00 NOP 149: __NOP(); 0x080015E0 BF00 NOP 150: __NOP(); 0x080015E2 BF00 NOP 151: __NOP(); 0x080015E4 BF00 NOP 152: __NOP(); 153: 0x080015E6 BF00 NOP 154: preemtp_disabled++; 155: //TIM4->DIER |= TIM_DIER_UIE; 0x080015E8 48B9 LDR r0,[pc,#740] ; @0x080018D0 0x080015EA 6800 LDR r0,[r0,#0x00] ;<<<< ISR POINTS HERE 0x080015EC 1C40 ADDS r0,r0,#1
Then i add new dummy variable to increment it, as recommended in article above
unsigned tmp=0; inline void disable_preemption(void) { TIM4->DIER &= ~(TIM_DIER_UIE); __DSB(); //convay reload neaded, or inetrrup may be after we disabled it tmp++; preemtp_disabled++;
0x080015D6 8008 STRH r0,[r1,#0x00] << DISABLE INTERRUPT 146: __DSB(); //convay reload neaded, or inetrrup may be after we disabled it 0x080015D8 F3BF8F4F DSB.W 147: tmp++; 148: 0x080015DC 48BB LDR r0,[pc,#748] ; @0x080018CC 0x080015DE 6800 LDR r0,[r0,#0x00] 0x080015E0 1C40 ADDS r0,r0,#1 0x080015E2 49BA LDR r1,[pc,#744] ; @0x080018CC 0x080015E4 6008 STR r0,[r1,#0x00] <<<<RETURN FORM ISR 149: preemtp_disabled++; 150: //TIM4->DIER |= TIM_DIER_UIE; 0x080015E6 48BA LDR r0,[pc,#744] ; @0x080018D0 0x080015E8 6800 LDR r0,[r0,#0x00]
in this case ISR ret address never exceeded 0x080015E4.
Now it's clear to me than M3-M4 are very tricky cores
For off-core peripherals it is better to read back (or write twice).