blob: dcd3f484abba72946e3f2c3b96a7e79516e9c08b [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Safe wrapper to talk to USB devices from a host.
//!
//! # Supports
//! - OS backends: Linux and MacOS.
//! - Bulk only interfaces with a single IN and OUT pipe.
//!
//! # Examples
//!
//! See tests for examples using the Zedmon power monitor.
use fuchsia_async::unblock;
use futures::io::{AsyncRead, AsyncWrite};
use futures::task::{Context, Poll};
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::future::Future;
use std::io::{ErrorKind, Read, Write};
use std::os::raw::c_void;
use std::pin::Pin;
use std::sync::{Mutex, RwLock};
use thiserror::Error;
mod usb;
#[derive(Error, Debug)]
pub enum Error {
#[error("No device matched.")]
NoDeviceMatched,
#[error("Serial number missing.")]
SerialNumberMissing,
}
/// Standard result type that defaults to our [`Error`] for the error type.
pub type Result<V, E = Error> = std::result::Result<V, E>;
/// A collection of information about an interface.
///
/// Used for matching interfaces to open.
pub type InterfaceInfo = usb::usb_ifc_info;
/// Opens a USB interface.
///
/// `matcher` will be called on every discovered interface. When `matcher` returns true, that
/// interface will be opened.
pub trait Open<T> {
fn open<F>(matcher: &mut F) -> Result<T>
where
F: FnMut(&InterfaceInfo) -> bool;
}
/// A USB Interface.
///
/// See top-level crate docs for an example.
#[derive(Debug)]
pub struct Interface {
interface: *mut usb::UsbInterface,
}
/// Send implementation for USB interface.
///
/// This struct wraps a raw pointer which according to the Rust documentation found at
/// https://doc.rust-lang.org/nomicon/send-and-sync.html: "However raw pointers
/// are, strictly speaking, marked as thread-unsafe as more of a lint. Doing anything useful with
/// a raw pointer requires dereferencing it, which is already unsafe. In that sense, one could
/// argue that it would be "fine" for them to be marked as thread safe."
unsafe impl Send for Interface {}
lazy_static! {
static ref IFACE_REGISTRY: RwLock<HashMap<String, Mutex<Interface>>> =
RwLock::new(HashMap::new());
}
/// An Asynchronous USB Interface. This wraps the synchronous calls to allow for yields between
/// writing.
pub struct AsyncInterface {
serial: String,
write_task: Option<Pin<Box<dyn Future<Output = std::io::Result<usize>> + Send>>>,
read_task: Option<Pin<Box<dyn Future<Output = std::io::Result<Vec<u8>>> + Send>>>,
}
impl Interface {
// This shouldn't be called more than once, since it deletes the underlying C++ object.
unsafe fn close(&mut self) {
// Foreign function call requires unsafe block.
unsafe {
usb::interface_close(self.interface);
}
}
}
impl Open<Interface> for Interface {
fn open<F>(matcher: &mut F) -> Result<Interface>
where
F: FnMut(&InterfaceInfo) -> bool,
{
// Generate a trampoline for calling our matcher from a C callback.
extern "C" fn trampoline<F>(ifc_ptr: *mut usb::usb_ifc_info, data: *mut c_void) -> bool
where
F: FnMut(&InterfaceInfo) -> bool,
{
// Undoes the cast of `matcher` to `*mut c_void` performed in the call to
// `usb::interface_open`, below.
let callback: &mut F = unsafe { &mut *(data as *mut F) };
// Casts the raw interface pointer to a safe reference. Requires that `ifc_ptr`, as
// as provided by the C++ `interface_open`, be a valid pointer to a `usb::usb_ifc_info`.
let interface = unsafe { &*ifc_ptr };
(*callback)(interface)
}
// Call into the low level driver to open the interface. The matcher itself is passesd
// as a void pointer which is re-intepreted by the above trampoline. The foreign function
// call requires an unsafe block.
let device_ptr = unsafe {
usb::interface_open(Some(trampoline::<F>), matcher as *mut F as *mut c_void, 200)
};
if !device_ptr.is_null() {
return Ok(Interface { interface: device_ptr as *mut usb::UsbInterface });
} else {
return Err(Error::NoDeviceMatched);
}
}
}
impl Drop for Interface {
fn drop(&mut self) {
// Unsafe: This is the only call to this function
unsafe {
self.close();
}
}
}
impl Read for Interface {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let buf_ptr = buf.as_mut_ptr() as *mut c_void;
// Foreign function call requires unsafe block.
let ret =
unsafe { usb::interface_read(self.interface, buf_ptr, buf.len() as usb::ssize_t) };
if ret < 0 {
return Err(std::io::Error::new(ErrorKind::Other, format!("Read error: {}", ret)));
}
return Ok(ret as usize);
}
}
impl Write for Interface {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let buf_ptr = buf.as_ptr() as *const c_void;
// Foreign function call requires unsafe block.
let ret =
unsafe { usb::interface_write(self.interface, buf_ptr, buf.len() as usb::ssize_t) };
if ret < 0 {
return Err(std::io::Error::new(ErrorKind::Other, format!("Write error: {}", ret)));
}
return Ok(ret as usize);
}
fn flush(&mut self) -> std::io::Result<()> {
// Do nothing as we're not buffered.
Ok(())
}
}
impl Debug for AsyncInterface {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("AsyncInterface").field("serial", &self.serial).finish()
}
}
impl Drop for AsyncInterface {
fn drop(&mut self) {
let mut write_guard =
IFACE_REGISTRY.write().expect("could not acquire write lock on interface registry");
if let Some(iface) = (*write_guard).remove(&self.serial) {
// To be clear, when the Interface is dropped, it will close and clean up the
// C++ object.
drop(iface);
}
}
}
impl Open<AsyncInterface> for AsyncInterface {
fn open<F>(matcher: &mut F) -> Result<AsyncInterface>
where
F: FnMut(&InterfaceInfo) -> bool,
{
Self::open_interface(matcher, true)
}
}
impl AsyncInterface {
/// Opens the interface with the matcher (not draining the
/// device's buffer), and returns if the connection could be successfully opened
pub fn check<F>(matcher: &mut F) -> bool
where
F: FnMut(&InterfaceInfo) -> bool,
{
tracing::debug!("AsyncInterface checking interface");
Self::open_interface(matcher, false).is_ok()
}
fn open_interface<F>(matcher: &mut F, drain_input_queue: bool) -> Result<Self>
where
F: FnMut(&InterfaceInfo) -> bool,
{
let mut serial = None;
let mut cb = |info: &InterfaceInfo| -> bool {
if matcher(info) {
let null_pos = match info.serial_number.iter().position(|&c| c == 0) {
Some(p) => p,
None => {
return false;
}
};
serial.replace(
(*String::from_utf8_lossy(&info.serial_number[..null_pos])).to_string(),
);
return true;
}
false
};
Interface::open(&mut cb).and_then(|mut iface| match serial {
Some(s) => {
tracing::debug!("AsyncInterface open_interface() for serial {}.", s);
if drain_input_queue {
tracing::debug!(
"AsyncInterface open_interface() for serial {}. About to clear buffer",
s
);
// Clears out anything that was in the usb buffer waiting.
let mut buffer = Vec::new();
let _read_res = iface.read_to_end(&mut buffer);
}
let mut write_guard = IFACE_REGISTRY
.write()
.expect("could not acquire write lock on interface registry");
(*write_guard).insert(s.clone(), Mutex::new(iface));
Ok(AsyncInterface { serial: s, write_task: None, read_task: None })
}
None => Err(Error::SerialNumberMissing),
})
}
}
impl AsyncWrite for AsyncInterface {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<std::io::Result<usize>> {
if self.write_task.is_none() {
let buffer = buf[..].to_vec();
let serial_clone = self.serial.clone();
self.write_task.replace(Box::pin(unblock(move || {
let read_guard = IFACE_REGISTRY
.read()
.expect("could not acquire read lock on interface registry");
if let Some(iface_lock) = (*read_guard).get(&serial_clone) {
iface_lock.lock().unwrap().write(&buffer)
} else {
Err(std::io::Error::new(
ErrorKind::Other,
format!("Interface missing from registry"),
))
}
})));
}
if let Some(ref mut task) = self.write_task {
match task.as_mut().poll(cx) {
Poll::Ready(s) => {
self.write_task = None;
Poll::Ready(s)
}
Poll::Pending => Poll::Pending,
}
} else {
Poll::Ready(Err(std::io::Error::new(
ErrorKind::Other,
format!("Could not add async task to write"),
)))
}
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
// Interface is closed in the drop method.
Poll::Ready(Ok(()))
}
}
impl AsyncRead for AsyncInterface {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
mut buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
if self.read_task.is_none() {
let mut buffer = buf[..].to_vec();
let serial_clone = self.serial.clone();
self.read_task.replace(Box::pin(unblock(move || {
let read_guard = IFACE_REGISTRY
.read()
.expect("could not acquire read lock on interface registry");
if let Some(iface_lock) = (*read_guard).get(&serial_clone) {
let read = iface_lock.lock().unwrap().read(&mut buffer)?;
buffer.truncate(read);
Ok(buffer)
} else {
Err(std::io::Error::new(
ErrorKind::Other,
format!("Interface missing from registry"),
))
}
})));
}
if let Some(ref mut task) = self.read_task {
match task.as_mut().poll(cx) {
Poll::Ready(s) => {
self.read_task = None;
match s {
Ok(v) => Poll::Ready(buf.write(&v)),
Err(e) => Poll::Ready(Err(e)),
}
}
Poll::Pending => Poll::Pending,
}
} else {
Poll::Ready(Err(std::io::Error::new(
ErrorKind::Other,
format!("Could not add async task to read"),
)))
}
}
}
/// Tests are based on the [Zedmon power monitor](https://fuchsia.googlesource.com/zedmon).
///
/// In order to run them, the host should be connected to exactly one Zedmon device, satisfying:
/// - Hardware version 2.1;
/// - Firmware built from the Zedmon repository's revision 9765b27b5f, or equivalent.
#[cfg(test)]
mod tests {
use super::*;
fn zedmon_match(ifc: &InterfaceInfo) -> bool {
(ifc.dev_vendor == 0x18d1)
&& (ifc.dev_product == 0xaf00)
&& (ifc.ifc_class == 0xff)
&& (ifc.ifc_subclass == 0xff)
&& (ifc.ifc_protocol == 0x00)
}
#[test]
fn test_zedmon_enumeration() {
let mut serials = Vec::new();
let mut cb = |info: &InterfaceInfo| -> bool {
if zedmon_match(info) {
let null_pos = match info.serial_number.iter().position(|&c| c == 0) {
Some(p) => p,
None => {
return false;
}
};
serials
.push((*String::from_utf8_lossy(&info.serial_number[..null_pos])).to_string());
}
false
};
let result = Interface::open(&mut cb);
assert!(result.is_err(), "Enumeration matcher should not open any device.");
assert_eq!(serials.len(), 1, "Host should have exactly one zedmon device connected");
}
#[test]
fn test_zedmon_read_parameter() {
// Open USB interface.
let mut matcher = |info: &InterfaceInfo| -> bool { zedmon_match(info) };
let mut interface = Interface::open(&mut matcher).unwrap();
// Send a Query Parameter request.
interface.write_all(&[0x02, 0x00]).unwrap();
// Read response.
let mut packet = [0x00; 64];
let len = interface.read(&mut packet).unwrap();
// Verify the parameter is as we expect. Format of this packet can be
// found at https://fuchsia.googlesource.com/zedmon/+/HEAD/docs/usb_proto.md
assert_eq!(
packet[..len - 1],
[
0x83, 0x73, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x69, 0x73, 0x74, 0x61,
0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
0x0a, 0xd7, 0x23, 0x3c, 0x00, 0x00, 0x00
][..]
);
}
}