Skip to content

boot: boot_serial: add optional mcumgr parameters command#2746

Open
JPHutchins wants to merge 1 commit into
mcu-tools:mainfrom
intercreate:feature-zephyr-mcumgr-params
Open

boot: boot_serial: add optional mcumgr parameters command#2746
JPHutchins wants to merge 1 commit into
mcu-tools:mainfrom
intercreate:feature-zephyr-mcumgr-params

Conversation

@JPHutchins

Copy link
Copy Markdown
Contributor

Summary

Adds an optional, Kconfig-gated mcumgr MCUmgr parameters command to the
serial-recovery SMP server (OS management group 0, command ID 6,
read-only), mirroring the command provided by Zephyr's SMP server.

When CONFIG_BOOT_MGMT_MCUMGR_PARAMS is enabled the server answers with the
CBOR map {"buf_size": <CONFIG_BOOT_SERIAL_MAX_RECEIVE_SIZE>, "buf_count": 1},
allowing SMP clients (e.g. smpclient)
to auto-negotiate serial fragmentation instead of hardcoding buffer sizes.

buf_count is 1 because serial recovery reassembles one command at a time
into a single buffer.

Why the 128-byte line-length requirement

mcumgr parameters do not carry the transport line length (MTU); SMP serial
clients assume 128-byte fragments and derive line_buffers = buf_size // 128.
To keep the advertised buf_size consistent, the option
depends on BOOT_MAX_LINE_INPUT_LEN = 128 — setting a different line length
makes Kconfig refuse the option.

Compatibility

Identical wire contract to upstream Zephyr's os_mgmt_mcumgr_params — same
group/id, read-only, same buf_size/buf_count keys and zcbor_uint32_put
encoding — so existing SMP clients read it unchanged.

ROM / RAM cost

Measured on nrf52840dk/nrf52840 with serial_recovery.conf, with vs.
without the option: +116 B flash, +0 B RAM.

Test coverage

Added CONFIG_BOOT_MGMT_MCUMGR_PARAMS=y to the serial_recovery_all_options
twister scenario, so the feature is build-covered on every PR via the existing
Zephyr twister CI.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds optional support for the mcumgr OS group "MCUmgr parameters" command (ID 6) to MCUboot's serial recovery, allowing SMP clients to query the transport buffer size/count for negotiating serial fragmentation.

Changes:

  • New Kconfig BOOT_MGMT_MCUMGR_PARAMS (gated on default 128-byte line length) and corresponding MCUBOOT_BOOT_MGMT_MCUMGR_PARAMS macro mapping.
  • New bs_mcumgr_params() handler in boot_serial.c dispatched from boot_serial_input() for NMGR_ID_MCUMGR_PARAMS (6).
  • Documentation, release note, and sample.yaml test config updates.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
boot/boot_serial/src/boot_serial.c Implements the new CBOR response handler and wires it into the command dispatch.
boot/boot_serial/src/boot_serial_priv.h Adds the NMGR_ID_MCUMGR_PARAMS command ID constant.
boot/zephyr/Kconfig.serial_recovery New Kconfig option with dependency on default line length.
boot/zephyr/include/mcuboot_config/mcuboot_config.h Maps Kconfig symbol to MCUboot macro.
boot/zephyr/sample.yaml Enables the new option in the serial mgmt all-options test.
docs/serial_recovery.md Documents the new supported command.
docs/release-notes.d/serial-recovery-mcumgr-params.md Adds a release note entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/serial_recovery.md
Comment thread boot/boot_serial/src/boot_serial.c Outdated
Comment thread boot/zephyr/Kconfig.serial_recovery

@nordicjm nordicjm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good addition, just some styling parts then ready to merge

