Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
working-directory: tinyboot-cli
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y libudev-dev
- run: sudo apt-get update && sudo apt-get install -y libudev-dev
- run: rustup toolchain install stable --profile minimal --component rustfmt,clippy
- run: cargo fmt --all -- --check
- run: cargo clippy -- -D warnings
Expand All @@ -59,6 +59,7 @@ jobs:
name: Examples
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
example: [system-flash, user-flash]
defaults:
Expand Down
4 changes: 2 additions & 2 deletions examples/ch32/system-flash/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use panic_halt as _;

use tinyboot_ch32_boot::{
BaudRate, BootCtl, BootCtlConfig, BootMetaStore, Core, Duplex, Platform, Pull, Storage,
BaudRate, BootCtl, BootCtlConfig, BootMetaStore, Duplex, Platform, Pull, Storage,
StorageConfig, Usart, UsartConfig, UsartMapping,
};

Expand Down Expand Up @@ -54,5 +54,5 @@ fn main() -> ! {

const BOOT_VER: u16 = tinyboot_ch32_boot::pkg_version!();
let platform = Platform::new(transport, storage, boot_meta, ctl, BOOT_VER);
Core::new(platform).run();
tinyboot_ch32_boot::run(platform);
}
4 changes: 2 additions & 2 deletions examples/ch32/user-flash/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use defmt_rtt as _;
use panic_halt as _;

use tinyboot_ch32_boot::{
BaudRate, BootCtl, BootCtlConfig, BootMetaStore, Core, Duplex, Platform, Pull, Storage,
BaudRate, BootCtl, BootCtlConfig, BootMetaStore, Duplex, Platform, Pull, Storage,
StorageConfig, Usart, UsartConfig, UsartMapping,
};

