Skip to content

Commit 67ec0fd

Browse files
ziuziakowskajwnrt
authored andcommitted
[sw,opentitanlib] QEMU I2C transport implementation
This implements a I2C transport for QEMU. The transport uses a PTY to communicate with a `ot-i2c_host_proxy` device to issue transfers to devices on the internal QEMU I2C buses. See `docs/opentitan/i2c_host_proxy.md` in QEMU for more imformation on the protocol. Signed-off-by: Alice Ziuziakowska <[email protected]>
1 parent 3af5763 commit 67ec0fd

File tree

4 files changed

+177
-0
lines changed

4 files changed

+177
-0
lines changed

rules/opentitan/qemu.bzl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,14 @@ def _test_dispatch(ctx, exec_env, firmware):
434434
# Create a chardev for the SPI device:
435435
qemu_args += ["-chardev", "pty,id=spidev"]
436436

437+
# Create a chardev and proxy device for each I2C bus:
438+
qemu_args += ["-chardev", "pty,id=i2c0"]
439+
qemu_args += ["-chardev", "pty,id=i2c1"]
440+
qemu_args += ["-chardev", "pty,id=i2c2"]
441+
qemu_args += ["-device", "ot-i2c_host_proxy,bus=ot-i2c0,chardev=i2c0"]
442+
qemu_args += ["-device", "ot-i2c_host_proxy,bus=ot-i2c1,chardev=i2c1"]
443+
qemu_args += ["-device", "ot-i2c_host_proxy,bus=ot-i2c2,chardev=i2c2"]
444+
437445
# Scale the Ibex clock by an `icount` factor.
438446
qemu_args += ["-icount", "shift={}".format(param["icount"])]
439447

