Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 95 additions & 8 deletions boot/boot_serial/src/boot_serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ BOOT_LOG_MODULE_DECLARE(mcuboot);
#define BOOT_DIRECT_UPLOAD_SECONDARY_SLOT_ID_REMAINDER 0

static char in_buf[MCUBOOT_SERIAL_MAX_RECEIVE_SIZE + 1];
#ifndef MCUBOOT_SERIAL_RAW_PROTOCOL
static char dec_buf[MCUBOOT_SERIAL_MAX_RECEIVE_SIZE + 1];
#endif
const struct boot_uart_funcs *boot_uf;
static struct nmgr_hdr *bs_hdr;
static bool bs_entry;
Expand Down Expand Up @@ -1374,13 +1376,7 @@ static void
boot_serial_output(void)
{
char *data;
int len, out;
uint16_t crc;
uint16_t totlen;
char pkt_cont[2] = { SHELL_NLIP_DATA_START1, SHELL_NLIP_DATA_START2 };
char pkt_start[2] = { SHELL_NLIP_PKT_START1, SHELL_NLIP_PKT_START2 };
char buf[BOOT_SERIAL_OUT_MAX + sizeof(*bs_hdr) + sizeof(crc) + sizeof(totlen)];
char encoded_buf[BASE64_ENCODE_SIZE(sizeof(buf))];
int len;

data = bs_obuf;
len = (uintptr_t)cbor_state->payload_mut - (uintptr_t)bs_obuf;
Expand All @@ -1390,6 +1386,18 @@ boot_serial_output(void)
bs_hdr->nh_len = htons(len);
bs_hdr->nh_group = htons(bs_hdr->nh_group);

#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL
boot_uf->write((const char *)bs_hdr, sizeof(*bs_hdr));
boot_uf->write(data, len);
#else
int out;
uint16_t crc;
uint16_t totlen;
char pkt_cont[2] = { SHELL_NLIP_DATA_START1, SHELL_NLIP_DATA_START2 };
char pkt_start[2] = { SHELL_NLIP_PKT_START1, SHELL_NLIP_PKT_START2 };
char buf[BOOT_SERIAL_OUT_MAX + sizeof(*bs_hdr) + sizeof(crc) + sizeof(totlen)];
char encoded_buf[BASE64_ENCODE_SIZE(sizeof(buf))];

#ifdef __ZEPHYR__
crc = crc16_itu_t(CRC16_INITIAL_CRC, (uint8_t *)bs_hdr, sizeof(*bs_hdr));
crc = crc16_itu_t(crc, data, len);
Expand Down Expand Up @@ -1441,10 +1449,12 @@ boot_serial_output(void)

boot_uf->write("\n", 1);
}
#endif /* MCUBOOT_SERIAL_RAW_PROTOCOL */

BOOT_LOG_DBG("TX");
}

#ifndef MCUBOOT_SERIAL_RAW_PROTOCOL
/*
* Returns 1 if full packet has been received.
*/
Expand Down Expand Up @@ -1503,6 +1513,44 @@ boot_serial_in_dec(char *in, int inlen, char *out, int *out_off, int maxout)

return 1;
}
#endif /* !MCUBOOT_SERIAL_RAW_PROTOCOL */

#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL
/* Dispatch every complete raw SMP packet at the front of "buf" (length from the
* SMP header, capacity "max_input") via boot_serial_input(), keeping any
* trailing bytes; returns the count of unconsumed bytes left in "buf". */
static int
boot_serial_input_raw(char *buf, int len, int max_input)
{
while (len >= (int)sizeof(struct nmgr_hdr)) {
const struct nmgr_hdr *hdr = (const struct nmgr_hdr *)buf;
const int pkt_len = (int)sizeof(*hdr) + ntohs(hdr->nh_len);

if ((hdr->nh_op != NMGR_OP_READ && hdr->nh_op != NMGR_OP_WRITE) ||
pkt_len > max_input) {
/* Not a valid request, or larger than the receive buffer. */
return 0;
}

if (len < pkt_len) {
/* More fragments expected. */
break;
}

boot_serial_input(buf, pkt_len);

/* Preserve any bytes that belong to the following packet. */
if (len > pkt_len) {
memmove(buf, buf + pkt_len, (size_t)(len - pkt_len));
len -= pkt_len;
} else {
len = 0;
}
}

return len;
}
#endif /* MCUBOOT_SERIAL_RAW_PROTOCOL */

