opentitanlib/rescue/
usbdfu.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, bail};
6use std::cell::{Cell, Ref, RefCell};
7use std::time::Duration;
8
9use crate::app::{TransportWrapper, UartRx};
10use crate::rescue::dfu::*;
11use crate::rescue::{EntryMode, Rescue, RescueError, RescueMode, RescueParams};
12use crate::util::usb::UsbBackend;
13
14pub struct UsbDfu {
15    usb: RefCell<Option<UsbBackend>>,
16    interface: Cell<u8>,
17    params: RescueParams,
18    reset_delay: Duration,
19    enter_delay: Duration,
20}
21
22impl UsbDfu {
23    const CLASS: u8 = 254;
24    const SUBCLASS: u8 = 1;
25    const PROTOCOL: u8 = 2;
26    pub fn new(params: RescueParams) -> Self {
27        UsbDfu {
28            usb: RefCell::new(None),
29            interface: Cell::default(),
30            params,
31            reset_delay: Duration::from_millis(50),
32            enter_delay: Duration::from_secs(20),
33        }
34    }
35
36    fn device(&self) -> Ref<'_, UsbBackend> {
37        let device = self.usb.borrow();
38        Ref::map(device, |d| d.as_ref().expect("device handle"))
39    }
40}
41
42impl Rescue for UsbDfu {
43    fn enter(&self, transport: &TransportWrapper, mode: EntryMode) -> Result<()> {
44        log::info!(
45            "Setting {:?}({}) to trigger rescue mode.",
46            self.params.trigger,
47            self.params.value
48        );
49        self.params.set_trigger(transport, true)?;
50
51        match mode {
52            EntryMode::Reset => transport.reset_with_delay(UartRx::Keep, self.reset_delay)?,
53            EntryMode::Reboot => self.reboot()?,
54            EntryMode::None => {}
55        }
56
57        let device = UsbBackend::from_interface_with_timeout(
58            Self::CLASS,
59            Self::SUBCLASS,
60            Self::PROTOCOL,
61            self.params.usb_serial.as_deref(),
62            self.enter_delay,
63        );
64        log::info!("Rescue triggered; clearing trigger condition.");
65        self.params.set_trigger(transport, false)?;
66        let device = device?;
67
68        let config = device.active_config_descriptor()?;
69        for intf in config.interfaces() {
70            for desc in intf.descriptors() {
71                if desc.class_code() == Self::CLASS
72                    && desc.sub_class_code() == Self::SUBCLASS
73                    && desc.protocol_code() == Self::PROTOCOL
74                {
75                    device.claim_interface(intf.number())?;
76                    self.interface.set(intf.number());
77                    break;
78                }
79            }
80        }
81        self.usb.replace(Some(device));
82        Ok(())
83    }
84
85    fn set_mode(&self, mode: RescueMode) -> Result<()> {
86        let setting = match mode {
87            // FIXME: the RescueMode to AltSetting values either need to be permanently fixed, or
88            // the alt interfaces need to describe themselves via a string descriptor.
89            RescueMode::Rescue => 0,
90            RescueMode::RescueB => 1,
91            RescueMode::DeviceId => 2,
92            RescueMode::BootLog => 3,
93            RescueMode::BootSvcReq => 4,
94            RescueMode::BootSvcRsp => 4,
95            RescueMode::OwnerBlock => 5,
96            RescueMode::GetOwnerPage0 => 5,
97            _ => bail!(RescueError::BadMode(format!(
98                "mode {mode:?} not supported by DFU"
99            ))),
100        };
101
102        let device = self.device();
103        log::info!("Mode {mode} is AltSetting {setting}");
104        device.set_alternate_setting(self.interface.get(), setting)?;
105        Ok(())
106    }
107
108    fn set_speed(&self, _speed: u32) -> Result<u32> {
109        log::warn!("set_speed is not implemented for DFU");
110        Ok(0)
111    }
112
113    fn reboot(&self) -> Result<()> {
114        let usb = self.device();
115        usb.release_interface(self.interface.get())?;
116        match usb.reset() {
117            Ok(_) => {}
118            Err(e) => log::warn!("USB reset: {e}"),
119        }
120        Ok(())
121    }
122
123    fn send(&self, data: &[u8]) -> Result<()> {
124        for chunk in data.chunks(2048) {
125            let _ = self.download(chunk)?;
126            let status = loop {
127                let status = self.get_status()?;
128                match status.state() {
129                    DfuState::DnLoadIdle | DfuState::Error => {
130                        break status;
131                    }
132                    _ => {
133                        std::thread::sleep(Duration::from_millis(status.poll_timeout() as u64));
134                    }
135                }
136            };
137            status.status()?;
138        }
139        // Send a zero-length chunk to signal the end.
140        let _ = self.download(&[])?;
141        let status = self.get_status()?;
142        log::warn!("State after DFU download: {}", status.state());
143        Ok(())
144    }
145
146    fn recv(&self) -> Result<Vec<u8>> {
147        let mut data = vec![0u8; 2048];
148        /*
149         * FIXME: what am I supposed to do here?
150         * The spec seems to indicate that I should keep performing `upload` until I get back a
151         * short or zero length packet.
152        let mut offset = 0;
153        loop {
154            log::info!("upload at {offset}");
155            let length = self.upload(&mut data[offset..])?;
156            if length == 0 || length < data.len() - offset {
157                break;
158            }
159            offset += length;
160        }
161        */
162        self.upload(&mut data)?;
163        let status = self.get_status()?;
164        log::warn!("State after DFU upload: {}", status.state());
165        Ok(data)
166    }
167}
168
169impl DfuOperations for UsbDfu {
170    fn write_control(
171        &self,
172        request_type: u8,
173        request: u8,
174        value: u16,
175        index: u16,
176        data: &[u8],
177    ) -> Result<usize> {
178        let usb = self.device();
179        usb.write_control(request_type, request, value, index, data)
180    }
181
182    fn read_control(
183        &self,
184        request_type: u8,
185        request: u8,
186        value: u16,
187        index: u16,
188        data: &mut [u8],
189    ) -> Result<usize> {
190        let usb = self.device();
191        usb.read_control(request_type, request, value, index, data)
192    }
193
194    fn get_interface(&self) -> u8 {
195        self.interface.get()
196    }
197}