sw/host/opentitanlib/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ rust_library(
215215
"src/transport/proxy/mod.rs",
216216
"src/transport/proxy/spi.rs",
217217
"src/transport/proxy/uart.rs",
218+
"src/transport/qemu/i2c.rs",
218219
"src/transport/qemu/mod.rs",
219220
"src/transport/qemu/monitor.rs",
220221
"src/transport/qemu/reset.rs",
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
5+
use std::cell::{Cell, RefCell};
6+
use std::io::{Read, Write};
7+
use std::path::Path;
8+
9+
use anyhow::Context;
10+
use anyhow::bail;
11+
use serialport::TTYPort;
12+
13+
use crate::io::i2c::I2cError;
14+
use crate::io::i2c::{Bus, Transfer};
15+
use crate::transport::TransportError;
16+
17+
pub struct QemuI2c {
18+
pty: RefCell<TTYPort>,
19+
default_addr: Cell<Option<u8>>,
20+
}
21+
22+
impl QemuI2c {
23+
pub fn new<P: AsRef<Path>>(pty: P) -> anyhow::Result<QemuI2c> {
24+
let pty = pty.as_ref().to_str().context("path not UTF-8")?;
25+
let pty = serialport::new(pty, 115200)
26+
.timeout(std::time::Duration::from_secs(1))
27+
.open_native()
28+
.context("failed to open I2C PTY")?;
29+
let pty = RefCell::new(pty);
30+
31+
Ok(QemuI2c {
32+
pty,
33+
default_addr: Cell::new(None),
34+
})
35+
}
36+
}
37+
38+
fn check_response<R: Read>(pty: &mut R, s: &'static str) -> anyhow::Result<()> {
39+
let mut byte_buf = [0u8; 1];
40+
pty.read_exact(&mut byte_buf)?;
41+
match byte_buf[0] {
42+
b'.' => Ok(()),
43+
b'!' => bail!(s),
44+
b'x' => bail!("Malformed command"),
45+
_ => unreachable!(),
46+
}
47+
}
48+
49+
impl Bus for QemuI2c {
50+
fn get_max_speed(&self) -> anyhow::Result<u32> {
51+
Err(TransportError::UnsupportedOperation.into())
52+
}
53+
54+
fn set_max_speed(&self, _max_speed: u32) -> anyhow::Result<()> {
55+
Err(TransportError::UnsupportedOperation.into())
56+
}
57+
58+
fn set_default_address(&self, addr: u8) -> anyhow::Result<()> {
59+
self.default_addr.set(Some(addr));
60+
Ok(())
61+
}
62+
63+
fn run_transaction(
64+
&self,
65+
addr: Option<u8>,
66+
transaction: &mut [Transfer],
67+
) -> anyhow::Result<()> {
68+
let target_addr = addr
69+
.or(self.default_addr.get())
70+
.ok_or(I2cError::MissingAddress)?;
71+
72+
let mut pty = self.pty.borrow_mut();
73+
74+
// Write and check protocol version
75+
let i2c_protocol_version = 1_u8;
76+
77+
write!(pty, "i")?;
78+
pty.write_all(&[i2c_protocol_version])?;
79+
check_response(&mut *pty, "Protocol version mismatch")?;
80+
81+
for transfer in transaction.iter_mut() {
82+
// Start/repeated start condition
83+
write!(pty, "S")?;
84+
85+
match transfer {
86+
Transfer::Write(buf) => {
87+
// Write I2C address + write bit (0)
88+
let start_byte: u8 = target_addr << 1;
89+
pty.write_all(std::slice::from_ref(&start_byte))?;
90+
check_response(&mut *pty, "Write transfer NACKed")?;
91+
92+
// Write data byte and check for no NACKs
93+
for byte in buf.iter() {
94+
write!(pty, "W")?;
95+
pty.write_all(std::slice::from_ref(byte))?;
96+
check_response(&mut *pty, "Written byte NACKed")?;
97+
}
98+
}
99+
100+
Transfer::Read(buf) => {
101+
// Write I2C address + read bit (1)
102+
let start_byte: u8 = (target_addr << 1) | 1;
103+
pty.write_all(std::slice::from_ref(&start_byte))?;
104+
check_response(&mut *pty, "Read transfer NACKed")?;
105+
106+
// Issue read and read data byte
107+
for byte in buf.iter_mut() {
108+
write!(pty, "R")?;
109+
pty.read_exact(std::slice::from_mut(byte))?;
110+
}
111+
}
112+
}
113+
}
114+
// Stop condition
115+
write!(pty, "P")?;
116+
117+
Ok(())
118+
}
119+
}

sw/host/opentitanlib/src/transport/qemu/mod.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
33
// SPDX-License-Identifier: Apache-2.0
44

5+
pub mod i2c;
56
pub mod monitor;
67
pub mod reset;
78
pub mod spi;
@@ -16,8 +17,10 @@ use anyhow::{Context, bail};
1617
use crate::backend::qemu::QemuOpts;
1718
use crate::io::gpio::{GpioError, GpioPin};
1819
use crate::io::uart::Uart;
20+
use crate::transport::Bus;
1921
use crate::transport::Target;
2022
use crate::transport::common::uart::SerialPortUart;
23+
use crate::transport::qemu::i2c::QemuI2c;
2124
use crate::transport::qemu::monitor::{Chardev, ChardevKind, Monitor};
2225
use crate::transport::qemu::reset::QemuReset;
2326
use crate::transport::qemu::spi::QemuSpi;
@@ -34,6 +37,9 @@ const QEMU_RESET_PIN_IDX: u8 = u8::MAX;
3437
/// this number.
3538
const CONSOLE_BAUDRATE: u32 = 115200;
3639

40+
/// Number of I2Cs.
41+
const NUM_I2CS: usize = 3;
42+
3743
/// Represents a connection to a running QEMU emulation.
3844
pub struct Qemu {
3945
/// Connection to the QEMU monitor which can control the emulator.
@@ -48,6 +54,9 @@ pub struct Qemu {
4854
/// SPI device.
4955
spi: Option<Rc<dyn Target>>,
5056

57+
/// I2C devices.
58+
i2cs: [Option<Rc<dyn Bus>>; NUM_I2CS],
59+
5160
/// QEMU log modelled as a UART.
5261
log: Option<Rc<dyn Uart>>,
5362
}
@@ -122,6 +131,26 @@ impl Qemu {
122131
}
123132
};
124133

134+
// Try connecting to each of the I2C buses.
135+
let mut i2cs = [const { None }; NUM_I2CS];
136+
for (idx, i2cn) in i2cs.iter_mut().enumerate() {
137+
let chardev_id = format!("i2c{}", idx);
138+
*i2cn = match find_chardev(&chardevs, &chardev_id) {
139+
Some(ChardevKind::Pty { path }) => {
140+
let i2c = QemuI2c::new(path).context("failed to connect to QEMU I2C PTY")?;
141+
let i2c: Rc<dyn Bus> = Rc::new(i2c);
142+
Some(i2c)
143+
}
144+
_ => {
145+
log::info!(
146+
"could not find pty chardev with id={}, skipping this I2C bus",
147+
&chardev_id
148+
);
149+
None
150+
}
151+
};
152+
}
153+
125154
// Resetting is done over the monitor, but we model it like a pin to enable strapping it.
126155
let reset = QemuReset::new(Rc::clone(&monitor));
127156
let reset = Rc::new(reset);
@@ -132,6 +161,7 @@ impl Qemu {
132161
console,
133162
log,
134163
spi,
164+
i2cs,
135165
})
136166
}
137167
}
@@ -170,6 +200,25 @@ impl Transport for Qemu {
170200
}
171201
}
172202

203+
fn i2c(&self, instance: &str) -> anyhow::Result<Rc<dyn Bus>> {
204+
match instance {
205+
"0" => Ok(Rc::clone(
206+
self.i2cs[0].as_ref().context("QEMU I2C 0 not connected")?,
207+
)),
208+
"1" => Ok(Rc::clone(
209+
self.i2cs[1].as_ref().context("QEMU I2C 1 not connected")?,
210+
)),
211+
"2" => Ok(Rc::clone(
212+
self.i2cs[2].as_ref().context("QEMU I2C 2 not connected")?,
213+
)),
214+
_ => Err(TransportError::InvalidInstance(
215+
TransportInterfaceType::I2c,
216+
instance.to_string(),
217+
)
218+
.into()),
219+
}
220+
}
221+
173222
fn gpio_pin(&self, instance: &str) -> anyhow::Result<Rc<dyn GpioPin>> {
174223
let pin = u8::from_str(instance).with_context(|| format!("can't convert {instance:?}"))?;
175224

0 commit comments

Comments
 (0)