/*
* Task which waits reading console, expecting to get image over
Expand All @@ -1513,10 +1561,15 @@ boot_serial_read_console(const struct boot_uart_funcs *f,int timeout_in_ms)
{
int rc;
int off;
#ifndef MCUBOOT_SERIAL_RAW_PROTOCOL
int dec_off = 0;
#endif
int full_line;
int max_input;
int elapsed_in_ms = 0;
#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
uint32_t raw_input_start = 0;
#endif

#ifndef MCUBOOT_SERIAL_WAIT_FOR_DFU
bool allow_idle = true;
Expand All @@ -1533,7 +1586,15 @@ boot_serial_read_console(const struct boot_uart_funcs *f,int timeout_in_ms)
* from serial console (if single-thread mode is used).
*/
#ifndef MCUBOOT_SERIAL_WAIT_FOR_DFU
if (allow_idle == true) {
/*
* Stay out of CPU idle while a partial raw packet is buffered, so the
* input-expiration timeout below is enforced promptly.
*/
if (allow_idle == true
#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
&& off == 0
#endif
) {
MCUBOOT_CPU_IDLE();
allow_idle = false;
}
Expand All @@ -1550,6 +1611,24 @@ boot_serial_read_console(const struct boot_uart_funcs *f,int timeout_in_ms)
goto check_timeout;
}
off += rc;
#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL
/*
* Raw protocol: bytes are accumulated until one or more full SMP
* packets have been received; a single read may carry several complete
* packets (or a packet plus the start of the next). Dispatch all
* complete packets and keep any trailing bytes for the next read.
*/
#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
bool was_idle = (off == rc);
#endif
off = boot_serial_input_raw(in_buf, off, max_input);
#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
if (off > 0 && was_idle) {
/* First bytes of a new packet buffered; start its expiry window. */
raw_input_start = k_uptime_get_32();
}
#endif
#else
if (!full_line) {
if (off == max_input) {
/*
Expand All @@ -1573,7 +1652,15 @@ boot_serial_read_console(const struct boot_uart_funcs *f,int timeout_in_ms)
boot_serial_input(&dec_buf[2], dec_off - 2);
}
off = 0;
#endif /* MCUBOOT_SERIAL_RAW_PROTOCOL */
check_timeout:
#ifdef MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
if (off > 0 && (k_uptime_get_32() - raw_input_start) >=
MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT_MS) {
/* Stalled partial raw packet expired; discard it. */
off = 0;
}
#endif
/* Subtract elapsed time */
#ifdef MCUBOOT_SERIAL_WAIT_FOR_DFU
elapsed_in_ms = (k_uptime_get_32() - start);
Expand Down
38 changes: 36 additions & 2 deletions boot/zephyr/Kconfig.serial_recovery
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ menuconfig MCUBOOT_SERIAL
select REBOOT
select SERIAL
select UART_INTERRUPT_DRIVEN
select BASE64
select CRC
select BASE64 if !BOOT_SERIAL_RAW_PROTOCOL
select CRC if !BOOT_SERIAL_RAW_PROTOCOL
select ZCBOR
depends on !BOOT_FIRMWARE_LOADER
help
Expand Down Expand Up @@ -46,6 +46,40 @@ config BOOT_SERIAL_CDC_ACM

endchoice

config BOOT_SERIAL_RAW_PROTOCOL
bool "Raw (SMP without framing and encoding) protocol"
help
If y, SMP (mcumgr) commands are exchanged over the serial port as raw SMP
packets instead of using the SMP over console format. This drops the
base64 encoding and the additional console-specific framing (packet
start/continuation markers, length prefix and CRC), which reduces flash
and RAM usage and increases transfer speed, at the cost of requiring a
binary-capable serial link. Packet boundaries are derived from the
length field of the SMP header. The SMP client must be configured
to use a matching raw transport.

config BOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
bool "Raw protocol input expiration"
depends on BOOT_SERIAL_RAW_PROTOCOL
default y
help
Discard a partially received raw SMP packet after a period without new
data, so that a stalled or malformed transfer cannot permanently wedge
serial recovery. Unlike the SMP over console format, the raw protocol has
no CRC or framing to detect a truncated packet, so there is otherwise no
way to resynchronise the input stream. This mirrors Zephyr's
MCUMGR_TRANSPORT_RAW_UART_INPUT_TIMEOUT (zephyrproject-rtos/zephyr#101821)
and is enabled by default because raw recovery is unsafe without it.

config BOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT_MS
int "Raw protocol input expiration timeout (ms)"
depends on BOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
default 3000
help
Time in milliseconds after first receiving data for a raw SMP packet,
after which a still-incomplete packet is dropped and the receive buffer
is reset to await a new command.

config MCUBOOT_SERIAL_DIRECT_IMAGE_UPLOAD
bool "Allow to select image number for DFU"
depends on !SINGLE_APPLICATION_SLOT
Expand Down
10 changes: 10 additions & 0 deletions boot/zephyr/include/mcuboot_config/mcuboot_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,16 @@
#define MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO
#endif

#ifdef CONFIG_BOOT_SERIAL_RAW_PROTOCOL
#define MCUBOOT_SERIAL_RAW_PROTOCOL
#endif

#ifdef CONFIG_BOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
#define MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT
#define MCUBOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT_MS \
CONFIG_BOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT_MS
#endif

#ifdef CONFIG_MCUBOOT_SERIAL
#define MCUBOOT_SERIAL_RECOVERY
#endif
Expand Down
6 changes: 6 additions & 0 deletions boot/zephyr/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ tests:
integration_platforms:
- nrf52840dk/nrf52840
tags: bootloader_mcuboot
sample.bootloader.mcuboot.serial_recovery_raw:
extra_args: EXTRA_CONF_FILE=serial_recovery_raw.conf
platform_allow: nrf52840dk/nrf52840
integration_platforms:
- nrf52840dk/nrf52840
tags: bootloader_mcuboot
sample.bootloader.mcuboot.serial_recovery_all_options:
extra_args: EXTRA_CONF_FILE=serial_recovery.conf
extra_configs:
Expand Down
61 changes: 61 additions & 0 deletions boot/zephyr/serial_adapter.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,44 @@ console_write(const char *str, int cnt)
}
}

#ifdef CONFIG_BOOT_SERIAL_RAW_PROTOCOL
/*
* Raw protocol read: the data is binary, so it is returned without a NUL
* terminator. A fragment may be larger than the caller's buffer, so retain the
* unconsumed tail for the next call rather than dropping it (the fragment's
* buffer is not recycled by boot_uart_fifo_getline() until it is fully consumed
* here). Returns the number of bytes copied into "str".
*/
static int
console_read_raw(char *str, int str_size, int *newline)
{
static char *pending;
static int pending_len;
int n;

if (pending_len == 0) {
pending_len = boot_uart_fifo_getline(&pending);
if (pending == NULL) {
*newline = 0;
return 0;
}
}

n = MIN(pending_len, str_size);
memcpy(str, pending, n);
pending += n;
pending_len -= n;
*newline = 1;
return n;
}
#endif

int
console_read(char *str, int str_size, int *newline)
{
#ifdef CONFIG_BOOT_SERIAL_RAW_PROTOCOL
return console_read_raw(str, str_size, newline);
#else
char *line;
int len;

Expand All @@ -107,6 +142,7 @@ console_read(char *str, int str_size, int *newline)
str[len] = '\0';
*newline = 1;
return len + 1;
#endif
}

int
Expand Down Expand Up @@ -159,6 +195,16 @@ boot_uart_fifo_callback(const struct device *dev, void *user_data)
cmd = CONTAINER_OF(node, struct line_input, node);
}

