1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

b/c/particle-mesh: Support particle bootloader

Closes: https://github.com/RIOT-OS/RIOT/issues/12320
This commit is contained in:
chrysn 2020-05-07 16:37:33 +02:00
parent c3b70d5a65
commit 7582be6d5e
9 changed files with 317 additions and 12 deletions

View File

@ -9,18 +9,36 @@ PORT_DARWIN ?= $(firstword $(sort $(wildcard /dev/tty.SLAB_USBtoUART*)))
# add the common header files to the include path
INCLUDES += -I$(RIOTBOARD)/common/particle-mesh/include
# This board uses a DAP-Link programmer
# Flashing support is provided through pyocd (default) and openocd.
# For openocd, a version built against the development branch and containing
# the support for nrf52 cpu is required.
PROGRAMMER ?= pyocd
ifeq (pyocd,$(PROGRAMMER))
# The board is not recognized automatically by pyocd, so the CPU target
# option is passed explicitly
FLASH_TARGET_TYPE ?= -t nrf52840
include $(RIOTMAKE)/tools/pyocd.inc.mk
else ifeq (openocd,$(PROGRAMMER))
DEBUG_ADAPTER ?= dap
ifeq (1,$(PARTICLE_MONOFIRMWARE))
CFLAGS += -DPARTICLE_MONOFIRMWARE
ROM_OFFSET = 0x30000
FW_ROM_LEN = 0xc4000
FLASHFILE = $(BINFILE)-checksummed
# Setting DFU_ARGS won't work as the implied --reset causes errors.
FFLAGS = -d 0x2B04:0xD00E -a 0 -s 0x30000:leave -D $(FLASHFILE)
PROGRAMMER = dfu-util
include $(RIOTMAKE)/tools/dfu.inc.mk
else
# This board uses a DAP-Link programmer
# Flashing support is provided through pyocd (default) and openocd.
# For openocd, a version built against the development branch and containing
# the support for nrf52 cpu is required.
PROGRAMMER ?= pyocd
ifeq (pyocd,$(PROGRAMMER))
# The board is not recognized automatically by pyocd, so the CPU target
# option is passed explicitly
FLASH_TARGET_TYPE ?= -t nrf52840
include $(RIOTMAKE)/tools/pyocd.inc.mk
else ifeq (openocd,$(PROGRAMMER))
DEBUG_ADAPTER ?= dap
endif
endif
MONOFIRMWARETOOL = ${RIOTBOARD}/common/particle-mesh/monofirmware-tool.py
# Rule (variations) for building a monofirmware populated with the right
# (lengths and) checksum in it; only used iuf PARTICLE_MONOFIRMWARE=1
%.bin-checksummed: %.bin %.elf ${MONOFIRMWARETOOL}
${MONOFIRMWARETOOL} $*.elf $< $@
include $(RIOTBOARD)/common/nrf52/Makefile.include

View File