Expand Down Expand Up @@ -60,5 +60,5 @@ fn main() -> ! {

const BOOT_VER: u16 = tinyboot_ch32_boot::pkg_version!();
let platform = Platform::new(transport, storage, boot_meta, ctl, BOOT_VER);
Core::new(platform).run();
tinyboot_ch32_boot::run(platform);
}
2 changes: 1 addition & 1 deletion tinyboot-ch32-app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl TBBootClient for Ch32BootClient {
buf[8..16].copy_from_slice(&meta);
flash::unlock();
iwdg::feed();
let w = flash::FlashWriter::ob();
let w = flash::FlashWriter::opt();
w.erase_start();
w.erase(flash::OB_BASE);
w.operation_end();
Expand Down
10 changes: 9 additions & 1 deletion tinyboot-ch32-boot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ pub use platform::{
};

// Re-exports so boot examples only need this one crate.
pub use tinyboot::Core;
pub use tinyboot::traits::boot::Platform;
pub use tinyboot_ch32_hal::gpio::Pull;
pub use tinyboot_ch32_hal::{Pin, UsartMapping};
pub use tinyboot_protocol::pkg_version;

/// Protocol write buffer size (2 × page size).
const PROTOCOL_BUF_SIZE: usize = 2 * tinyboot_ch32_hal::flash::PAGE_SIZE;

/// Run the bootloader. Hides the protocol buffer size const generic.
#[inline(always)]
pub fn run(platform: Platform<Usart, Storage, BootMetaStore, BootCtl>) -> ! {
tinyboot::Core::<_, _, _, _, PROTOCOL_BUF_SIZE>::new(platform).run()
}

#[unsafe(link_section = ".tinyboot_version")]
#[used]
static BOOT_VERSION: u16 = tinyboot_protocol::pkg_version!();
4 changes: 2 additions & 2 deletions tinyboot-ch32-boot/src/platform/boot_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl BootMetaStore {
}
let buf = unsafe { &*(buf.as_ptr() as *const [u8; 16]) };

let w = FlashWriter::ob();
let w = FlashWriter::opt();
w.erase_start();
w.erase(OB_BASE);
w.operation_end();
Expand All @@ -80,7 +80,7 @@ impl BootMetaStore {
return None;
}
let next = current & (current >> 1);
let w = FlashWriter::ob();
let w = FlashWriter::opt();
w.write_start();
w.write(META_OB_BASE + offset as u32 * 2, next as u16);
w.operation_end();
Expand Down
40 changes: 21 additions & 19 deletions tinyboot-ch32-boot/src/platform/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ use embedded_storage::nor_flash::{
};
use tinyboot::traits::boot::Storage as StorageTrait;

use tinyboot_ch32_hal::flash::FlashWriter;

const FLASH_WRITE_SIZE: usize = 2;
const FLASH_ERASE_SIZE: usize = 64;
use tinyboot_ch32_hal::flash::{BUF_LOAD_SIZE, FlashWriter, PAGE_SIZE};

/// Flash storage configuration.
pub struct StorageConfig {
Expand Down Expand Up @@ -59,25 +56,23 @@ impl ErrorType for Storage {
}

impl NorFlash for Storage {
const WRITE_SIZE: usize = FLASH_WRITE_SIZE;
const ERASE_SIZE: usize = FLASH_ERASE_SIZE;
const WRITE_SIZE: usize = PAGE_SIZE;
const ERASE_SIZE: usize = PAGE_SIZE;

fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
if !(from as usize).is_multiple_of(FLASH_ERASE_SIZE)
|| !(to as usize).is_multiple_of(FLASH_ERASE_SIZE)
{
if !(from as usize).is_multiple_of(PAGE_SIZE) || !(to as usize).is_multiple_of(PAGE_SIZE) {
return Err(StorageError::NotAligned);
}
if to as usize > self.app_size {
return Err(StorageError::OutOfBounds);
}
let writer = FlashWriter::standard();
let writer = FlashWriter::usr();
writer.erase_start();
let mut addr = self.app_base + from;
let end = self.app_base + to;
while addr < end {
writer.erase(addr);
addr += FLASH_ERASE_SIZE as u32;
addr += PAGE_SIZE as u32;
}
writer.operation_end();
// Write-protection check is debug-only: unlock() disables protection
Expand All @@ -92,22 +87,29 @@ impl NorFlash for Storage {
}

fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
if !(offset as usize).is_multiple_of(FLASH_WRITE_SIZE)
|| !bytes.len().is_multiple_of(FLASH_WRITE_SIZE)
if !(offset as usize).is_multiple_of(PAGE_SIZE)
|| bytes.len() > PAGE_SIZE
|| !bytes.len().is_multiple_of(BUF_LOAD_SIZE)
{
return Err(StorageError::NotAligned);
}
if offset as usize + bytes.len() > self.app_size {
return Err(StorageError::OutOfBounds);
}
let writer = FlashWriter::standard();
let base = self.app_base + offset;
let mut addr = base;
let mut ptr = bytes.as_ptr() as *const u32;
let writer = FlashWriter::usr();
writer.write_start();
let mut addr = self.app_base + offset;
for pair in bytes.chunks_exact(2) {
let halfword = u16::from_le_bytes([pair[0], pair[1]]);
writer.write(addr, halfword);
addr += 2;
writer.fast_write_buf_reset();
for _ in 0..bytes.len() / BUF_LOAD_SIZE {
// SAFETY: ptr advances within bounds, read_unaligned handles alignment
let word = unsafe { ptr.read_unaligned() };
writer.fast_write_buf_load(addr, word);
addr += BUF_LOAD_SIZE as u32;
ptr = unsafe { ptr.add(1) };
}
writer.fast_write_page_program(base);
writer.operation_end();

// See erase() for rationale on debug-only write-protection check.
Expand Down
2 changes: 1 addition & 1 deletion tinyboot-ch32-hal/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ fn generate_pin_and_usart_mapping(out: &Path) -> Result<(), Box<dyn Error>> {
// regs()
writeln!(
code,
" pub fn regs(self) -> ch32_metapac::usart::Usart {{"
" pub const fn regs(self) -> ch32_metapac::usart::Usart {{"
)?;
writeln!(code, " match self {{")?;
for ((peri, remap), group) in &groups {
Expand Down
90 changes: 60 additions & 30 deletions tinyboot-ch32-hal/src/flash/v0.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
const KEY1: u32 = 0x4567_0123;
const KEY2: u32 = 0xCDEF_89AB;

fn flash() -> ch32_metapac::flash::Flash {
ch32_metapac::FLASH
}
const FLASH: ch32_metapac::flash::Flash = ch32_metapac::FLASH;

#[inline(always)]
fn wait_busy() {
while flash().statr().read().bsy() {}
while FLASH.statr().read().bsy() {}
}

/// Unlock flash controller for all operations (KEYR + MODEKEYR + OBKEYR).
pub fn unlock() {
flash().keyr().write(|w| w.set_keyr(KEY1));
flash().keyr().write(|w| w.set_keyr(KEY2));
flash().modekeyr().write(|w| w.set_modekeyr(KEY1));
flash().modekeyr().write(|w| w.set_modekeyr(KEY2));
flash().obkeyr().write(|w| w.set_optkey(KEY1));
flash().obkeyr().write(|w| w.set_optkey(KEY2));
FLASH.keyr().write(|w| w.set_keyr(KEY1));
FLASH.keyr().write(|w| w.set_keyr(KEY2));
FLASH.modekeyr().write(|w| w.set_modekeyr(KEY1));
FLASH.modekeyr().write(|w| w.set_modekeyr(KEY2));
FLASH.obkeyr().write(|w| w.set_optkey(KEY1));
FLASH.obkeyr().write(|w| w.set_optkey(KEY2));
}

/// Lock flash controller.
#[inline(always)]
pub fn lock() {
flash().ctlr().write(|w| {
FLASH.ctlr().write(|w| {
w.set_lock(true);
w.set_flock(true);
});
Expand All @@ -35,8 +33,16 @@ pub struct FlashWriter {
write_bit: u8,
}

const PG: u8 = 0; // PG bit - standard programming - 2b
/// Flash page size in bytes (erase and fast-write granularity).
pub const PAGE_SIZE: usize = 64;

/// Fast-write buffer load size in bytes.
pub const BUF_LOAD_SIZE: usize = 4;

const FTPG: u8 = 16; // FTPG bit - fast page programming - 64B
const FTER: u8 = 17; // FTER bit - fast page erase - 64B
const BUFLOAD: u8 = 18; // BUFLOAD bit - fast-program buffer load
const BUFRST: u8 = 19; // BUFRST bit - fast-program buffer reset

const OBPG: u8 = 4; // OBPG bit - option byte programming
const OBER: u8 = 5; // OBER bit - option byte erase
Expand All @@ -45,16 +51,16 @@ const OBWRE: u8 = 9; // OBWRE bit - option byte write enabled. This needs to be
const STRT: u8 = 6; // STRT bit - start operation

impl FlashWriter {
/// Writer for regular operations (PG / FTER).
pub const fn standard() -> Self {
/// Writer for user flash (FTPG / FTER).
pub const fn usr() -> Self {
Self {
erase_bit: FTER,
write_bit: PG,
write_bit: FTPG,
}
}

/// Writer for option bytes (OBPG / OBER).
pub const fn ob() -> Self {
pub const fn opt() -> Self {
Self {
erase_bit: OBER,
write_bit: OBPG,
Expand All @@ -63,13 +69,13 @@ impl FlashWriter {

/// check for write protection error
pub fn check_wrprterr(&self) -> bool {
let statr = flash().statr().read();
let statr = FLASH.statr().read();
if statr.wrprterr() {
flash().statr().write(|w| w.set_wrprterr(true));
FLASH.statr().write(|w| w.set_wrprterr(true));
return true;
}
if statr.eop() {
flash().statr().write(|w| w.set_eop(true));
FLASH.statr().write(|w| w.set_eop(true));
}
false
}
Expand All @@ -79,10 +85,11 @@ impl FlashWriter {
pub fn write_start(&self) {
let write_bit = self.write_bit as usize;
let ctlr_start = (1 << OBWRE) | (1 << write_bit);
flash().ctlr().write(|w| w.0 = ctlr_start);
FLASH.ctlr().write(|w| w.0 = ctlr_start);
}

/// Halfword (2-byte) write.
/// Halfword (2-byte) write. used only for OB writes.
/// Use `fast_write_*` for flash writes.
#[inline(always)]
pub fn write(&self, addr: u32, value: u16) {
unsafe { core::ptr::write_volatile(addr as *mut u16, value) };
Expand All @@ -94,33 +101,56 @@ impl FlashWriter {
pub fn erase_start(&self) {
let erase_bit = self.erase_bit as usize;
let ctlr_start = (1 << OBWRE) | (1 << erase_bit);
flash().ctlr().write(|w| w.0 = ctlr_start);
FLASH.ctlr().write(|w| w.0 = ctlr_start);
}

/// Erase (64-byte page for flash, full OB erase for option bytes).
pub fn erase(&self, addr: u32) {
let erase_bit = self.erase_bit as usize;
let ctlr = (1 << OBWRE) | (1 << erase_bit) | (1 << STRT);

flash().addr().write(|w| w.set_addr(addr));
flash().ctlr().write(|w| w.0 = ctlr);
FLASH.addr().write(|w| w.set_addr(addr));
FLASH.ctlr().write(|w| w.0 = ctlr);
wait_busy();
}

// End write or erase operation
#[inline(always)]
pub fn operation_end(&self) {
let ctlr_end = 1 << OBWRE; // preserve OB write enable bit
flash().ctlr().write(|w| w.0 = ctlr_end);
FLASH.ctlr().write(|w| w.0 = ctlr_end);
}

/// Clear the internal 64-byte fast-programming buffer (BUFRST).
pub fn fast_write_buf_reset(&self) {
let ctlr = (1 << OBWRE) | (1 << FTPG) | (1 << BUFRST);
FLASH.ctlr().write(|w| w.0 = ctlr);
wait_busy();
}

/// Load 4 bytes into the fast-programming buffer at `addr`.
pub fn fast_write_buf_load(&self, addr: u32, value: u32) {
unsafe { core::ptr::write_volatile(addr as *mut u32, value) };
let ctlr = (1 << OBWRE) | (1 << FTPG) | (1 << BUFLOAD);
FLASH.ctlr().write(|w| w.0 = ctlr);
wait_busy();
}

/// Program the buffered page to flash at `page_addr`.
pub fn fast_write_page_program(&self, page_addr: u32) {
FLASH.addr().write(|w| w.set_addr(page_addr));
let ctlr = (1 << OBWRE) | (1 << FTPG) | (1 << STRT);
FLASH.ctlr().write(|w| w.0 = ctlr);
wait_busy();
}
}

pub fn is_boot_mode() -> bool {
flash().statr().read().boot_mode()
FLASH.statr().read().boot_mode()
}

pub fn set_boot_mode(mode: bool) {
flash().boot_modekeyp().write(|w| w.set_modekeyr(KEY1));
flash().boot_modekeyp().write(|w| w.set_modekeyr(KEY2));
flash().statr().write(|w| w.set_boot_mode(mode));
FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY1));
FLASH.boot_modekeyp().write(|w| w.set_modekeyr(KEY2));
FLASH.statr().write(|w| w.set_boot_mode(mode));
}
13 changes: 12 additions & 1 deletion tinyboot-cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ impl<T: embedded_io::Read + embedded_io::Write> Client<T> {
on_progress("Writing", chunk_idx, total_chunks);
}

// 4. Verify — CRC only covers firmware bytes (no padding)
// 4. Flush buffered writes
self.flush()?;

// 5. Verify — CRC only covers firmware bytes (no padding)
let expected_crc = crc16(CRC_INIT, firmware);

self.frame.cmd = Cmd::Verify;
Expand All @@ -194,6 +197,14 @@ impl<T: embedded_io::Read + embedded_io::Write> Client<T> {
Ok(info)
}

/// Flush buffered writes on the device.
pub fn flush(&mut self) -> Result<(), FlashError> {
self.frame.cmd = Cmd::Flush;
self.frame.addr = 0;
self.frame.len = 0;
self.transact()
}

/// Reset the device. Does not wait for a response since the device resets immediately.
/// `bootloader=true` (addr=1): enter bootloader. `bootloader=false` (addr=0): boot app.
pub fn reset(&mut self, bootloader: bool) {
Expand Down
Loading
Loading