Downloadable Firmware in a Flash - Embedded.com

Downloadable Firmware in a Flash

Looking for a way to update flash-based firmware in a design? This article describes a handy software architecture that will help you avoid common mistakes.

The problem with any approach to field firmware updates is that if the upgrade contains a flaw, the target system may become an expensive doorstop. Many of the pitfalls are obvious and straightforward, but some insidious defects don't appear until after a product has been deployed.

Users are unequaled in their ability to expose and exploit product defects. To make matters worse, they generally fail to heed warnings such as “System damage will occur if power is interrupted while programming is underway.” They will happily attempt to reboot an otherwise functional system in the middle of the update process and then file a warranty claim for the now “defective” product.

Any well-designed firmware upgrade system must be able to recover from user errors and other catastrophic events to the fullest extent possible. The best way to accomplish this is to implement a fundamentally sound firmware update strategy that avoids these problems entirely. This article presents one such strategy. The design is suitable for direct implementation, but you can modify it based on the features your application does or does not need.

The microprogrammer

A microprogrammer is a system-level description of how an embedded system behaves before, during, and after the firmware update process. This behavior is defined to help avoid many of the problems associated with other approaches to downloadable firmware. Careful implementation of this behavior eliminates the remaining concerns.

The first step in a microprogrammer-based firmware update is to place the embedded system into a state where it expects the download process to occur. The transition to this state can be triggered in a number of ways; by the user pressing a button marked UPGRADE on the device's interface panel, for instance, or when the system detects the start or end of a file transfer, or by some other means. In any case, the target system realizes that its firmware will soon be updated and brings any controlled processes to a safe and stable halt configuration.

Next, the target system receives a small application called a microprogrammer . The microprogrammer assumes control of the system and begins receiving the new application firmware and programming it into flash. When it's done, the target system begins running the new firmware.

One of the biggest selling points of a microprogrammer-based approach to downloadable firmware is its flexibility. The microprogrammer's implementation can be changed even in the final moments before the firmware update process begins, which lets you apply bug fixes and enhancements to deployed systems.

A microprogrammer does not consume resources in the target system except when programming is actually underway. Furthermore, since an effective microprogrammer is small, the target system doesn't require a bulky, sophisticated communications protocol to receive it at the start of the process. A simple text file transfer is often reliable enough.

The safety of a microprogrammer-based firmware update is sometimes its most compelling advantage. When the target system's flash chip is not used for any purpose other than firmware storage, the code to erase and program the flash chip does not exist in the system until it is actually needed. As such, the system is unlikely to accidentally erase its own firmware, even in severe cases of program runaway.

However, one of the drawbacks is that the microprogrammer is usually implemented as a stand-alone program, so its code must be managed separately from both the application that downloads it to the target system and the application that it delivers to the target system. Careful attention to program modularity may help to minimize the workload.

A microprogrammer is generally downloaded to and run from RAM. This simply isn't possible for certain microprocessor architectures, in particular the venerable 8051 and family. Hardware-oriented workarounds exist for these cases, but the limitations they impose often outweigh their benefits.

Download

The code in Listing 1 illustrates the functionality a target system needs in order to download and run a microprogrammer. In the example, the target receives a plaintext Motorola S Record file from some I/O channel (perhaps a serial port), which is decoded and written to RAM. The target then activates the microprogrammer by jumping into the downloaded code at the end of the transfer.

Listing 1: Code to download and run a microprogrammer

typedef void (*entrypoint_t)(void);

void get_and_run_microprogrammer(){ char programmer_buf[PROGRAMMER_BUFF_SIZE]; int len; char sbuf[256]; unsigned long addr; enum srec_type_t type; entrypoint_t entrypoint;

while (1) { if (read_srecord(&type, &len, &addr, sbuf)) { switch (type) { case SREC_TYPE_S1: case SREC_TYPE_S2: case SREC_TYPE_S3: /* record contains data (code) */ memcpy(programmer_buf + addr, sbuf, len); break;

case SREC_TYPE_S7: /* record contains address of downloaded main() */ entrypoint = (entrypoint_t)(programmer_buf + addr); break;

case SREC_TYPE_S9: /* record indicates end of data (code),- run it */ entrypoint(); break; } } }}

