mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
riotboot: implement serial bootloader feature
This commit is contained in:
parent
b911dc6d59
commit
7a07b7471f
24
bootloaders/riotboot_serial/doc.txt
Normal file
24
bootloaders/riotboot_serial/doc.txt
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
@defgroup bootloader_riotboot_serial riotboot serial bootloader
|
||||
@ingroup bootloaders
|
||||
|
||||
# Overview
|
||||
`riotboot_dfu` is a variation on @ref bootloader_riotboot that adds the capability to flash
|
||||
a new firmware using a serial (UART) connection.
|
||||
|
||||
After reset, riotboot will wait for `RIOTBOOT_DELAY_MS` ms, if within that time no command is
|
||||
received it will automatically start the application.
|
||||
|
||||
If it reads the `B` character, it will enter bootloader mode where flash sectors can be erased
|
||||
and written.
|
||||
|
||||
The application in `dist/tools/riotboot_serial` is used to talk to the bootloader and to flash
|
||||
`.hex` files using the bootloader's serial protocol.
|
||||
|
||||
# Flashing a device
|
||||
To flash a device that is equipped with the riotboot serial bootloader, set the `PROGRAMMER` variable
|
||||
to `riotboot_serial` and chose the `PORT` accordingly.
|
||||
|
||||
e.g.
|
||||
|
||||
make BOARD=same54-xpro PORT=/dev/ttyACM0 PROGRAMMER=riotboot_serial flash
|
@ -622,6 +622,13 @@ ifneq (,$(filter riotboot_slot, $(USEMODULE)))
|
||||
USEMODULE += riotboot_hdr
|
||||
endif
|
||||
|
||||
ifneq (,$(filter riotboot_serial, $(USEMODULE)))
|
||||
FEATURES_REQUIRED += periph_flashpage
|
||||
FEATURES_REQUIRED += periph_uart
|
||||
USEMODULE += riotboot
|
||||
USEMODULE += checksum
|
||||
endif
|
||||
|
||||
ifneq (,$(filter riotboot_hdr, $(USEMODULE)))
|
||||
USEMODULE += checksum
|
||||
USEMODULE += riotboot
|
||||
|
120
sys/include/riotboot/serial.h
Normal file
120
sys/include/riotboot/serial.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Benjamin Valentin
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup sys_riotboot_serial Serial Bootloader Protocol
|
||||
* @ingroup sys
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief riotboot as a serial bootloader
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef RIOTBOOT_SERIAL_H
|
||||
#define RIOTBOOT_SERIAL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief riotboot commands
|
||||
* Commands typically have the format [type|length|value]
|
||||
* where type and length are one byte and value is $length bytes.
|
||||
*
|
||||
* Commands are signed with a CRC-8 checksum that is calculated
|
||||
* over the entire record. The Checksum is not part of length.
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* start application.
|
||||
* parameter:
|
||||
* - '\n': launch default application
|
||||
* - '0' : launch slot 0
|
||||
* - '1' : launch slot 1
|
||||
*/
|
||||
#define RIOTBOOT_CMD_BOOT 'b'
|
||||
|
||||
/**
|
||||
* erase page
|
||||
* parameter:
|
||||
* - length of page number (8 bit - should be 4)
|
||||
* - page number (32 bit little endian)
|
||||
*/
|
||||
#define RIOTBOOT_CMD_ERASE 'e'
|
||||
|
||||
/**
|
||||
* write data
|
||||
* parameter:
|
||||
* - length of data + address (8 bit)
|
||||
* - destination address (32 bit little endian)
|
||||
* - data (up to (RX_BUF_LEN-6) bytes)
|
||||
*/
|
||||
#define RIOTBOOT_CMD_WRITE 'w'
|
||||
|
||||
/**
|
||||
* get page of address
|
||||
* parameter:
|
||||
* - length of address (8 bit - should be 4)
|
||||
* - address (32 bit little endian)
|
||||
*
|
||||
* returns status code followed by 32 bit little endian
|
||||
* value that represents the page in which the address lies.
|
||||
*/
|
||||
#define RIOTBOOT_CMD_GET_PAGE 'P'
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief riotboot response codes to commands
|
||||
* @{
|
||||
*/
|
||||
/** operation successful */
|
||||
#define RIOTBOOT_STAT_OK '.'
|
||||
/** CRC error - try again */
|
||||
#define RIOTBOOT_STAT_BAD_CRC '?'
|
||||
/** illegal parameter */
|
||||
#define RIOTBOOT_STAT_ILLEGAL '!'
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief riotboot serial loader synchronisation
|
||||
* @{
|
||||
*/
|
||||
/* sent to stop auto-boot */
|
||||
#define RIOTBOOT_ENTER_LOADER 'B'
|
||||
/* sent to probe if auto-boot is paused and riotboot is active */
|
||||
#define RIOTBOOT_PROBE '?'
|
||||
|
||||
/* continuously sent by riotboot before booting */
|
||||
#define RIOTBOOT_STAT_WAITING 'b'
|
||||
/* indicates riotboot is ready to accept commands */
|
||||
#define RIOTBOOT_STAT_READY '>'
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief CRC-8 Polynom used for riotboot chunks
|
||||
*/
|
||||
#ifndef RIOTBOOT_CRC8_POLY
|
||||
#define RIOTBOOT_CRC8_POLY (0x31)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Start interactive serial bootloader
|
||||
* @return slot to boot, -1 if default slot should be started
|
||||
*/
|
||||
int riotboot_serial_loader(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RIOTBOOT_SERIAL_H */
|
259
sys/riotboot/serial.c
Normal file
259
sys/riotboot/serial.c
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Benjamin Valentin
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup sys_riotboot_serial
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Serial Bootloader
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "stdio_uart.h"
|
||||
#include "periph/uart.h"
|
||||
#include "periph/flashpage.h"
|
||||
#include "unaligned.h"
|
||||
#include "checksum/crc8.h"
|
||||
#include "riotboot/serial.h"
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#ifndef RIOTBOOT_UART_DEV
|
||||
#define RIOTBOOT_UART_DEV STDIO_UART_DEV
|
||||
#endif
|
||||
|
||||
#ifndef RIOTBOOT_UART_BAUDRATE
|
||||
#define RIOTBOOT_UART_BAUDRATE STDIO_UART_BAUDRATE
|
||||
#endif
|
||||
|
||||
#ifndef RIOTBOOT_DELAY_MS
|
||||
#define RIOTBOOT_DELAY_MS (1000)
|
||||
#endif
|
||||
|
||||
#define RX_BUF_LEN (40)
|
||||
|
||||
/* we send a characters each iteration -> 8 bit + start & stop bit */
|
||||
#define RIOTBOOT_DELAY (RIOTBOOT_DELAY_MS * RIOTBOOT_UART_BAUDRATE / 10000)
|
||||
|
||||
static inline void uart_write_byte(uart_t uart, uint8_t data)
|
||||
{
|
||||
uart_write(uart, &data, 1);
|
||||
}
|
||||
|
||||
/* send 'hello' byte until we get enter bootloader byte or timeout */
|
||||
static bool _bootdelay(unsigned tries, volatile bool *boot_default)
|
||||
{
|
||||
if (tries == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (--tries && *boot_default) {
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, 0);
|
||||
}
|
||||
|
||||
return *boot_default;
|
||||
}
|
||||
|
||||
__attribute__ ((aligned(4)))
|
||||
static struct {
|
||||
uint8_t pos; /* current pos in rx buffer */
|
||||
uint8_t remaining; /* remaining bytes to read */
|
||||
union {
|
||||
uint8_t u8[RX_BUF_LEN]; /* rx buffer */
|
||||
struct {
|
||||
uint8_t type; /* command type */
|
||||
uint8_t len; /* length of data (without checksum) */
|
||||
uint8_t data[]; /* data is aligned at word boundary */
|
||||
} val;
|
||||
} rx;
|
||||
} ctx;
|
||||
|
||||
/**
|
||||
* Format:
|
||||
* [ Type (1 byte) | Length (1 byte) | value (n bytes) | checksum (1 byte) ]
|
||||
*/
|
||||
static void _uart_rx_cmd(void *arg, uint8_t data)
|
||||
{
|
||||
bool *reading = arg;
|
||||
uint8_t crc;
|
||||
|
||||
/* ignore RX while processing buffer */
|
||||
if (!reading) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.rx.u8[ctx.pos] = data;
|
||||
|
||||
switch (ctx.pos) {
|
||||
case 0:
|
||||
switch (data) {
|
||||
case RIOTBOOT_CMD_BOOT:
|
||||
case RIOTBOOT_CMD_ERASE:
|
||||
case RIOTBOOT_CMD_WRITE:
|
||||
case RIOTBOOT_CMD_GET_PAGE:
|
||||
ctx.remaining = 1;
|
||||
break;
|
||||
/* re-create initial sync handshake if already in bootloader */
|
||||
case RIOTBOOT_PROBE:
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_WAITING);
|
||||
return;
|
||||
case RIOTBOOT_ENTER_LOADER:
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_READY);
|
||||
*reading = false;
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case 1:
|
||||
/* data length + checksum byte + dummy byte for fall-through */
|
||||
ctx.remaining = data + 2;
|
||||
|
||||
/* boot command needs no checksum */
|
||||
if (ctx.rx.val.type == RIOTBOOT_CMD_BOOT) {
|
||||
*reading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* bail out early if the buffer would not fit */
|
||||
/* data len + sizeof(type, len) */
|
||||
if (ctx.remaining + 2 >= RX_BUF_LEN) {
|
||||
crc = 0;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* fall-through */
|
||||
default:
|
||||
|
||||
/* end of data block not reached */
|
||||
if (--ctx.remaining) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* fall-through */
|
||||
case (RX_BUF_LEN - 1):
|
||||
|
||||
/* calculate checksum */
|
||||
crc = crc8(ctx.rx.u8, ctx.pos, RIOTBOOT_CRC8_POLY, 0xFF);
|
||||
|
||||
error:
|
||||
ctx.remaining = 0;
|
||||
ctx.pos = 0;
|
||||
|
||||
/* checksum error */
|
||||
if (crc != data) {
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_BAD_CRC);
|
||||
break;
|
||||
}
|
||||
|
||||
*reading = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ctx.remaining) {
|
||||
++ctx.pos;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void _get_page(uintptr_t addr)
|
||||
{
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_OK);
|
||||
uint32_t page = flashpage_page((void *)addr);
|
||||
uart_write(RIOTBOOT_UART_DEV, (void *)&page, sizeof(page));
|
||||
}
|
||||
|
||||
static void _erase(uint16_t sector)
|
||||
{
|
||||
/* don't erase bootloader */
|
||||
if ((uintptr_t)flashpage_addr(sector) < SLOT0_OFFSET) {
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_ILLEGAL);
|
||||
return;
|
||||
}
|
||||
|
||||
flashpage_erase(sector);
|
||||
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_OK);
|
||||
}
|
||||
|
||||
static void _write(uint32_t addr, uint8_t len, const uint8_t *data)
|
||||
{
|
||||
/* don't overwrite bootloader */
|
||||
if (addr < SLOT0_OFFSET) {
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_ILLEGAL);
|
||||
return;
|
||||
}
|
||||
|
||||
flashpage_write((void *)addr, data, len);
|
||||
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_OK);
|
||||
}
|
||||
|
||||
int riotboot_serial_loader(void)
|
||||
{
|
||||
volatile bool reading = true;
|
||||
|
||||
uart_init(RIOTBOOT_UART_DEV, RIOTBOOT_UART_BAUDRATE,
|
||||
_uart_rx_cmd, (void *)&reading);
|
||||
|
||||
/* give the user some time to interrupt auto boot */
|
||||
if (_bootdelay(RIOTBOOT_DELAY, &reading)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
|
||||
/* we can't use mutex in riotboot */
|
||||
while (reading) {}
|
||||
|
||||
switch (ctx.rx.val.type) {
|
||||
case RIOTBOOT_CMD_BOOT:
|
||||
switch (ctx.rx.val.len) {
|
||||
case '\n':
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_OK);
|
||||
return -1;
|
||||
#ifdef MODULE_RIOTBOOT_SLOT
|
||||
case '0':
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_OK);
|
||||
return 0;
|
||||
case '1':
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_OK);
|
||||
return 1;
|
||||
#endif
|
||||
default:
|
||||
uart_write_byte(RIOTBOOT_UART_DEV, RIOTBOOT_STAT_ILLEGAL);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case RIOTBOOT_CMD_GET_PAGE:
|
||||
_get_page(unaligned_get_u32(ctx.rx.val.data));
|
||||
break;
|
||||
case RIOTBOOT_CMD_ERASE:
|
||||
_erase(unaligned_get_u16(ctx.rx.val.data));
|
||||
break;
|
||||
case RIOTBOOT_CMD_WRITE:
|
||||
_write(unaligned_get_u32(ctx.rx.val.data), /* address */
|
||||
ctx.rx.val.len - sizeof(uint32_t), /* sizeof(data) - sizeof(address) */
|
||||
&ctx.rx.val.data[4]); /* data */
|
||||
break;
|
||||
}
|
||||
|
||||
reading = true;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
Loading…
Reference in New Issue
Block a user