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:
parent
c3b70d5a65
commit
7582be6d5e
@ -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
|
||||
|
@ -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);
|
||||
|
84
boards/common/particle-mesh/bootloader.c
Normal file
84
boards/common/particle-mesh/bootloader.c
Normal 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
|
||||
|
||||
/** @} */
|
@ -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
|
||||
|
@ -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
|
||||
* @{
|
||||
|
88
boards/common/particle-mesh/monofirmware-tool.py
Executable file
88
boards/common/particle-mesh/monofirmware-tool.py
Executable 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)
|
@ -1 +1,2 @@
|
||||
CFLAGS += "-DPARTICLE_PLATFORM_ID=12"
|
||||
include $(RIOTBOARD)/common/particle-mesh/Makefile.include
|
||||
|
@ -1 +1,2 @@
|
||||
CFLAGS += "-DPARTICLE_PLATFORM_ID=13"
|
||||
include $(RIOTBOARD)/common/particle-mesh/Makefile.include
|
||||
|
@ -1 +1,2 @@
|
||||
CFLAGS += "-DPARTICLE_PLATFORM_ID=14"
|
||||
include $(RIOTBOARD)/common/particle-mesh/Makefile.include
|
||||
|
Loading…
Reference in New Issue
Block a user