Skip to content
9 changes: 2 additions & 7 deletions examples/ch32/system-flash/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ use tinyboot_ch32_boot::{
StorageConfig, Usart, UsartConfig, UsartMapping,
};

// --- Flash layout ---

const BOOT_BASE: u32 = 0x1FFF_F000;
const BOOT_SIZE: u32 = 1920;
const APP_BASE: u32 = 0x0800_0000;
const APP_SIZE: usize = 16 * 1024;

Expand Down Expand Up @@ -50,14 +46,13 @@ fn main() -> ! {
});

let storage = Storage::new(StorageConfig {
boot_base: BOOT_BASE,
boot_size: BOOT_SIZE,
app_base: APP_BASE,
app_size: APP_SIZE,
});
let boot_meta = BootMetaStore::default();
let ctl = BootCtl::new(BootCtlConfig {});

let platform = Platform::new(transport, storage, boot_meta, ctl);
const BOOT_VER: u16 = tinyboot_ch32_boot::pkg_version!();
let platform = Platform::new(transport, storage, boot_meta, ctl, BOOT_VER);
Core::new(platform).run();
}
18 changes: 3 additions & 15 deletions examples/ch32/user-flash/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,7 @@ use tinyboot_ch32_boot::{
StorageConfig, Usart, UsartConfig, UsartMapping,
};

// --- Flash layout (must match memory.x) ---

const BOOT_BASE: u32 = 0x0800_0000;
const BOOT_SIZE: u32 = 4 * 1024;

/// Application entry point (execution alias).
const APP_ENTRY: u32 = 0x0000_1000;

/// Application FPEC programming address (0x0800_0000 base).
const APP_BASE: u32 = 0x0800_1000;

/// Full 12KB available for the application.
const APP_SIZE: usize = 12 * 1024;

#[unsafe(export_name = "main")]
Expand Down Expand Up @@ -60,16 +49,15 @@ fn main() -> ! {
});

let storage = Storage::new(StorageConfig {
boot_base: BOOT_BASE,
boot_size: BOOT_SIZE,
app_base: APP_BASE,
app_size: APP_SIZE,
});
let boot_meta = BootMetaStore::default();
let ctl = BootCtl::new(BootCtlConfig {
app_entry: APP_ENTRY,
app_entry: APP_BASE,
});

let platform = Platform::new(transport, storage, boot_meta, ctl);
const BOOT_VER: u16 = tinyboot_ch32_boot::pkg_version!();
let platform = Platform::new(transport, storage, boot_meta, ctl, BOOT_VER);
Core::new(platform).run();
}
1 change: 1 addition & 0 deletions tinyboot-ch32-boot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 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;