Comment thread boot/boot_serial/src/boot_serial.c Outdated
Comment on lines +1276 to +1282
if (
zcbor_map_start_encode(cbor_state, 10) &&
zcbor_tstr_put_lit_cast(cbor_state, "buf_size") &&
zcbor_uint32_put(cbor_state, MCUBOOT_SERIAL_MAX_RECEIVE_SIZE) &&
zcbor_tstr_put_lit_cast(cbor_state, "buf_count") &&
zcbor_uint32_put(cbor_state, 1) &&
zcbor_map_end_encode(cbor_state, 10)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use in-tree style with bool:

if (flash_area_align(fap) > 1 &&

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a fix

Comment thread boot/zephyr/Kconfig.serial_recovery Outdated
Comment on lines +118 to +125
If enabled, support for the mcumgr OS group "MCUmgr parameters"
command (command ID 6) is added, mirroring the command provided by
Zephyr's SMP server. It reports the SMP transport buffer size
(BOOT_SERIAL_MAX_RECEIVE_SIZE) and buffer count so that SMP clients
can negotiate optimal serial fragmentation. The parameters do not
carry the transport line length, so this option requires
BOOT_MAX_LINE_INPUT_LEN to remain at the standard 128-byte fragment
size. buf_count will always be 1.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100 char line length

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@JPHutchins JPHutchins force-pushed the feature-zephyr-mcumgr-params branch from 43e95b5 to ee0a699 Compare June 1, 2026 18:39
@JPHutchins JPHutchins requested review from Copilot and nordicjm June 1, 2026 18:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Comment thread docs/serial_recovery.md
Comment thread boot/zephyr/Kconfig.serial_recovery Outdated
Comment thread boot/zephyr/Kconfig.serial_recovery
Comment thread boot/boot_serial/src/boot_serial.c
Comment thread boot/boot_serial/src/boot_serial.c
@JPHutchins JPHutchins marked this pull request as draft June 3, 2026 18:25
@JPHutchins

Copy link
Copy Markdown
Contributor Author

@nordicjm Converted back to draft while I investigate an important finding. I've added emulated integration tests over at smpclient and confirmed that Zephyr's smp server mcumgr params reports the unencoded, not encoded, buffer size. For mcuboot, the simplest backwards compatible solution is to report 685B (calculated at compile time) "netbuf" size for its 1024 buffer. This is the worst case base64 cost - 4 for framing.

This also suggests the Zephyr's smp server has a larger incoming-encoded buffer than it's decoded buffer (what is reported by mcumgr params). I will take a look at the kconfig + compile time assertions to double check that it's safe.

Comment thread boot/zephyr/Kconfig.serial_recovery
@JPHutchins

JPHutchins commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

The below is mostly for agent follow-up.

Following up on my note above — I traced both code bases to pin down whether buf_size is the encoded or decoded buffer, and there's a genuine ambiguity on the MCUboot side that I'd rather resolve by reading the source than by guessing a number. Pointers below are pinned to mcuboot@f48e87e and zephyr@3701def.

Client side: buf_size is consumed as the decoded netbuf

smpclient reads buf_size and sends at most buf_size − 4 unencoded bytes — the 4 being the SMP serial frame's 2-byte length prefix + 2-byte CRC16 that share the reassembly buffer with the message. Verified empirically against native_sim/QEMU/mps2 servers: buf_size − 4 round-trips, buf_size − 3 is dropped (intercreate/smpclient#81).

One caveat for the wire math: this is the decoded size. smpclient's Auto strategy recently changed — it used to model buf_size as an encoded line-buffer budget. For an advertised buf_size = 1024 the old client capped messages at 685 B; the new one caps at 1020 B (1024 − 4). So whatever this command advertises is now taken literally as the decoded reassembly size.

Zephyr confirms the decoded reading — and keeps encoded vs. decoded as separate knobs

  • The params command reports CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE / …_COUNT: os_mgmt.c#L543-L551
  • NETBUF_SIZE is documented as "Size of each MCUmgr buffer … >= transport-MTU + transport-overhead": transport/Kconfig#L40-L49
  • The serial transport base64-decodes each fragment into that netbuf, pulls the 2-byte big-endian total length off the front, and strips the trailing 2-byte CRC16 — so the netbuf holds [len(2) | message | crc(2)] and the max message is NETBUF_SIZE − 4. That's the literal source of the −4: decode into nb, serial_util.c#L48-L63, pull length #L38-L46, strip CRC #L198-L206
  • The encoded reception is a different buffer entirely — the UART fragment buffer is sized by CONFIG_MCUMGR_TRANSPORT_UART_MTU, independent of NETBUF_SIZE: smp_uart.c#L23, #L83. So in Zephyr the encoded-in buffer and the decoded netbuf are separate configs.

MCUboot: one constant does both jobs → the ambiguity

MCUboot uses a single MCUBOOT_SERIAL_MAX_RECEIVE_SIZE for both buffers:

  • in_buf (one encoded line at a time) and dec_buf (the decoded frame, accumulated across fragments) are both [MCUBOOT_SERIAL_MAX_RECEIVE_SIZE + 1]: boot_serial.c#L163-L164
  • decode accumulates into dec_buf, bounded by maxout = sizeof(in_buf): #L1526, #L1565
  • but the Kconfig documents the constant as an encoded quantity — BOOT_MAX_LINE_INPUT_LEN (128) × BOOT_LINE_BUFS (8) = 1024, "Maximum length of received commands": line length #L76, line bufs #L84, max receive size #L91-L98

So the constant is documented as encoded but applied (in dec_buf) as decoded. Unlike Zephyr, which separates the two. That leaves the open question of what to advertise:

  • Decoded reading (mirror Zephyr's NETBUF_SIZE): advertise MAX_RECEIVE_SIZE → client sends up to MAX_RECEIVE_SIZE − 4 = 1020 B, whose encoded form (~×4/3) is ~1368 chars over ~11 fragments. dec_buf can hold the decoded result, but that exceeds the "1024 encoded command" reading of the help text.
  • Encoded reading (honor the documented budget): advertise the base64-decode of it. Worst-case decode is floor(3/4 × N) — base64 is a 3→4 expansion, so the reverse is ×3/4, not 2/3 — i.e. floor(3/4 × 1024) − 4 = 764.
  • The 685 I floated earlier is the encoded per-fragment model (reserving delimiter/padding slack per 128-char line), more conservative still.

I don't want to pick a number until we've confirmed MCUboot's real decoded ceiling: does dec_buf genuinely accept MAX_RECEIVE_SIZE decoded bytes end-to-end, or is the design intent that no more than MAX_RECEIVE_SIZE encoded bytes ever arrive? I'll follow up once smpclient's integration suite can drive a full max-size round-trip through serial recovery against a fixture with CONFIG_BOOT_MGMT_MCUMGR_PARAMS enabled.

@JPHutchins

JPHutchins commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@nordicjm Converted back to draft while I investigate an important finding. I've added emulated integration tests over at smpclient and confirmed that Zephyr's smp server mcumgr params reports the unencoded, not encoded, buffer size. For mcuboot, the simplest backwards compatible solution is to report 685B (calculated at compile time) "netbuf" size for its 1024 buffer. This is the worst case base64 cost - 4 for framing.

This also suggests the Zephyr's smp server has a larger incoming-encoded buffer than it's decoded buffer (what is reported by mcumgr params). I will take a look at the kconfig + compile time assertions to double check that it's safe.

I suspect it's all OK. So MCUBoot has a 1024 byte decoded packet buffer. The smp client can (and should) send the "max encoded packet" of ~1,368 bytes. Fragmented across ~11 128 byte encoded SMP frames. Assuming that each frame is decoded and written to the dec_buf in its decoded form as soon as it is received. Unrelated, but that means that the incoming buffer only needs to be size 128, but it doesn't matter, mcuboot should always have tons of RAM to spare.

@JPHutchins

Copy link
Copy Markdown
Contributor Author

Traced both code bases (read-only) to settle this: the PR is correct as written, and no other mcuboot change is needed beyond an optional doc/assert tidy-up. Pinned to mcuboot@4ae6504.

1. buf_size = MCUBOOT_SERIAL_MAX_RECEIVE_SIZE is right

It's the decoded ceiling, and decode is incremental per line — exactly as you assumed. boot_serial_in_dec() base64-decodes each line straight into dec_buf at a running offset (base64_decode(&out[*out_off], maxout - *out_off, …); *out_off += rc;, with maxout = sizeof(in_buf) = MAX_RECEIVE_SIZE + 1); a PKT_START line resets dec_off, a DATA_START line appends (boot_serial.c#L1460, #L1564-L1568).

So the ceiling is MAX_RECEIVE_SIZE decoded → max message MAX_RECEIVE_SIZE − 4; nothing bounds the total encoded frame. A 1020-byte message arrives as ~1404 encoded bytes over ~12 lines and fits. My earlier 685/764 were the encoded line-buffer model and under-report — drop them; advertise MAX_RECEIVE_SIZE as the PR already does.

2. No other mcuboot changes needed

Every decode/read path is bounds-checked, so there's no memory-safety footgun in the buffer Kconfig:

  • the per-line cap is enforced in serial_adapter.cif (cur < CONFIG_BOOT_MAX_LINE_INPUT_LEN) stores, the rest is dropped (#L160-L165);
  • BOOT_LINE_BUFS is a recycled pool (getline recycle, #L183), not a frame-size cap; and in_buf only ever holds one ≤128-byte line — so the 128-byte incoming buffer you mentioned is all that's needed (it's oversized to MAX_RECEIVE_SIZE+1, but that's RAM, not safety).

The only thing worth folding into this PR is documentation hygiene on MAX_RECEIVE_SIZE: its help frames it as BOOT_MAX_LINE_INPUT_LEN × BOOT_LINE_BUFS (an encoded line-pool product), but the code uses it as the decoded dec_buf ceiling — which is exactly what this new params command advertises. Re-wording it to "maximum decoded SMP frame (also reported via mcumgr params)" would prevent the same encoded-vs-decoded confusion that sent me to 685, and a BUILD_ASSERT codifying whatever relation you settle on would make a misconfig fail at build rather than silently dropping frames.

Empirical

Confirmed against real Zephyr SMP servers (native_sim/QEMU/mps2) in smpclient: a buf_size − 4 message round-trips while the on-wire frame is ~1.37× buf_size (e.g. 2048 → 2044-byte message → 2801-byte, 23-line frame). MCUboot's recovery decoded ceiling is established from the source above; the end-to-end max-size round-trip through recovery is what a fixture built with CONFIG_BOOT_MGMT_MCUMGR_PARAMS (this PR's feature) will let us pin.

(The analogous documented-but-unenforced relation on the Zephyr side now has a draft BUILD_ASSERT, separate from here.)

@JPHutchins JPHutchins force-pushed the feature-zephyr-mcumgr-params branch from ee0a699 to 48afec5 Compare June 4, 2026 22:38
@JPHutchins JPHutchins marked this pull request as ready for review June 4, 2026 22:41
@JPHutchins

Copy link
Copy Markdown
Contributor Author

Moved out of draft again after doing some exhaustive integration testing and research. I'm happy with it, but make sure to review the Kconfig that I decided to update based on the research.

@JPHutchins JPHutchins requested a review from d3zd3z June 4, 2026 22:42
Comment thread boot/zephyr/Kconfig.serial_recovery Outdated
if enabled, support for the mcumgr echo command is being added.

config BOOT_MGMT_MCUMGR_PARAMS
bool "SMP server buffer size"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would change the prompt name to have e.g. MCUmgr OS mgmt params command as SMP server buffer size is not what it is

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thank you!

Add support for the mcumgr OS management group "MCUmgr parameters"
command (group 0, command ID 6), gated behind the new
CONFIG_BOOT_MGMT_MCUMGR_PARAMS option. The read-only command reports
the SMP transport buffer parameters as a CBOR map, mirroring the
command provided by Zephyr's SMP server so that existing SMP clients
can query them unchanged.

The serial recovery reassembles one command at a time into a single
buffer, so the response reports a buf_size of
CONFIG_BOOT_SERIAL_MAX_RECEIVE_SIZE and a buf_count of 1. The
parameters do not carry the transport line length, which SMP serial
clients assume to be 128 bytes, so the option depends on
CONFIG_BOOT_MAX_LINE_INPUT_LEN remaining at its default of 128.

Clarify the BOOT_SERIAL_MAX_RECEIVE_SIZE help to describe it as the
maximum decoded SMP frame (the value reported as buf_size) rather than
an encoded line-buffer product, and add a Kconfig range so that a
buffer too small to hold a fragment fails at configuration time instead
of silently dropping frames.

Signed-off-by: JP Hutchins <jp@intercreate.io>
Assisted-By: Claude:opus-4.8
@JPHutchins JPHutchins force-pushed the feature-zephyr-mcumgr-params branch from 48afec5 to 2a207c6 Compare June 12, 2026 19:36
@JPHutchins JPHutchins requested a review from nordicjm June 12, 2026 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants