Skip to content

Commit 4355dad

Browse files
committed
let ch32 skip the app check's length test, word the cli refusal for both causes
1 parent c03aa09 commit 4355dad

10 files changed

Lines changed: 43 additions & 18 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,24 @@ 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+
719
## [tinyboot-core 0.6.2] - 2026-10-20
820

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+
925
### Fixed
1026

1127
- 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.

‎ch32/Cargo.toml‎

Lines changed: 2 additions & 2 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
@@ -53,7 +53,7 @@ license = "MIT OR Apache-2.0"
5353

5454
[workspace.dependencies]
5555
tinyboot-core = { version = "0.6.2", path = "../lib/core", default-features = false }
56-
tinyboot-ch32 = { version = "0.7.0", path = ".", 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
}

‎lib/core/src/core.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ where
9696
}) == stored;
9797
}
9898
// No CRC stored (virgin / debugger-flashed).
99-
self.platform.app_present()
99+
self.platform.storage.app_present()
100100
}
101101

102102
#[inline(always)]

‎lib/core/src/platform.rs‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,4 @@ where
3838
ctl,
3939
}
4040
}
41-
42-
/// The app region's first word is programmed. Without a stored CRC
43-
/// this is the only sign of an app: an erased chip reads all 0xFF.
44-
#[inline(always)]
45-
pub(crate) fn app_present(&self) -> bool {
46-
let data = self.storage.as_slice();
47-
// SAFETY: flash is word-aligned and the length is checked first.
48-
data.len() >= 4
49-
&& unsafe { core::ptr::read_volatile(data.as_ptr() as *const u32) } != 0xFFFF_FFFF
50-
}
5141
}

‎lib/core/src/protocol.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ impl<
332332
// chips that hand off by run mode, so a half-written image
333333
// would run and an erased app region would hang the chip.
334334
if !flags.contains(ResetFlags::BOOTLOADER)
335-
&& (state == BootState::Updating || !self.platform.app_present())
335+
&& (state == BootState::Updating || !self.platform.storage.app_present())
336336
{
337337
self.frame.status = Status::Unsupported;
338338
} else {

‎lib/core/src/traits.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ pub trait Storage:
5353
{
5454
/// Zero-copy read access to the app region.
5555
fn as_slice(&self) -> &[u8];
56+
57+
/// The app region's first word is programmed. Without a stored CRC
58+
/// this is the only sign of an app: an erased chip reads all 0xFF.
59+
/// A platform that knows its region holds a word may skip the length
60+
/// check.
61+
#[inline(always)]
62+
fn app_present(&self) -> bool {
63+
let data = self.as_slice();
64+
// SAFETY: flash is word-aligned and the length is checked first.
65+
data.len() >= 4
66+
&& unsafe { core::ptr::read_volatile(data.as_ptr() as *const u32) } != 0xFFFF_FFFF
67+
}
5668
}
5769

5870
/// Boot control primitives.

0 commit comments

Comments
 (0)