Skip to main content

tinyboot_core/
app.rs

1//! App-side tinyboot client.
2//!
3//! Handles boot confirmation and responds to host commands (Info, Reset)
4//! so the CLI can query and reset the device without physical access.
5
6use crate::traits::app::BootClient;
7use tinyboot_protocol::frame::{Frame, InfoData};
8use tinyboot_protocol::{Cmd, Status};
9
10/// App-side configuration.
11pub struct AppConfig {
12    /// App region capacity in bytes.
13    pub capacity: u32,
14    /// Erase page size in bytes.
15    pub erase_size: u16,
16    /// Boot version (read from flash by the caller).
17    pub boot_version: u16,
18    /// App version (typically from `pkg_version!()`).
19    pub app_version: u16,
20}
21
22/// App-side tinyboot client. Handles Info/Reset commands and boot confirmation.
23pub struct App<B: BootClient> {
24    frame: Frame,
25    config: AppConfig,
26    client: B,
27}
28
29impl<B: BootClient> App<B> {
30    /// Create a new app client.
31    pub fn new(config: AppConfig, client: B) -> Self {
32        Self {
33            frame: Frame::default(),
34            config,
35            client,
36        }
37    }
38
39    /// Confirm boot — transitions Validating to Idle, preserving checksum.
40    /// Call after all peripherals are initialized.
41    pub fn confirm(&mut self) {
42        self.client.confirm();
43    }
44
45    /// Poll for tinyboot commands (blocking).
46    pub fn poll<R: embedded_io::Read, W: embedded_io::Write>(&mut self, rx: &mut R, tx: &mut W) {
47        let status = match self.frame.read(rx) {
48            Ok(s) => s,
49            Err(_) => return,
50        };
51        if status == Status::Ok {
52            self.handle_cmd();
53        } else {
54            self.frame.len = 0;
55            self.frame.status = status;
56        }
57        if self.frame.cmd != Cmd::Reset {
58            let _ = self.frame.send(tx);
59            let _ = tx.flush();
60        }
61    }
62
63    /// Poll for tinyboot commands (async).
64    pub async fn poll_async<R: embedded_io_async::Read, W: embedded_io_async::Write>(
65        &mut self,
66        rx: &mut R,
67        tx: &mut W,
68    ) {
69        let status = match self.frame.read_async(rx).await {
70            Ok(s) => s,
71            Err(_) => return,
72        };
73        if status == Status::Ok {
74            self.handle_cmd();
75        } else {
76            self.frame.len = 0;
77            self.frame.status = status;
78        }
79        if self.frame.cmd != Cmd::Reset {
80            let _ = self.frame.send_async(tx).await;
81            let _ = tx.flush().await;
82        }
83    }
84
85    fn handle_cmd(&mut self) {
86        self.frame.status = Status::Ok;
87        match self.frame.cmd {
88            Cmd::Info => {
89                self.frame.len = 12;
90                self.frame.data.info = InfoData {
91                    capacity: self.config.capacity,
92                    erase_size: self.config.erase_size,
93                    boot_version: self.config.boot_version,
94                    app_version: self.config.app_version,
95                    mode: 1, // app
96                };
97            }
98            Cmd::Reset => {
99                if self.frame.addr == 1 {
100                    self.client.request_update();
101                }
102                self.client.system_reset();
103            }
104            _ => {
105                self.frame.len = 0;
106                self.frame.status = Status::Unsupported;
107            }
108        }
109    }
110}