Example of a Serial bootloader for the K64F platform

Dependencies:   mbed

Introduction

Once in a while on mbed questions arise regarding making your own bootloader. So I decided to make one. Due to the device specific parts in the code this only works on the K64F. Porting it to other Freescale devices should be fairly straightforward. Devices from other manufacturers will require completely different code, although it could still be used as a basic guide.

Disclaimer

Bricking your device!

This rewrites your flash memory, including the security byte which determines among others the access external programmers get, and which can completely block future access.

Additionally this is an example, it is not intended to be used in a mission critical system in a nuclear power plant.

Requirements

I set some requirements for this bootloader. To start with, it should work when build from the online compiler. So no special compiler options to place functions in the correct sections. Even more important, it should be able to load regular programs from the mbed online compiler.

There are different options to get the data to the mbed, but for simplicity the goal is to just use the USBTX/USBRX serial pins. Finally there are some different types of bootloaders, for example they can launch at start up. That is not going to be easy with the requirement to also be able to load regular programs, although it might be possible, but it is not a requirement for here. In principle it is sufficient if the new user program has to call the bootloader, but an alternative is presented which allows a button to be able to enter bootloader mode, regardless of user code (not completely regardless, but it does not need to explictitly add any code for the bootloader).

Bootloader options

When a bootloader recides in the first part of the flash memory, it will run at start up and allows you to always upload a new program, regardless of your old program (it is always possible to brick it). However this means your user program needs to be compiled with an offset, and the online compiler cannot do this.

The alternative is putting the bootloader at the end of the flash memory. From here it can reprogram the first parts of the flash memory, but it will not automatically start up. Still this options has been used here.

The bootloader

The first requirement is placing the functions at the end of the flash memory. The online compiler (and Keil, other compilers will have different options) uses the following syntax to place functions at an absolute location:

__attribute__((section(".ARM.__at_0x10000"))) void bootloader(void)

Here obviously the 0x10000 is the absolute address where the function is placed. You may realise this is in fact not the end of the flash memory on the K64F. You are correct, I randomly choose this and cannot be bothered to change it. It allows 64kB for your user program, which is sufficient for just trying it out.

You do not need to worry about placing it on top of other functions/values: The compiler checks this, and gives a clear error message on the location and sizes of the collision.

It is important to realise that you cannot depend on any functions in the 'regular' area of your flash! This can be reprogrammed to contain any value/data, so every single functions and variable used by your bootloader needs to be specifically placed in your bootloader section. This also means that we cannot use mbed library functions, unless you manage to move the entire mbed library. Which could be done for the C API functions, but for here we use an easier solution, we just code it ourselves :D.

Serial code

Using void setupserial(); the serial peripheral is set up. This assumes the clock is already running correctly, so it depends a bit on the user program, but for any normal mbed user program this will be the case. I started using the K20D50M serial_api.c file from the mbed library, which contains all registers which are being set (The K64F code uses the KSDK drivers, which besides harder to use also cannot be used, because they will not reside in the correct memory area). The code simply hardcodes some register values to be correct for 9600 baud rate at USBTX/USBRX.

void write(char *value); is used to write messages on the serial connection.

Flash programming

Now we can send serial data (and receiving it is also pretty straight forward), we need to be able to program or flash memory. We use this program for this: http://developer.mbed.org/users/Sissors/code/FreescaleIAP/. However also this needs to be placed in the correct flash location. Since I have not been able yet to do it in a nice way in the library, it was done here manually.

To program flash it first needs to be erased. This takes a relative large amount of time, and due to the setup of this bootloader we do not know beforehand how many sectors need to be erased. So it just erases 15 4kB sectors, allowing for a program size of 60kB.

Once erased, the program will wait until the first char arrived. From this moment on it will enter a loop where received chars are placed in a 16-byte buffer. In principle the K64F should be able to program per 8-byte, however this resulted in error statuses from the flash peripheral. Since it works fine with programming per 16-bytes we use this. Every time 16-new bytes have arrived they are programmed into the flash memory, and a counter is increased.

In the loop also a timeout counter runs. Once no new chars have arrived for a period of time it assumes the program has been completely sent. The remaining bytes in the buffer are programmed, and the MCU resets itself, loading automatically the new user program.

Non-Maskable Interrupt

