I though about this necessity many times in my life, but at the end the practical applications of these methods are kind-of useless.
For instance, re-writing code in flash and jumping at a specific location is what every bootloader is doing right now. The problems are:
a. The bootloader has to know where to jump, so this information has to be saved somewhere (in 99% of cases this address is hardwired). If my code can be loaded at an arbitrary position in flash, then I have to "remember where" by saving the address in flash.
b. Flash sectors are usually big and they have to be erased by sector (so writing any new information, even a byte, requires blowing up and wasting an entire 16K sector, for example).
So if the idea is either to update, in flash, a piece of code, a single function or the entire program firmware, it requires erasing and writing sectors in multiple of 16KB (and this number is getting bigger as flash size increases).
Still, if there is a workaround for this, then thinking "it's practical to have relocatable code because the code is updated at every boot so we always have the latest version" is the wrong approach since there is a finite number of times one can write in flash before it gets damaged.
Also, the article is about Cortex-M, so we have to think about the vector table. Address on the vector table cannot be "relocatable" as these are fetched as-is and not translated. So now you have to point all your interrupts to a single interrupt vector that gets the interrupt type, searches the correct function, translates it, and then jump to it.
It's also true that you can relocate the vector table in RAM, so you can modify these address on the fly, but that's extra work.
At the end of the day, you'll be better playing around with linker scripts and updating programs or pieces of code by pointing to known addresses (sections) from the linker script.
Edit: I didn't mean to say that relocatable code is just about writing on flash at every boot. Instead I wanted to point out that relocatable could lead to this idea.
I think you've missed the point as explained in the first few paragraphs: they want a kind of blue/green deployment system to cover the possibility of upgrades failing. Nowhere does it say they will write to Flash on every boot, just on the successful ones.
On the other hand there seems to be something weird here since PIC works perfectly fine in normal userland.
In normal userland you have lots of RAM, and the whole virtual address space to play with, and you can make any part of it you like either read-only or read-write, so it doesn't matter much if you end up with either (a) a fixed offset between the code section and the rw data section or (b) a big global offset table. As the article points out, those are more awkward in a small-RAM microcontroller environment with no virtual memory.
I have a device with 512k flash. I use 64k for a bootloader, 208k each for two possible application versions, the rest for settings.
If I can use relocatable code, I can download a new firmware version during normal operation of the device with no need to switch over to the bootloader. When it's time to switch version, a quick reboot is all that's needed. This means downtime of a second or two instead of several minutes, and that's very valuable.
A possible work-around is to prepare two binaries for each firmware version, one per possible location, but then I'd need to negotiate with the device to know which image to send. Better to just have one image fit any location.
Ok. Your explanation was clearer to me than the one from the article. But still, how does your bootloader know which version to use?
What I did many times in the past is to have two areas in flash: "normal run" and "update". "Normal run" has the running firmware and "update" is an area where the running firmware will use to store a new version.
It goes like this: the new firmware version is flashed to the "update" area in flash, usually with a header and a checksum. Then the bootloader will check for this header/checksum in the "update" area and if valid, will flash the contents of "update" area into "normal run" area. Then the contents of "update" are invalidated (erased) and the bootloader jumps to "normal run" as usual.
If any of the above fails, the firmware will be just re-written. The above is also valid for checking if the new firmware is authorized to run (signed, whatever).
I know some Nordic chips have special registers for this purpose.
In our case the images contain some meta data, including version and checksums. The bootloader can default to starting the newest version, and be sure that everything is fine by checking the checksum. If needed the bootloader can be instructed to either remain in bootloader mode (to receive a new firmware image) or to start the older firmware.
Saving the entry where the bootloader should jump in some fixed location is not necessary.
In a firmware update solution that I have written many years ago, there were multiple flash sectors where the firmware could be located.
In each flash sector there was a word reserved for a counter that counted how many firmware updates had been done to the device, i.e. every time when the device received a new firmware image that was written in the flash, the counter was incremented from the value written in the flash sector for the current firmware version.
The bootloader verified the sectors that could contain a firmware image for integrity by computing a hash, then among the valid firmware images it chose the one with the highest counter, i.e. the most recent update. The firmware entry address was stored at a fixed offset from the start of the flash sector.
So there were no flash writes, except for the single sector written by each firmware update, and the counters that stored the number of flash sector writes could also be used to check whether the flash endurance limit might be reached.
Because there were multiple addresses in the flash where the firmware could be located, it might have been useful if the firmware images could have been relocatable.
Without such an option, every time when the firmware was built, multiple binary images were created with the appropriate linker scripts, each linked for one of the possible flash sectors.
The multiple images were concatenated into a single fat binary. When a device received a command for firmware update, it chose an available flash sector and then, while receiving the firmware file, it ignored the parts of the file intended for other flash sectors and it wrote in the flash only the parts intended for the locally chosen sector.
Because the devices were controlled over a relatively fast communication link, sending a fat binary did not cause any visible delay, because writing the flash was slower than the speed at which the firmware image could be sent.
If an extremely slow serial interface would have been used instead, it is likely that a more complex communication protocol would have been required, to interrogate the device about available flash sectors and then send only the suitable firmware image.
10 comments
[ 6.8 ms ] story [ 33.2 ms ] threadFor instance, re-writing code in flash and jumping at a specific location is what every bootloader is doing right now. The problems are:
a. The bootloader has to know where to jump, so this information has to be saved somewhere (in 99% of cases this address is hardwired). If my code can be loaded at an arbitrary position in flash, then I have to "remember where" by saving the address in flash.
b. Flash sectors are usually big and they have to be erased by sector (so writing any new information, even a byte, requires blowing up and wasting an entire 16K sector, for example).
So if the idea is either to update, in flash, a piece of code, a single function or the entire program firmware, it requires erasing and writing sectors in multiple of 16KB (and this number is getting bigger as flash size increases).
Still, if there is a workaround for this, then thinking "it's practical to have relocatable code because the code is updated at every boot so we always have the latest version" is the wrong approach since there is a finite number of times one can write in flash before it gets damaged.
Also, the article is about Cortex-M, so we have to think about the vector table. Address on the vector table cannot be "relocatable" as these are fetched as-is and not translated. So now you have to point all your interrupts to a single interrupt vector that gets the interrupt type, searches the correct function, translates it, and then jump to it.
It's also true that you can relocate the vector table in RAM, so you can modify these address on the fly, but that's extra work.
At the end of the day, you'll be better playing around with linker scripts and updating programs or pieces of code by pointing to known addresses (sections) from the linker script.
Edit: I didn't mean to say that relocatable code is just about writing on flash at every boot. Instead I wanted to point out that relocatable could lead to this idea.
On the other hand there seems to be something weird here since PIC works perfectly fine in normal userland.
I have a device with 512k flash. I use 64k for a bootloader, 208k each for two possible application versions, the rest for settings.
If I can use relocatable code, I can download a new firmware version during normal operation of the device with no need to switch over to the bootloader. When it's time to switch version, a quick reboot is all that's needed. This means downtime of a second or two instead of several minutes, and that's very valuable.
A possible work-around is to prepare two binaries for each firmware version, one per possible location, but then I'd need to negotiate with the device to know which image to send. Better to just have one image fit any location.
What I did many times in the past is to have two areas in flash: "normal run" and "update". "Normal run" has the running firmware and "update" is an area where the running firmware will use to store a new version.
It goes like this: the new firmware version is flashed to the "update" area in flash, usually with a header and a checksum. Then the bootloader will check for this header/checksum in the "update" area and if valid, will flash the contents of "update" area into "normal run" area. Then the contents of "update" are invalidated (erased) and the bootloader jumps to "normal run" as usual.
If any of the above fails, the firmware will be just re-written. The above is also valid for checking if the new firmware is authorized to run (signed, whatever).
I know some Nordic chips have special registers for this purpose.
In a firmware update solution that I have written many years ago, there were multiple flash sectors where the firmware could be located.
In each flash sector there was a word reserved for a counter that counted how many firmware updates had been done to the device, i.e. every time when the device received a new firmware image that was written in the flash, the counter was incremented from the value written in the flash sector for the current firmware version.
The bootloader verified the sectors that could contain a firmware image for integrity by computing a hash, then among the valid firmware images it chose the one with the highest counter, i.e. the most recent update. The firmware entry address was stored at a fixed offset from the start of the flash sector.
So there were no flash writes, except for the single sector written by each firmware update, and the counters that stored the number of flash sector writes could also be used to check whether the flash endurance limit might be reached.
Because there were multiple addresses in the flash where the firmware could be located, it might have been useful if the firmware images could have been relocatable.
Without such an option, every time when the firmware was built, multiple binary images were created with the appropriate linker scripts, each linked for one of the possible flash sectors.
The multiple images were concatenated into a single fat binary. When a device received a command for firmware update, it chose an available flash sector and then, while receiving the firmware file, it ignored the parts of the file intended for other flash sectors and it wrote in the flash only the parts intended for the locally chosen sector.
Because the devices were controlled over a relatively fast communication link, sending a fat binary did not cause any visible delay, because writing the flash was slower than the speed at which the firmware image could be sent.
If an extremely slow serial interface would have been used instead, it is likely that a more complex communication protocol would have been required, to interrogate the device about available flash sectors and then send only the suitable firmware image.