boot: serial: end serial recovery on inactivity - #2830
boot: serial: end serial recovery on inactivity#2830TheArkadiuszGrzelka wants to merge 1 commit into
Conversation
cac05f4 to
94f2d78
Compare
|
All four points are addressed, and the gating request too: everything the option adds is behind MCUBOOT_SERIAL_BOOT_MODE_INACTIVITY_TIMEOUT, so at the default of 0 the read loop, the command handler and boot_serial_start() / boot_serial_check_start() compile exactly as they do today - no code changes when it is disabled. The file-scope bs_timeout_ms is gone; the countdown is back to the local timeout_in_ms, with a bs_rearm flag only under that guard. |
Serial recovery has no way out once it is entered. boot_serial_enter() calls boot_serial_start(), which sets bs_entry and never clears it, and the every-boot wait latches the same flag on the first MCUmgr command, so the read loop in boot_serial_read_console() runs until the device is reset by hand. An aborted upload therefore strands a device that has a perfectly good image to fall back on, and no watchdog can rescue it: MCUBOOT_WATCHDOG_FEED() is called inside that loop, so a hung recovery is fed exactly like a busy one. Bounding the whole session instead would cut a slow transfer in half; a full image over a 9600 baud RS485 link takes minutes. The discriminating signal is data, not time: a transfer that is merely slow keeps delivering MCUmgr commands, while an aborted one goes silent. So add an inactivity timeout. boot_serial_start_inactivity() counts down like boot_serial_check_start(), but every received command restarts the countdown instead of latching bs_entry, and it reports whether any command arrived at all. A transfer in progress extends the wait for as long as it keeps making progress, however slow the link; only silence ends recovery. Exposed as CONFIG_BOOT_SERIAL_INACTIVITY_TIMEOUT (int, ms, default 0), offered under both entrance methods that can resume an installed image: BOOT_SERIAL_BOOT_MODE and BOOT_SERIAL_WAIT_FOR_DFU. At the default of 0 nothing new is compiled in and the images are identical to the ones built before this commit. Recovery is left by resetting the SoC rather than by returning into the boot path. The image was selected by boot_go() before recovery was entered, so returning after an upload would jump to the image that upload has just replaced; nothing in the bootloader unwinds the state a finished MCUmgr session leaves behind either, and returning has been observed to lock up a Cortex-M0+ (STM32C0). A reset costs one boot, starts from a state the bootloader already knows how to handle, and is the same exit MCUmgr's own reset command takes. Hence the dependency on REBOOT. Under BOOT_SERIAL_WAIT_FOR_DFU the reset is taken only if at least one command was received; a window that expires with no command at all is not a session and boots exactly as it did before. BOOT_SERIAL_NO_APPLICATION and the GPIO and pin-reset entrances stay unbounded on purpose: with no bootable image, or with a human holding a pin, there is nothing to resume. boot_serial_enter() takes the timeout as an argument rather than being duplicated, so the status callback, the indication LED, the log line and the boot_console_init() error check stay in one place for every entrance method. Signed-off-by: Arkadiusz Grzelka <devitwise@gmail.com>
94f2d78 to
eeb595f
Compare
|
Re-measured everything on hardware against this branch, not against the v2.4.0
Correction to the descriptionThe 33.1 s in the description does not reproduce against this branch: the same MCUBOOT_WATCHDOG_FEED();
uint32_t start = k_uptime_get_32();
rc = f->read(...);
elapsed_in_ms = (k_uptime_get_32() - start);
timeout_in_ms -= elapsed_in_ms;Only This is pre-existing and independent of this patch, which only makes the number Why it resets instead of returningI built a variant with
The single MCUmgr command is the only difference between the two runs. The option when it is offChecked on a second SoC, a PIC32CM5112GC00100 (Cortex-M23), KconfigThat is in the current diff: |
nordicjm
left a comment
There was a problem hiding this comment.
tried setting BOOT_SERIAL_WAIT_FOR_DFU_TIMEOUT to 2500 and BOOT_SERIAL_INACTIVITY_TIMEOUT to 3000, and it enters, I send a command, I get a response, I wait and it boots the application, however it seems to wait 9 seconds instead of 3, wondering if a #2842 fix is needed?
|
Yes, that is exactly what #2842 fixes. #2842 has just been updated to your style comment and repushed on current main, so testing the two together would confirm it from your side as well. |
What is broken today
Serial recovery has no way out once it is entered.
boot_serial_enter()calls
boot_serial_start(), which setsbs_entryand never clears it, andthe every-boot wait (
CONFIG_BOOT_SERIAL_WAIT_FOR_DFU) latches the same flagon the first MCUmgr command, so the read loop in
boot_serial_read_console()runs until the device is reset by hand.
That is fine when a human is standing next to the board. It is not fine for a
field device whose only recovery transport is the same wire the application
talks on: an aborted upload strands a unit that has a perfectly good image to
fall back on, and no watchdog can rescue it, because
MCUBOOT_WATCHDOG_FEED()runs inside that very loop. A hung recovery is fed exactly like a busy one.
Why a plain timeout is the wrong shape
CONFIG_BOOT_SERIAL_WAIT_FOR_DFUalready has a timeout, but it is apre-transfer window: the first MCUmgr command latches
bs_entryand the waitbecomes permanent. Bounding the whole session instead would cut a slow transfer
in half - a full image over a 9600 baud RS485 link takes minutes.
The discriminating signal is data, not elapsed time. A slow transfer keeps
delivering MCUmgr commands; an aborted one goes silent.
The change
boot_serial_start_inactivity()counts down likeboot_serial_check_start(),but every received command restarts the countdown instead of latching
bs_entry, and it reports whether any command arrived at all. A transfer inprogress extends the wait for as long as it keeps making progress, however slow
the link; only silence ends recovery.
Exposed as
CONFIG_BOOT_SERIAL_INACTIVITY_TIMEOUT(int, ms, default 0),offered under both entrance methods that have an installed image to resume:
BOOT_SERIAL_BOOT_MODEandBOOT_SERIAL_WAIT_FOR_DFU.Nothing existing is removed or re-gated:
same size as one built without the patch (see the table below).
CONFIG_BOOT_SERIAL_NO_APPLICATIONstays unbounded on purpose - with nobootable image there is nothing to resume, so ending recovery there would be
strictly worse. The GPIO and pin-reset entrances stay unbounded too.
Why it exits by resetting the SoC
The image was selected by
boot_go()before recovery was entered, so returninginto the boot path after an upload would jump to the image that upload has just
replaced. Nothing in the bootloader unwinds the state a finished MCUmgr session
leaves behind either. Returning was tried first and locks the CPU up on a
Cortex-M0+ (STM32C071): a variant that returns instead of resetting ends in
lockup once a single MCUmgr command has been received before the silence, with
PC
0xFFFFFFFE, S_LOCKUP set in DHCSR, and MSP loaded from the image headermagic
0x96F3B83Dinstead of a vector table. The same image with no commandreceived boots the application normally. A reset costs one boot, starts from a state the
bootloader already knows how to handle, and is the same exit MCUmgr's own reset
command takes.
Under
BOOT_SERIAL_WAIT_FOR_DFUthe reset is taken only if at least onecommand was received. A window that expires with no command at all is not a
session, and boots exactly as it does today.
A non-zero timeout therefore requires
CONFIG_REBOOT, expressed as a Kconfigdependency.
boot_serial_enter()takes the timeout as an argument rather than beingduplicated, so the status callback, the indication LED, the log line and the
boot_console_init()error check stay in one place for every entrance method.Verification
Size, on
pic32cm_gc00_cpro/pic32cm5112gc00100(Cortex-M23),WAIT_FOR_DFUentrance, this branch against its merge base:
BOOT_SERIAL_INACTIVITY_TIMEOUT=0: the MCUboot binary is byte for byteidentical to one built without this patch, and
boot_serial_start_inactivityis not linked in.
BOOT_SERIAL_INACTIVITY_TIMEOUT=30000: 41000 B against 40836 B, so 164 B.A build with
REBOOT=nwas checked too: the symbol falls back to 0 and theimage matches the timeout-unset one. No new warnings in any of the builds.
On hardware: STM32C071 (Cortex-M0+), MCUmgr and Modbus sharing one RS485 line
at 9600 baud,
BOOT_SERIAL_INACTIVITY_TIMEOUT=30000, and for the last two runsBOOT_SERIAL_WAIT_FOR_DFU=ywithBOOT_SERIAL_WAIT_FOR_DFU_TIMEOUT=5000. Timeis measured from the last byte the host sent to the first answer from the
application.
WAIT_FOR_DFUWAIT_FOR_DFUThe 165 s against a 30 s setting is not this patch.
boot_serial_read_console()charges the timeout for
f->read()only, while the twok_uptime_get_32()calls that do the measuring fall outside the measured window, and on a core
without hardware divide they dominate: sampling the PC over SWD through a whole
window put 72% of the samples in
__udivmoddi4,__udivsi3andsys_clock_tick_get, against 4.8% inconsole_read. The same effect measures3.2x on the Cortex-M23 board above. It is pre-existing and reported as #2841;
the countdown stays proportional, and every run in the table is consistent with
one factor.
staging slot, an upload aborted mid-transfer has already overwritten the
primary slot. The timeout fires and the device resets, but with no valid
image it lands in
BOOT_SERIAL_NO_APPLICATIONand waits there for a retry.That is the intended behaviour; no timeout value can conjure back an image
that was overwritten.