diff --git a/rules/opentitan/qemu.bzl b/rules/opentitan/qemu.bzl index f2d06394038f3..18977e484119f 100644 --- a/rules/opentitan/qemu.bzl +++ b/rules/opentitan/qemu.bzl @@ -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"])] diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD index ae3b48e3b986d..c38f65fa0fe6b 100644 --- a/sw/device/tests/BUILD +++ b/sw/device/tests/BUILD @@ -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}" @@ -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}" @@ -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}" diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD index 4198d4671e0f3..bcbc070ba5343 100644 --- a/sw/host/opentitanlib/BUILD +++ b/sw/host/opentitanlib/BUILD @@ -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", diff --git a/sw/host/opentitanlib/src/app/config/opentitan_qemu.json b/sw/host/opentitanlib/src/app/config/opentitan_qemu.json index dd3e366636cb4..3a14f8170a78d 100644 --- a/sw/host/opentitanlib/src/app/config/opentitan_qemu.json +++ b/sw/host/opentitanlib/src/app/config/opentitan_qemu.json @@ -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. diff --git a/sw/host/opentitanlib/src/transport/qemu/mod.rs b/sw/host/opentitanlib/src/transport/qemu/mod.rs index d2ca2f8e656aa..c85b66468849e 100644 --- a/sw/host/opentitanlib/src/transport/qemu/mod.rs +++ b/sw/host/opentitanlib/src/transport/qemu/mod.rs @@ -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; @@ -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 @@ -62,6 +67,9 @@ pub struct Qemu { /// GPIO pins (not including reset pin). gpio: Option>>, + /// VBUS sense pin (actually goes via the `usbdev-cmd` chardev). + vbus_sense: Option>, + /// QEMU log modelled as a UART. log: Option>, } @@ -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 = 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 }) => { @@ -194,6 +221,7 @@ impl Qemu { monitor, reset, uarts, + vbus_sense, log, spi, i2cs, @@ -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()) } diff --git a/sw/host/opentitanlib/src/transport/qemu/usbdev.rs b/sw/host/opentitanlib/src/transport/qemu/usbdev.rs new file mode 100644 index 0000000000000..1271f59498fca --- /dev/null +++ b/sw/host/opentitanlib/src/transport/qemu/usbdev.rs @@ -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, + pub asserted: Cell, +} + +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 { + 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, + value: Option, + _pull: Option, + _analog_value: Option, + ) -> anyhow::Result<()> { + if let Some(value) = value { + return self.write(value); + } + + Ok(()) + } +}