opentitanlib/ownership/
rescue.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;
6use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
7use serde::Deserialize;
8use serde_annotate::Annotate;
9use std::convert::TryFrom;
10use std::io::{Read, Write};
11
12use super::GlobalFlags;
13use super::misc::{TlvHeader, TlvTag};
14use crate::chip::boot_svc::BootSvcKind;
15use crate::with_unknown;
16
17with_unknown! {
18    pub enum RescueProtocol: u8 [default = Self::None] {
19        None = 0,
20        Xmodem = b'X',
21        UsbDfu = b'U',
22        SpiDfu = b'S',
23    }
24
25    pub enum RescueTrigger: u8 [default = Self::UartBreak] {
26        None = 0,
27        UartBreak = 1,
28        Strapping = 2,
29        Gpio = 3,
30    }
31
32    pub enum CommandTag: u32 [default = Self::Unknown] {
33        Unknown = 0,
34        Empty = BootSvcKind::EmptyRequest.0,
35        MinBl0SecVerRequest = BootSvcKind::MinBl0SecVerRequest.0,
36        NextBl0SlotRequest = BootSvcKind::NextBl0SlotRequest.0,
37        OwnershipUnlockRequest = BootSvcKind::OwnershipUnlockRequest.0,
38        OwnershipActivateRequest =   BootSvcKind::OwnershipActivateRequest.0,
39
40        // The rescue protocol-level commands are represented in big-endian order.
41        Rescue = u32::from_be_bytes(*b"RESQ"),
42        RescueB = u32::from_be_bytes(*b"RESB"),
43        Reboot = u32::from_be_bytes(*b"REBO"),
44        GetBootLog = u32::from_be_bytes(*b"BLOG"),
45        BootSvcReq = u32::from_be_bytes(*b"BREQ"),
46        BootSvcRsp = u32::from_be_bytes(*b"BRSP"),
47        OwnerBlock = u32::from_be_bytes(*b"OWNR"),
48        GetOwnerPage0 = u32::from_be_bytes(*b"OPG0"),
49        GetOwnerPage1 = u32::from_be_bytes(*b"OPG1"),
50        GetDeviceId = u32::from_be_bytes(*b"OTID"),
51        Wait = u32::from_be_bytes(*b"WAIT"),
52    }
53}
54
55fn is_default<T: Default + Eq>(val: &T) -> bool {
56    *val == T::default()
57}
58
59/// Describes the configuration of the rescue feature of the ROM_EXT.
60#[derive(Debug, Deserialize, Annotate)]
61pub struct OwnerRescueConfig {
62    /// Header identifying this struct.
63    #[serde(
64        skip_serializing_if = "GlobalFlags::not_debug",
65        default = "OwnerRescueConfig::default_header"
66    )]
67    pub header: TlvHeader,
68    /// The type of rescue protocol to use (ie: Xmodem).
69    #[serde(alias = "rescue_type")]
70    pub protocol: RescueProtocol,
71    /// The rescue triggering mechanism (UartBreak, Strapping or Gpio).
72    pub trigger: RescueTrigger,
73    /// The index of the trigger (e.g. Strapping combo or GPIO pin number).
74    pub trigger_index: u8,
75    /// Whether or not to enable the GPIO pull resistor (only if trigger is GPIO).
76    pub gpio_pull_en: bool,
77    /// The GPIO trigger value (only if trigger is GPIO).
78    pub gpio_value: bool,
79    /// Enter rescue mode if boot fails (not implemented yet).
80    #[serde(skip_serializing_if = "is_default")]
81    pub _enter_on_failure: bool,
82    /// Enable a timeout (rescue exits after a period of no activity; not implemented yet).
83    #[serde(skip_serializing_if = "is_default")]
84    pub _timeout_enable: bool,
85    /// The timeout in seconds (not implemented yet).
86    #[serde(skip_serializing_if = "is_default")]
87    pub _timeout: u8,
88    /// The start of the rescue flash region (in pages).
89    pub start: u16,
90    /// The size of the rescue flash region (in pages).
91    pub size: u16,
92    /// An allow-list of commands permitted to be executed by the rescue module.
93    pub command_allow: Vec<CommandTag>,
94}
95
96impl Default for OwnerRescueConfig {
97    fn default() -> Self {
98        Self {
99            header: Self::default_header(),
100            protocol: RescueProtocol::default(),
101            gpio_pull_en: false,
102            gpio_value: false,
103            _enter_on_failure: false,
104            _timeout_enable: false,
105            _timeout: 0,
106            trigger: RescueTrigger::default(),
107            trigger_index: 0,
108            start: 0u16,
109            size: 0u16,
110            command_allow: Vec::new(),
111        }
112    }
113}
114
115impl OwnerRescueConfig {
116    const BASE_SIZE: usize = 16;
117    const GPIO_PULL_BIT: u8 = 0x02;
118    const GPIO_VALUE_BIT: u8 = 0x01;
119    const ENTER_ON_FAIL_BIT: u8 = 0x80;
120    const TIMEOUT_EN_BIT: u8 = 0x40;
121    const TIMEOUT_MASK: u8 = 0x3f;
122    const TRIGGER_SHIFT: u8 = 6;
123    const INDEX_MASK: u8 = 0x3f;
124
125    pub fn default_header() -> TlvHeader {
126        TlvHeader::new(TlvTag::Rescue, 0, "0.0")
127    }
128    pub fn read(src: &mut impl Read, header: TlvHeader) -> Result<Self> {
129        let protocol = RescueProtocol(src.read_u8()?);
130        let gpio = src.read_u8()?;
131        let timeout = src.read_u8()?;
132        let trigger = src.read_u8()?;
133        let start = src.read_u16::<LittleEndian>()?;
134        let size = src.read_u16::<LittleEndian>()?;
135        let allow_len = (header.length - Self::BASE_SIZE) / std::mem::size_of::<u32>();
136        let mut command_allow = Vec::new();
137        for _ in 0..allow_len {
138            command_allow.push(CommandTag(src.read_u32::<LittleEndian>()?));
139        }
140        Ok(Self {
141            header,
142            protocol,
143            gpio_pull_en: gpio & Self::GPIO_PULL_BIT != 0,
144            gpio_value: gpio & Self::GPIO_VALUE_BIT != 0,
145            _enter_on_failure: timeout & Self::ENTER_ON_FAIL_BIT != 0,
146            _timeout_enable: timeout & Self::TIMEOUT_EN_BIT != 0,
147            _timeout: timeout & Self::TIMEOUT_MASK,
148            trigger: RescueTrigger(trigger >> Self::TRIGGER_SHIFT),
149            trigger_index: trigger & Self::INDEX_MASK,
150            start,
151            size,
152            command_allow,
153        })
154    }
155    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
156        let header = TlvHeader::new(
157            TlvTag::Rescue,
158            Self::BASE_SIZE + self.command_allow.len() * std::mem::size_of::<u32>(),
159            "0.0",
160        );
161        header.write(dest)?;
162        dest.write_u8(u8::from(self.protocol))?;
163        dest.write_u8(
164            if self.gpio_pull_en {
165                Self::GPIO_PULL_BIT
166            } else {
167                0
168            } | if self.gpio_value {
169                Self::GPIO_VALUE_BIT
170            } else {
171                0
172            },
173        )?;
174        dest.write_u8(
175            self._timeout & Self::TIMEOUT_MASK
176                | if self._timeout_enable {
177                    Self::TIMEOUT_EN_BIT
178                } else {
179                    0
180                }
181                | if self._enter_on_failure {
182                    Self::ENTER_ON_FAIL_BIT
183                } else {
184                    0
185                },
186        )?;
187        dest.write_u8(
188            self.trigger_index & Self::INDEX_MASK | u8::from(self.trigger) << Self::TRIGGER_SHIFT,
189        )?;
190        dest.write_u16::<LittleEndian>(self.start)?;
191        dest.write_u16::<LittleEndian>(self.size)?;
192        for allow in self.command_allow.iter() {
193            dest.write_u32::<LittleEndian>(u32::from(*allow))?;
194        }
195        Ok(())
196    }
197
198    pub fn all() -> Self {
199        OwnerRescueConfig {
200            protocol: RescueProtocol::Xmodem,
201            trigger: RescueTrigger::UartBreak,
202            // Start after the ROM_EXT.
203            start: 32,
204            // End at the end of the partition.
205            size: 224,
206            command_allow: vec![
207                CommandTag::Empty,
208                CommandTag::MinBl0SecVerRequest,
209                CommandTag::NextBl0SlotRequest,
210                CommandTag::OwnershipUnlockRequest,
211                CommandTag::OwnershipActivateRequest,
212                CommandTag::Rescue,
213                CommandTag::RescueB,
214                CommandTag::Reboot,
215                CommandTag::GetBootLog,
216                CommandTag::BootSvcReq,
217                CommandTag::BootSvcRsp,
218                CommandTag::OwnerBlock,
219                CommandTag::GetOwnerPage0,
220                CommandTag::GetOwnerPage1,
221                CommandTag::GetDeviceId,
222                CommandTag::Wait,
223            ],
224            ..Default::default()
225        }
226    }
227}
228
229#[cfg(test)]
230mod test {
231    use super::*;
232    use crate::util::hexdump::{hexdump_parse, hexdump_string};
233
234    const OWNER_RESCUE_CONFIG_BIN: &str = "\
23500000000: 52 45 53 51 4c 00 00 00 58 00 00 40 20 00 64 00  RESQL...X..@ .d.\n\
23600000010: 45 4d 50 54 4d 53 45 43 4e 45 58 54 55 4e 4c 4b  EMPTMSECNEXTUNLK\n\
23700000020: 41 43 54 56 51 53 45 52 42 53 45 52 47 4f 4c 42  ACTVQSERBSERGOLB\n\
23800000030: 51 45 52 42 50 53 52 42 52 4e 57 4f 30 47 50 4f  QERBPSRBRNWO0GPO\n\
23900000040: 31 47 50 4f 44 49 54 4f 54 49 41 57              1GPODITOTIAW\n\
240";
241    const OWNER_RESCUE_CONFIG_JSON: &str = r#"{
242  protocol: "Xmodem",
243  trigger: "UartBreak",
244  trigger_index: 0,
245  gpio_pull_en: false,
246  gpio_value: false,
247  start: 32,
248  size: 100,
249  command_allow: [
250    "Empty",
251    "MinBl0SecVerRequest",
252    "NextBl0SlotRequest",
253    "OwnershipUnlockRequest",
254    "OwnershipActivateRequest",
255    "Rescue",
256    "RescueB",
257    "GetBootLog",
258    "BootSvcReq",
259    "BootSvcRsp",
260    "OwnerBlock",
261    "GetOwnerPage0",
262    "GetOwnerPage1",
263    "GetDeviceId",
264    "Wait"
265  ]
266}"#;
267
268    #[test]
269    fn test_owner_rescue_config_write() -> Result<()> {
270        let orc = OwnerRescueConfig {
271            header: TlvHeader::default(),
272            protocol: RescueProtocol::Xmodem,
273            trigger: RescueTrigger::UartBreak,
274            start: 32,
275            size: 100,
276            command_allow: vec![
277                CommandTag::Empty,
278                CommandTag::MinBl0SecVerRequest,
279                CommandTag::NextBl0SlotRequest,
280                CommandTag::OwnershipUnlockRequest,
281                CommandTag::OwnershipActivateRequest,
282                CommandTag::Rescue,
283                CommandTag::RescueB,
284                CommandTag::GetBootLog,
285                CommandTag::BootSvcReq,
286                CommandTag::BootSvcRsp,
287                CommandTag::OwnerBlock,
288                CommandTag::GetOwnerPage0,
289                CommandTag::GetOwnerPage1,
290                CommandTag::GetDeviceId,
291                CommandTag::Wait,
292            ],
293            ..Default::default()
294        };
295
296        let mut bin = Vec::new();
297        orc.write(&mut bin)?;
298        eprintln!("{}", hexdump_string(&bin)?);
299        assert_eq!(hexdump_string(&bin)?, OWNER_RESCUE_CONFIG_BIN);
300        Ok(())
301    }
302
303    #[test]
304    fn test_owner_rescue_config_read() -> Result<()> {
305        let buf = hexdump_parse(OWNER_RESCUE_CONFIG_BIN)?;
306        let mut cur = std::io::Cursor::new(&buf);
307        let header = TlvHeader::read(&mut cur)?;
308        let orc = OwnerRescueConfig::read(&mut cur, header)?;
309        let doc = serde_annotate::serialize(&orc)?.to_json5().to_string();
310        eprintln!("{}", doc);
311        assert_eq!(doc, OWNER_RESCUE_CONFIG_JSON);
312        Ok(())
313    }
314}