@ -45,6 +45,31 @@ void board_nrfantenna_select(enum board_nrfantenna_selection choice)
void board_init(void)
{
#ifdef PARTICLE_MONOFIRMWARE
/* For comparison with MicroPython's hook into this bootloader, they'd set
* SCB->VTOR here. That is necessary because while the particle bootloader
* largely mimics the ARM bootup by requiring a VTOR at the start of the
* writable firmware, it does not set the VTOR to the loaded data.
*
* That step is not executed *right* here as cpu_init will do it a few
* lines down anyway. */
/* Force keeping the metadata -- the __attribute__((used)) in their macro
* expansions and the KEEP on the section in the linker only almost do
* that: at least *something* from the object file needs to be referenced
* to pull them all in. */
extern uint32_t particle_monofirmware_padding;
uint32_t x;
x = (uint32_t)&particle_monofirmware_padding;
(void)x;
/* Clear out POWER_CLOCK and GPIOTE interrupts set by the bootloader. (If
* actual RIOT code enables them, it'll do so after the board_init call).
* */
NVIC_DisableIRQ(0);
NVIC_DisableIRQ(6);
#endif
/* initialize the boards LEDs */
gpio_init(LED0_PIN, GPIO_OUT);
gpio_set(LED0_PIN);

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2014-2016 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @author Christian Amsüss <chrysn@fsfe.org>
*/
#ifdef PARTICLE_MONOFIRMWARE
#include <stdint.h>
#include "vectors_cortexm.h"
extern const void *_isr_vectors;
/* As described in Particle's dynalib/inc/module_info.h (there under LGPL-3
* terms; detailed license compatibility discussion skipped for not crossing
* any threshold of originality) */
typedef struct module_info_t {
const void* module_start_address;
const void* module_end_address;
uint8_t reserved;
uint8_t flags;
uint16_t module_version;
uint16_t platform_id;
uint8_t module_function;
uint8_t module_index;
uint32_t dependency; /* was module_dependency_t; kept in here to ensure it's blank */
uint32_t dependency2; /* was module_dependency_t */
} module_info_t;
/* 64 words is the distance between the known ISR table and the fixed-position
* module_info at 0x200. If the ISR length is changed for any reason, this must
* be adapted until particle_monofirmware_module_info is precisely at 0x30200.
* (monofirmware-tool.py will complain if it isn't).
*
* This is not `static` as *something* from this module needs to be visible to
* the outside for the __attribute__((used)) and the KEEP on the section in the
* linker script to work; see also the reference to it in board.c. */
ISR_VECTOR(50) const uint32_t particle_monofirmware_padding[64] = {0, };
#ifdef PARTICLE_MONOFIRMWARE_CHECKSUMLIMIT
/* Watch the ISR vector sequence here: This is placed *after* the module_info
* in the flash (52 > 51), but placed before it in the code to be referencable
* from there.
*
* The actual value is replaced by monofirmware-tool.py.
* */
ISR_VECTOR(52) static const uint8_t particle_monofirmware_checksum[4] = {0, 0, 0, 0};
#endif
ISR_VECTOR(51) static const struct module_info_t particle_monofirmware_module_info = {
.module_start_address = &_isr_vectors,
#ifdef PARTICLE_MONOFIRMWARE_CHECKSUMLIMIT
.module_end_address = &particle_monofirmware_checksum,
/* else, it is set by the monofirmware-tool.
*
* For that (more common) case where we check the full firmware, we would
* want to set set
*
* .module_end_address = &_etext + (&_erelocate - &_srelocate),
*
* but that can not be determined at compile time. Instead, this will be
* populated by monofirmware-tool.py, which needs to write in here anyway
* for the checksum. */
#endif
.module_version = 0x138, /* module version that micropython uses */
.platform_id = PARTICLE_PLATFORM_ID,
.module_index = 3, /* MOD_FUNC_MONO_FIRMWARE from dynalib/inc/module_info.h */
};
#else
typedef int dont_be_pedantic;
#endif
/** @} */

View File

@ -35,6 +35,38 @@ To flash the board with OpenOCD, use the `PROGRAMMER` variable:
PROGRAMMER=openocd make BOARD=<board name> -C examples/hello-world flash
```
#### Alternative flashing procedure: Particle bootloader and DFU-Util
As an alternative to using the Particle Mesh debugger,
the USB DFU mode bootloader shipped with Particle Mesh devices can be used.
That mode can be entered by keeping the SETUP/MODE button pressed after power-up or reset
until the RGB LED **blinks yellow fast**.
In this mode, the bootloader and Soft Device are kept in place,
reducing the available flash memory to 784kB,
but in turn, devices can easily be switched back to using Particle software.
In order to use this mode, set `PARTICLE_MONOFIRMWARE=1` wherever you set the `BOARD`.
This will trigger the use of the appropriate linker script,
and switch in [`dfu-util`](http://dfu-util.sourceforge.net/) as the default programmer,
which needs to be installed.
<!-- Using PARTICLE_MONOFIRMWARE=1 images with an external programmer is untested but might just work once checksums are static. -->
On Linux hosts, devices for the DFU mode are frequently only accessible to root,
causing error messages like
dfu-util: Cannot open DFU device 2b04:d00e
dfu-util: No DFU capable USB device available
To make Particle devices writable for all users,
follow [Particle's instructions](https://docs.particle.io/support/particle-tools-faq/installing-dfu-util/)
on setting up dfu-util.
The Particle bootloader checks the firmware's checksum at startup time.
As these checks would be frustrated by flash writes inside RIOT (typically using the @ref drivers_periph_flashpage),
the CFLAG @ref PARTICLE_MONOFIRMWARE_CHECKSUMLIMIT "-DPARTICLE_MONOFIRMWARE_CHECKSUMLIMIT" can be set.
Then, the checksum is only calculated over the memory region that contains the interrupt vector and the bootloader manifest.
### Reset the board
The on-board reset button doesn't work, so to trigger a reset of the board, use

View File

@ -26,6 +26,61 @@
extern "C" {
#endif
/**
*
* @name Bootloader configuration options
* @{
*/
/** @brief Build a firmware suitable for the Particle bootloader
*
* If this is defined, additional metadata about the firmware is included in
* the firmware (called the module_info in Particle), and additional code is
* inserted in board setup.
*
* Do not define this manually; instead, set `PARTICLE_MONOFIRMWARE=1` as an
* variable in the build scripts like `BOARD` is defined, and the particle
* common make file defines it and configures suitable postprocessing of the
* binary.
*
* @see @ref boards_common_particle-mesh
*
*/
#ifdef DOXYGEN
#define PARTICLE_MONOFIRMWARE
#endif
/** @brief Limit Particle bootloader checksumming to the binary start
*
* If this define is set in the Makefile, the binary size announced to the
* bootloader is limited to the reset vector and the firmware metadata, and
* only that part is checksummed.
*
* This is useful when @ref drivers_periph_flashpage is used, as otherwise the
* firmware's writes on itself would invalidate its checksum.
*
* @see @ref boards_common_particle-mesh
*/
#ifdef DOXYGEN
#define PARTICLE_MONOFIRMWARE_CHECKSUMLIMIT
#endif
/** @brief Platform ID of the board for the Particle bootloader
*
* This is set by the individual board's build configuration, and gets used
* when building with @ref PARTICLE_MONOFIRMWARE; then, it is put into the
* module information for the board bootloader to verify that the firmware was
* built for the right device.
*
* The individual values are documented in the Particle DeviceOS source code in
* `build/platform-id.mk`.
*/
#ifdef DOXYGEN
#define PARTICLE_PLATFORM_ID
#endif
/** @} */
/**
* @name LED pin configuration
* @{

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Verify that a pair of .bin/.elf has the particle_monofirmware_module_info at
the right place. If there is a particle_monofirmware_checksum symbol, verify
that the module_info says the firmware ends at it, and populate the
checksum. Otherwise, append the checksum and set the end-of-firmware field
accordingly."""
import os
import sys
import zlib
import subprocess
import argparse
expected_romstart = 0x30000
expected_moduleinfo_position = expected_romstart + 0x200
expected_moduleinfo_size = 0x18
# offsetof(module_end_address, module_info_t)
end_field_offset = 4
end_field_start = expected_moduleinfo_position - expected_romstart + end_field_offset
end_field_end = end_field_start + 4
def check(condition, message):
if not condition:
print(message, file=sys.stderr)
sys.exit(1)
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("elf_in", help="Program in ELF form")
p.add_argument("bin_in", help="objdump'd ROM binary of elf_in")
p.add_argument("bin_out", help="File to write the modified bin to")
args = p.parse_args()
symbols = subprocess.check_output([
os.environ.get('NM', 'nm'),
"--format=posix",
"--print-size",
"--defined-only",
args.elf_in,
])
symbols = [line.split(' ') for line in symbols.decode('ascii').split('\n') if line]
position = {s[0]: int(s[2], 16) for s in symbols}
size = {s[0]: int(s[3], 16) for s in symbols if s[3]}
symtype = {s[0]: s[1] for s in symbols}
romstart = min(pos for (name, pos) in position.items() if symtype[name] not in '?A')
check(romstart == expected_romstart, "Defined symbols do not start at %#x" % expected_romstart)
check(position.get('particle_monofirmware_module_info') == expected_moduleinfo_position,
"""Struct particle_monofirmware_module_info not found at %#x but on %#x,
adjust the size of particle_monofirmware_padding to match""" %
(expected_moduleinfo_position, position.get('particle_monofirmware_module_info')))
check(size.get('particle_monofirmware_module_info') == expected_moduleinfo_size,
"Struct particle_monofirmware_module_info not of expected size %d" % expected_moduleinfo_size)
binary = bytearray(open(args.bin_in, 'rb').read())
indicated_start = int.from_bytes(binary[expected_moduleinfo_position - romstart:][:4], 'little')
check(romstart == indicated_start,
"particle_monofirmware_module_info.module_start does not point to the module start")
if 'particle_monofirmware_checksum' in position:
print("monifirmware-tool: Checksum symbol found, populating it.")
check(size.get('particle_monofirmware_checksum') == 4, "Checksum not of expected size 4")
checksum_until = position['particle_monofirmware_checksum'] - romstart
indicated_end = int.from_bytes(binary[end_field_start:end_field_end], 'little')
check(indicated_end == position['particle_monofirmware_checksum'],
"particle_monofirmware_module_info.module_end does not point to particle_monofirmware_checksum")
else:
print("monifirmware-tool: No checksum symbol found, appending the checksum to the binary.")
checksum_until = len(binary)
# Populating the length: When the whole binary (including the rom
# initialization block) is checksummed, the end address can't be populated
# from C because that would require evaluating `&_etext + (&_erelocate -
# &_srelocate)` in advance
binary[end_field_start:end_field_end] = (checksum_until + romstart).to_bytes(4, 'little')
checksum = zlib.crc32(binary[:checksum_until]).to_bytes(4, 'big')
binary[checksum_until:checksum_until + 4] = checksum
with open(args.bin_out, "wb") as o:
o.write(binary)

View File

@ -1 +1,2 @@
CFLAGS += "-DPARTICLE_PLATFORM_ID=12"
include $(RIOTBOARD)/common/particle-mesh/Makefile.include

View File

@ -1 +1,2 @@
CFLAGS += "-DPARTICLE_PLATFORM_ID=13"
include $(RIOTBOARD)/common/particle-mesh/Makefile.include

View File

@ -1 +1,2 @@
CFLAGS += "-DPARTICLE_PLATFORM_ID=14"
include $(RIOTBOARD)/common/particle-mesh/Makefile.include