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
6 changes: 3 additions & 3 deletions ch32/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "tinyboot-ch32"
description = "tinyboot ch32 implementation"
version = "0.7.1"
version = "0.7.2"
edition.workspace = true
repository.workspace = true
license.workspace = true
Expand Down Expand Up @@ -52,8 +52,8 @@ repository = "https://github.com/OpenServoCore/tinyboot"
license = "MIT OR Apache-2.0"

[workspace.dependencies]
tinyboot-core = { version = "0.6.2", path = "../lib/core", default-features = false }
tinyboot-ch32 = { version = "0.7.1", path = ".", default-features = false }
tinyboot-core = { version = "0.6.3", path = "../lib/core", default-features = false }
tinyboot-ch32 = { version = "0.7.2", path = ".", default-features = false }
embedded-hal = "1"
embedded-io = "0.7"
embedded-storage = "0.3"
Expand Down
5 changes: 4 additions & 1 deletion ch32/src/platform/transport/usart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,15 @@ impl<W: Wire, E: TxEnable> Usart<W, E> {

afio::set_usart_remap(usart_n, config.mapping.remap_value());

let wire = config.wire.init(tx_pin, rx_pin);
let tx_en = config.tx_en.init();

let brr = stored_brr(config.pclk, brr(config.pclk, config.baud.hz()));
usart::init(regs, brr, W::HALF_DUPLEX);

// After TE: a TX pin switched to its USART function before then
// follows an output that idles low.
let wire = config.wire.init(tx_pin, rx_pin);

Usart { regs, wire, tx_en }
}

Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions docs/shared-bus.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ It never answers a frame it can't read (bad CRC, unknown command, payload too bi

This means a device sitting in its bootloader never disturbs the bus. Several devices waiting for firmware at once is fine too. Each one stays quiet until the host picks it.

A device hears every frame on the bus, not just its own. A reply from another device is read and dropped without a CRC check, so the device is listening again right after the last byte. A request is different. Its CRC is checked after the last byte arrives, about 3 us per frame byte on a 48 MHz CH32, and the device cannot hear during that time. The host only sends its next request after the reply or after its listen window, so this never overlaps in normal use. If you write your own host and send a request that draws no reply, leave that much time before the next frame.

## How `Select` works

`Select` carries a seed and a prefix of id bits. Every device whose id starts with the prefix becomes selected and replies with its id. Every other device becomes deselected.
Expand Down
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repository = "https://github.com/OpenServoCore/tinyboot"
license = "MIT OR Apache-2.0"

[workspace.dependencies]
tinyboot-protocol = { version = "0.5.0", path = "protocol" }
tinyboot-protocol = { version = "0.5.2", path = "protocol" }
embedded-io = "0.7"
embedded-io-async = "0.7"
embedded-storage = "0.3"
Expand Down
2 changes: 1 addition & 1 deletion lib/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "tinyboot-core"
description = "Core bootloader library for tinyboot"
version = "0.6.2"
version = "0.6.3"
edition.workspace = true
repository.workspace = true
license.workspace = true
Expand Down
7 changes: 5 additions & 2 deletions lib/core/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl<C: BootCtl, M: BootMetaStore, const P: usize, A: Addressing> App<C, M, P, A