Notice that the programmer_buf[] memory space is allocated as an automatic variable, which means that it has no fixed location in the target system's memory image. This implies that the addresses in the incoming records are relative rather than absolute and that the incoming code is position independent. If your compiler can't produce position-independent code, then programmer_buf[] must be assigned to a fixed location in memory, and the addresses in the incoming records must reside at that location.

The incoming microprogrammer image can be placed on top of other data if the target system does not have the resources to permanently allocate programmer_buf[] . At this point, the system has suspended normal operations anyway, making the bulk of its total RAM space available for use by the microprogrammer.

A basic microprogrammer

The top-level code for a microprogrammer is shown in Listing 2. This code also receives an S Record file from some source and decodes it. The microprogrammer writes the incoming data to flash, and the system reboots at the end of the file transfer. Although overly simplistic (a plaintext file transfer is probably not reliable enough for large programs), this code illustrates all the important features of a microprogrammer.

Listing 2: A basic microprogrammer

void programmer(){	int len;	char buf[256];	unsigned long addr;	enum srec_type_t type;

while (1) { if (read_srecord(&type, &len, &addr, buf)) { switch (type) { case SREC_TYPE_S1: case SREC_TYPE_S2: case SREC_TYPE_S3: /* record contains data or code, program it */ if (!is_section_erased(addr, len)) erase_flash(addr, len); write_flash(addr, len, buf); break; case SREC_TYPE_S9: /* record indicates end of data, reset and run */ reset(); break; } } }}

In addition to actually erasing flash, the function erase_flash() manages a simple data structure that keeps track of which sections of the flash chip need to be erased and which ones have already been erased. This data structure is checked by the is_section_erased() function to prevent multiple erasures of flash sections when data arrives out of order, which is a common occurrence in an S Record file.

Read-only memory

Regardless of how you modify the microprogrammer-based system description to fit your own requirements, you will encounter some common problems in the final implementation.

Some debuggers, emulators in particular, struggle with the idea of code space that can be written to by the target microprocessor. Most debugging tools treat code space as read-only, and some will generate error messages or simply disallow a write if they detect one.

In general, the debugger's efforts to protect code space are well intentioned. A program writing to its code space usually signals a serious programming error, except when a firmware update is in progress. Unfortunately, the debugger can't tell the difference. The remedy is to implement a memory alias for the flash chip, in a different memory space that is not considered read-only by the debugger.

The typical implementation of a memory alias is straightforward. Simply double the size of the chip select used to activate the device and apply an offset to any address that refers to the “physical” address region to move it to the “alias” region. The best place to apply this offset is usually in the flash chip driver itself, as shown in the hypothetical write_flash() function in Listing 3.

Listing 3: Implementing memory aliasing in a write_flash() function

#define PHYS_BASE_ADDRESS 0x00000 // physical base address#define ALIAS_BASE_ADDRESS 0x80000 // alias base address

void write_flash(long addr, unsigned char * data, int len){ addr = (addr - PHYS_BASE_ADDRESS + ALIAS_BASE_ADDRESS); while (length) { ... }}

Integral programmer

One variation on the microprogrammer approach is to build the microprogrammer's functionality into the target system-a so-called bootloader —instead of downloading it to RAM as the first step of the firmware update process. This strategy has its advantages, but creates the need to copy the programmer's code from flash to RAM before the flash chip is erased and reprogrammed. In other words, the code must self-relocate to RAM at some point during its operation, so that it can erase and write to the flash chip. This approach also requires that the code involved be position-independent.

Listing 4: A basic relocation strategy

typedef int (*entrypoint_t)(void);

entrypoint_t relocate_programmer(){

/* relocate the code */ memcpy(RAM_PROG_START, ROM_PROG_START, PROG_LEN);

/* find programmer() in ram: its location is the same offset from RAM_PROG_START as the rom version is from ROM_PROG_START */ return (entrypoint_t)((char*)programmer - (char*)ROM_PROG_START + (char*)RAM_PROG_START);}