#[unsafe(link_section = ".tinyboot_version")]
#[used]
Expand Down
1 change: 1 addition & 0 deletions tinyboot-ch32-boot/src/platform/boot_ctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct BootCtl {

impl BootCtl {
/// Create boot control from configuration.
#[inline(always)]
pub fn new(_config: BootCtlConfig) -> Self {
Self {
#[cfg(not(feature = "system-flash"))]
Expand Down
22 changes: 16 additions & 6 deletions tinyboot-ch32-boot/src/platform/boot_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct BootMetaStore {

impl Default for BootMetaStore {
/// Read all OB metadata into the struct from option bytes.
#[inline(always)]
fn default() -> Self {
let mut meta = core::mem::MaybeUninit::<Self>::uninit();
let ptr = meta.as_mut_ptr() as *mut u8;
Expand All @@ -40,14 +41,23 @@ impl Default for BootMetaStore {
impl BootMetaStore {
/// Erase OB and rewrite chip config + cached meta bytes.
fn write(&self) {
let mut buf = [0xFFu8; 16];
for (i, slot) in buf[..8].iter_mut().enumerate() {
*slot = unsafe { core::ptr::read_volatile((OB_BASE + i as u32 * 2) as *const u8) };
}
let meta = self as *const Self as *const u8;
let mut buf = core::mem::MaybeUninit::<[u8; 16]>::uninit();
let ptr = buf.as_mut_ptr() as *mut u8;
// Read 8 chip config bytes from OB (stride-2 volatile reads)
for i in 0..8 {
buf[8 + i] = unsafe { *meta.add(i) };
unsafe {
*ptr.add(i) = core::ptr::read_volatile((OB_BASE + i as u32 * 2) as *const u8);
}
}
// Copy 8 meta struct bytes as two word copies
let meta = self as *const Self as *const u32;
let dst = unsafe { ptr.add(8) as *mut u32 };
unsafe {
*dst = *meta;
*dst.add(1) = *meta.add(1);
}
let buf = unsafe { buf.assume_init() };

let w = FlashWriter::ob();
w.erase_start();
w.erase(OB_BASE);
Expand Down
17 changes: 1 addition & 16 deletions tinyboot-ch32-boot/src/platform/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ const FLASH_ERASE_SIZE: usize = 64;

/// Flash storage configuration.
pub struct StorageConfig {
/// Physical base address of the boot region.
pub boot_base: u32,
/// Size of the boot region in bytes.
pub boot_size: u32,
/// Physical base address of the app region.
pub app_base: u32,
/// Size of the app region in bytes.
Expand All @@ -39,18 +35,15 @@ impl NorFlashError for StorageError {

/// CH32 flash storage implementing [`NorFlash`] and the tinyboot [`Storage`](tinyboot::traits::boot::Storage) trait.
pub struct Storage {
boot_base: u32,
boot_size: u32,
app_base: u32,
app_size: usize,
}

impl Storage {
/// Create storage from configuration.
#[inline(always)]
pub fn new(config: StorageConfig) -> Self {
Storage {
boot_base: config.boot_base,
boot_size: config.boot_size,
app_base: config.app_base,
app_size: config.app_size,
}
Expand Down Expand Up @@ -131,14 +124,6 @@ impl StorageTrait for Storage {
unsafe { core::slice::from_raw_parts(self.app_ptr(), self.app_size) }
}

fn boot_base(&self) -> usize {
self.boot_base as usize
}

fn boot_size(&self) -> usize {
self.boot_size as usize
}

fn unlock(&mut self) {
tinyboot_ch32_hal::flash::unlock();
}
Expand Down
38 changes: 29 additions & 9 deletions tinyboot-ch32-boot/src/platform/transport/usart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,21 @@ impl tinyboot::traits::boot::Transport for Usart {}

impl Usart {
/// Initialize the USART peripheral with the given configuration.
#[inline(always)]
pub fn new(config: &UsartConfig) -> Self {
let tx_pin = config.mapping.tx_pin();
let rx_pin = config.mapping.rx_pin();
let remap = config.mapping.remap_value();
let regs = config.mapping.regs();
let half_duplex = matches!(config.duplex, Duplex::Half);

// Enable clocks
rcc::enable_gpio(tx_pin.port_index());
if rx_pin.port_index() != tx_pin.port_index() {
rcc::enable_gpio(rx_pin.port_index());
}
rcc::enable_afio();
rcc::enable_usart1();
// Batch-enable GPIO port(s), AFIO, and USART1 clocks
rcc::enable_apb2(
(1 << (2 + tx_pin.port_index()))
| (1 << (2 + rx_pin.port_index()))
| 1 // AFIO (bit 0)
| (1 << 14), // USART1 (bit 14)
);

// Set pin remap if non-default
if remap != 0 {
Expand Down Expand Up @@ -116,6 +117,7 @@ impl Usart {
}
}

#[inline(always)]
fn set_tx_mode(&self) {
if let Some(ref tx_en) = self.tx_en {
if tx_en.active_high {
Expand All @@ -126,6 +128,7 @@ impl Usart {
}
}

#[inline(always)]
fn set_rx_mode(&self) {
if let Some(ref tx_en) = self.tx_en {
if tx_en.active_high {
Expand All @@ -149,15 +152,32 @@ impl embedded_io::Read for Usart {
buf[0] = usart::read_byte(self.regs);
Ok(1)
}

fn read_exact(
&mut self,
buf: &mut [u8],
) -> Result<(), embedded_io::ReadExactError<Self::Error>> {
let regs = self.regs;
for byte in buf {
*byte = usart::read_byte(regs);
}
Ok(())
}
}

impl embedded_io::Write for Usart {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.write_all(buf)?;
Ok(buf.len())
}

fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.set_tx_mode();
let regs = self.regs;
for &byte in buf {
usart::write_byte(self.regs, byte);
usart::write_byte(regs, byte);
}
Ok(buf.len())
Ok(())
}

fn flush(&mut self) -> Result<(), Self::Error> {
Expand Down
1 change: 1 addition & 0 deletions tinyboot-ch32-hal/src/afio/v0.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[inline(always)]
pub fn set_usart1_remap(remap: u8) {
ch32_metapac::AFIO.pcfr1().write(|w| {
w.set_usart1_rm(remap & 1 != 0);
Expand Down
5 changes: 5 additions & 0 deletions tinyboot-ch32-hal/src/flash/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ fn flash() -> ch32_metapac::flash::Flash {
ch32_metapac::FLASH
}

#[inline(always)]
fn wait_busy() {
while flash().statr().read().bsy() {}
}
Expand Down Expand Up @@ -74,19 +75,22 @@ impl FlashWriter {
}

/// Start write operation
#[inline(always)]
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);
}

/// Halfword (2-byte) write.
#[inline(always)]
pub fn write(&self, addr: u32, value: u16) {
unsafe { core::ptr::write_volatile(addr as *mut u16, value) };
wait_busy();
}

/// Start erase operation
#[inline(always)]
pub fn erase_start(&self) {
let erase_bit = self.erase_bit as usize;
let ctlr_start = (1 << OBWRE) | (1 << erase_bit);
Expand All @@ -104,6 +108,7 @@ impl FlashWriter {
}

// 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);
Expand Down
7 changes: 7 additions & 0 deletions tinyboot-ch32-hal/src/rcc/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ pub fn enable_usart1() {
.apb2pcenr()
.modify(|w| w.set_usart1en(true));
}

/// Batch-enable APB2 peripherals in a single write.
/// Only safe at init before other peripherals are enabled.
#[inline(always)]
pub fn enable_apb2(bits: u32) {
ch32_metapac::RCC.apb2pcenr().write(|w| w.0 = bits);
}
4 changes: 4 additions & 0 deletions tinyboot-ch32-hal/src/usart/usart_common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use ch32_metapac::usart::Usart as Regs;

#[inline(always)]
pub fn init(r: Regs, pclk: u32, baud: u32, half_duplex: bool) {
// 8N1: write zeroes all bits (M=0, PCE=0, STOP=0b00), then set TE+RE
r.ctlr1().write(|w| {
Expand All @@ -18,16 +19,19 @@ pub fn init(r: Regs, pclk: u32, baud: u32, half_duplex: bool) {
r.ctlr1().modify(|w| w.set_ue(true));
}

#[inline(always)]
pub fn read_byte(r: ch32_metapac::usart::Usart) -> u8 {
while !r.statr().read().rxne() {}
r.datar().read().dr() as u8
}

#[inline(always)]
pub fn write_byte(r: ch32_metapac::usart::Usart, byte: u8) {
while !r.statr().read().txe() {}
r.datar().write(|w| w.set_dr(byte as u16));
}

#[inline(always)]
pub fn flush(r: ch32_metapac::usart::Usart) {
while !r.statr().read().tc() {}
}
16 changes: 11 additions & 5 deletions tinyboot-protocol/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,20 @@ impl Frame {
}
}

/// Send the frame. Two `write_all` calls: body, then CRC.
/// Send the frame. CRC is placed inline after data for a single write.
pub fn send<W: embedded_io::Write>(&mut self, w: &mut W) -> Result<(), W::Error> {
self.sync = Sync::valid();
let body_len = 10 + self.len as usize;
self.crc = crc16(CRC_INIT, self.as_bytes(0, body_len)).to_le_bytes();
w.write_all(self.as_bytes(0, body_len))?;
w.write_all(&self.crc)
let crc = crc16(CRC_INIT, self.as_bytes(0, body_len)).to_le_bytes();
// SAFETY: len <= MAX_PAYLOAD (64), data is 64 bytes, so len+1 < 64 holds
// as long as len < 63. Frame::read validates len <= MAX_PAYLOAD and our
// responses never exceed 12 bytes of data.
let len = self.len as usize;
unsafe {
*self.data.raw.get_unchecked_mut(len) = crc[0];
*self.data.raw.get_unchecked_mut(len + 1) = crc[1];
}
w.write_all(self.as_bytes(0, body_len + 2))
}

/// Read one frame from the transport.
Expand Down Expand Up @@ -306,7 +313,6 @@ mod tests {
assert_eq!(frame2.addr, 0x0800);
assert_eq!(frame2.status, Status::Request);
assert_eq!(unsafe { &frame2.data.raw[..2] }, &[0xDE, 0xAD]);
assert_eq!(frame2.crc, frame.crc);
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions tinyboot/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ where
C: BootCtl,
{
/// Create a new bootloader core from a platform implementation.
#[inline(always)]
pub fn new(platform: Platform<T, S, B, C>) -> Self {
Core { platform }
}

/// Run the bootloader. Checks boot state, validates the app, and either
/// boots the application or enters the protocol loop. Does not return.
#[inline(always)]
pub fn run(mut self) -> ! {
log_info!("Bootloader started");

Expand Down Expand Up @@ -80,6 +82,7 @@ where
&& unsafe { core::ptr::read_volatile(data.as_ptr() as *const u32) } != 0xFFFF_FFFF
}

#[inline(always)]
fn enter_bootloader(&mut self) -> ! {
log_info!("Entering bootloader mode");
self.platform.storage.unlock();
Expand Down
Loading
Loading