opentitanlib/rescue/
mod.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Result, ensure};
6use clap::{Args, ValueEnum};
7use std::time::Duration;
8use thiserror::Error;
9
10use crate::app::TransportWrapper;
11use crate::chip::boot_log::BootLog;
12use crate::chip::boot_svc::{BootSlot, BootSvc, OwnershipActivateRequest, OwnershipUnlockRequest};
13use crate::chip::device_id::DeviceId;
14use crate::io::gpio::PinMode;
15use crate::io::spi::SpiParams;
16use crate::io::uart::UartParams;
17use crate::util::parse_int::ParseInt;
18use crate::with_unknown;
19
20pub mod dfu;
21pub mod serial;
22pub mod spidfu;
23pub mod usbdfu;
24pub mod xmodem;
25
26pub use serial::RescueSerial;
27pub use spidfu::SpiDfu;
28pub use usbdfu::UsbDfu;
29
30#[derive(Debug, Error)]
31pub enum RescueError {
32    #[error("bad mode: {0}")]
33    BadMode(String),
34    #[error("configuration error: {0}")]
35    Configuration(String),
36    #[error("bad protocol: {0}")]
37    BadProtocol(String),
38    #[error("not found: {0}")]
39    NotFound(String),
40}
41
42#[derive(ValueEnum, Default, Debug, Clone, Copy, PartialEq)]
43pub enum RescueProtocol {
44    #[default]
45    Xmodem,
46    UsbDfu,
47    SpiDfu,
48}
49
50#[derive(ValueEnum, Default, Debug, Clone, Copy, PartialEq)]
51pub enum RescueTrigger {
52    #[default]
53    SerialBreak,
54    Gpio,
55    Strap,
56}
57
58#[derive(Clone, Debug, Args)]
59pub struct RescueParams {
60    /// Rescue Protocol
61    #[arg(short, long, value_enum, default_value_t = RescueProtocol::Xmodem)]
62    pub protocol: RescueProtocol,
63    #[arg(short, long, value_enum, default_value_t = RescueTrigger::SerialBreak)]
64    pub trigger: RescueTrigger,
65    #[arg(short, long, default_value = "")]
66    pub value: String,
67    #[command(flatten)]
68    pub spi: SpiParams,
69    #[command(flatten)]
70    pub uart: UartParams,
71    #[arg(long)]
72    pub usb_serial: Option<String>,
73    #[arg(long, value_parser = humantime::parse_duration, default_value = "15s")]
74    pub enter_delay: Duration,
75}
76
77impl RescueParams {
78    pub fn create(&self, transport: &TransportWrapper) -> Result<Box<dyn Rescue>> {
79        match self.protocol {
80            RescueProtocol::Xmodem => self.create_serial(transport),
81            RescueProtocol::UsbDfu => self.create_usbdfu(transport),
82            RescueProtocol::SpiDfu => self.create_spidfu(transport),
83        }
84    }
85
86    pub fn set_trigger(&self, transport: &TransportWrapper, trigger: bool) -> Result<()> {
87        match self.trigger {
88            RescueTrigger::SerialBreak => unimplemented!(),
89            RescueTrigger::Gpio => self.set_gpio(transport, trigger),
90            RescueTrigger::Strap => self.set_strap(transport, trigger),
91        }
92    }
93
94    fn create_serial(&self, transport: &TransportWrapper) -> Result<Box<dyn Rescue>> {
95        ensure!(
96            self.trigger == RescueTrigger::SerialBreak,
97            RescueError::Configuration(format!(
98                "Xmodem does not support trigger {:?}",
99                self.trigger
100            ))
101        );
102        ensure!(
103            self.value.is_empty(),
104            RescueError::Configuration(format!(
105                "Xmodem does not support trigger value {:?}",
106                self.value
107            ))
108        );
109        Ok(Box::new(RescueSerial::new(self.uart.create(transport)?)))
110    }
111
112    fn create_usbdfu(&self, _transport: &TransportWrapper) -> Result<Box<dyn Rescue>> {
113        ensure!(
114            self.trigger != RescueTrigger::SerialBreak,
115            RescueError::Configuration(format!(
116                "Usb-DFU does not support trigger {:?}",
117                self.trigger
118            ))
119        );
120        ensure!(
121            !self.value.is_empty(),
122            RescueError::Configuration("Usb-DFU requires a trigger value".into())
123        );
124        Ok(Box::new(UsbDfu::new(self.clone())))
125    }
126
127    fn create_spidfu(&self, transport: &TransportWrapper) -> Result<Box<dyn Rescue>> {
128        ensure!(
129            self.trigger != RescueTrigger::SerialBreak,
130            RescueError::Configuration(format!(
131                "Usb-DFU does not support trigger {:?}",
132                self.trigger
133            ))
134        );
135        ensure!(
136            !self.value.is_empty(),
137            RescueError::Configuration("Usb-DFU requires a trigger value".into())
138        );
139        Ok(Box::new(SpiDfu::new(
140            self.spi.create(transport, "BOOTSTRAP")?,
141            self.clone(),
142        )))
143    }
144
145    fn set_strap(&self, transport: &TransportWrapper, trigger: bool) -> Result<()> {
146        let mut value = if trigger {
147            u8::from_str(&self.value)?
148        } else {
149            0u8
150        };
151        for strap in ["SW_STRAP0", "SW_STRAP1", "SW_STRAP2"] {
152            let pin = transport.gpio_pin(strap)?;
153            match value & 3 {
154                0 => pin.write(false)?,
155                1 | 2 => log::error!("weak straps not supported yet"),
156                3 => pin.write(true)?,
157                _ => unreachable!(),
158            };
159            value >>= 2;
160        }
161        Ok(())
162    }
163
164    fn parse_pin(&self) -> Result<(&str, bool)> {
165        if let Some(pin) = self.value.strip_prefix('+') {
166            Ok((pin, true))
167        } else if let Some(pin) = self.value.strip_prefix('-') {
168            Ok((pin, false))
169        } else {
170            Ok((self.value.as_str(), true))
171        }
172    }
173
174    fn set_gpio(&self, transport: &TransportWrapper, trigger: bool) -> Result<()> {
175        let (name, mut value) = self.parse_pin()?;
176        if !trigger {
177            value = !value
178        };
179        let pin = transport.gpio_pin(name)?;
180        pin.set(Some(PinMode::PushPull), Some(value), None, None)?;
181        Ok(())
182    }
183}
184
185with_unknown! {
186pub enum RescueMode: u32 {
187    Rescue = u32::from_be_bytes(*b"RESQ"),
188    RescueB = u32::from_be_bytes(*b"RESB"),
189    BootLog = u32::from_be_bytes(*b"BLOG"),
190    BootSvcReq = u32::from_be_bytes(*b"BREQ"),
191    BootSvcRsp = u32::from_be_bytes(*b"BRSP"),
192    OwnerBlock = u32::from_be_bytes(*b"OWNR"),
193    GetOwnerPage0 = u32::from_be_bytes(*b"OPG0"),
194    GetOwnerPage1 = u32::from_be_bytes(*b"OPG1"),
195    DeviceId = u32::from_be_bytes(*b"OTID"),
196    EraseOwner = u32::from_be_bytes(*b"KLBR"),
197}
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
201/// Determines how `Rescue::enter` should enter rescue mode.
202pub enum EntryMode {
203    /// Reset the chip using the external power-on reset signal.
204    Reset,
205    /// Assume the chip is in rescue mode and needs to soft-reboot back to rescue mode.
206    Reboot,
207    /// Do nothing; assume the chip is ready to go to rescue mode.
208    None,
209}
210
211pub trait Rescue {
212    fn enter(&self, transport: &TransportWrapper, mode: EntryMode) -> Result<()>;
213    fn set_mode(&self, mode: RescueMode) -> Result<()>;
214    fn send(&self, data: &[u8]) -> Result<()>;
215    fn recv(&self) -> Result<Vec<u8>>;
216
217    // Not supported by all backends
218    fn set_speed(&self, speed: u32) -> Result<u32>;
219    fn reboot(&self) -> Result<()>;
220
221    fn get_raw(&self, mode: RescueMode) -> Result<Vec<u8>> {
222        self.set_mode(mode)?;
223        self.recv()
224    }
225
226    fn set_raw(&self, mode: RescueMode, data: &[u8]) -> Result<()> {
227        self.set_mode(mode)?;
228        self.send(data)
229    }
230
231    fn update_firmware(&self, slot: BootSlot, image: &[u8]) -> Result<()> {
232        let mode = if slot == BootSlot::SlotB {
233            RescueMode::RescueB
234        } else {
235            RescueMode::Rescue
236        };
237        self.set_raw(mode, image)
238    }
239
240    fn get_boot_log(&self) -> Result<BootLog> {
241        let blog = self.get_raw(RescueMode::BootLog)?;
242        Ok(BootLog::try_from(blog.as_slice())?)
243    }
244
245    fn get_boot_svc(&self) -> Result<BootSvc> {
246        let bsvc = self.get_raw(RescueMode::BootSvcRsp)?;
247        Ok(BootSvc::try_from(bsvc.as_slice())?)
248    }
249
250    fn get_device_id(&self) -> Result<DeviceId> {
251        let id = self.get_raw(RescueMode::DeviceId)?;
252        DeviceId::read(&mut std::io::Cursor::new(&id))
253    }
254
255    fn empty(&self, payload: &[u32]) -> Result<()> {
256        let message = BootSvc::empty(payload);
257        self.set_raw(RescueMode::BootSvcReq, &message.to_bytes()?)
258    }
259
260    fn set_min_bl0_sec_ver(&self, ver: u32) -> Result<()> {
261        let message = BootSvc::min_bl0_sec_ver(ver);
262        self.set_raw(RescueMode::BootSvcReq, &message.to_bytes()?)
263    }
264
265    fn set_next_bl0_slot(&self, primary: BootSlot, next: BootSlot) -> Result<()> {
266        let message = BootSvc::next_boot_bl0_slot(primary, next);
267        self.set_raw(RescueMode::BootSvcReq, &message.to_bytes()?)
268    }
269
270    fn ownership_unlock(&self, unlock: OwnershipUnlockRequest) -> Result<()> {
271        let message = BootSvc::ownership_unlock(unlock);
272        self.set_raw(RescueMode::BootSvcReq, &message.to_bytes()?)
273    }
274
275    fn ownership_activate(&self, activate: OwnershipActivateRequest) -> Result<()> {
276        let message = BootSvc::ownership_activate(activate);
277        self.set_raw(RescueMode::BootSvcReq, &message.to_bytes()?)
278    }
279
280    fn set_owner_config(&self, data: &[u8]) -> Result<()> {
281        self.set_raw(RescueMode::OwnerBlock, data)
282    }
283
284    fn erase_owner(&self) -> Result<()> {
285        self.set_raw(RescueMode::EraseOwner, &[])
286    }
287}