Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions rules/opentitan/qemu.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ def _test_dispatch(ctx, exec_env, firmware):
qemu_args += ["-chardev", "pty,id=gpio"]
qemu_args += ["-global", "ot-gpio-eg.chardev=gpio"]

# Create a chardev for the USBDEV control:
qemu_args += ["-chardev", "pty,id=usbdev-cmd"]
qemu_args += ["-chardev", "pty,id=usbdev-host"]

# Scale the Ibex clock by an `icount` factor.
qemu_args += ["-icount", "shift={}".format(param["icount"])]

Expand Down
26 changes: 26 additions & 0 deletions sw/device/tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4603,6 +4603,18 @@ opentitan_test(
""",
test_harness = "//sw/host/tests/chip/usb:usb_harness",
),
qemu = qemu_params(
globals = {
# We do not need a full host for this test so just drive VBUS
# in override mode.
"ot-usbdev.vbus-override": "true",
},
test_cmd = """
--vbus-sense-en=VBUS_SENSE_EN
--no-wait-for-usb-device
""",
test_harness = "//sw/host/tests/chip/usb:usb_harness",
),
silicon = silicon_params(
test_cmd = """
--bootstrap="{firmware}"
Expand Down Expand Up @@ -4782,6 +4794,14 @@ opentitan_test(
""",
test_harness = "//sw/host/tests/chip/usb:usb_harness",
),
qemu = qemu_params(
# Require the PHY_PINS_SENSE register.
tags = ["broken"],
test_cmd = """
--no-wait-for-usb-device
""",
test_harness = "//sw/host/tests/chip/usb:usb_harness",
),
silicon = silicon_params(
test_cmd = """
--bootstrap="{firmware}"
Expand Down Expand Up @@ -4818,6 +4838,12 @@ opentitan_test(
""",
test_harness = "//sw/host/tests/chip/usb:usb_harness",
),
qemu = qemu_params(
test_cmd = """
--no-wait-for-usb-device
""",
test_harness = "//sw/host/tests/chip/usb:usb_harness",
),
silicon = silicon_params(
test_cmd = """
--bootstrap="{firmware}"
Expand Down
1 change: 1 addition & 0 deletions sw/host/opentitanlib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ rust_library(
"src/transport/qemu/reset.rs",
"src/transport/qemu/spi.rs",
"src/transport/qemu/uart.rs",
"src/transport/qemu/usbdev.rs",
"src/transport/ti50emulator/emu.rs",
"src/transport/ti50emulator/gpio.rs",
"src/transport/ti50emulator/i2c.rs",
Expand Down
7 changes: 7 additions & 0 deletions sw/host/opentitanlib/src/app/config/opentitan_qemu.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
"alias_of": "255",
"level": true,
},
// VBUS control currently has to go through a special QEMU chardev because
// QEMU does not yet implement a real pinmux.
{
"name": "VBUS_SENSE_EN",
"alias_of": "254",
"level": false,
},
// NOTE: these pins are actually going directly to GPIOs, not through pins.
// This is okay for straps which have a fixed GPIO, but should not be used
// for muxed GPIOs.
Expand Down
30 changes: 30 additions & 0 deletions sw/host/opentitanlib/src/transport/qemu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod monitor;
pub mod reset;
pub mod spi;
pub mod uart;
pub mod usbdev;

use std::cell::RefCell;
use std::collections::HashMap;
Expand All @@ -30,12 +31,16 @@ use crate::transport::qemu::monitor::{Chardev, ChardevKind, Monitor};
use crate::transport::qemu::reset::QemuReset;
use crate::transport::qemu::spi::QemuSpi;
use crate::transport::qemu::uart::QemuUart;
use crate::transport::qemu::usbdev::QemuVbusSense;
use crate::transport::{
Capabilities, Capability, Transport, TransportError, TransportInterfaceType,
};

/// ID of the fake pin we use to model resets.
const QEMU_RESET_PIN_IDX: u8 = u8::MAX;
/* Until we have a more complete model of the pinmux, we need to model this
* MIO directly. */
const QEMU_VBUS_SENSE_PIN_IDX: u8 = u8::MAX - 1;

/// Baudrate for QEMU's consoles. These are PTYs so it currently doesn't matter,
/// but we must use a non-zero value because the pacing calculations divide by
Expand All @@ -62,6 +67,9 @@ pub struct Qemu {
/// GPIO pins (not including reset pin).
gpio: Option<Rc<RefCell<QemuGpio>>>,

/// VBUS sense pin (actually goes via the `usbdev-cmd` chardev).
vbus_sense: Option<Rc<dyn GpioPin>>,

/// QEMU log modelled as a UART.
log: Option<Rc<dyn Uart>>,
}
Expand Down Expand Up @@ -119,6 +127,25 @@ impl Qemu {
);
}

// USBDEV control:
let vbus_sense = match find_chardev(&chardevs, "usbdev-cmd") {
Some(ChardevKind::Pty { path }) => {
let tty = serialport::new(
path.to_str().context("TTY path not UTF8")?,
CONSOLE_BAUDRATE,
)
.open_native()
.context("failed to open QEMU usbdev-cmd PTY")?;

let vbus_sense: Rc<dyn GpioPin> = Rc::new(QemuVbusSense::new(tty));
Some(vbus_sense)
}
_ => {
log::info!("could not find pty chardev with id=usbdev, skipping USBDEV");
None
}
};

// QEMU log, not really a UART but modelled as one:
let log = match find_chardev(&chardevs, "log") {
Some(ChardevKind::Pty { path }) => {
Expand Down Expand Up @@ -194,6 +221,7 @@ impl Qemu {
monitor,
reset,
uarts,
vbus_sense,
log,
spi,
i2cs,
Expand Down Expand Up @@ -269,6 +297,8 @@ impl Transport for Qemu {
Ok(Rc::clone(gpio))
} else if pin == QEMU_RESET_PIN_IDX {
Ok(Rc::clone(&self.reset))
} else if pin == QEMU_VBUS_SENSE_PIN_IDX && self.vbus_sense.is_some() {
Ok(Rc::clone(self.vbus_sense.as_ref().unwrap()))
} else {
Err(GpioError::InvalidPinNumber(pin).into())
}
Expand Down
65 changes: 65 additions & 0 deletions sw/host/opentitanlib/src/transport/qemu/usbdev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

use serialport::TTYPort;
use std::cell::{Cell, RefCell};
use std::io::Write;

use crate::io::gpio::{GpioPin, PinMode, PullMode};

/// Pin-like interface for controlling USB VBUS.
pub struct QemuVbusSense {
pub usbdev: RefCell<TTYPort>,
pub asserted: Cell<bool>,
}

impl QemuVbusSense {
pub fn new(usbdev: TTYPort) -> QemuVbusSense {
QemuVbusSense {
usbdev: RefCell::new(usbdev),
asserted: Cell::new(false),
}
}
}

impl GpioPin for QemuVbusSense {
fn read(&self) -> anyhow::Result<bool> {
Ok(self.asserted.get())
}

fn write(&self, value: bool) -> anyhow::Result<()> {
if value == self.asserted.get() {
return Ok(());
}

let cmd = if value { "vbus_on" } else { "vbus_off" };
writeln!(*self.usbdev.borrow_mut(), "{}", cmd)?;

self.asserted.set(value);

Ok(())
}

fn set_mode(&self, _mode: PinMode) -> anyhow::Result<()> {
Ok(())
}

fn set_pull_mode(&self, _mode: PullMode) -> anyhow::Result<()> {
Ok(())
}

fn set(
&self,
_mode: Option<PinMode>,
value: Option<bool>,
_pull: Option<PullMode>,
_analog_value: Option<f32>,
) -> anyhow::Result<()> {
if let Some(value) = value {
return self.write(value);
}

Ok(())
}
}
Loading