Firmware
Low-level code driving the system. Application-specific, with direct hardware interactions.
Stored in non-volatile memory, e.. EEPROM, flash memory.
Types of firmware:
- type-1: general purpose OS-based
- popular OS retrofitted for embedded system
- hardware interaction abstracted by kernel
- example: Linux on a router
- type-2: embedded OS-based
- custom OS for embedded systems
- created to solve custom challenges
- reduced functionality compared to traditional OS, e.g. no MMU/virtual memory
- type-3: no OS abstraction
- can be entirely custom
- may include “OS-library” with OS functions
- system and app code compiled together, with no strict kernel/user separation
ARM (Advanced RISC Machine) instruction set family:
- Cortex-A (application processor): highest performance, optimized for rich OSes
- Cortex-R (real time): fast response, optimized for hard real-time apps
- Cortex-M: smallest/lowest power, optimized for microcontrollers
32-bit ARM assembly:
- registers:
- r0-r12: general purpose
- r13: stack pointer
- r14: link register (return address of last call)
- r15: program counter
- cpsr: current program status register (has flags)
- mov instruction reserved for moving things from one register to another
- no classical push/pop instructions
Memory maps
- type 2/3 firmware typically has no need or resources for virtual address spaces
- instead have single physical address, everything mapped at predefined addresses
Firmware programming models:
- super-loop architecture
- single loop for everything: IO, calculations. never returns, or only on error.
- simple logic
- hard to scale for comms between subsystems, interrupts, and starved inputs.
- ROTS (real time operating system)
- different tasks for different subsystems
- primitives for inter-task comms
- scheduler can enforce real-time constraints, may use timer interrupts
Interrupts (IRQs)
- events requiring attention from CPU
- generated by hardware or software
- processing may be disabled (masked)
- typically associated with numbers, optionally with priorities
Interrupt vector
- determines how to handle incoming interrupts
- maps interrupt numbers to callbacks - interrupt service routines (ISR)
- reprogrammable during runtime
Interacting with peripherals:
- interrupts (channel from peripheral to CPU)
- direct memory access (DMA) allows direct data transfer between memory and peripheral, CPU only needed to initiate the transfer
- memory-mapped IO (MMIO) - directly interfaces via memory, organize in registers
- register types: status, data, control
- port-mapped IO - not present in ARM, IN/OUT instructions on x86
Typical peripheral accesses:
- configuration: firmware initialization, before and after peripheral usage
- status: after configuration for confirmation, to check operationality
- data: when data to be sent/received
- reading data:
- IRQ-based:
- when data arrives, raise interrupt & read and store data in ISR
- data arrival is ensured, but async processing ads complexity, and might starve main firmware task due to many interrupts
- polling-based
- when data expected, loop over status register to check availability, and if available, read data register
- so, no sudden changes of control flow, and data only received when desired. but arrival can easily be missed, and lots of time spent polling.
Firmware file formats
- no standard, usually code and data interleaved
- unpacking: restoring sections and files from binary firmware image
Firmware extraction:
- firmware updates: may be downloadable from vendor website, device initiated retrieval is sniffable
- on-device dump: complex devices may contain flash storage accessible from software, and/or debug shell. for shell, probably need to find UART ports. Once shell access is gained, dump using e.g.
dd
or xmodem
.
- for bootloaders, there might be a bootloader mode. you might need hardware access for that. but may allow to re-flash firmware and to read firmware.
- debugging interfaces: usually can read memory, a wide-spread interface is JTAG
- flash dumping: frequently requires soldering, e.g. removing the chip or adding wires to pins of the chip.
- fault injection: altering intended behavior via faults, could be used to force outputs to be longer, or to skip readout protection checks. most prominent techniques are voltage fault injection and electromagnetic fault injection. likely needs removal of electronic components.