Skip to content

Commit 2b4da3a

Browse files
authored
refuse a reset to the app when no app is present (#53)
* refuse a reset to the app when no app is present * let ch32 skip the app check's length test, word the cli refusal for both causes
1 parent b7235c2 commit 2b4da3a

13 files changed

Lines changed: 117 additions & 16 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ Releases up through `v0.4.0` were workspace-wide and tagged `vX.Y.Z`. From `0.4.
44

55
## [Unreleased]
66

7+
## [tinyboot 0.5.2] - 2026-10-20
8+
9+
### Fixed
10+
11+
- A refused reset to the app reports that an update is in progress or no app is flashed, instead of only the former.
12+
13+
## [tinyboot-ch32 0.7.1] - 2026-10-20
14+
15+
### Changed
16+
17+
- `Storage` overrides `app_present()` without the length check, since its app base is always readable flash. The bootloader's reset guard from tinyboot-core 0.6.2 costs 4 bytes instead of 18.
18+
19+
## [tinyboot-core 0.6.2] - 2026-10-20
20+
21+
### Added
22+
23+
- `Storage::app_present()`, provided: the app region's first word is programmed. A platform that knows its region holds a word overrides it without the length check.
24+
25+
### Fixed
26+
27+
- The bootloader refuses `Reset` to the app with `Unsupported` when the app region's first word is erased, as it does mid-update, and stays in the bootloader. A system-flash bootloader hands off by boot mode without checking the app, so a chip with no app flashed jumped into blank flash and hung until a power cycle.
28+
729
## [tinyboot-protocol 0.5.1] - 2026-10-20
830

931
### Changed

‎ch32/Cargo.toml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "tinyboot-ch32"
33
description = "tinyboot ch32 implementation"
4-
version = "0.7.0"
4+
version = "0.7.1"
55
edition.workspace = true
66
repository.workspace = true
77
license.workspace = true
@@ -52,8 +52,8 @@ repository = "https://github.com/OpenServoCore/tinyboot"
5252
license = "MIT OR Apache-2.0"
5353

5454
[workspace.dependencies]
55-
tinyboot-core = { version = "0.6.1", path = "../lib/core", default-features = false }
56-
tinyboot-ch32 = { version = "0.7.0", path = ".", default-features = false }
55+
tinyboot-core = { version = "0.6.2", path = "../lib/core", default-features = false }
56+
tinyboot-ch32 = { version = "0.7.1", path = ".", default-features = false }
5757
embedded-hal = "1"
5858
embedded-io = "0.7"
5959
embedded-storage = "0.3"

‎ch32/src/platform/storage.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ impl StorageTrait for Storage {
8787
fn as_slice(&self) -> &[u8] {
8888
unsafe { core::slice::from_raw_parts(self.app_ptr(), self.app_size) }
8989
}
90+
91+
#[inline(always)]
92+
fn app_present(&self) -> bool {
93+
// SAFETY: the app base is a word-aligned flash address on every
94+
// chip, readable whatever the region's length.
95+
unsafe { core::ptr::read_volatile(self.app_ptr() as *const u32) != 0xFFFF_FFFF }
96+
}
9097
}
9198

9299
impl ReadNorFlash for Storage {

‎cli/Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎cli/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "tinyboot"
33
description = "CLI firmware flasher for tinyboot bootloader"
4-
version = "0.5.1"
4+
version = "0.5.2"
55
edition = "2024"
66
repository = "https://github.com/OpenServoCore/tinyboot"
77
license = "MIT OR Apache-2.0"

‎cli/src/main.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,11 @@ fn parse_baud(rate: &str) -> Result<Option<BaudRate>, Box<dyn std::error::Error>
285285
})
286286
}
287287

288-
/// The bootloader refuses to start the app while an update is in progress.
288+
/// The bootloader refuses to start the app mid-update or with no app flashed.
289289
fn refused_app(e: FlashError) -> Box<dyn std::error::Error> {
290290
match e {
291291
FlashError::Device(Status::Unsupported) => "the bootloader refused to start the app: \
292-
an update is in progress. Flash again, or `tinyboot reset --bootloader`."
292+
an update is in progress or no app is flashed. Flash an app."
293293
.into(),
294294
e => e.into(),
295295
}

‎docs/remote-updates.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ When all four hold, the bootloader hands off to the app. As soon as you run a re
9797
| App's `confirm()` never reaches due to bug | Same as above — trials run out, bootloader wins. |
9898
| Host crashes mid-flash | Same as "power lost during erase or write" — safe, just re-flash. |
9999
| `Reset` to the app mid-flash | Refused with `Unsupported`; the device stays in the bootloader. Re-flash. |
100+
| `Reset` to the app with no app flashed | Refused with `Unsupported`; the device stays in the bootloader. Flash an app. |
100101
| Image built for another board | Verify returns `Incompatible` (bootloader has a compatibility key); flash the right one. |
101102

102103
## Several devices on one bus

‎lib/core/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "tinyboot-core"
33
description = "Core bootloader library for tinyboot"
4-
version = "0.6.1"
4+
version = "0.6.2"
55
edition.workspace = true
66
repository.workspace = true
77
license.workspace = true

‎lib/core/src/core.rs‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,8 @@ where
9595
self.platform.storage.as_slice().get_unchecked(..sz)
9696
}) == stored;
9797
}
98-
// No CRC stored (virgin / debugger-flashed) — require at least one
99-
// non-0xFF word to treat the app as present.
100-
let data = self.platform.storage.as_slice();
101-
data.len() >= 4
102-
&& unsafe { core::ptr::read_volatile(data.as_ptr() as *const u32) } != 0xFFFF_FFFF
98+
// No CRC stored (virgin / debugger-flashed).
99+
self.platform.storage.app_present()
103100
}
104101

105102
#[inline(always)]

‎lib/core/src/protocol.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,10 @@ impl<
330330
let flags = unsafe { flags.reset };
331331
// A reset to the app skips the bootloader's META check on
332332
// chips that hand off by run mode, so a half-written image
333-
// would run.
334-
if state == BootState::Updating && !flags.contains(ResetFlags::BOOTLOADER) {
333+
// would run and an erased app region would hang the chip.
334+
if !flags.contains(ResetFlags::BOOTLOADER)
335+
&& (state == BootState::Updating || !self.platform.storage.app_present())
336+
{
335337
self.frame.status = Status::Unsupported;
336338
} else {
337339
self.cmd_reset(addr, flags);

0 commit comments

Comments
 (0)