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