The code in Listing 4 shows how to copy the integral programmer's code into RAM and how to find the RAM version of the function programmer() . The symbols RAM_PROG_START, PROG_LEN, and ROM_PROG_START mark the regions in RAM and ROM where the programmer's code (which may be a subset of the total application) is located and can often be computed automatically by the program's linker. The complicated looking casting in the entrypoint calculation forces the compiler to do byte-sized address calculations when computing the value of entrypoint .

When the caller invokes the function at the address returned by relocate_programmer() , control passes to the RAM copy of the microprogrammer code, and your debugger, if in use, stops showing any symbolic information related to the programmer() function. Why? Because programmer() is now running from an address that is different from the address at which it was originally located, so any symbolic information provided to the debugger by the linker is now meaningless.

One solution to this problem is to re-link the application with programmer() at the RAM address, and then import this symbol information into the debugger. This would be a convenient fix except that not all debuggers support incremental additions to their symbol table.

Another option is simply to suffer until the debugging of programmer() is complete, at which point you don't need to look at the code any more-in theory, anyway.

Relocation

Not all embedded development toolchains can produce code that is relocatable at runtime. Such position-dependent code must run from the address at which the linker placed it,or the program will crash.

When a position-dependent microprogrammer is all that is available, it must be downloaded to the location in RAM from which the linker intended it to run. This implies that the memory reserved to hold the microprogrammer must be allocated at a known location, as I mentioned earlier.

With a bootloader approach, there are two options. The first is to compile the programmer code as a stand-alone program located at its destination address in RAM. This code image is then included in the application image (perhaps by translating its binary image into a constant character array) and copied to RAM at the start of the firmware update process. This is a lot like a microprogrammer implementation, with the microprogrammer “downloaded” from the target's onboard memory instead of from a serial port.

The second option is to handle the integral programmer code as initialized data and use the runtime environment's normal initialization procedures to copy the code into RAM. The GNU compiler supports this using its _attribute_ language extension. Several commercial compilers provide this capability as well. The only limitation of this strategy is that it requires enough RAM space to hold the integral programmer code plus the program's other data.

Avoiding disaster

Even in the most carefully designed downloadable firmware feature, the possibility still exists that the target hardware could attempt a startup using an accidentally blank flash chip. The sort of outcome to avoid in this case is the sudden activation of, say, the application's rotating machinery, which subsequently gobbles up an unprepared user. The prescription for this case-which is best applied before a user is actually eaten-involves a careful study of the target processor's reaction to the illegal instructions and/or data represented by the 0xff 's of an unprogrammed section of flash memory. Some processors eventually halt processing and tristate their control signals to let them float to whatever value is dictated by external hardware. Without pullup resistors or other precautions in place to force these uncontrolled signals to safe states, unpredictable and potentially lethal results are likely.

Who's watching the watchdog?

In systems that support downloadable firmware, an unavoidable application defect that forces a watchdog timeout and system reset can lock the microprogrammer out of the embedded system. The extreme case is an accidental while(1); statement in a program's main() function. The loop-within-a-loop that results (the infinite program loop, wrapped by the infinite watchdog timeout and system-reset loop) keeps the target from responding to the UPGRADE button, because the system restarts before the button is even checked.

Systems that support downloadable firmware must carefully examine all available status circuitry to determine why the system is starting up and force a transition to UPGRADE mode in the event that an excessive number of consecutive watchdog or other resets are detected. Many systems do not interrupt power to RAM when a watchdog timeout occurs, so it is safe to store a “magic number” and count off the number of resets there. The counter is incremented on each reset, and, once a certain number is reached, the boot code can halt the application in an effort to avoid the code that forces the reset.

Power interruption

If power is lost unexpectedly during flash reprogramming, then the target's flash chip is left in a potentially inconsistent state when power is restored. Maybe the programming operation finished and everything is fine, but probably not. In the best case, the system's boot code is intact but portions of application code are missing. In the worst case, the flash chip is completely blank.