#ifdef CONFIG_BOOT_SERIAL_RAW_PROTOCOL
cmd->line[cur++] = byte;

if (cur >= CONFIG_BOOT_MAX_LINE_INPUT_LEN) {
cmd->len = cur;
sys_slist_append(&lines_queue, &cmd->node);
cur = 0;
cmd = NULL;
}
#else
if (cur < CONFIG_BOOT_MAX_LINE_INPUT_LEN) {
cmd->line[cur++] = byte;
}
Expand All @@ -169,7 +215,22 @@ boot_uart_fifo_callback(const struct device *dev, void *user_data)
cur = 0;
cmd = NULL;
}
#endif
}

#ifdef CONFIG_BOOT_SERIAL_RAW_PROTOCOL
/*
* No line delimiter exists in raw mode, so deliver whatever has been
* received during this interrupt as a fragment; the boot serial reader
* reassembles full packets using the SMP header length field.
*/
if (cmd != NULL && cur > 0) {
cmd->len = cur;
sys_slist_append(&lines_queue, &cmd->node);
cur = 0;
cmd = NULL;
}
#endif
}

static int
Expand Down
4 changes: 4 additions & 0 deletions boot/zephyr/serial_recovery_raw.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONFIG_MCUBOOT_SERIAL=y
CONFIG_BOOT_SERIAL_UART=y
CONFIG_BOOT_SERIAL_RAW_PROTOCOL=y
CONFIG_UART_CONSOLE=n
12 changes: 12 additions & 0 deletions docs/release-notes.d/serial-recovery-raw-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- Serial recovery: added the `BOOT_SERIAL_RAW_PROTOCOL` option,
which exchanges SMP packets over the serial port in raw binary
form instead of the SMP over console encoding (no base64, length
prefix, CRC or console framing). This reduces flash and RAM usage
and increases transfer speed at the cost of requiring a
binary-capable serial link. It mirrors Zephyr's
`CONFIG_UART_MCUMGR_RAW_PROTOCOL` transport.
- Serial recovery: added `BOOT_SERIAL_RAW_PROTOCOL_INPUT_TIMEOUT`
(enabled by default with the raw protocol), which discards a
partially received raw SMP packet after a period without new data
so a stalled transfer cannot wedge recovery. Mirrors Zephyr's
`CONFIG_MCUMGR_TRANSPORT_RAW_UART_INPUT_TIMEOUT`.
Loading
Loading