All (mbed) Freescale MCUs have a NMI setup which is called when the correct pin is pulled to zero. This interrupt vector resides in bytes 8-9-10-11, and on the K64F is connected to SW3, which makes it easy to use. The code currently simply replaces those 4 bytes with the location of the bootloader function. This means that regardless of the user program, it will be modified to start the bootloader upon pressing SW3 on your board.

Usage

After loading this program on your K64F the regular way, it will directly start the bootloader. Later on you can enter the bootloader by pressing SW3, or call it from your user code (for this you need to make a function pointer towards the location of the bootloader).

I assume for this Teraterm is used, other terminal programs might have similar options. After confirming you want to start the bootloader by typing 'y', it will erase the current program. Next you need to send the binary intact to the K64F via Teraterm. You can NOT do this via drag and dropping files into it, Teraterm will mutilate them. Instead go to File > Send File, and locate your file. Next make sure the "Binary" option is checked. If everything goes correctly you see a stream '#'s appear in your serial window, followed by a "Done programming!". It resets itself and your bootloaded program should run directly.

The bootloader simply expects the binary data to be transmitted via the Serial connection, so any programs which does so will do.

Remarks and Comments

Since you cannot rely on any functions outside the bootloader, you have to write everything yourself, or move existing libraries one function at a time. This makes it easiest if you stick to simple peripherals, such as SPI or Serial. It might be doable to move USBDevice over, although it would be far from trivial. I wouldn't even consider starting with Ethernet.

But what if you really want to use Ethernet? Let your user program handle it. Downside is of course your user program needs to handle this, and you cannot use any random program. But one example would be loading the new binary via whatever means you want, and storing it on an external memory chip. Then your bootloader program only needs to read it from your memory chip back.

If your code needs to be able to call the bootloader, for example after you loaded it via Ethernet, you need to add a pointer to the bootloader in your code, even though your newly loaded code does not contain the bootloader itself. This can be done using:

void *(*bootloader)(void) = (void *(*)(void))0x10001;

This creates a bootloader point of type void bootloader(void), pointing at a bootloader at 0x10000. If your bootloader function is at another memory location: you need the memory location plus 1. After this your code can call the bootloader simply as a normal function: bootloader();.

If you want it to start always upon reset, you can overwrite the Reset Handler. This is located right before the NMI, at bytes 4-5-6-7. It should work the same way as doing it the NMI way, with two differences. First of all currently the code relies on the clock being set up correctly. That won't be the case. In addition I do not know if it will correctly be able to make new variables on the stack, without code having run to initialize that. (My guess: It won't and you need to do that manually in your code).

The second difference is that you of course still need to be able to run the user code, so it needs to store the old reset vector in another location.