In the first case, a checksum can be used to detect the problem, and the system can force a transition to UPGRADE mode whether the user is requesting one or not. The only solution to the second case is to keep it from happening.

One way to avoid producing a completely blank flash chip is to never erase the section of flash that contains the system's bootcode. By leaving this code intact at all times, a hopefully adequate environment is maintained even if power is interrupted. This approach is not always an option, however. The flash chip may be a single-sector part that can only do an all-or-nothing erase, or the “boot sector” of the flash chip may be larger than the boot code it contains and the wasted space needed for application code.

A careful strategy for erasing and reprogramming the boot region of a flash chip can minimize the risk of damage to a system from unexpected power interruption and, in some cases, eliminate it entirely.

The strategy works like this. When a request to reprogram the section of flash containing boot code is detected, the programmer first creates a copy of the position-independent code in that section in another section(or assumes that a prepositioned clone of the system's boot code exists). The boot code is then erased, and the target's reset vector is quickly restored to point to the temporary copy of the boot code. Once the boot sector is programmed, the reset vector is rewritten to point to the new startup code.

One of two keys to the success of this strategy hinges on careful selection of the addresses used for the temporary and permanent copies of the boot code. If the permanent address is lower than the temporary address by a power of two, then the switch from the temporary reset vector to the permanent one can take place by only changing bits from one to zero, which makes a new erase of the flash sector unnecessary. This makes the switch from the temporary reset vector to the permanent one an atomic operation. No matter when power is interrupted, the vector still points to a valid location.

The other critical element of this strategy is to eliminate the risk of power interruption in the moment between when the boot sector is erased and when the temporary reset vector is written. The amount of energy required to complete this operation can be computed by looking at the power consumption and timing specifications in the datasheet for the flash chip and microprocessor. It's usually in a range where additional capacitance in the system's power supply circuitry can bridge the gap if a power loss occurs. By checking that power has not been already lost at the start of the operation and running without interruption until the sector is erased and the temporary reset vector is written, the remaining opportunity for damage is eliminated.

The limitation of this strategy is that it depends on a processor startup process that reads a reset vector to get the value of the initial program counter. In processors that simply start running code from a fixed address after a reset, it may be possible to modify this strategy to accomplish the same thing with clever combinations of opcodes.

Hardware alternatives

The microprogrammer-based firmware update strategy and its variations are all firmware-based because they require that some code exist in the target system before that code can be reprogrammed. This creates a chicken-and-egg problem. If you need code in the embedded system to put new code into the embedded system, how do you get the initial code there in the first place?

At least two existing hardware options can jump-start this process: background debug mode (BDM) and JTAG. (For more information on JTAG, check out this month's Beginner's Corner)

A processor with a BDM port provides what is in essence a serial port tied directly to the guts of the processor itself. By sending the right commands and data through this port, you can push a copy of your microprogrammer into the target's RAM space and hand over control to it. The BDM port can also be used to stimulate the I/O lines of the flash chip, thus programming it directly. Many BDM-based development systems include scripts and programs that implement this functionality, but it can also be done by hand with a few chips, a PC's printer port, and a careful study of the processor's datasheets.

JTAG is a fundamentally different technology designed to facilitate reading and writing of a chip's I/O lines, usually while the chip's microprocessor (if it has one) is held in reset. Like BDM, however, this capability can be used to stimulate a RAM or flash chip to push a microprogrammer application into it. And also like BDM, a JTAG interface can be built with just a few components and some persistent detective work in the target processor's manual.

A JTAG bus transceiver chip, versions of which are available from several vendors, can be added to systems that lack JTAG support.

Flexible and safe

A microprogrammer-based downloadable firmware feature, when properly implemented, safely adds considerable flexibility to an embedded system that uses flash memory. The techniques described here will help you avoid common mistakes and reap the uncommon benefits that microprogrammers and flash memory can offer.

Bill Gatliff is an independent consultant and a contributing editor to Embedded Systems Programming. He welcomes questions and comments at

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.