Hard Fault encountered when writing to FMC->PFB01CR on MK60F12

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Hard Fault encountered when writing to FMC->PFB01CR on MK60F12

Jump to solution
2,554 Views
davidbender
Contributor II

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?

Labels (1)
0 Kudos
Reply
1 Solution
1,887 Views
davidbender
Contributor II

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.

View solution in original post

0 Kudos
Reply
6 Replies
1,887 Views
mjbcswitzerland
Specialist V

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

0 Kudos
Reply
1,887 Views
davidbender
Contributor II

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

0 Kudos
Reply
1,887 Views
davidbender
Contributor II

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

0 Kudos
Reply
1,888 Views
davidbender
Contributor II

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.

0 Kudos
Reply
1,887 Views
egoodii
Senior Contributor III

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

*****************************************************************************/

0 Kudos
Reply
1,887 Views
mjbcswitzerland
Specialist V

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