Advanced Computer Systems Course - CC2650 FW update feature
|Led by||Prof. Sivan Toledo|
|Contributors||Idan Berkovits (205404130)|
|Omri Lifshitz (205490675)|
The goal of our project was to develop firmware update “on the fly” feature, without intervening with the device’s current run.
Many IoT products, and in particular products that use wireless connections, are out of reach for physical connections and thus a different solution is required in order to update their firmware. The device has a ROM bootloader that enables updating the firmware over UART communication, but requires entering a special bootloader mode in order to do so, and thus is probably irrelevant for many IoT products. In our project, we developed a module for the CC2650 that enables updating the firmware of a running device without intervening with its current run.
In this section will describe the different logical modules in the project and the developement stages.
The first thing that was required for such a product was the ability to control and interact with the device. We decided to implement the protocol used by the device’s ROM bootloader. This is a packet based protocol between the bootloader and the client, where each packet is of the following format:
- Size (1-byte)
- Checksum (1-byte)
- Data. First byte represents the command type.
That way, we can create a client that interacts both with the module we were trying to create and the device’s ROM bootloader. The interaction with the ROM bootloader was very helpful in the first stages of the project in order to check that our client works and we are able to fully communicate with the device.
We implemented the client using .NET serial communication libraries. We created a command line interface that wraps the serial communication with the following commands:
- Connect – connects to the given COM port and synchronizes the device’s UART baud rate with the COM port’s.
- Ping – Tests the connection with the device.
- Status – Returns the result status of the previous command (success or informative failure message).
- Download – Downloads a large amount of data to the device at a given address and verifies that it was written successfully using CRC32.
- Write – Writes to the memory at a given address. Used mostly to deliver small amount of data.
- Read – Reads number of bytes given from the memory starting at a given address.
- Reset – Resets the device.
Programming the flash using our client
The output of TI’s CCS6.2 build process is an ELF format file which needs to be loaded to the device.
The ELF file is comprised of sections of different types and permissions. During the loading process, we ignore all sections without read, write or execute permissions and thus we do not need to handle irrelevant sections such as the symbol table.
The ‘.data’ and ‘.bss’ sections are intended to be loaded to the RAM and therefore are empty and we do not need to regard them. Their data is set by the reset vector which is an executable section at address 0x0.
We therefore iterate over all sections to be written to the flash and program them using our client. The remaining sections we have to handle are:
- ‘.resetVector’ – code that fills the data with values.
- ‘.cinit’ – Prepares the device before running the main function.
- ‘.text’ – Where most of the code is stored. This is both our code and the OS’s.
- ‘.const.*’ – Tables of constants stored to the flash.
- ‘.ccfg’ – Section written to a reserved address in the end of flash and is used by the device load the configurations at startup.
One of the main issues when upgrading software is avoiding overwriting the currently running code while downloading the new software.
This led us to the following design:
The first sector of the flash is reserved for a bootloader that jumps to the software we wish to run. This bootloader is never overwritten by the newly downloaded software. This sits at the first sector of the flash because the device always begins its run at address 0x0.
The CCFG is stored at the specified address as before.
The rest of the flash is separated into two slacks, each one intended for a different software; one is the currently running software and the other is designated for downloading the new software.
In order to implement this firmware upgrade mechanism, we needed to add two new functionalities to the bootloader protocol:
- Update – Sent by the client to the device and states the size of the software to be downloaded. The device then checks if there is enough space for the given software, erases the software from the target not in use (in order to enable writing to this slack) and returns the address to which the software needs to be downloaded. The address is returned because the code needs to be relocated accordingly.
- Change FW – Assumes the software has been written and verified, and overwrites the bootloader so that it runs the new software on the next reset. After that, all that needs to be done is to call the device’s reset method and the new software will run.
To complete the goal described in the introduction, we need to implement the abovementioned bootloader protocol over wireless communication, such Bluetooth, so we will be able to update the device’s firmware without having physical connection to the device. We decided that part was out of scope for our project and focused on the parts described above.
Bootloader server module
We implemented software update using the device’s UART interface. We ran an independent task whose sole responsibility was to handle the UART communication. This task continuously waited in blocking mode for a byte to be received stating the size of the packet, and then waited for the full packet to be received after knowing its size. This task ran at low priority in order to not starve the rest of the system functionalities while waiting for data to be received.
We decided to implement this using the UART in blocking mode at a low priority task rather than interrupt for simplicity sake as our main goal was to show the firmware update logic and not the use of the UART interface. The use of a low priority task makes sure that we do not intervene with the device’s functionality as other events running at higher priorities and interrupt context will still run. This may be an issue if we take into consideration power consumption and so on, but again this was only done to show the firmware upgrade functionality.
To parse the elf file, we used the ‘pyelftools’ python library. It gives a convenient API to iterate over the sections, including relocations.
Challenges and Unresolved Issues
The main challenge in our project was to create a relocatable executable file.
We needed to use the compiler and linker used by TI’s CCS environment as we needed to link TI’s RTOS as well. This meant that we needed to compile and link the program to a different address using the CCS environment.
In order to overcome this problem, we tried some solutions:
- Take the Project_Zero_Stack as an example for a project linked to a different address. We were not able to do that, we suspect because this project is used as an external library and not as a standalone.
- Link the program as PIC (position independent code), but after lots of research in the interface, we had to assume CCS does not support this functionality.
- We did manage to link the ELF as a relocatable executable using the CCS environment. This meant, that during the loading of the ELF to the flash, all of the relocations needed to be resolved. In order to do that we used many TI and ARM technical reference explaining how to resolve each relocation. Unfortunately, as we went along with developing the relocation resolver, we found that it is more difficult than we expected: the different reference manuals we used were contradicting each other, and other tools like IDA (Interactive Disassembler) did not help. In addition, it would have been very difficult to debug relocation problems (which would have been fine if we found a decent manual). Therefore, we decided settle for the parts we implemented so far, specified above.