/// Poll for one command (blocking).
pub fn poll<R: embedded_io::Read, W: embedded_io::Write>(&mut self, rx: &mut R, tx: &mut W) {
let status = match self.frame.read(rx) {
let status = match self.frame.read_request(rx) {
Ok(s) => s,
Err(_) => return,
};
Expand All @@ -132,7 +132,7 @@ impl<C: BootCtl, M: BootMetaStore, const P: usize, A: Addressing> App<C, M, P, A
rx: &mut R,
tx: &mut W,
) {
let status = match self.frame.read_async(rx).await {
let status = match self.frame.read_request_async(rx).await {
Ok(s) => s,
Err(_) => return,
};
Expand All @@ -155,6 +155,9 @@ impl<C: BootCtl, M: BootMetaStore, const P: usize, A: Addressing> App<C, M, P, A
if !self.addressing.selected() {
return false;
}
} else if status == Status::Request {
// Another device's reply.
return false;
} else if status != Status::Ok {
self.frame.len = 0;
self.frame.status = status;
Expand Down
5 changes: 4 additions & 1 deletion lib/core/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ impl<
/// to other requests; it never answers a frame it cannot read or
/// another device's reply.
pub fn dispatch(&mut self) -> Result<(), ReadError> {
let status = self.frame.read(&mut self.platform.transport)?;
let status = self.frame.read_request(&mut self.platform.transport)?;

let reply = if A::ADDRESSABLE {
if status != Status::Ok || self.frame.status != Status::Request {
Expand All @@ -285,6 +285,9 @@ impl<
} else {
false
}
} else if status == Status::Request {
// Another device's reply.
false
} else if status != Status::Ok {
self.frame.len = 0;
self.frame.status = status;
Expand Down
77 changes: 77 additions & 0 deletions lib/core/tests/addressing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,68 @@ fn unaddressed_bootloader_refuses_select() {
);
}

/// Bytes of a frame as another device or the host sends it.
fn frame_bytes(cmd: Cmd, status: Status, data: &[u8]) -> Vec<u8> {
let mut t = MockTransport::new();
load(&mut t, cmd, status, 0, 0, data);
t.rx_buf[..t.rx_len].to_vec()
}

/// Queue `frames` back to back on `t`'s receive side.
fn queue(t: &mut MockTransport, frames: &[Vec<u8>]) {
let all = frames.concat();
t.rx_buf[..all.len()].copy_from_slice(&all);
t.rx_len = all.len();
t.rx_pos = 0;
t.tx_pos = 0;
t.waited = None;
}

#[test]
fn a_heard_reply_then_a_select_for_this_device_is_answered() {
let mut ps = platforms(1);
let mut devs = devices(&mut ps, &UID_FNS[..1]);
let d = &mut devs[0];
let heard = frame_bytes(Cmd::Info, Status::Ok, &[0x5A; 18]);
let seed = 5;
let sel = frame_bytes(
Cmd::Select,
Status::Request,
&select_data(seed, &bits_of(&UIDS[0])),
);
queue(&mut d.platform.transport, &[heard.clone(), sel]);

d.dispatch().unwrap();
assert_eq!(d.platform.transport.rx_pos, heard.len());
assert!(reply(&d.platform.transport).is_none());

d.dispatch().unwrap();
let f = reply(&d.platform.transport).expect("no Select reply");
assert_eq!(uid_of(&f), UIDS[0]);
let slot = (crc16(CRC_INIT, &UIDS[0]) >> seed) as u8 & 15;
assert_eq!(d.platform.transport.waited, Some(slot));
}

#[test]
fn an_unaddressed_bootloader_never_answers_a_heard_reply() {
let mut p = platform(BootState::Idle);
let mut d = Dispatcher::<_, _, _, _, TEST_BUF_SIZE>::new(&mut p);
let mut bad = frame_bytes(Cmd::Info, Status::Ok, &[0; 18]);
let n = bad.len();
bad[n - 1] ^= 1;
let info = frame_bytes(Cmd::Info, Status::Request, &[]);
queue(
&mut d.platform.transport,
&[frame_bytes(Cmd::Write, Status::Ok, &[]), bad, info],
);
d.dispatch().unwrap();
assert!(reply(&d.platform.transport).is_none());
d.dispatch().unwrap();
assert!(reply(&d.platform.transport).is_none());
d.dispatch().unwrap();
assert_eq!(reply(&d.platform.transport).unwrap().status, Status::Ok);
}

static APP_WAITED: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0xFF);

fn app_wait(n: u8) {
Expand Down Expand Up @@ -415,4 +477,19 @@ fn addressable_app_answers_only_once_selected() {
app.poll(&mut rx, &mut tx);
let f = reply(&tx).expect("no Info reply");
assert_eq!(unsafe { f.data.info }.mode, 1);

// Another device's reply, then a request: the app reads past the one
// and answers the other.
tx.tx_pos = 0;
queue(
&mut rx,
&[
frame_bytes(Cmd::Info, Status::Ok, &[0; 18]),
frame_bytes(Cmd::Info, Status::Request, &[]),
],
);
app.poll(&mut rx, &mut tx);
assert!(reply(&tx).is_none());
app.poll(&mut rx, &mut tx);
assert!(reply(&tx).is_some());
}
2 changes: 1 addition & 1 deletion lib/protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "tinyboot-protocol"
description = "Protocol implementation for tinyboot bootloader"
version = "0.5.1"
version = "0.5.2"
edition.workspace = true
repository.workspace = true
license.workspace = true
Expand Down
Loading
Loading