opentitanlib/rescue/
spidfu.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::RefCell;
7use std::rc::Rc;
8use std::time::{Duration, Instant};
9use zerocopy::{Immutable, IntoBytes};
10
11use crate::app::{TransportWrapper, UartRx};
12use crate::chip::rom_error::RomError;
13use crate::io::spi::Target;
14use crate::rescue::dfu::*;
15use crate::rescue::{EntryMode, Rescue, RescueError, RescueMode, RescueParams};
16use crate::spiflash::SpiFlash;
17use crate::spiflash::sfdp::Sdfu;
18
19#[repr(C)]
20#[derive(Default, Debug, Immutable, IntoBytes)]
21struct SetupData {
22    request_type: u8,
23    request: u8,
24    value: u16,
25    index: u16,
26    length: u16,
27}
28
29pub struct SpiDfu {
30    spi: Rc<dyn Target>,
31    flash: RefCell<SpiFlash>,
32    sdfu: RefCell<Sdfu>,
33    params: RescueParams,
34    reset_delay: Duration,
35    enter_delay: Duration,
36}
37
38impl SpiDfu {
39    const SET_INTERFACE: u8 = 0x0b;
40
41    pub fn new(spi: Rc<dyn Target>, params: RescueParams) -> Self {
42        SpiDfu {
43            spi,
44            flash: RefCell::default(),
45            sdfu: RefCell::default(),
46            params,
47            reset_delay: Duration::from_millis(50),
48            enter_delay: Duration::from_secs(5),
49        }
50    }
51
52    fn wait_for_device(spi: &dyn Target, timeout: Duration) -> Result<SpiFlash> {
53        let deadline = Instant::now() + timeout;
54        loop {
55            match SpiFlash::from_spi(spi) {
56                Ok(flash) => return Ok(flash),
57                Err(e) => {
58                    if Instant::now() < deadline {
59                        std::thread::sleep(Duration::from_millis(100));
60                    } else {
61                        return Err(e);
62                    }
63                }
64            }
65        }
66    }
67}
68
69impl Rescue for SpiDfu {
70    fn enter(&self, transport: &TransportWrapper, mode: EntryMode) -> Result<()> {
71        log::info!(
72            "Setting {:?}({}) to trigger rescue mode.",
73            self.params.trigger,
74            self.params.value
75        );
76        self.params.set_trigger(transport, true)?;
77        match mode {
78            EntryMode::Reset => transport.reset_with_delay(UartRx::Keep, self.reset_delay)?,
79            EntryMode::Reboot => {
80                self.reboot()?;
81                // Give the chip a chance to reset before attempting to re-read
82                // the SFDP from the SPI device.
83                std::thread::sleep(Duration::from_millis(100));
84            }
85            EntryMode::None => {}
86        }
87
88        let flash = Self::wait_for_device(&*self.spi, self.enter_delay);
89        log::info!("Rescue triggered; clearing trigger condition.");
90        self.params.set_trigger(transport, false)?;
91        let mut flash = flash?;
92        log::info!("Flash = {:?}", flash.sfdp);
93        if let Some(sdfu) = flash.sfdp.as_ref().and_then(|sfdp| sfdp.sdfu.as_ref()) {
94            self.sdfu.replace(sdfu.clone());
95        } else {
96            return Err(RescueError::NotFound(
97                "Could not find SDFU parameters in the SDFP table".into(),
98            )
99            .into());
100        }
101        flash.set_address_mode_auto(&*self.spi)?;
102        self.flash.replace(flash);
103        Ok(())
104    }
105
106    fn set_mode(&self, mode: RescueMode) -> Result<()> {
107        let setting = match mode {
108            // FIXME: Remap "send" modes to their corresponding "recv" mode.
109            // The firmware will stage the recv data, then enter the send mode.
110            RescueMode::Rescue => RescueMode::Rescue,
111            RescueMode::RescueB => RescueMode::RescueB,
112            RescueMode::DeviceId => RescueMode::DeviceId,
113            RescueMode::BootLog => RescueMode::BootLog,
114            RescueMode::BootSvcReq => RescueMode::BootSvcRsp,
115            RescueMode::BootSvcRsp => RescueMode::BootSvcRsp,
116            RescueMode::OwnerBlock => RescueMode::GetOwnerPage0,
117            RescueMode::GetOwnerPage0 => RescueMode::GetOwnerPage0,
118            _ => bail!(RescueError::BadMode(format!(
119                "mode {mode:?} not supported by DFU"
120            ))),
121        };
122
123        log::info!("Mode {mode} is AltSetting {setting}");
124        let setting = u32::from(setting);
125        // This is a proprietary version of the standard USB SetInterface command.
126        self.write_control(
127            DfuRequestType::Vendor.into(),
128            Self::SET_INTERFACE,
129            (setting >> 16) as u16,
130            setting as u16,
131            &[],
132        )?;
133        Ok(())
134    }
135
136    fn set_speed(&self, _speed: u32) -> Result<u32> {
137        log::warn!("set_speed is not implemented for DFU");
138        Ok(0)
139    }
140
141    fn reboot(&self) -> Result<()> {
142        SpiFlash::chip_reset(&*self.spi)?;
143        Ok(())
144    }
145
146    fn send(&self, data: &[u8]) -> Result<()> {
147        let sdfu = self.sdfu.borrow();
148        for chunk in data.chunks(sdfu.dfu_size as usize) {
149            let _ = self.download(chunk)?;
150            let status = loop {
151                let status = self.get_status()?;
152                match status.state() {
153                    DfuState::DnLoadIdle | DfuState::Error => {
154                        break status;
155                    }
156                    _ => {
157                        std::thread::sleep(Duration::from_millis(status.poll_timeout() as u64));
158                    }
159                }
160            };
161            status.status()?;
162        }
163        // Send a zero-length chunk to signal the end.
164        let _ = self.download(&[])?;
165        let status = self.get_status()?;
166        log::warn!("State after DFU download: {}", status.state());
167        Ok(())
168    }
169
170    fn recv(&self) -> Result<Vec<u8>> {
171        let sdfu = self.sdfu.borrow();
172        let mut data = vec![0u8; sdfu.dfu_size as usize];
173        /*
174         * FIXME: what am I supposed to do here?
175         * The spec seems to indicate that I should keep performing `upload` until I get back a
176         * short or zero length packet.
177        let mut offset = 0;
178        loop {
179            log::info!("upload at {offset}");
180            let length = self.upload(&mut data[offset..])?;
181            if length == 0 || length < data.len() - offset {
182                break;
183            }
184            offset += length;
185        }
186        */
187        self.upload(&mut data)?;
188        let status = self.get_status()?;
189        log::warn!("State after DFU upload: {}", status.state());
190        Ok(data)
191    }
192}
193
194impl DfuOperations for SpiDfu {
195    fn get_interface(&self) -> u8 {
196        0
197    }
198
199    // Implement a USB-like control write transaction using OpenTitan's SPI interface.
200    // - Prepare an 8-byte SetupData structure and write it to the Mailbox.
201    //   Note: `flash.program` polls the SPI status BUSY bit for completion.
202    // - Read the Setup status back from the mailbox.  The status will be a
203    //   single 4-byte word of type `RomError`.
204    // - Write the data phase of the control transaction to SPI addresss 0.
205    fn write_control(
206        &self,
207        request_type: u8,
208        request: u8,
209        value: u16,
210        index: u16,
211        data: &[u8],
212    ) -> Result<usize> {
213        let setup = SetupData {
214            request_type,
215            request,
216            value,
217            index,
218            length: data.len().try_into()?,
219        };
220        let flash = self.flash.borrow();
221        let sdfu = self.sdfu.borrow();
222        flash.program(&*self.spi, sdfu.mailbox_address, setup.as_bytes())?;
223
224        let mut result = [0u8; 4];
225        flash.read(&*self.spi, sdfu.mailbox_address, &mut result)?;
226        Result::<(), RomError>::from(RomError(u32::from_le_bytes(result)))?;
227
228        flash.program(&*self.spi, 0, data)?;
229        Ok(data.len())
230    }
231
232    // Implement a USB-like control read transaction using OpenTitan's SPI interface.
233    // - Prepare an 8-byte SetupData structure and write it to the Mailbox.
234    //   Note: `flash.program` polls the SPI status BUSY bit for completion.
235    // - Read the Setup status back from the mailbox.  The status will be a
236    //   single 4-byte word of type `RomError`.
237    // - Read the data phase from SPI address 0.
238    fn read_control(
239        &self,
240        request_type: u8,
241        request: u8,
242        value: u16,
243        index: u16,
244        data: &mut [u8],
245    ) -> Result<usize> {
246        let setup = SetupData {
247            request_type,
248            request,
249            value,
250            index,
251            length: data.len().try_into()?,
252        };
253        let flash = self.flash.borrow();
254        let sdfu = self.sdfu.borrow();
255        flash.program(&*self.spi, sdfu.mailbox_address, setup.as_bytes())?;
256
257        let mut result = [0u8; 4];
258        flash.read(&*self.spi, sdfu.mailbox_address, &mut result)?;
259        Result::<(), RomError>::from(RomError(u32::from_le_bytes(result)))?;
260
261        flash.read(&*self.spi, 0, data)?;
262        Ok(data.len())
263    }
264}