When I finish erasing or programming flash, I attempt to run a routine (IN RAM) to clear the cache, consisting of only the following line
FMC->PFB01CR |= FMC_PFB01CR_S_B_INV_MASK | FMC_PFB01CR_CINV_WAY_MASK;
This causes a Hard Fault, with the following fault register values:
HFSR: 0x40000000
CFSR: 0x00000400
So as you can see, I am getting an imprecise Bus Error with no valid BFAR.
Any ideas what the problem may be?
解決済! 解決策の投稿を見る。
OK I finally figured it out...
There were LDR.N commands in the code copied to RAM that referenced offsets to the PC.
Unfortunately, these offsets were only valid for PC counter addresses in flash.
So at best I would get random writes into RAM.
The code I copied from MQX for invaliding k60 cache is the following
\ In section .text, align 4, keep-with-next
495 static void kinetis_flash_invalidate_cache
496 (
497 /* [IN] What exactly to invalidate */
498 uint32_t flags
499 )
500 {
501 /*
502 uint32_t PFB0CR_restore = FMC->PFB01CR;
503 FMC->PFB01CR = PFB0CR_restore | FMC_PFB01CR_S_B_INV_MASK | FMC_PFB01CR_CINV_WAY(15);
504 */
505 FMC->PFB01CR |= FMC_PFB01CR_S_B_INV_MASK | FMC_PFB01CR_CINV_WAY_MASK;
\ kinetis_flash_invalidate_cache: (+1)
\ 00000000 0x.... LDR.N R1,??DataTable5_13 ;; 0x4001f004
\ 00000002 0x6809 LDR R1,[R1, #+0]
\ 00000004 0xF451 0x0178 ORRS R1,R1,#0xF80000
\ 00000008 0x.... LDR.N R2,??DataTable5_13 ;; 0x4001f004
\ 0000000A 0x6011 STR R1,[R2, #+0]
506 return;
\ 0000000C 0x4770 BX LR ;; return
507 }
As you can see in Line 505, I will write to some address in RAM rather than FMC->PFB01CR
I don't know what the correct solution to this is, but I ended up passing in FMC->PFB01CR as parameter.
Lesson here: Do not blindly copy MQX code.
David
I would step through the code being run in RAM with the debugger since the code itself may be invalid. When there is a HW fault you will see an instruction reading or writing to an address that then causes the exception (check the address value to be what you expect it to be - often it is not). If it looks correct check the FMC register set in the debugger's peripheral view and try to do the same manually to see what happens.
Regards
Mark
The fault actually was not writing to FMC.
The fault occurs on the BX instruction (when leaving the function in RAM). The LR is the correct address but the fault occurs anyway. There is even another function call to RAM before this one that works properly. I am really puzzled by this...
UPDATE: It seems that I am only getting the Hard Fault error if I perform a Flash Erase, Flash Program Phrase or Flash Program Section. I am dutifully checking the CCIF over 512 times, in this fashion:
-Spinning 512 times checking if CCIF is still 1 even after initiating the operation (just in case FTFE is taking it's sweet time updating that register)
-Spinning while CCIF is still 0, waiting for it to become 1
Even with all this checking, I seem to get the Hard Fault error when I start executing from flash again....
NOTE: The executable code is in Program Flash 0, and the sector I am erasing is in Program Flash 1
OK I finally figured it out...
There were LDR.N commands in the code copied to RAM that referenced offsets to the PC.
Unfortunately, these offsets were only valid for PC counter addresses in flash.
So at best I would get random writes into RAM.
The code I copied from MQX for invaliding k60 cache is the following
\ In section .text, align 4, keep-with-next
495 static void kinetis_flash_invalidate_cache
496 (
497 /* [IN] What exactly to invalidate */
498 uint32_t flags
499 )
500 {
501 /*
502 uint32_t PFB0CR_restore = FMC->PFB01CR;
503 FMC->PFB01CR = PFB0CR_restore | FMC_PFB01CR_S_B_INV_MASK | FMC_PFB01CR_CINV_WAY(15);
504 */
505 FMC->PFB01CR |= FMC_PFB01CR_S_B_INV_MASK | FMC_PFB01CR_CINV_WAY_MASK;
\ kinetis_flash_invalidate_cache: (+1)
\ 00000000 0x.... LDR.N R1,??DataTable5_13 ;; 0x4001f004
\ 00000002 0x6809 LDR R1,[R1, #+0]
\ 00000004 0xF451 0x0178 ORRS R1,R1,#0xF80000
\ 00000008 0x.... LDR.N R2,??DataTable5_13 ;; 0x4001f004
\ 0000000A 0x6011 STR R1,[R2, #+0]
506 return;
\ 0000000C 0x4770 BX LR ;; return
507 }
As you can see in Line 505, I will write to some address in RAM rather than FMC->PFB01CR
I don't know what the correct solution to this is, but I ended up passing in FMC->PFB01CR as parameter.
Lesson here: Do not blindly copy MQX code.
The 'fundamental cause' is because the ARM architecture cannot load a 32-bit constant directly in one instruction -- so the solution compilers use is to make a table of such constants at the end of every code module (??DataTableXXXX), and use a PC-relative 'load' instruction to fetch that constant. One way to 'live' with this situation is to position the code you will need to copy as the LAST function in a module, and copy 'more than you think you need' of the code AND some of what follows to 'catch' the constants. Not as 'perfect' as Mark's solution, but 'somewhat more general'. For instance, 'early' silicon required the sys-clock dividers to be set with RAM code. Here is how I did that:
void (*fcn_ram_call)(uint_32); |
uint_32 fcn_thumb_flag; | |
uint_32 fcn_rom_addr; | |
uint_32 fcn_ram_addr; |
/* | |
* Allocate stack space for set_sys_dividers() function copy | |
* The sizeof(set_sys_dividers_ram_copy) must be enough to | |
* fit whole set_sys_dividers() function PLUS constants. | |
*/ | |
uint_32 fcn_ram_copy[64]; | |
uint_32 SIM_CLKDIV1_COPY; |
/* | |
* Copy set_sys_dividers() function to stack @ function_copy address | |
* Because Thumb-2 instruction mode is used its necessary to set | |
* bit[0] correctly to represent the opcode type of the branch target. | |
* For details see: | |
* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka12545.html | |
*/ |
fcn_thumb_flag = (uint_32)set_sys_dividers & 0x01; | |
fcn_rom_addr = (uint_32)set_sys_dividers & ~(uint_32)0x01; | |
fcn_ram_addr = (uint_32)fcn_ram_copy | (fcn_rom_addr & 0x02); |
_mem_copy ((pointer)fcn_rom_addr, (pointer)fcn_ram_addr, sizeof(fcn_ram_copy)-3); |
/* Get pointer of set_sys_dividers function to run_in_ram_fcn */ | |
fcn_ram_call = (void (*)(uint_32))(fcn_ram_addr | fcn_thumb_flag); |
Called 'later' via:
/* | |
* Set system clock dividers by function running from RAM | |
* This routine must be placed in RAM. It is a workaround for errata e2448. | |
*/ | |
fcn_ram_call(SIM_CLKDIV1_COPY); |
THAT RAM COPY put this function (and the following ROM pointers, within 64 words) out there:
/*FUNCTION*-------------------------------------------------------------------
*
* Function Name : set_sys_dividers
* Returned Value : void
* Comments :
* Flash prefetch must be disabled when the flash clock divider is changed.
* This cannot be performed while executing out of flash.
* There must be a short delay after the clock dividers are changed
* before prefetch can be re-enabled.
****This fn is copied to stack RAM, and must be at the end-of-sourcefile so
* that implicit constants are nearby. ***********************************
*
*END*----------------------------------------------------------------------*/
static void set_sys_dividers
(
/* [IN] SIM_CLKDIV1 mask */
uint_32 outdiv
)
{
uint_32 FMC_PFAPR_backup;
volatile uint_32 dummy;
/* Store present value of FMC_PFAPR */
FMC_PFAPR_backup = FMC_PFAPR;
/* Set M0PFD through M7PFD to 1 to disable prefetch */
FMC_PFAPR |= FMC_PFAPR_M7PFD_MASK | FMC_PFAPR_M6PFD_MASK |
FMC_PFAPR_M5PFD_MASK | FMC_PFAPR_M4PFD_MASK |
FMC_PFAPR_M3PFD_MASK | FMC_PFAPR_M2PFD_MASK |
FMC_PFAPR_M1PFD_MASK | FMC_PFAPR_M0PFD_MASK;
/* Set SIM_CLKDIV1 mask */
SIM_CLKDIV1 = outdiv;
/* Wait for dividers to change */
dummy = dummy;
/* Re-store original value of FMC_PFAPR */
FMC_PFAPR = FMC_PFAPR_backup;
}
/* Do not add any code between set_sys_dividers and set_sys_dividers_end functions. */
static void set_sys_dividers_end(void)
{
}
/******************************************************************************
* End of file
*****************************************************************************/
David
Often non-position-independent code is the cause of RAM code problems as you have discovered.
The remaining problem is that the code can change when the optimiser level changes, the compiler or version changes or the use of the functiopn call changes. This means that the code coudl break in the future. One example if that you may want to call the code from a second location and then the compiler may decide to inline the code and then everything fails again and another day is lost trying to work out why since "no code was actually modified" and you had forgotten about what you had done in the past....
If there is only a small amount of such code a common solution is to take a working solution (the machine code) and use that in an array that is copied to RAM (in the same way as the original code was).
The following is an example of a small piece of Flash code that is executed from RAM (from the uTasker project). Note that the HW pointer is passed in exactly the same way as your workaround because that solved initial difficulties. The propject is used with a number of different compilers and versiosn and inevitably it did eventually "break" in its original form. The machine code version (simply copied from working and commented for full understanding) will not be able to break ever again.
static void fnFlashRoutine(volatile unsigned char *ptrFTFL_BLOCK) // Reference C-code (not used)
{
*ptrFTFL_BLOCK = FTFL_STAT_CCIF; // launch the command - this clears the FTFL_STAT_CCIF flag (register is FTFL_FSTAT)
while (!(*ptrFTFL_BLOCK & FTFL_STAT_CCIF)) {} // wait for the command to terminate
}
// Machine code version used for real RAM operation (Cortex-M0+, M3 and M4 compatible)
//
static unsigned short fnFlashRoutine[] = { // to avoid potential compiler in-lining of the routine (removing position independency) the machine code is used directly
0x2180, // MOVS r1,#0x80 load the value 0x80 (command complete interrupt flag) to register r1
0x7001, // STRB r1,[r0,#0x00] write r1 (0x80) to the passed pointer location (r0)
0x7801, // LDRB r1,[r0,#0x00] read back from the same location to r1
0x0609, // LSLS r1,r1,#24 shift the register content by 24 bits to the left so that the command complete interrupt flag is at bit 31
0xd5fc, // BPL -4 if the command complete interrupt flag bit is '0' (register content is not negative value) branch back to read its value again
0x4770 // BX lr return from sub-routine
};
Regards
Mark