use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::bus::usb::{
Completion, ConfigurationDescriptor, Descriptors, DeviceDescriptor, Direction,
EndpointDescriptor, Function, InterfaceDescriptor, Peripheral, SetupPacket, Speed,
TransferType, UsbDevice, buses,
};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::Result;
use crate::core::props::{Props, ValueKind};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::machine::realize::Instance;
const CLASS_NAME: &str = "usb.mouse";
const STATE_VERSION: u32 = 1;
const CLASS_HID: u8 = 3;
const SUBCLASS_BOOT: u8 = 1;
const PROTOCOL_MOUSE: u8 = 2;
const DESC_HID: u8 = 0x21;
const DESC_REPORT: u8 = 0x22;
pub mod class_request {
pub const GET_REPORT: u8 = 0x01;
pub const GET_IDLE: u8 = 0x02;
pub const GET_PROTOCOL: u8 = 0x03;
pub const SET_REPORT: u8 = 0x09;
pub const SET_IDLE: u8 = 0x0a;
pub const SET_PROTOCOL: u8 = 0x0b;
}
const ENDPOINT: u8 = 1;
pub const REPORT_BYTES: usize = 3;
const INTERVAL_HIGH: u8 = 4;
const INTERVAL_FRAMES: u8 = 10;
pub const REPORT_DESCRIPTOR: &[u8] = &[
0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, 0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x02, 0x81, 0x06, 0xc0, 0xc0, ];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct MouseState {
pending: Option<[u8; REPORT_BYTES]>,
buttons: u8,
idle: u8,
protocol: u8,
}
struct MouseFunction {
descriptors: Descriptors,
speed: Speed,
state: Mutex<MouseState>,
}
impl fmt::Debug for MouseFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("MouseFunction");
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
impl MouseFunction {
fn new(vendor: u16, product: u16, speed: Speed) -> MouseFunction {
let device = DeviceDescriptor {
usb: 0x0200,
class: 0,
subclass: 0,
protocol: 0,
max_packet0: speed.max_control_packet() as u8,
vendor,
product,
device: 0x0100,
manufacturer: 0,
product_name: 0,
serial: 0,
configurations: 1,
};
let interface = InterfaceDescriptor {
number: 0,
alternate: 0,
endpoints: 1,
class: CLASS_HID,
subclass: SUBCLASS_BOOT,
protocol: PROTOCOL_MOUSE,
name: 0,
};
let endpoint = EndpointDescriptor {
address: ENDPOINT | Direction::BIT,
attributes: TransferType::Interrupt.attribute_bits(),
max_packet: REPORT_BYTES as u16,
interval: if speed == Speed::High {
INTERVAL_HIGH
} else {
INTERVAL_FRAMES
},
};
let mut body = Vec::new();
body.extend_from_slice(&interface.encode());
body.extend_from_slice(&hid_descriptor());
body.extend_from_slice(&endpoint.encode());
let mut descriptors = Descriptors::new().with_device(&device);
descriptors.add_configuration(
&ConfigurationDescriptor {
interfaces: 1,
value: 1,
name: 0,
attributes: ConfigurationDescriptor::REMOTE_WAKEUP,
max_power: 50,
},
&body,
);
if speed == Speed::High {
descriptors.set_qualifier(&device, 0);
}
MouseFunction {
descriptors,
speed,
state: Mutex::with_rank(LockRank::DEVICE, MouseState::default()),
}
}
fn current_report(&self) -> [u8; REPORT_BYTES] {
[self.state.lock().buttons, 0, 0]
}
}
fn hid_descriptor() -> [u8; 9] {
let len = (REPORT_DESCRIPTOR.len() as u16).to_le_bytes();
[
9,
DESC_HID,
0x11,
0x01,
0x00,
0x01,
DESC_REPORT,
len[0],
len[1],
]
}
impl Function for MouseFunction {
fn descriptors(&self) -> &Descriptors {
&self.descriptors
}
fn speed(&self) -> Speed {
self.speed
}
fn reset(&self) {
*self.state.lock() = MouseState::default();
}
fn control_in(&self, setup: SetupPacket) -> Option<Vec<u8>> {
if setup.request == crate::bus::usb::request::GET_DESCRIPTOR {
let (kind, index) = setup.descriptor();
return match (kind, index) {
(DESC_REPORT, 0) => Some(REPORT_DESCRIPTOR.to_vec()),
(DESC_HID, 0) => Some(hid_descriptor().to_vec()),
_ => None,
};
}
match setup.request {
class_request::GET_REPORT => Some(self.current_report().to_vec()),
class_request::GET_IDLE => Some(alloc::vec![self.state.lock().idle]),
class_request::GET_PROTOCOL => Some(alloc::vec![self.state.lock().protocol]),
_ => None,
}
}
fn control_out(&self, setup: SetupPacket, data: &[u8]) -> bool {
match setup.request {
class_request::SET_IDLE => {
self.state.lock().idle = (setup.value >> 8) as u8;
true
}
class_request::SET_PROTOCOL => {
self.state.lock().protocol = (setup.value & 1) as u8;
true
}
class_request::SET_REPORT => {
let _ = data;
true
}
_ => false,
}
}
fn endpoint_in(&self, endpoint: u8, dst: &mut [u8]) -> Completion {
if endpoint != ENDPOINT {
return Completion::stall();
}
let mut state = self.state.lock();
let Some(report) = state.pending.take() else {
return Completion::nak();
};
let n = report.len().min(dst.len());
dst[..n].copy_from_slice(&report[..n]);
Completion::ack(n as u64)
}
fn peek_in(&self, endpoint: u8, dst: &mut [u8]) -> Completion {
if endpoint != ENDPOINT {
return Completion::stall();
}
let state = self.state.lock();
let Some(report) = state.pending else {
return Completion::nak();
};
let n = report.len().min(dst.len());
dst[..n].copy_from_slice(&report[..n]);
Completion::ack(n as u64)
}
}
#[derive(Debug)]
pub struct HidMouse {
peripheral: Arc<Peripheral>,
function: Arc<MouseFunction>,
}
impl HidMouse {
pub fn new(props: &Props) -> Result<HidMouse> {
let mut r = props.reader();
let bus_name = r.require_str("bus")?.to_string();
let port = r.or_range("port", 0u64, 0..=u64::from(u8::MAX))?;
let vendor = r.or_range("vendor", 0u64, 0..=u64::from(u16::MAX))?;
let product = r.or_range("product", 0u64, 0..=u64::from(u16::MAX))?;
let spelling = r.or_str("speed", Speed::High.name())?;
let speed = Speed::from_name(spelling).ok_or_else(|| crate::Error::Config {
at: alloc::string::String::from(CLASS_NAME),
message: alloc::format!("`speed` is one of {:?}, not `{spelling}`", Speed::NAMES),
})?;
r.finish()?;
let bus = buses::attach(props, &bus_name, port as u8 + 1)?;
let mouse = HidMouse::new_detached_at_speed(vendor as u16, product as u16, speed);
bus.attach(port as u8, mouse.device())?;
Ok(mouse)
}
#[must_use]
pub fn new_detached(vendor: u16, product: u16) -> HidMouse {
HidMouse::new_detached_at_speed(vendor, product, Speed::High)
}
#[must_use]
pub fn new_detached_at_speed(vendor: u16, product: u16, speed: Speed) -> HidMouse {
let function = Arc::new(MouseFunction::new(vendor, product, speed));
let peripheral = Arc::new(Peripheral::new(Arc::clone(&function) as Arc<dyn Function>));
HidMouse {
peripheral,
function,
}
}
#[must_use]
pub fn device(&self) -> Arc<dyn UsbDevice> {
Arc::clone(&self.peripheral) as Arc<dyn UsbDevice>
}
#[must_use]
pub fn address(&self) -> crate::bus::usb::DeviceAddress {
self.peripheral.address()
}
#[must_use]
pub fn configuration(&self) -> u8 {
self.peripheral.endpoint0().configuration()
}
pub fn motion(&self, dx: i8, dy: i8, buttons: u8) {
let mut state = self.function.state.lock();
state.buttons = buttons & 0x7;
state.pending = Some([state.buttons, dx as u8, dy as u8]);
}
#[must_use]
pub fn has_report(&self) -> bool {
self.function.state.lock().pending.is_some()
}
#[must_use]
pub fn report_descriptor(&self) -> &'static [u8] {
REPORT_DESCRIPTOR
}
}
impl Device for HidMouse {
fn class(&self) -> &'static DeviceClass {
&MOUSE_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
self.peripheral.bus_reset();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
self.peripheral.endpoint0().save(w)?;
let state = *self.function.state.lock();
w.write_bool(state.pending.is_some())?;
let report = state.pending.unwrap_or([0; REPORT_BYTES]);
w.write_all(&report)?;
w.write_u8(state.buttons)?;
w.write_u8(state.idle)?;
w.write_u8(state.protocol)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
self.peripheral.endpoint0().load(r)?;
let has_report = r.read_bool()?;
let mut report = [0u8; REPORT_BYTES];
report.copy_from_slice(r.take(REPORT_BYTES)?);
let state = MouseState {
pending: has_report.then_some(report),
buttons: r.read_u8()?,
idle: r.read_u8()?,
protocol: r.read_u8()?,
};
*self.function.state.lock() = state;
Ok(())
}
}
impl Instance for HidMouse {}
pub static MOUSE_CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "a USB HID boot-protocol mouse: three buttons and relative X and Y on an interrupt \
endpoint, high speed by default and full or low if the board says so",
properties: &[
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: true,
summary: "the named USB bus to plug into",
},
PropertySpec {
name: "port",
kind: ValueKind::Uint,
required: false,
summary: "which port of that bus (default 0)",
},
PropertySpec {
name: "vendor",
kind: ValueKind::Uint,
required: false,
summary: "idVendor, as the device descriptor reports it (default 0)",
},
PropertySpec {
name: "product",
kind: ValueKind::Uint,
required: false,
summary: "idProduct (default 0)",
},
PropertySpec {
name: "speed",
kind: ValueKind::Str,
required: false,
summary: "how fast it signals: `high` (default), `full` or `low`",
},
],
construct: |props| Ok(Box::new(HidMouse::new(props)?)),
};
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&MOUSE_CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(HidMouse::new(props)?)))
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("bus", ValueKind::Str).required())
.prop(PropSchema::new("port", ValueKind::Uint).range(0, u64::from(u8::MAX)))
.prop(PropSchema::new("vendor", ValueKind::Uint).range(0, u64::from(u16::MAX)))
.prop(PropSchema::new("product", ValueKind::Uint).range(0, u64::from(u16::MAX)))
.prop(PropSchema::new("speed", ValueKind::Str).values(Speed::NAMES))
}
const _: () = {
assert!(REPORT_BYTES == 3);
};
#[cfg(test)]
impl HidMouse {
fn save_to(&self, out: &mut Vec<u8>) -> Result<()> {
self.peripheral.endpoint0().save(out)?;
let state = *self.function.state.lock();
out.write_bool(state.pending.is_some())?;
out.write_all(&state.pending.unwrap_or([0; REPORT_BYTES]))?;
out.write_u8(state.buttons)?;
out.write_u8(state.idle)?;
out.write_u8(state.protocol)
}
fn load_from(&self, bytes: &[u8]) -> Result<()> {
let mut r = ChunkReader::new(bytes);
self.load(&mut r)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bus::usb::{DeviceAddress, Status, UsbBus, request};
fn plugged() -> (Arc<UsbBus>, HidMouse) {
let bus = Arc::new(UsbBus::new(1));
let mouse = HidMouse::new_detached(0x1234, 0x5678);
bus.attach(0, mouse.device()).expect("an empty port");
bus.set_enabled(0, true);
(bus, mouse)
}
fn control_in(bus: &UsbBus, address: DeviceAddress, setup: SetupPacket) -> Vec<u8> {
assert_eq!(bus.setup(address, 0, setup), Status::Ack);
let mut out = Vec::new();
loop {
let mut packet = [0u8; 64];
let completion = bus.read(address, 0, &mut packet);
assert_eq!(completion.status, Status::Ack, "a data-stage IN");
let n = completion.len as usize;
out.extend_from_slice(&packet[..n]);
if n < packet.len() {
break;
}
}
assert_eq!(
bus.write(address, 0, &[]).status,
Status::Ack,
"status stage"
);
out
}
fn control_out(bus: &UsbBus, address: DeviceAddress, setup: SetupPacket) -> Status {
assert_eq!(bus.setup(address, 0, setup), Status::Ack);
bus.read(address, 0, &mut []).status
}
fn get_descriptor(kind: u8, index: u8, length: u16) -> SetupPacket {
SetupPacket {
request_type: 0x80,
request: request::GET_DESCRIPTOR,
value: (u16::from(kind) << 8) | u16::from(index),
index: 0,
length,
}
}
#[test]
fn the_device_descriptor_is_what_the_spec_says_it_is() {
let (bus, _mouse) = plugged();
let bytes = control_in(&bus, DeviceAddress::DEFAULT, get_descriptor(1, 0, 18));
assert_eq!(bytes.len(), 18);
assert_eq!(bytes[0], 18, "bLength");
assert_eq!(bytes[1], 1, "bDescriptorType");
assert_eq!(u16::from_le_bytes([bytes[2], bytes[3]]), 0x0200, "bcdUSB");
assert_eq!(bytes[7], 64, "high speed requires bMaxPacketSize0 = 64");
assert_eq!(u16::from_le_bytes([bytes[8], bytes[9]]), 0x1234);
assert_eq!(u16::from_le_bytes([bytes[10], bytes[11]]), 0x5678);
assert_eq!(bytes[17], 1, "bNumConfigurations");
}
#[test]
fn the_configuration_arrives_as_one_tree() {
let (bus, _mouse) = plugged();
let bytes = control_in(&bus, DeviceAddress::DEFAULT, get_descriptor(2, 0, 255));
assert_eq!(bytes.len(), 34);
assert_eq!(u16::from_le_bytes([bytes[2], bytes[3]]), 34, "wTotalLength");
assert_eq!(bytes[4], 1, "bNumInterfaces");
assert_eq!(bytes[9 + 5], CLASS_HID, "bInterfaceClass");
assert_eq!(bytes[9 + 6], SUBCLASS_BOOT, "bInterfaceSubClass");
assert_eq!(bytes[9 + 7], PROTOCOL_MOUSE, "bInterfaceProtocol");
assert_eq!(bytes[18 + 1], DESC_HID, "the HID descriptor follows");
assert_eq!(bytes[27 + 2], ENDPOINT | 0x80, "bEndpointAddress");
assert_eq!(bytes[27 + 3] & 0x3, 3, "an interrupt endpoint");
assert_eq!(bytes[27 + 6], INTERVAL_HIGH, "bInterval");
}
#[test]
fn a_short_wlength_truncates_rather_than_overruns() {
let (bus, _mouse) = plugged();
let bytes = control_in(&bus, DeviceAddress::DEFAULT, get_descriptor(1, 0, 8));
assert_eq!(bytes.len(), 8);
assert_eq!(bytes[7], 64);
}
#[test]
fn the_report_descriptor_comes_from_the_interface() {
let (bus, _mouse) = plugged();
let setup = SetupPacket {
request_type: 0x81,
request: request::GET_DESCRIPTOR,
value: (u16::from(DESC_REPORT) << 8),
index: 0,
length: 256,
};
let bytes = control_in(&bus, DeviceAddress::DEFAULT, setup);
assert_eq!(bytes, REPORT_DESCRIPTOR);
}
#[test]
fn the_address_changes_only_when_the_status_stage_completes() {
let (bus, mouse) = plugged();
let setup = SetupPacket {
request_type: 0x00,
request: request::SET_ADDRESS,
value: 7,
index: 0,
length: 0,
};
assert_eq!(bus.setup(DeviceAddress::DEFAULT, 0, setup), Status::Ack);
assert_eq!(
mouse.address(),
DeviceAddress::DEFAULT,
"USB 2.0 §9.4.6: the new address takes effect after the status stage, \
and the status stage is addressed to the old one"
);
assert_eq!(
bus.read(DeviceAddress::DEFAULT, 0, &mut []).status,
Status::Ack
);
assert_eq!(mouse.address(), DeviceAddress(7));
assert_eq!(
bus.setup(DeviceAddress::DEFAULT, 0, setup),
Status::NoDevice
);
}
#[test]
fn a_configured_mouse_delivers_reports_and_naks_when_idle() {
let (bus, mouse) = plugged();
let address = DeviceAddress::DEFAULT;
let configure = SetupPacket {
request_type: 0x00,
request: request::SET_CONFIGURATION,
value: 1,
index: 0,
length: 0,
};
assert_eq!(control_out(&bus, address, configure), Status::Ack);
assert_eq!(mouse.configuration(), 1);
let mut report = [0u8; 8];
assert_eq!(bus.read(address, ENDPOINT, &mut report).status, Status::Nak);
mouse.motion(5, -3, 0b001);
let completion = bus.read(address, ENDPOINT, &mut report);
assert_eq!(completion.status, Status::Ack);
assert_eq!(completion.len, 3);
assert_eq!(report[0], 0b001, "button 1");
assert_eq!(report[1] as i8, 5);
assert_eq!(report[2] as i8, -3);
assert_eq!(bus.read(address, ENDPOINT, &mut report).status, Status::Nak);
}
#[test]
fn a_debug_peek_does_not_consume_the_report() {
let (bus, mouse) = plugged();
mouse.motion(1, 1, 0);
let mut first = [0u8; 8];
assert_eq!(
bus.peek(DeviceAddress::DEFAULT, ENDPOINT, &mut first).len,
3
);
assert!(mouse.has_report(), "a debug read must not pop the endpoint");
let mut second = [0u8; 8];
assert_eq!(
bus.read(DeviceAddress::DEFAULT, ENDPOINT, &mut second).len,
3
);
assert_eq!(first[..3], second[..3]);
assert!(!mouse.has_report());
}
#[test]
fn the_class_requests_are_answered() {
let (bus, mouse) = plugged();
let address = DeviceAddress::DEFAULT;
let set_idle = SetupPacket {
request_type: 0x21,
request: class_request::SET_IDLE,
value: 0x2a00,
index: 0,
length: 0,
};
assert_eq!(control_out(&bus, address, set_idle), Status::Ack);
let idle = control_in(
&bus,
address,
SetupPacket {
request_type: 0xa1,
request: class_request::GET_IDLE,
value: 0,
index: 0,
length: 1,
},
);
assert_eq!(idle, alloc::vec![0x2a]);
mouse.motion(9, 9, 0b010);
let report = control_in(
&bus,
address,
SetupPacket {
request_type: 0xa1,
request: class_request::GET_REPORT,
value: 0x0100,
index: 0,
length: 3,
},
);
assert_eq!(report, alloc::vec![0b010, 0, 0]);
assert!(
mouse.has_report(),
"GET_REPORT is not a poll of the endpoint"
);
}
#[test]
fn an_unsupported_request_stalls_the_data_stage_and_not_the_setup() {
let (bus, _mouse) = plugged();
let setup = SetupPacket {
request_type: 0x80,
request: request::GET_DESCRIPTOR,
value: 0x0300,
index: 0,
length: 8,
};
assert_eq!(
bus.setup(DeviceAddress::DEFAULT, 0, setup),
Status::Ack,
"USB 2.0 §9.2.7: a SETUP is always acknowledged"
);
let mut buf = [0u8; 8];
assert_eq!(
bus.read(DeviceAddress::DEFAULT, 0, &mut buf).status,
Status::Stall,
"the request error lands on the data stage"
);
}
#[test]
fn a_bus_reset_returns_the_device_to_the_default_state() {
let (bus, mouse) = plugged();
let address = DeviceAddress::DEFAULT;
assert_eq!(
control_out(
&bus,
address,
SetupPacket {
request_type: 0,
request: request::SET_ADDRESS,
value: 9,
index: 0,
length: 0,
}
),
Status::Ack
);
assert_eq!(mouse.address(), DeviceAddress(9));
bus.reset_port(0);
assert_eq!(mouse.address(), DeviceAddress::DEFAULT);
assert_eq!(mouse.configuration(), 0);
}
#[test]
fn the_mouse_round_trips_through_a_snapshot() {
let (bus, mouse) = plugged();
let address = DeviceAddress::DEFAULT;
assert_eq!(
control_out(
&bus,
address,
SetupPacket {
request_type: 0,
request: request::SET_ADDRESS,
value: 3,
index: 0,
length: 0,
}
),
Status::Ack
);
assert_eq!(
control_out(
&bus,
DeviceAddress(3),
SetupPacket {
request_type: 0,
request: request::SET_CONFIGURATION,
value: 1,
index: 0,
length: 0,
}
),
Status::Ack
);
mouse.motion(-4, 4, 0b100);
assert_eq!(
bus.setup(DeviceAddress(3), 0, get_descriptor(1, 0, 18)),
Status::Ack
);
let mut first = [0u8; 8];
assert_eq!(bus.read(DeviceAddress(3), 0, &mut first).len, 8);
let mut saved = Vec::new();
mouse.save_to(&mut saved).expect("it saves");
let fresh = HidMouse::new_detached(0x1234, 0x5678);
fresh.load_from(&saved).expect("it loads");
let mut again = Vec::new();
fresh.save_to(&mut again).expect("it saves");
assert_eq!(saved, again, "the snapshot did not round trip");
let bus2 = Arc::new(UsbBus::new(1));
bus2.attach(0, fresh.device()).expect("an empty port");
bus2.set_enabled(0, true);
let mut rest = [0u8; 64];
let completion = bus2.read(DeviceAddress(3), 0, &mut rest);
assert_eq!(
completion.len, 10,
"eighteen bytes less the eight already read"
);
assert_eq!(rest[0], 0x34, "idVendor's low byte, at offset 8");
}
#[test]
fn a_queued_report_survives_a_snapshot() {
let (_bus, mouse) = plugged();
mouse.motion(7, -7, 0b011);
let mut saved = Vec::new();
mouse.save_to(&mut saved).expect("it saves");
let fresh = HidMouse::new_detached(0, 0);
fresh.load_from(&saved).expect("it loads");
assert!(fresh.has_report());
let bus = Arc::new(UsbBus::new(1));
bus.attach(0, fresh.device()).expect("an empty port");
bus.set_enabled(0, true);
let mut report = [0u8; 8];
assert_eq!(
bus.read(DeviceAddress::DEFAULT, ENDPOINT, &mut report).len,
3
);
assert_eq!(report[1] as i8, 7);
assert_eq!(report[2] as i8, -7);
}
}