opentitanlib/console/
spi.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 std::cell::{Cell, RefCell};
7use std::collections::VecDeque;
8use std::rc::Rc;
9use std::time::Duration;
10
11use crate::io::console::ConsoleDevice;
12use crate::io::gpio::GpioPin;
13use crate::io::spi::Target;
14use crate::spiflash::flash::SpiFlash;
15
16pub struct SpiConsoleDevice<'a> {
17    spi: &'a dyn Target,
18    flash: SpiFlash,
19    console_next_frame_number: Cell<u32>,
20    rx_buf: RefCell<VecDeque<u8>>,
21    next_read_address: Cell<u32>,
22    device_tx_ready_pin: Option<&'a Rc<dyn GpioPin>>,
23    ignore_frame_num: bool,
24}
25
26impl<'a> SpiConsoleDevice<'a> {
27    const SPI_FRAME_HEADER_SIZE: usize = 12;
28    const SPI_FLASH_READ_BUFFER_SIZE: u32 = 2048;
29    const SPI_MAX_DATA_LENGTH: usize = 2036;
30    const SPI_FRAME_MAGIC_NUMBER: u32 = 0xa5a5beef;
31    const SPI_FLASH_PAYLOAD_BUFFER_SIZE: usize = 256;
32    const SPI_TX_LAST_CHUNK_MAGIC_ADDRESS: u32 = 0x100;
33    const SPI_BOOT_MAGIC_PATTERN: u32 = 0xcafeb002;
34
35    pub fn new(
36        spi: &'a dyn Target,
37        device_tx_ready_pin: Option<&'a Rc<dyn GpioPin>>,
38        ignore_frame_num: bool,
39    ) -> Result<Self> {
40        let flash = SpiFlash {
41            ..Default::default()
42        };
43        Ok(Self {
44            spi,
45            flash,
46            rx_buf: RefCell::new(VecDeque::new()),
47            console_next_frame_number: Cell::new(0),
48            next_read_address: Cell::new(0),
49            device_tx_ready_pin,
50            ignore_frame_num,
51        })
52    }
53
54    pub fn reset_frame_counter(&self) {
55        self.console_next_frame_number.set(0);
56        self.next_read_address.set(0);
57    }
58
59    fn check_device_boot_up(&self, buf: &[u8]) -> Result<usize> {
60        for i in (0..buf.len()).step_by(4) {
61            let pattern: u32 = u32::from_le_bytes(buf[i..i + 4].try_into().unwrap());
62            if pattern != SpiConsoleDevice::SPI_BOOT_MAGIC_PATTERN {
63                return Ok(0);
64            }
65        }
66        // Set busy bit and wait for the device to clear the boot magic.
67        self.flash.program(self.spi, 0, buf)?;
68        self.reset_frame_counter();
69        Ok(0)
70    }
71
72    fn read_from_spi(&self) -> Result<usize> {
73        // Read the SPI console frame header.
74        let read_address = self.next_read_address.get();
75        let mut header = vec![0u8; SpiConsoleDevice::SPI_FRAME_HEADER_SIZE];
76        self.read_data(read_address, &mut header)?;
77
78        let magic_number: u32 = u32::from_le_bytes(header[0..4].try_into().unwrap());
79        let frame_number: u32 = u32::from_le_bytes(header[4..8].try_into().unwrap());
80        let data_len_bytes: usize = u32::from_le_bytes(header[8..12].try_into().unwrap()) as usize;
81        if magic_number != SpiConsoleDevice::SPI_FRAME_MAGIC_NUMBER
82            || (!self.ignore_frame_num && frame_number != self.console_next_frame_number.get())
83            || data_len_bytes > SpiConsoleDevice::SPI_MAX_DATA_LENGTH
84        {
85            if self.device_tx_ready_pin.is_none() {
86                self.check_device_boot_up(&header)?;
87            }
88            // This frame is junk, so we do not read the data
89            return Ok(0);
90        }
91        self.console_next_frame_number.set(frame_number + 1);
92
93        // Read the SPI console frame data.
94        let data_len_bytes_w_pad = (data_len_bytes + 3) & !3;
95        let mut data = vec![0u8; data_len_bytes_w_pad];
96        let data_address: u32 = (read_address
97            + u32::try_from(SpiConsoleDevice::SPI_FRAME_HEADER_SIZE).unwrap())
98            % SpiConsoleDevice::SPI_FLASH_READ_BUFFER_SIZE;
99        self.read_data(data_address, &mut data)?;
100
101        if self.device_tx_ready_pin.is_some() {
102            // When using the TX-indicator pin feature, we always write each SPI frame at the
103            // beginning of the flash buffer, and wait for the host to ready it out before writing
104            // another frame.
105            self.next_read_address.set(0);
106        } else {
107            let next_read_address: u32 = (read_address
108                + u32::try_from(SpiConsoleDevice::SPI_FRAME_HEADER_SIZE + data_len_bytes_w_pad)
109                    .unwrap())
110                % SpiConsoleDevice::SPI_FLASH_READ_BUFFER_SIZE;
111            self.next_read_address.set(next_read_address);
112        }
113
114        // Copy data to the internal data queue.
115        self.rx_buf.borrow_mut().extend(&data[..data_len_bytes]);
116        Ok(data_len_bytes)
117    }
118
119    fn read_data(&self, address: u32, buf: &mut [u8]) -> Result<&Self> {
120        let buf_len: usize = buf.len();
121        let space_to_end_of_buffer: usize =
122            usize::try_from(SpiConsoleDevice::SPI_FLASH_READ_BUFFER_SIZE - address).unwrap();
123        let first_part_size: usize = if buf_len > space_to_end_of_buffer {
124            space_to_end_of_buffer
125        } else {
126            buf_len
127        };
128        self.flash
129            .read(self.spi, address, &mut buf[0..first_part_size])?;
130        // Handle wrap-around
131        if first_part_size < buf_len {
132            self.flash
133                .read(self.spi, 0, &mut buf[first_part_size..buf_len])?;
134        }
135        Ok(self)
136    }
137}
138
139impl<'a> ConsoleDevice for SpiConsoleDevice<'a> {
140    fn poll_read(
141        &self,
142        cx: &mut std::task::Context<'_>,
143        buf: &mut [u8],
144    ) -> std::task::Poll<Result<usize>> {
145        // Have a non-zero polling interval to avoid busy polling.
146        // Each read would require some USB transaction, so using USB time slice (1ms) as the
147        // polling interval is deemed as a good value.
148        const POLL_INTERVAL: Duration = Duration::from_millis(1);
149
150        if self.rx_buf.borrow().is_empty() {
151            if let Some(ready_pin) = self.device_tx_ready_pin {
152                // If we are gated by the TX-ready pin, only perform the SPI console read if
153                // the ready pin is high.
154                if !ready_pin.read()? {
155                    return crate::util::runtime::poll_later(cx, POLL_INTERVAL);
156                }
157            }
158
159            if self.read_from_spi()? == 0 {
160                return crate::util::runtime::poll_later(cx, POLL_INTERVAL);
161            }
162        }
163
164        // Copy from the internal data queue to the output buffer.
165        let mut i: usize = 0;
166        while !self.rx_buf.borrow().is_empty() && i < buf.len() {
167            buf[i] = self.rx_buf.borrow_mut().pop_front().unwrap();
168            i += 1;
169        }
170
171        std::task::Poll::Ready(Ok(i))
172    }
173
174    fn write(&self, buf: &[u8]) -> Result<()> {
175        let buf_len: usize = buf.len();
176        let mut written_data_len: usize = 0;
177        while written_data_len < buf_len {
178            let mut write_address = SpiConsoleDevice::SPI_TX_LAST_CHUNK_MAGIC_ADDRESS;
179            let mut data_len: usize = buf_len - written_data_len;
180
181            if data_len > SpiConsoleDevice::SPI_FLASH_PAYLOAD_BUFFER_SIZE {
182                data_len = SpiConsoleDevice::SPI_FLASH_PAYLOAD_BUFFER_SIZE;
183                write_address = 0;
184            }
185
186            self.flash.program(
187                self.spi,
188                write_address,
189                &buf[written_data_len..written_data_len + data_len],
190            )?;
191            written_data_len += data_len;
192        }
193
194        Ok(())
195    }
196}