opentitanlib/transport/hyperdebug/
i2c.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, ensure};
6use std::cell::Cell;
7use std::cmp;
8use std::rc::Rc;
9use std::time::Duration;
10use zerocopy::{FromBytes, Immutable, IntoBytes};
11
12use crate::io::gpio::GpioPin;
13use crate::io::i2c::{self, Bus, DeviceStatus, DeviceTransfer, I2cError, ReadStatus, Transfer};
14use crate::transport::hyperdebug::{BulkInterface, Inner};
15use crate::transport::{TransportError, TransportInterfaceType};
16
17pub struct HyperdebugI2cBus {
18    inner: Rc<Inner>,
19    interface: BulkInterface,
20    cmsis_encapsulation: bool,
21    supports_i2c_device: bool,
22    bus_idx: u8,
23    mode: Cell<Mode>,
24    max_write_size: usize,
25    max_read_size: usize,
26    default_addr: Cell<Option<u8>>,
27}
28
29#[derive(Copy, Clone, Debug, Eq, PartialEq)]
30pub enum Mode {
31    Host,
32    Device,
33}
34
35const USB_MAX_SIZE: usize = 64;
36
37/// Wire format of USB packet to request a short I2C transaction
38/// (receiving at most 127 bytes).
39#[derive(Immutable, IntoBytes, FromBytes, Debug)]
40#[allow(dead_code)] // Fields not explicitly read anywhere
41#[repr(C, packed)]
42struct CmdTransferShort {
43    encapsulation_header: u8,
44    port: u8,
45    addr: u8,
46    write_count: u8,
47    read_count: u8,
48    data: [u8; USB_MAX_SIZE - 4],
49}
50
51/// Wire format of USB packet to request a long I2C transaction
52/// (receiving up to 32767 bytes).
53#[derive(Immutable, IntoBytes, FromBytes, Debug)]
54#[allow(dead_code)] // Fields not explicitly read anywhere
55#[repr(C, packed)]
56struct CmdTransferLong {
57    encapsulation_header: u8,
58    port: u8,
59    addr: u8,
60    write_count: u8,
61    read_count: u8,
62    read_count1: u8,
63    flags: u8,
64    data: [u8; USB_MAX_SIZE - 6],
65}
66
67/// Wire format of USB packet containing I2C transaction response.
68#[derive(Immutable, IntoBytes, FromBytes, Debug)]
69#[allow(dead_code)] // Reserved field not read anywhere
70#[repr(C, packed)]
71struct RspTransfer {
72    encapsulation_header: u8,
73    status_code: u16,
74    reserved: u16,
75    data: [u8; USB_MAX_SIZE],
76}
77impl RspTransfer {
78    fn new() -> Self {
79        Self {
80            encapsulation_header: 0,
81            status_code: 0,
82            reserved: 0,
83            data: [0; USB_MAX_SIZE],
84        }
85    }
86}
87
88#[derive(Immutable, IntoBytes, FromBytes, Debug)]
89#[allow(dead_code)] // Fields not explicitly read anywhere
90#[repr(C, packed)]
91struct CmdGetDeviceStatus {
92    encapsulation_header: u8,
93    port: u8,
94    device_cmd: u8,
95    timeout_ms: u16,
96}
97
98// Values for use in `CmdGetDeviceStatus.device_cmd`.
99const I2C_DEVICE_CMD_GET_DEVICE_STATUS: u8 = 0x00;
100const I2C_DEVICE_CMD_PREPARE_READ_DATA: u8 = 0x01;
101
102// Bits for use in upper half of `CmdGetDeviceStatus.port`.
103const I2C_DEVICE_FLAG_STICKY: u8 = 0x80;
104
105#[derive(Immutable, IntoBytes, FromBytes, Debug)]
106#[repr(C, packed)]
107struct RspGetDeviceStatus {
108    encapsulation_header: u8,
109    struct_size: u16,
110    read_status: u8,
111    blocked_read_addr: u8,
112    transcript_size: u16,
113    data: [u8; USB_MAX_SIZE],
114}
115impl RspGetDeviceStatus {
116    fn new() -> Self {
117        Self {
118            encapsulation_header: 0,
119            struct_size: 0,
120            read_status: 0,
121            blocked_read_addr: 0,
122            transcript_size: 0,
123            data: [0u8; USB_MAX_SIZE],
124        }
125    }
126}
127
128#[derive(Immutable, IntoBytes, FromBytes, Debug)]
129#[allow(dead_code)] // Fields not explicitly read anywhere
130#[repr(C, packed)]
131struct CmdPrepareReadData {
132    encapsulation_header: u8,
133    port: u8,
134    device_cmd: u8,
135    data_size: u16,
136    data: [u8; USB_MAX_SIZE - 4],
137}
138
139impl HyperdebugI2cBus {
140    /// If `cmsis_encapsulation` is set to true, then every request on the USB bus will have an
141    /// extra byte "in front" containing this value.  Correspondingly, every response is expected
142    /// to carry this same value as the first byte.
143    const CMSIS_DAP_CUSTOM_COMMAND_I2C: u8 = 0x81;
144
145    /// Alternative command only available when protocol encapsulated in CMSIS.
146    const CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE: u8 = 0x82;
147
148    pub fn open(
149        inner: &Rc<Inner>,
150        i2c_interface: &BulkInterface,
151        cmsis_encapsulation: bool,
152        supports_i2c_device: bool,
153        idx: u8,
154        mode: Mode,
155    ) -> Result<Self> {
156        ensure!(
157            idx < 16,
158            TransportError::InvalidInstance(TransportInterfaceType::I2c, idx.to_string())
159        );
160        let mut usb_handle = inner.usb_device.borrow_mut();
161
162        // Exclusively claim I2C interface, preparing for bulk transfers.
163        usb_handle.claim_interface(i2c_interface.interface)?;
164
165        Ok(Self {
166            inner: Rc::clone(inner),
167            interface: *i2c_interface,
168            cmsis_encapsulation,
169            supports_i2c_device,
170            bus_idx: idx,
171            mode: Cell::new(mode),
172            max_read_size: 0x8000,
173            max_write_size: 0x1000,
174            default_addr: Cell::new(None),
175        })
176    }
177
178    /// Transmit data for a single I2C operation, using one or more USB packets.
179    fn transmit_then_receive(
180        &self,
181        addr: u8,
182        wbuf: &[u8],
183        rbuf: &mut [u8],
184        gsc_ready: bool,
185    ) -> Result<()> {
186        ensure!(
187            rbuf.len() < self.max_read_size,
188            I2cError::InvalidDataLength(rbuf.len())
189        );
190        ensure!(
191            wbuf.len() < self.max_write_size,
192            I2cError::InvalidDataLength(wbuf.len())
193        );
194        let encapsulation_header_size = if self.cmsis_encapsulation { 1 } else { 0 };
195        let mut index = if rbuf.len() < 128 && !gsc_ready {
196            // Short format header
197            let mut req = CmdTransferShort {
198                encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C,
199                port: self.bus_idx | (((wbuf.len() & 0x0F00) >> 4) as u8),
200                addr,
201                write_count: (wbuf.len() & 0x00FF) as u8,
202                read_count: rbuf.len() as u8,
203                data: [0; USB_MAX_SIZE - 4],
204            };
205            let databytes = cmp::min(USB_MAX_SIZE - 4 - encapsulation_header_size, wbuf.len());
206            req.data[..databytes].clone_from_slice(&wbuf[..databytes]);
207            self.usb_write_bulk(&req.as_bytes()[1 - encapsulation_header_size..1 + 4 + databytes])?;
208            databytes
209        } else {
210            // Long format header (wider read_count field and additional flags)
211            let mut req = CmdTransferLong {
212                encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C,
213                port: self.bus_idx | (((wbuf.len() & 0x0F00) >> 4) as u8),
214                addr,
215                write_count: (wbuf.len() & 0x00FF) as u8,
216                read_count: (rbuf.len() & 0x007F | 0x0080) as u8,
217                read_count1: (rbuf.len() >> 7) as u8,
218                flags: if gsc_ready { 0x80 } else { 0x00 },
219                data: [0; USB_MAX_SIZE - 6],
220            };
221            let databytes = cmp::min(USB_MAX_SIZE - 6 - encapsulation_header_size, wbuf.len());
222            req.data[..databytes].clone_from_slice(&wbuf[..databytes]);
223            self.usb_write_bulk(&req.as_bytes()[1 - encapsulation_header_size..1 + 6 + databytes])?;
224            databytes
225        };
226
227        // Transmit any more data without further header.
228        while index < wbuf.len() {
229            let databytes = cmp::min(USB_MAX_SIZE, wbuf.len() - index);
230            self.usb_write_bulk(&wbuf[index..index + databytes])?;
231            index += databytes;
232        }
233
234        let mut resp = RspTransfer::new();
235        let mut bytecount = 0;
236        while bytecount < 4 + encapsulation_header_size {
237            let read_count = self.usb_read_bulk(
238                &mut resp.as_mut_bytes()[1 - encapsulation_header_size + bytecount..][..64],
239            )?;
240            ensure!(
241                read_count > 0,
242                TransportError::CommunicationError("Truncated I2C response".to_string())
243            );
244            bytecount += read_count;
245        }
246        if encapsulation_header_size == 1 {
247            ensure!(
248                resp.encapsulation_header == Self::CMSIS_DAP_CUSTOM_COMMAND_I2C,
249                TransportError::CommunicationError(
250                    "Unrecognized CMSIS-DAP response to I2C request".to_string()
251                )
252            );
253        }
254        match resp.status_code {
255            0 => (),
256            1 => bail!(I2cError::Timeout),
257            2 => bail!(I2cError::Busy),
258            n => bail!(TransportError::CommunicationError(format!(
259                "I2C error: {}",
260                n
261            ))),
262        }
263        let databytes = bytecount - 4 - encapsulation_header_size;
264        rbuf[..databytes].clone_from_slice(&resp.data[..databytes]);
265        let mut index = databytes;
266        while index < rbuf.len() {
267            let databytes = self.usb_read_bulk(&mut rbuf[index..])?;
268            ensure!(
269                databytes > 0,
270                TransportError::CommunicationError(
271                    "Unrecognized reponse to I2C request".to_string()
272                )
273            );
274            index += databytes;
275        }
276        Ok(())
277    }
278
279    /// Send one USB packet.
280    fn usb_write_bulk(&self, buf: &[u8]) -> Result<()> {
281        self.inner
282            .usb_device
283            .borrow()
284            .write_bulk(self.interface.out_endpoint, buf)?;
285        Ok(())
286    }
287
288    /// Receive one USB packet.
289    fn usb_read_bulk(&self, buf: &mut [u8]) -> Result<usize> {
290        self.inner
291            .usb_device
292            .borrow()
293            .read_bulk(self.interface.in_endpoint, buf)
294    }
295
296    /// Receive one USB packet with non-default timeout.
297    fn usb_read_bulk_timeout(&self, buf: &mut [u8], timeout: Duration) -> Result<usize> {
298        self.inner
299            .usb_device
300            .borrow()
301            .read_bulk_timeout(self.interface.in_endpoint, buf, timeout)
302    }
303}
304
305impl Bus for HyperdebugI2cBus {
306    fn set_mode(&self, mode: i2c::Mode) -> Result<()> {
307        match mode {
308            // Put I2C debugger into host mode (this is the default).
309            i2c::Mode::Host => {
310                self.inner
311                    .cmd_no_output(&format!("i2c set mode {} host", &self.bus_idx))?;
312                self.mode.set(Mode::Host);
313            }
314            // Put I2C debugger into device mode.
315            i2c::Mode::Device(addr) => {
316                ensure!(
317                    self.supports_i2c_device,
318                    TransportError::UnsupportedOperation,
319                );
320                self.inner
321                    .cmd_no_output(&format!("i2c set mode {} device {}", &self.bus_idx, addr))?;
322                self.mode.set(Mode::Device);
323            }
324        }
325        Ok(())
326    }
327
328    /// Gets the maximum allowed speed of the I2C bus.
329    fn get_max_speed(&self) -> Result<u32> {
330        let mut buf = String::new();
331        let captures = self.inner.cmd_one_line_output_match(
332            &format!("i2c info {}", &self.bus_idx),
333            &super::SPI_REGEX,
334            &mut buf,
335        )?;
336        Ok(captures.get(3).unwrap().as_str().parse().unwrap())
337    }
338
339    /// Sets the maximum allowed speed of the I2C bus, typical values are 100_000, 400_000 or
340    /// 1_000_000.
341    fn set_max_speed(&self, max_speed: u32) -> Result<()> {
342        self.inner
343            .cmd_no_output(&format!("i2c set speed {} {}", &self.bus_idx, max_speed))
344    }
345
346    fn set_pins(
347        &self,
348        serial_clock: Option<&Rc<dyn GpioPin>>,
349        serial_data: Option<&Rc<dyn GpioPin>>,
350        gsc_ready: Option<&Rc<dyn GpioPin>>,
351    ) -> Result<()> {
352        if serial_clock.is_some() || serial_data.is_some() {
353            bail!(I2cError::InvalidPin);
354        }
355        if let Some(pin) = gsc_ready {
356            self.inner.cmd_no_output(&format!(
357                "i2c set ready {} {}",
358                &self.bus_idx,
359                pin.get_internal_pin_name().ok_or(I2cError::InvalidPin)?
360            ))?;
361        }
362        Ok(())
363    }
364
365    fn set_default_address(&self, addr: u8) -> Result<()> {
366        self.default_addr.set(Some(addr));
367        Ok(())
368    }
369
370    fn run_transaction(&self, addr: Option<u8>, mut transaction: &mut [Transfer]) -> Result<()> {
371        let addr = addr
372            .or(self.default_addr.get())
373            .ok_or(I2cError::MissingAddress)?;
374        while !transaction.is_empty() {
375            match transaction {
376                [
377                    Transfer::Write(wbuf),
378                    Transfer::GscReady,
379                    Transfer::Read(rbuf),
380                    ..,
381                ] => {
382                    // Hyperdebug can do I2C write followed by I2C read as a single USB
383                    // request/reply.  Take advantage of that by detecting pairs of
384                    // Transfer::Write followed by Transfer::Read.
385                    ensure!(
386                        wbuf.len() <= self.max_write_size,
387                        I2cError::InvalidDataLength(wbuf.len())
388                    );
389                    ensure!(
390                        rbuf.len() <= self.max_read_size,
391                        I2cError::InvalidDataLength(rbuf.len())
392                    );
393                    self.transmit_then_receive(addr, wbuf, rbuf, true)?;
394                    // Skip three steps ahead, as three items were processed.
395                    transaction = &mut transaction[3..];
396                }
397                [Transfer::Write(wbuf), Transfer::Read(rbuf), ..] => {
398                    // Hyperdebug can do I2C write followed by I2C read as a single USB
399                    // request/reply.  Take advantage of that by detecting pairs of
400                    // Transfer::Write followed by Transfer::Read.
401                    ensure!(
402                        wbuf.len() <= self.max_write_size,
403                        I2cError::InvalidDataLength(wbuf.len())
404                    );
405                    ensure!(
406                        rbuf.len() <= self.max_read_size,
407                        I2cError::InvalidDataLength(rbuf.len())
408                    );
409                    self.transmit_then_receive(addr, wbuf, rbuf, false)?;
410                    // Skip two steps ahead, as two items were processed.
411                    transaction = &mut transaction[2..];
412                }
413                [Transfer::Write(wbuf), Transfer::GscReady, ..] => {
414                    ensure!(
415                        wbuf.len() <= self.max_write_size,
416                        I2cError::InvalidDataLength(wbuf.len())
417                    );
418                    self.transmit_then_receive(addr, wbuf, &mut [], true)?;
419                    // Skip two steps ahead, as two items were processed.
420                    transaction = &mut transaction[2..];
421                }
422                [Transfer::Write(wbuf), ..] => {
423                    ensure!(
424                        wbuf.len() <= self.max_write_size,
425                        I2cError::InvalidDataLength(wbuf.len())
426                    );
427                    self.transmit_then_receive(addr, wbuf, &mut [], false)?;
428                    transaction = &mut transaction[1..];
429                }
430                [Transfer::Read(rbuf), ..] => {
431                    ensure!(
432                        rbuf.len() <= self.max_read_size,
433                        I2cError::InvalidDataLength(rbuf.len())
434                    );
435                    self.transmit_then_receive(addr, &[], rbuf, false)?;
436                    transaction = &mut transaction[1..];
437                }
438                [] => (),
439                _ => bail!(TransportError::UnsupportedOperation),
440            }
441        }
442        Ok(())
443    }
444
445    fn get_device_status(&self, timeout: Duration) -> Result<DeviceStatus> {
446        ensure!(
447            self.cmsis_encapsulation && self.supports_i2c_device,
448            TransportError::UnsupportedOperation
449        );
450        ensure!(self.mode.get() == Mode::Device, I2cError::NotInDeviceMode);
451        let timeout_ms = timeout.as_millis();
452        let req = CmdGetDeviceStatus {
453            encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
454            port: self.bus_idx,
455            device_cmd: I2C_DEVICE_CMD_GET_DEVICE_STATUS,
456            timeout_ms: if timeout_ms > 65535 {
457                65535
458            } else {
459                timeout_ms as u16
460            },
461        };
462        self.usb_write_bulk(req.as_bytes())?;
463
464        let mut resp = RspGetDeviceStatus::new();
465        let mut bytecount = 0;
466        while bytecount < 7 {
467            let read_count = self.usb_read_bulk_timeout(
468                &mut resp.as_mut_bytes()[bytecount..][..64],
469                Duration::from_millis(req.timeout_ms as u64 + 500),
470            )?;
471            ensure!(
472                read_count > 0,
473                TransportError::CommunicationError("Truncated I2C response".to_string())
474            );
475            bytecount += read_count;
476        }
477        ensure!(
478            resp.encapsulation_header == Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
479            TransportError::CommunicationError(
480                "Unrecognized CMSIS-DAP response to I2C request".to_string()
481            )
482        );
483        let skip_bytes = resp.struct_size - 6;
484
485        let mut databytes: Vec<u8> = Vec::new();
486        databytes.extend_from_slice(&resp.data[..bytecount - 7]);
487
488        while databytes.len() < (skip_bytes + resp.transcript_size) as usize {
489            let original_length = databytes.len();
490            databytes.resize(original_length + 64, 0u8);
491            let c = self.usb_read_bulk(&mut databytes[original_length..])?;
492            databytes.resize(original_length + c, 0u8);
493        }
494
495        let mut transfers = Vec::new();
496        let mut idx = skip_bytes as usize;
497        while idx < databytes.len() {
498            let addr = databytes[idx] >> 1;
499            let is_read = (databytes[idx] & 0x01) != 0;
500            let timeout = (databytes[idx + 1] & 0x01) != 0;
501            let transfer_len = databytes[idx + 2] as usize + ((databytes[idx + 3] as usize) << 8);
502            idx += 4;
503            if is_read {
504                // Read transfer
505                transfers.push(DeviceTransfer::Read {
506                    addr,
507                    timeout,
508                    len: transfer_len,
509                });
510            } else {
511                // Write transfer
512                transfers.push(DeviceTransfer::Write {
513                    addr,
514                    data: databytes[idx..idx + transfer_len].to_vec(),
515                });
516                idx += (transfer_len + 3) & !3;
517            }
518        }
519
520        let read_status = match resp.read_status {
521            0 => ReadStatus::Idle,
522            1 => ReadStatus::DataPrepared,
523            2 => ReadStatus::WaitingForData(resp.blocked_read_addr >> 1),
524            _ => bail!(TransportError::CommunicationError(
525                "Unrecognized I2C read status".to_string()
526            )),
527        };
528
529        Ok(DeviceStatus {
530            transfers,
531            read_status,
532        })
533    }
534
535    fn prepare_read_data(&self, data: &[u8], sticky: bool) -> Result<()> {
536        ensure!(
537            self.cmsis_encapsulation && self.supports_i2c_device,
538            TransportError::UnsupportedOperation
539        );
540        ensure!(self.mode.get() == Mode::Device, I2cError::NotInDeviceMode);
541        if data.len() > 1024 {
542            bail!(TransportError::CommunicationError(
543                "Data exceeds maximum length".to_string()
544            ))
545        }
546        let flags = if sticky { I2C_DEVICE_FLAG_STICKY } else { 0 };
547        let mut req = CmdPrepareReadData {
548            encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
549            port: self.bus_idx | flags,
550            device_cmd: I2C_DEVICE_CMD_PREPARE_READ_DATA,
551            data_size: data.len() as u16,
552            data: [0; USB_MAX_SIZE - 4],
553        };
554        // Compute how many data bytes fit in the first USB packet after the header.
555        let mut index = std::cmp::min(64 - 5, data.len());
556        req.data[..index].clone_from_slice(&data[..index]);
557        self.usb_write_bulk(&req.as_bytes()[..5 + index])?;
558
559        while index < data.len() {
560            let packet_len = std::cmp::min(64, data.len() - index);
561            self.usb_write_bulk(&data[index..index + packet_len])?;
562            index += packet_len;
563        }
564
565        let mut resp = 0u8;
566        let c = self.usb_read_bulk(std::slice::from_mut(&mut resp))?;
567        ensure!(
568            c == 1 && resp == Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
569            TransportError::CommunicationError(
570                "Unrecognized CMSIS-DAP response to I2C request".to_string()
571            )
572        );
573        Ok(())
574    }
575}