usb: support for listing attached usb devices

Originally, crosvm would list details about an attached usb device for a
given port. This change allows getting details about multiple ports at
once. This is intended to simplify command line usage and downstream
consumers like concierge.

TEST=various vmc commands
     Chrome UI for handling USB devices
BUG=chromium:831850

Change-Id: I55681a7fea7425c897a22a579dcc15567683ef54
Reviewed-on: https://chromium-review.googlesource.com/1529765
Commit-Ready: Zach Reizner <zachr@chromium.org>
Tested-by: Zach Reizner <zachr@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
Zach Reizner 2019-03-18 20:58:31 -07:00 committed by chrome-bot
parent d99cd0ae0b
commit aff94ca6da
3 changed files with 65 additions and 32 deletions

View file

@ -18,7 +18,10 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::time::Duration;
use sys_util::net::UnixSeqpacket;
use sys_util::{error, WatchingEvents};
use vm_control::{UsbControlCommand, UsbControlResult, UsbControlSocket};
use vm_control::{
UsbControlAttachedDevice, UsbControlCommand, UsbControlResult, UsbControlSocket,
USB_CONTROL_MAX_PORTS,
};
const SOCKET_TIMEOUT_MS: u64 = 2000;
@ -142,6 +145,27 @@ impl ProviderInner {
}
}
fn handle_list_devices(&self, ports: [u8; USB_CONTROL_MAX_PORTS]) -> UsbControlResult {
let mut devices: [UsbControlAttachedDevice; USB_CONTROL_MAX_PORTS] = Default::default();
for (result_index, &port_id) in ports.iter().enumerate() {
match self.usb_hub.get_port(port_id).and_then(|p| {
p.get_backend_device()
.as_ref()
.map(|d| (d.get_vid(), d.get_pid()))
}) {
Some((vendor_id, product_id)) => {
devices[result_index] = UsbControlAttachedDevice {
port: port_id,
vendor_id,
product_id,
}
}
None => continue,
}
}
UsbControlResult::Devices(devices)
}
fn on_event_helper(&self) -> Result<()> {
let cmd = self.sock.recv().map_err(Error::ReadControlSock)?;
match cmd {
@ -287,23 +311,8 @@ impl ProviderInner {
}
Ok(())
}
UsbControlCommand::ListDevice { port } => {
let port_number = port;
let result = match self.usb_hub.get_port(port_number) {
Some(port) => match port.get_backend_device().as_ref() {
Some(device) => {
let vid = device.get_vid();
let pid = device.get_pid();
UsbControlResult::Device {
port: port_number,
vid,
pid,
}
}
None => UsbControlResult::NoSuchDevice,
},
None => UsbControlResult::NoSuchPort,
};
UsbControlCommand::ListDevice { ports } => {
let result = self.handle_list_devices(ports);
// The send failure will be logged, but event loop still think the event is
// handled.
let _ = self.sock.send(&result).map_err(Error::WriteControlSock);

View file

@ -28,7 +28,7 @@ use sys_util::{
};
use vm_control::{
BalloonControlCommand, DiskControlCommand, MaybeOwnedFd, UsbControlCommand, UsbControlResult,
VmControlRequestSocket, VmRequest, VmResponse,
VmControlRequestSocket, VmRequest, VmResponse, USB_CONTROL_MAX_PORTS,
};
use crate::argument::{print_help, set_arguments, Argument};
@ -1105,14 +1105,12 @@ fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
}
}
fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
let port: u8 = args
.next()
.map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
p.parse::<u8>()
.map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
})?;
let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { port });
fn usb_list(args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
for (index, port) in ports.iter_mut().enumerate() {
*port = index as u8
}
let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
match response {
VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
@ -1121,9 +1119,9 @@ fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
}
fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() < 3 {
if args.len() < 2 {
print_help("crosvm usb",
"[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list PORT] VM_SOCKET...", &[]);
"[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list] VM_SOCKET...", &[]);
return Err(());
}

View file

@ -88,6 +88,13 @@ impl Default for VmRunMode {
}
}
/// The maximum number of devices that can be listed in one `UsbControlCommand`.
///
/// This value was set to be equal to `xhci_regs::MAX_PORTS` for convenience, but it is not
/// necessary for correctness. Importing that value directly would be overkill because it would
/// require adding a big dependency for a single const.
pub const USB_CONTROL_MAX_PORTS: usize = 16;
#[derive(MsgOnSocket, Debug)]
pub enum BalloonControlCommand {
/// Set the size of the VM's balloon.
@ -129,10 +136,23 @@ pub enum UsbControlCommand {
port: u8,
},
ListDevice {
port: u8,
ports: [u8; USB_CONTROL_MAX_PORTS],
},
}
#[derive(MsgOnSocket, Copy, Clone, Debug, Default)]
pub struct UsbControlAttachedDevice {
pub port: u8,
pub vendor_id: u16,
pub product_id: u16,
}
impl UsbControlAttachedDevice {
fn valid(self) -> bool {
self.port != 0
}
}
#[derive(MsgOnSocket, Debug)]
pub enum UsbControlResult {
Ok { port: u8 },
@ -140,7 +160,7 @@ pub enum UsbControlResult {
NoSuchDevice,
NoSuchPort,
FailedToOpenDevice,
Device { port: u8, vid: u16, pid: u16 },
Devices([UsbControlAttachedDevice; USB_CONTROL_MAX_PORTS]),
}
impl Display for UsbControlResult {
@ -153,7 +173,13 @@ impl Display for UsbControlResult {
NoSuchDevice => write!(f, "no_such_device"),
NoSuchPort => write!(f, "no_such_port"),
FailedToOpenDevice => write!(f, "failed_to_open_device"),
Device { port, vid, pid } => write!(f, "device {} {:04x} {:04x}", port, vid, pid),
Devices(devices) => {
write!(f, "devices")?;
for d in devices.iter().filter(|d| d.valid()) {
write!(f, " {} {:04x} {:04x}", d.port, d.vendor_id, d.product_id)?;
}
std::result::Result::Ok(())
}
}
}
}