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