---------------------------------------------
Summary of this long thread as of 2/4/2015:
There is a serious bug in Tiva TM4C123 microprocessors that may lead to undefined behavior (such as stack corruption) under the following conditions:
- A flash WRITE / ERASE operations is performed
- The code performing the flash operations is itself running from flash (i.e. not copied to SRAM)
- The master clock is faster than 40MHz
The problem is not currently mentioned in the datasheet or the errata. The suggested workaround is to lower the clock frequency to 40MHz during flash operations, or to copy code performing flash operations to SRAM.
----------------------------------------------------------------
Original post:
I think I may have stumbled across a hardware bug in Tiva's flash implementation. I checked the errata and did not see this issue mentioned there.
It appears that when a flash erase operation is in progress, fetching and execution of instructions from flash may be broken. The expected behavior is for instruction fetching to block while the erase is in progress, and then to resume normally. Of course I am talking about erasing pages which do not contain executable code.
The manifestation of this bug is that code randomly fails due to stack corruption. Timing is important -- inserting a single NOP often causes the bug to go away. Running the code in a debugger may also produce a different outcome.
The code in question looks like this:
bool Flash::Busy() const {
return FLASH_FMC_R &
(FLASH_FMC_WRITE | FLASH_FMC_ERASE | FLASH_FMC_MERASE);
}
void Flash::StartErasePage(uint32_t address) {
CHECK(((address & 1023) == 0) && (address < end_address()));
while (Busy()) {}
FLASH_FMA_R = address;
FLASH_FMC_R = (FLASH_FMC_WRKEY | FLASH_FMC_ERASE);
num_erased_pages_++;
asm("nop");
}
void Flash::ErasePage(uint32_t address) {
StartErasePage(address);
while (Busy()) {}
}
Invoking ErasePage(64 * 1024) causes stack corruption in my test. This is apparently caused by an instruction, "mov sp, r7" not working correctly. That is, if I break before this mov instruction, and then step over it, the debugger shows the value of sp as unchanged (and not equal to r7). My guess is that the instruction is not executed, or executed incorrectly, due to the pending flash erase operation. Replacing
FLASH_FMC_R = (FLASH_FMC_WRKEY | FLASH_FMC_ERASE);
with
FLASH_FMD_R = (FLASH_FMC_WRKEY | FLASH_FMC_ERASE);
results in an identical binary, except that the erase operation is replaced by a nop. In this case, the "mov sp, r7" instruction works as expected.
-------------------------------------------
A bit more details:
The function StartErasePage() is compiled by gcc as follows:
00000300 <_ZN5Flash14StartErasePageEm>:
void Flash::StartErasePage(uint32_t address) {
300: b580 push {r7, lr}
302: b082 sub sp, #8
304: af00 add r7, sp, #0
306: 6078 str r0, [r7, #4]
308: 6039 str r1, [r7, #0]
CHECK(((address & 1023) == 0) && (address < end_address()));
30a: 683b ldr r3, [r7, #0]
30c: ea4f 5383 mov.w r3, r3, lsl #22
310: ea4f 5393 mov.w r3, r3, lsr #22
314: 2b00 cmp r3, #0
316: d105 bne.n 324 <_ZN5Flash14StartErasePageEm+0x24>
318: f000 f898 bl 44c <_ZN5Flash11end_addressEv>
31c: 4602 mov r2, r0
31e: 683b ldr r3, [r7, #0]
320: 429a cmp r2, r3
322: d802 bhi.n 32a <_ZN5Flash14StartErasePageEm+0x2a>
324: f04f 0301 mov.w r3, #1
328: e001 b.n 32e <_ZN5Flash14StartErasePageEm+0x2e>
32a: f04f 0300 mov.w r3, #0
32e: 2b00 cmp r3, #0
330: d000 beq.n 334 <_ZN5Flash14StartErasePageEm+0x34>
332: be02 bkpt 0x0002
while (Busy()) {}
334: 6878 ldr r0, [r7, #4]
336: f7ff ffcd bl 2d4 <_ZNK5Flash4BusyEv>
33a: 4603 mov r3, r0
33c: 2b00 cmp r3, #0
33e: d1f9 bne.n 334 <_ZN5Flash14StartErasePageEm+0x34>
FLASH_FMA_R = address;
340: f44f 4350 mov.w r3, #53248 ; 0xd000
344: f2c4 030f movt r3, #16399 ; 0x400f
348: 683a ldr r2, [r7, #0]
34a: 601a str r2, [r3, #0]
FLASH_FMC_R = (FLASH_FMC_WRKEY | FLASH_FMC_ERASE);
34c: f24d 0308 movw r3, #53256 ; 0xd008
350: f2c4 030f movt r3, #16399 ; 0x400f
354: f04f 0202 mov.w r2, #2
358: f2ca 4242 movt r2, #42050 ; 0xa442
35c: 601a str r2, [r3, #0]
num_erased_pages_++;
35e: 687b ldr r3, [r7, #4]
360: 681b ldr r3, [r3, #0]
362: f103 0201 add.w r2, r3, #1
366: 687b ldr r3, [r7, #4]
368: 601a str r2, [r3, #0]
asm("nop");
36a: bf00 nop
}
36c: f107 0708 add.w r7, r7, #8
370: 46bd mov sp, r7
372: bd80 pop {r7, pc}
Using the launchpad, OpenOCD, and arm-none-eabi-gdb, it can be shown that the instruction at offset 0x370 has no effect:
(gdb) b *0x370
Breakpoint 1 at 0x370: file tiva_flash.cc, line 30.
(gdb) display/i $pc
1: x/i $pc
=> 0x0 <_stack_ptr>: ldrb r4, [r7, #31]
(gdb) c
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.
Breakpoint 1, 0x00000370 in Flash::StartErasePage (this=0x387, address=536903588) at tiva_flash.cc:30
30 }
1: x/i $pc
=> 0x370 <Flash::StartErasePage(unsigned long)+112>: mov sp, r7
(gdb) info registers
r0 0x0 0
r1 0xc00 3072
r2 0x1 1
r3 0x20007fb8 536903608
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x20007f9c 536903580
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0x0 0
r12 0x0 0
sp 0x20007f94 0x20007f94
lr 0x33b 827
pc 0x371 0x371 <Flash::StartErasePage(unsigned long)+112>
xpsr 0x0 0
(gdb) ni
Info : halted: PC: 0x00000372
0x00000372 30 }
1: x/i $pc
=> 0x372 <Flash::StartErasePage(unsigned long)+114>: pop {r7, pc}
(gdb) info registers
r0 0x0 0
r1 0xc00 3072
r2 0x1 1
r3 0x20007fb8 536903608
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x20007f9c 536903580
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0x0 0
r12 0x0 0
sp 0x20007f94 0x20007f94
lr 0x33b 827
pc 0x373 0x373 <Flash::StartErasePage(unsigned long)+114>
xpsr 0x0 0
Note how after stepping through "mov sp, r7", the value of sp does not change. This of course results in stack corruption.
The above is not completely deterministic -- sometimes this code works ok in the debugger -- and as I said inserting a single NOP somewhere in the code may also change the above behavior. But clearly something fishy is going on here.
If this is a previously unknown issue, and a TI engineer is interested in investigating this further, I will be happy to share a couple of .elf files (with debugging turned on) that demonstrate this problem.
A workaround appears to be waiting for the erase operation to complete before making any modifications to the SP register.
------------------------------------------------------------------------------
- Crystal frequency: 16 mhz, but running at 80 mhz via PLL
- Silicone revision: latest (multiple Tiva launchpad boards bought directly from TI in the last couple of months)
- Tools: arm-none-eabi-gcc / gdb on Linux; OpenOCD.