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
55/// Describes the configuration of the rescue feature of the ROM_EXT.
56#[derive(Debug, Deserialize, Annotate, PartialEq)]
57pub struct OwnerRescueConfig {
58    /// Header identifying this struct.
59    #[serde(
60        skip_serializing_if = "GlobalFlags::not_debug",
61        default = "OwnerRescueConfig::default_header"
62    )]
63    pub header: TlvHeader,
64    /// The type of rescue protocol to use (ie: Xmodem).
65    #[serde(alias = "rescue_type")]
66    pub protocol: RescueProtocol,
67    /// The rescue triggering mechanism (UartBreak, Strapping or Gpio).
68    #[serde(default)]
69    pub trigger: RescueTrigger,
70    /// The index of the trigger (e.g. Strapping combo or GPIO pin number).
71    #[serde(default)]
72    pub trigger_index: u8,
73    /// Whether or not to enable the GPIO pull resistor (only if trigger is GPIO).
74    #[serde(default)]
75    pub gpio_pull_en: bool,
76    /// The GPIO trigger value (only if trigger is GPIO).
77    #[serde(default)]
78    pub gpio_value: bool,
79    /// Enter rescue mode if the reboot reason is watchdog timeout.
80    #[serde(default)]
81    pub enter_on_watchdog: bool,
82    /// Enter rescue mode if boot fails.
83    pub enter_on_failure: bool,
84    /// The inactivity timeout in seconds (zero means disabled).
85    pub timeout: u8,
86    /// The start of the rescue flash region (in pages).
87    pub start: u16,
88    /// The size of the rescue flash region (in pages).
89    pub size: u16,
90    /// An allow-list of commands permitted to be executed by the rescue module.
91    pub command_allow: Vec<CommandTag>,
92}
93
94impl Default for OwnerRescueConfig {
95    fn default() -> Self {
96        Self {
97            header: Self::default_header(),
98            protocol: RescueProtocol::default(),
99            gpio_pull_en: false,
100            gpio_value: false,
101            enter_on_watchdog: false,
102            enter_on_failure: false,
103            timeout: 0,
104            trigger: RescueTrigger::default(),
105            trigger_index: 0,
106            start: 0u16,
107            size: 0u16,
108            command_allow: Vec::new(),
109        }
110    }
111}
112
113impl OwnerRescueConfig {
114    const BASE_SIZE: usize = 16;
115    const MISC_GPIO_WATCHDOG_TIMEOUT_EN_BIT: u8 = 0x80;
116    const MISC_GPIO_PULL_BIT: u8 = 0x02;
117    const MISC_GPIO_VALUE_BIT: u8 = 0x01;
118    const ENTER_ON_FAIL_BIT: u8 = 0x80;
119    const TIMEOUT_MASK: u8 = 0x7f;
120    const TRIGGER_SHIFT: u8 = 6;
121    const INDEX_MASK: u8 = 0x3f;
122
123    pub fn default_header() -> TlvHeader {
124        TlvHeader::new(TlvTag::Rescue, 0, "0.0")
125    }
126    pub fn read(src: &mut impl Read, header: TlvHeader) -> Result<Self> {
127        let protocol = RescueProtocol(src.read_u8()?);
128        let gpio = src.read_u8()?;
129        let timeout = src.read_u8()?;
130        let trigger = src.read_u8()?;
131        let start = src.read_u16::<LittleEndian>()?;
132        let size = src.read_u16::<LittleEndian>()?;
133        let allow_len = (header.length - Self::BASE_SIZE) / std::mem::size_of::<u32>();
134        let mut command_allow = Vec::new();
135        for _ in 0..allow_len {
136            command_allow.push(CommandTag(src.read_u32::<LittleEndian>()?));
137        }
138        Ok(Self {
139            header,
140            protocol,
141            gpio_pull_en: gpio & Self::MISC_GPIO_PULL_BIT != 0,
142            gpio_value: gpio & Self::MISC_GPIO_VALUE_BIT != 0,
143            enter_on_watchdog: gpio & Self::MISC_GPIO_WATCHDOG_TIMEOUT_EN_BIT != 0,
144            enter_on_failure: timeout & Self::ENTER_ON_FAIL_BIT != 0,
145            timeout: timeout & Self::TIMEOUT_MASK,
146            trigger: RescueTrigger(trigger >> Self::TRIGGER_SHIFT),
147            trigger_index: trigger & Self::INDEX_MASK,
148            start,
149            size,
150            command_allow,
151        })
152    }
153    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
154        let header = TlvHeader::new(
155            TlvTag::Rescue,
156            Self::BASE_SIZE + self.command_allow.len() * std::mem::size_of::<u32>(),
157            "0.0",
158        );
159        header.write(dest)?;
160        dest.write_u8(u8::from(self.protocol))?;
161        dest.write_u8(
162            if self.enter_on_watchdog {
163                Self::MISC_GPIO_WATCHDOG_TIMEOUT_EN_BIT
164            } else {
165                0
166            } | if self.gpio_pull_en {
167                Self::MISC_GPIO_PULL_BIT
168            } else {
169                0
170            } | if self.gpio_value {
171                Self::MISC_GPIO_VALUE_BIT
172            } else {
173                0
174            },
175        )?;
176        dest.write_u8(
177            self.timeout & Self::TIMEOUT_MASK
178                | if self.enter_on_failure {
179                    Self::ENTER_ON_FAIL_BIT
180                } else {
181                    0
182                },
183        )?;
184        dest.write_u8(
185            self.trigger_index & Self::INDEX_MASK | u8::from(self.trigger) << Self::TRIGGER_SHIFT,
186        )?;
187        dest.write_u16::<LittleEndian>(self.start)?;
188        dest.write_u16::<LittleEndian>(self.size)?;
189        for allow in self.command_allow.iter() {
190            dest.write_u32::<LittleEndian>(u32::from(*allow))?;
191        }
192        Ok(())
193    }
194
195    pub fn all() -> Self {
196        OwnerRescueConfig {
197            protocol: RescueProtocol::Xmodem,
198            trigger: RescueTrigger::UartBreak,
199            // Start after the ROM_EXT.
200            start: 32,
201            // End at the end of the partition.
202            size: 224,
203            command_allow: vec![
204                CommandTag::Empty,
205                CommandTag::MinBl0SecVerRequest,
206                CommandTag::NextBl0SlotRequest,
207                CommandTag::OwnershipUnlockRequest,
208                CommandTag::OwnershipActivateRequest,
209                CommandTag::Rescue,
210                CommandTag::RescueB,
211                CommandTag::Reboot,
212                CommandTag::GetBootLog,
213                CommandTag::BootSvcReq,
214                CommandTag::BootSvcRsp,
215                CommandTag::OwnerBlock,
216                CommandTag::GetOwnerPage0,
217                CommandTag::GetOwnerPage1,
218                CommandTag::GetDeviceId,
219                CommandTag::Wait,
220            ],
221            ..Default::default()
222        }
223    }
224}
225
226#[cfg(test)]
227mod test {
228    use super::*;
229    use crate::util::hexdump::{hexdump_parse, hexdump_string};
230
231    const OWNER_RESCUE_CONFIG_BIN: &str = "\
23200000000: 52 45 53 51 4c 00 00 00 58 80 00 40 20 00 64 00  RESQL...X..@ .d.\n\
23300000010: 45 4d 50 54 4d 53 45 43 4e 45 58 54 55 4e 4c 4b  EMPTMSECNEXTUNLK\n\
23400000020: 41 43 54 56 51 53 45 52 42 53 45 52 47 4f 4c 42  ACTVQSERBSERGOLB\n\
23500000030: 51 45 52 42 50 53 52 42 52 4e 57 4f 30 47 50 4f  QERBPSRBRNWO0GPO\n\
23600000040: 31 47 50 4f 44 49 54 4f 54 49 41 57              1GPODITOTIAW\n\
237";
238    const OWNER_RESCUE_CONFIG_JSON: &str = r#"{
239  protocol: "Xmodem",
240  trigger: "UartBreak",
241  trigger_index: 0,
242  gpio_pull_en: false,
243  gpio_value: false,
244  enter_on_watchdog: true,
245  enter_on_failure: false,
246  timeout: 0,
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            enter_on_watchdog: true,
275            start: 32,
276            size: 100,
277            command_allow: vec![
278                CommandTag::Empty,
279                CommandTag::MinBl0SecVerRequest,
280                CommandTag::NextBl0SlotRequest,
281                CommandTag::OwnershipUnlockRequest,
282                CommandTag::OwnershipActivateRequest,
283                CommandTag::Rescue,
284                CommandTag::RescueB,
285                CommandTag::GetBootLog,
286                CommandTag::BootSvcReq,
287                CommandTag::BootSvcRsp,
288                CommandTag::OwnerBlock,
289                CommandTag::GetOwnerPage0,
290                CommandTag::GetOwnerPage1,
291                CommandTag::GetDeviceId,
292                CommandTag::Wait,
293            ],
294            ..Default::default()
295        };
296
297        let mut bin = Vec::new();
298        orc.write(&mut bin)?;
299        eprintln!("{}", hexdump_string(&bin)?);
300        assert_eq!(hexdump_string(&bin)?, OWNER_RESCUE_CONFIG_BIN);
301        Ok(())
302    }
303
304    #[test]
305    fn test_owner_rescue_config_read() -> Result<()> {
306        let buf = hexdump_parse(OWNER_RESCUE_CONFIG_BIN)?;
307        let mut cur = std::io::Cursor::new(&buf);
308        let header = TlvHeader::read(&mut cur)?;
309        let orc = OwnerRescueConfig::read(&mut cur, header)?;
310        let doc = serde_annotate::serialize(&orc)?.to_json5().to_string();
311        eprintln!("{}", doc);
312        assert_eq!(doc, OWNER_RESCUE_CONFIG_JSON);
313        Ok(())
314    }
315}