Committer:
Sissors
Date:
Thu Mar 12 19:04:30 2015 +0000
Revision:
2:8c44f28c122c
Parent:
1:782a3ddc329e
Child:
4:8d109a566486
v0.9, works on K20 :D

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Sissors 1:782a3ddc329e 1 #include "FreescaleIAP.h"
Sissors 1:782a3ddc329e 2
Sissors 1:782a3ddc329e 3 //#define IAPDEBUG
Sissors 1:782a3ddc329e 4
Sissors 2:8c44f28c122c 5 #define FLASH_LOC(loc) __attribute__((section(".ARM.__at_ ## loc ##")))
Sissors 2:8c44f28c122c 6
Sissors 1:782a3ddc329e 7 #ifdef TARGET_K64F
Sissors 1:782a3ddc329e 8 //For K64F
Sissors 1:782a3ddc329e 9 # include "MK64F12.h"
Sissors 1:782a3ddc329e 10 # define USE_ProgramPhrase 1
Sissors 1:782a3ddc329e 11 # define FTFA FTFE
Sissors 1:782a3ddc329e 12 # define FTFA_FSTAT_FPVIOL_MASK FTFE_FSTAT_FPVIOL_MASK
Sissors 1:782a3ddc329e 13 # define FTFA_FSTAT_ACCERR_MASK FTFE_FSTAT_ACCERR_MASK
Sissors 1:782a3ddc329e 14 # define FTFA_FSTAT_RDCOLERR_MASK FTFE_FSTAT_RDCOLERR_MASK
Sissors 1:782a3ddc329e 15 # define FTFA_FSTAT_CCIF_MASK FTFE_FSTAT_CCIF_MASK
Sissors 1:782a3ddc329e 16 # define FTFA_FSTAT_MGSTAT0_MASK FTFE_FSTAT_MGSTAT0_MASK
Sissors 1:782a3ddc329e 17 #else
Sissors 1:782a3ddc329e 18 //Different names used on at least the K20:
Sissors 1:782a3ddc329e 19 # ifndef FTFA_FSTAT_FPVIOL_MASK
Sissors 1:782a3ddc329e 20 # define FTFA FTFL
Sissors 1:782a3ddc329e 21 # define FTFA_FSTAT_FPVIOL_MASK FTFL_FSTAT_FPVIOL_MASK
Sissors 1:782a3ddc329e 22 # define FTFA_FSTAT_ACCERR_MASK FTFL_FSTAT_ACCERR_MASK
Sissors 1:782a3ddc329e 23 # define FTFA_FSTAT_RDCOLERR_MASK FTFL_FSTAT_RDCOLERR_MASK
Sissors 1:782a3ddc329e 24 # define FTFA_FSTAT_CCIF_MASK FTFL_FSTAT_CCIF_MASK
Sissors 1:782a3ddc329e 25 # define FTFA_FSTAT_MGSTAT0_MASK FTFL_FSTAT_MGSTAT0_MASK
Sissors 1:782a3ddc329e 26 # endif
Sissors 1:782a3ddc329e 27 #endif
Sissors 1:782a3ddc329e 28
Sissors 1:782a3ddc329e 29
Sissors 1:782a3ddc329e 30 enum FCMD {
Sissors 1:782a3ddc329e 31 Read1s = 0x01,
Sissors 1:782a3ddc329e 32 ProgramCheck = 0x02,
Sissors 1:782a3ddc329e 33 ReadResource = 0x03,
Sissors 1:782a3ddc329e 34 ProgramLongword = 0x06,
Sissors 1:782a3ddc329e 35 ProgramPhrase = 0x07,
Sissors 1:782a3ddc329e 36 EraseSector = 0x09,
Sissors 1:782a3ddc329e 37 Read1sBlock = 0x40,
Sissors 1:782a3ddc329e 38 ReadOnce = 0x41,
Sissors 1:782a3ddc329e 39 ProgramOnce = 0x43,
Sissors 1:782a3ddc329e 40 EraseAll = 0x44,
Sissors 1:782a3ddc329e 41 VerifyBackdoor = 0x45
Sissors 1:782a3ddc329e 42 };
Sissors 1:782a3ddc329e 43
Sissors 1:782a3ddc329e 44 inline void run_command(void);
Sissors 1:782a3ddc329e 45 bool check_boundary(int address, unsigned int length);
Sissors 1:782a3ddc329e 46 bool check_align(int address);
Sissors 1:782a3ddc329e 47 IAPCode verify_erased(int address, unsigned int length);
Sissors 1:782a3ddc329e 48 IAPCode check_error(void);
Sissors 1:782a3ddc329e 49 IAPCode program_word(int address, char *data);
Sissors 1:782a3ddc329e 50
Sissors 2:8c44f28c122c 51 __attribute__((section(".ARM.__at_0x11000"))) IAPCode erase_sector(int address) {
Sissors 1:782a3ddc329e 52 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 53 printf("IAP: Erasing at %x\r\n", address);
Sissors 1:782a3ddc329e 54 #endif
Sissors 1:782a3ddc329e 55 if (check_align(address))
Sissors 1:782a3ddc329e 56 return AlignError;
Sissors 1:782a3ddc329e 57
Sissors 1:782a3ddc329e 58 //Setup command
Sissors 1:782a3ddc329e 59 FTFA->FCCOB0 = EraseSector;
Sissors 1:782a3ddc329e 60 FTFA->FCCOB1 = (address >> 16) & 0xFF;
Sissors 1:782a3ddc329e 61 FTFA->FCCOB2 = (address >> 8) & 0xFF;
Sissors 1:782a3ddc329e 62 FTFA->FCCOB3 = address & 0xFF;
Sissors 1:782a3ddc329e 63
Sissors 1:782a3ddc329e 64 run_command();
Sissors 1:782a3ddc329e 65
Sissors 1:782a3ddc329e 66 return check_error();
Sissors 1:782a3ddc329e 67 }
Sissors 1:782a3ddc329e 68
Sissors 1:782a3ddc329e 69 __attribute__((section(".ARM.__at_0x11100"))) IAPCode program_flash(int address, char *data, unsigned int length) {
Sissors 1:782a3ddc329e 70 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 71 printf("IAP: Programming flash at %x with length %d\r\n", address, length);
Sissors 1:782a3ddc329e 72 #endif
Sissors 1:782a3ddc329e 73 if (check_align(address))
Sissors 1:782a3ddc329e 74 return AlignError;
Sissors 1:782a3ddc329e 75
Sissors 1:782a3ddc329e 76 IAPCode eraseCheck = verify_erased(address, length);
Sissors 1:782a3ddc329e 77 if (eraseCheck != Success)
Sissors 1:782a3ddc329e 78 return eraseCheck;
Sissors 1:782a3ddc329e 79
Sissors 1:782a3ddc329e 80 IAPCode progResult;
Sissors 1:782a3ddc329e 81 #ifdef USE_ProgramPhrase
Sissors 1:782a3ddc329e 82 for (int i = 0; i < length; i+=8) {
Sissors 1:782a3ddc329e 83 progResult = program_word(address + i, data + i);
Sissors 1:782a3ddc329e 84 if (progResult != Success)
Sissors 1:782a3ddc329e 85 return progResult;
Sissors 1:782a3ddc329e 86 }
Sissors 1:782a3ddc329e 87 #else
Sissors 1:782a3ddc329e 88 for (int i = 0; i < length; i+=4) {
Sissors 1:782a3ddc329e 89 progResult = program_word(address + i, data + i);
Sissors 1:782a3ddc329e 90 if (progResult != Success)
Sissors 1:782a3ddc329e 91 return progResult;
Sissors 1:782a3ddc329e 92 }
Sissors 1:782a3ddc329e 93 #endif
Sissors 1:782a3ddc329e 94 return Success;
Sissors 1:782a3ddc329e 95 }
Sissors 1:782a3ddc329e 96
Sissors 1:782a3ddc329e 97 __attribute__((section(".ARM.__at_0x11300"))) uint32_t flash_size(void) {
Sissors 1:782a3ddc329e 98 uint32_t retval = (SIM->FCFG2 & 0x7F000000u) >> (24-13);
Sissors 1:782a3ddc329e 99 if (SIM->FCFG2 & (1<<23)) //Possible second flash bank
Sissors 1:782a3ddc329e 100 retval += (SIM->FCFG2 & 0x007F0000u) >> (16-13);
Sissors 1:782a3ddc329e 101 return retval;
Sissors 1:782a3ddc329e 102 }
Sissors 1:782a3ddc329e 103
Sissors 1:782a3ddc329e 104 __attribute__((section(".ARM.__at_0x11400"))) IAPCode program_word(int address, char *data) {
Sissors 1:782a3ddc329e 105 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 106 #ifdef USE_ProgramPhrase
Sissors 1:782a3ddc329e 107 printf("IAP: Programming word at %x, %d - %d - %d - %d - %d - %d - %d - %d\r\n", address, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
Sissors 1:782a3ddc329e 108 #else
Sissors 1:782a3ddc329e 109 printf("IAP: Programming word at %x, %d - %d - %d - %d\r\n", address, data[0], data[1], data[2], data[3]);
Sissors 1:782a3ddc329e 110 #endif
Sissors 1:782a3ddc329e 111
Sissors 1:782a3ddc329e 112 #endif
Sissors 1:782a3ddc329e 113 if (check_align(address))
Sissors 1:782a3ddc329e 114 return AlignError;
Sissors 1:782a3ddc329e 115 #ifdef USE_ProgramPhrase
Sissors 1:782a3ddc329e 116 FTFA->FCCOB0 = ProgramPhrase;
Sissors 1:782a3ddc329e 117 FTFA->FCCOB1 = (address >> 16) & 0xFF;
Sissors 1:782a3ddc329e 118 FTFA->FCCOB2 = (address >> 8) & 0xFF;
Sissors 1:782a3ddc329e 119 FTFA->FCCOB3 = address & 0xFF;
Sissors 1:782a3ddc329e 120 FTFA->FCCOB4 = data[3];
Sissors 1:782a3ddc329e 121 FTFA->FCCOB5 = data[2];
Sissors 1:782a3ddc329e 122 FTFA->FCCOB6 = data[1];
Sissors 1:782a3ddc329e 123 FTFA->FCCOB7 = data[0];
Sissors 1:782a3ddc329e 124 FTFA->FCCOB8 = data[7];
Sissors 1:782a3ddc329e 125 FTFA->FCCOB9 = data[6];
Sissors 1:782a3ddc329e 126 FTFA->FCCOBA = data[5];
Sissors 1:782a3ddc329e 127 FTFA->FCCOBB = data[4];
Sissors 1:782a3ddc329e 128 #else
Sissors 1:782a3ddc329e 129 //Setup command
Sissors 1:782a3ddc329e 130 FTFA->FCCOB0 = ProgramLongword;
Sissors 1:782a3ddc329e 131 FTFA->FCCOB1 = (address >> 16) & 0xFF;
Sissors 1:782a3ddc329e 132 FTFA->FCCOB2 = (address >> 8) & 0xFF;
Sissors 1:782a3ddc329e 133 FTFA->FCCOB3 = address & 0xFF;
Sissors 1:782a3ddc329e 134 FTFA->FCCOB4 = data[3];
Sissors 1:782a3ddc329e 135 FTFA->FCCOB5 = data[2];
Sissors 1:782a3ddc329e 136 FTFA->FCCOB6 = data[1];
Sissors 1:782a3ddc329e 137 FTFA->FCCOB7 = data[0];
Sissors 1:782a3ddc329e 138 #endif
Sissors 1:782a3ddc329e 139 run_command();
Sissors 1:782a3ddc329e 140
Sissors 1:782a3ddc329e 141 return check_error();
Sissors 1:782a3ddc329e 142 }
Sissors 1:782a3ddc329e 143
Sissors 1:782a3ddc329e 144 /* Clear possible flags which are set, run command, wait until done */
Sissors 1:782a3ddc329e 145 __attribute__((section(".ARM.__at_0x11500"))) inline void run_command(void) {
Sissors 1:782a3ddc329e 146 //Clear possible old errors, start command, wait until done
Sissors 1:782a3ddc329e 147 __disable_irq(); //Disable IRQs, preventing IRQ routines from trying to access flash (thanks to https://mbed.org/users/mjr/)
Sissors 1:782a3ddc329e 148 FTFA->FSTAT = FTFA_FSTAT_FPVIOL_MASK | FTFA_FSTAT_ACCERR_MASK | FTFA_FSTAT_RDCOLERR_MASK;
Sissors 1:782a3ddc329e 149 FTFA->FSTAT = FTFA_FSTAT_CCIF_MASK;
Sissors 1:782a3ddc329e 150 while (!(FTFA->FSTAT & FTFA_FSTAT_CCIF_MASK));
Sissors 1:782a3ddc329e 151 __enable_irq();
Sissors 1:782a3ddc329e 152 }
Sissors 1:782a3ddc329e 153
Sissors 1:782a3ddc329e 154
Sissors 1:782a3ddc329e 155
Sissors 1:782a3ddc329e 156 /* Check if no flash boundary is violated
Sissors 1:782a3ddc329e 157 Returns true on violation */
Sissors 1:782a3ddc329e 158 __attribute__((section(".ARM.__at_0x11600"))) bool check_boundary(int address, unsigned int length) {
Sissors 1:782a3ddc329e 159 int temp = (address+length - 1) / SECTOR_SIZE;
Sissors 1:782a3ddc329e 160 address /= SECTOR_SIZE;
Sissors 1:782a3ddc329e 161 bool retval = (address != temp);
Sissors 1:782a3ddc329e 162 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 163 if (retval)
Sissors 1:782a3ddc329e 164 printf("IAP: Boundary violation\r\n");
Sissors 1:782a3ddc329e 165 #endif
Sissors 1:782a3ddc329e 166 return retval;
Sissors 1:782a3ddc329e 167 }
Sissors 1:782a3ddc329e 168
Sissors 1:782a3ddc329e 169 /* Check if address is correctly aligned
Sissors 1:782a3ddc329e 170 Returns true on violation */
Sissors 1:782a3ddc329e 171 __attribute__((section(".ARM.__at_0x11700"))) bool check_align(int address) {
Sissors 1:782a3ddc329e 172 bool retval = address & 0x03;
Sissors 1:782a3ddc329e 173 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 174 if (retval)
Sissors 1:782a3ddc329e 175 printf("IAP: Alignment violation\r\n");
Sissors 1:782a3ddc329e 176 #endif
Sissors 1:782a3ddc329e 177 return retval;
Sissors 1:782a3ddc329e 178 }
Sissors 1:782a3ddc329e 179
Sissors 1:782a3ddc329e 180 /* Check if an area of flash memory is erased
Sissors 1:782a3ddc329e 181 Returns error code or Success (in case of fully erased) */
Sissors 1:782a3ddc329e 182 __attribute__((section(".ARM.__at_0x11800"))) IAPCode verify_erased(int address, unsigned int length) {
Sissors 1:782a3ddc329e 183 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 184 printf("IAP: Verify erased at %x with length %d\r\n", address, length);
Sissors 1:782a3ddc329e 185 #endif
Sissors 1:782a3ddc329e 186
Sissors 1:782a3ddc329e 187 if (check_align(address))
Sissors 1:782a3ddc329e 188 return AlignError;
Sissors 1:782a3ddc329e 189
Sissors 1:782a3ddc329e 190 //Setup command
Sissors 1:782a3ddc329e 191 FTFA->FCCOB0 = Read1s;
Sissors 1:782a3ddc329e 192 FTFA->FCCOB1 = (address >> 16) & 0xFF;
Sissors 1:782a3ddc329e 193 FTFA->FCCOB2 = (address >> 8) & 0xFF;
Sissors 1:782a3ddc329e 194 FTFA->FCCOB3 = address & 0xFF;
Sissors 1:782a3ddc329e 195 FTFA->FCCOB4 = (length >> 10) & 0xFF;
Sissors 1:782a3ddc329e 196 FTFA->FCCOB5 = (length >> 2) & 0xFF;
Sissors 1:782a3ddc329e 197 FTFA->FCCOB6 = 0;
Sissors 1:782a3ddc329e 198
Sissors 1:782a3ddc329e 199 run_command();
Sissors 1:782a3ddc329e 200
Sissors 1:782a3ddc329e 201 IAPCode retval = check_error();
Sissors 1:782a3ddc329e 202 if (retval == RuntimeError) {
Sissors 1:782a3ddc329e 203 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 204 printf("IAP: Flash was not erased\r\n");
Sissors 1:782a3ddc329e 205 #endif
Sissors 1:782a3ddc329e 206 return EraseError;
Sissors 1:782a3ddc329e 207 }
Sissors 1:782a3ddc329e 208 return retval;
Sissors 1:782a3ddc329e 209
Sissors 1:782a3ddc329e 210 }
Sissors 1:782a3ddc329e 211
Sissors 1:782a3ddc329e 212 /* Check if an error occured
Sissors 1:782a3ddc329e 213 Returns error code or Success*/
Sissors 1:782a3ddc329e 214 __attribute__((section(".ARM.__at_0x11900"))) IAPCode check_error(void) {
Sissors 1:782a3ddc329e 215 if (FTFA->FSTAT & FTFA_FSTAT_FPVIOL_MASK) {
Sissors 1:782a3ddc329e 216 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 217 printf("IAP: Protection violation\r\n");
Sissors 1:782a3ddc329e 218 #endif
Sissors 1:782a3ddc329e 219 return ProtectionError;
Sissors 1:782a3ddc329e 220 }
Sissors 1:782a3ddc329e 221 if (FTFA->FSTAT & FTFA_FSTAT_ACCERR_MASK) {
Sissors 1:782a3ddc329e 222 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 223 printf("IAP: Flash access error\r\n");
Sissors 1:782a3ddc329e 224 #endif
Sissors 1:782a3ddc329e 225 return AccessError;
Sissors 1:782a3ddc329e 226 }
Sissors 1:782a3ddc329e 227 if (FTFA->FSTAT & FTFA_FSTAT_RDCOLERR_MASK) {
Sissors 1:782a3ddc329e 228 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 229 printf("IAP: Collision error\r\n");
Sissors 1:782a3ddc329e 230 #endif
Sissors 1:782a3ddc329e 231 return CollisionError;
Sissors 1:782a3ddc329e 232 }
Sissors 1:782a3ddc329e 233 if (FTFA->FSTAT & FTFA_FSTAT_MGSTAT0_MASK) {
Sissors 1:782a3ddc329e 234 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 235 printf("IAP: Runtime error\r\n");
Sissors 1:782a3ddc329e 236 #endif
Sissors 1:782a3ddc329e 237 return RuntimeError;
Sissors 1:782a3ddc329e 238 }
Sissors 1:782a3ddc329e 239 #ifdef IAPDEBUG
Sissors 1:782a3ddc329e 240 printf("IAP: No error reported\r\n");
Sissors 1:782a3ddc329e 241 #endif
Sissors 1:782a3ddc329e 242 return Success;
Sissors 1:782a3ddc329e 243 }