mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 18:20:34 +00:00
usb: add host backend
Host backend implement backend device. It will attach a read device to xhci controller. Also squashed from ce6b35, author: zachr@. CQ-DEPEND=CL:1510820 BUG=chromium:831850 TEST=local build Change-Id: Idcf2d7d6aca92de9859b7c38d1bf1d98032eae91 Reviewed-on: https://chromium-review.googlesource.com/1512761 Commit-Ready: Jingkui Wang <jkwang@google.com> Tested-by: kokoro <noreply+kokoro@google.com> Tested-by: Zach Reizner <zachr@chromium.org> Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
parent
874f2e83ed
commit
257d004b0c
13 changed files with 1588 additions and 12 deletions
157
devices/src/usb/host_backend/context.rs
Normal file
157
devices/src/usb/host_backend/context.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use super::error::*;
|
||||
use std::os::raw::c_short;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::sync::{Arc, Weak};
|
||||
use sys_util::WatchingEvents;
|
||||
use usb::usb_util::hotplug::UsbHotplugHandler;
|
||||
use usb::usb_util::libusb_context::{LibUsbContext, LibUsbPollfdChangeHandler};
|
||||
use usb::usb_util::libusb_device::LibUsbDevice;
|
||||
use utils::{EventHandler, EventLoop};
|
||||
use vm_control::MaybeOwnedFd;
|
||||
|
||||
/// Context wraps libusb context with libusb event handling.
|
||||
pub struct Context {
|
||||
context: LibUsbContext,
|
||||
event_loop: Arc<EventLoop>,
|
||||
event_handler: Arc<EventHandler>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Create a new context.
|
||||
#[cfg(not(feature = "sandboxed-libusb"))]
|
||||
pub fn new(event_loop: Arc<EventLoop>) -> Result<Context> {
|
||||
let context = LibUsbContext::new().map_err(Error::CreateLibUsbContext)?;
|
||||
let ctx = Context {
|
||||
context: context.clone(),
|
||||
event_loop,
|
||||
event_handler: Arc::new(LibUsbEventHandler {
|
||||
context: context.clone(),
|
||||
}),
|
||||
};
|
||||
ctx.init_event_handler()?;
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
#[cfg(feature = "sandboxed-libusb")]
|
||||
pub fn new(event_loop: Arc<EventLoop>) -> Result<Context> {
|
||||
let context = LibUsbContext::new_jailed().map_err(Error::CreateLibUsbContext)?;
|
||||
let ctx = Context {
|
||||
context: context.clone(),
|
||||
event_loop,
|
||||
event_handler: Arc::new(LibUsbEventHandler {
|
||||
context: context.clone(),
|
||||
}),
|
||||
};
|
||||
ctx.init_event_handler()?;
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
pub fn set_hotplug_handler<H: UsbHotplugHandler + Sized>(&self, handler: H) {
|
||||
if let Err(e) = self.context.set_hotplug_cb(handler) {
|
||||
error!("cannot set hotplug handler: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_event_handler(&self) -> Result<()> {
|
||||
for pollfd in self.context.get_pollfd_iter() {
|
||||
usb_debug!("event loop add event {} events handler", pollfd.fd);
|
||||
self.event_loop
|
||||
.add_event(
|
||||
&MaybeOwnedFd::Borrowed(pollfd.fd),
|
||||
WatchingEvents::new(pollfd.events as u32),
|
||||
Arc::downgrade(&self.event_handler),
|
||||
)
|
||||
.map_err(Error::AddToEventLoop)?;
|
||||
}
|
||||
|
||||
self.context
|
||||
.set_pollfd_notifiers(Box::new(PollfdChangeHandler {
|
||||
event_loop: self.event_loop.clone(),
|
||||
event_handler: Arc::downgrade(&self.event_handler),
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get libusb device with matching bus, addr, vid and pid.
|
||||
#[cfg(not(feature = "sandboxed-libusb"))]
|
||||
pub fn get_device(&self, bus: u8, addr: u8, vid: u16, pid: u16) -> Option<LibUsbDevice> {
|
||||
let device_iter = match self.context.get_device_iter() {
|
||||
Ok(iter) => iter,
|
||||
Err(e) => {
|
||||
error!("could not get libusb device iterator: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
for device in device_iter {
|
||||
if device.get_bus_number() == bus && device.get_address() == addr {
|
||||
if let Ok(descriptor) = device.get_device_descriptor() {
|
||||
if descriptor.idProduct == pid && descriptor.idVendor == vid {
|
||||
return Some(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error!("device not found bus {}, addr {}", bus, addr);
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "sandboxed-libusb")]
|
||||
pub fn get_device(&self, fd: std::fs::File) -> Option<LibUsbDevice> {
|
||||
match self.context.get_device_from_fd(fd) {
|
||||
Ok(dev) => Some(dev),
|
||||
Err(e) => {
|
||||
error!("could not build device from fd: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LibUsbEventHandler {
|
||||
context: LibUsbContext,
|
||||
}
|
||||
|
||||
impl EventHandler for LibUsbEventHandler {
|
||||
fn on_event(&self, _fd: RawFd) -> std::result::Result<(), ()> {
|
||||
self.context.handle_events_nonblock();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct PollfdChangeHandler {
|
||||
event_loop: Arc<EventLoop>,
|
||||
event_handler: Weak<EventHandler>,
|
||||
}
|
||||
|
||||
impl LibUsbPollfdChangeHandler for PollfdChangeHandler {
|
||||
fn add_poll_fd(&self, fd: RawFd, events: c_short) {
|
||||
self.event_loop.add_event(
|
||||
&MaybeOwnedFd::Borrowed(fd),
|
||||
WatchingEvents::new(events as u32),
|
||||
self.event_handler.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_poll_fd(&self, fd: RawFd) {
|
||||
if let Some(h) = self.event_handler.upgrade() {
|
||||
match h.on_event(0) {
|
||||
Ok(()) => {}
|
||||
Err(e) => error!("cannot handle event: {:?}", e),
|
||||
}
|
||||
}
|
||||
match self
|
||||
.event_loop
|
||||
.remove_event_for_fd(&MaybeOwnedFd::Borrowed(fd))
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => error!(
|
||||
"failed to remove poll change handler from event loop: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
78
devices/src/usb/host_backend/error.rs
Normal file
78
devices/src/usb/host_backend/error.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use msg_socket::MsgError;
|
||||
use std::fmt::{self, Display};
|
||||
use usb::usb_util::error::Error as UsbUtilError;
|
||||
use usb::xhci::scatter_gather_buffer::Error as BufferError;
|
||||
use usb::xhci::xhci_transfer::Error as XhciTransferError;
|
||||
use utils::Error as UtilsError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
AddToEventLoop(UtilsError),
|
||||
StartAsyncJobQueue(UtilsError),
|
||||
QueueAsyncJob(UtilsError),
|
||||
CreateLibUsbContext(UsbUtilError),
|
||||
GetActiveConfig(UsbUtilError),
|
||||
SetActiveConfig(UsbUtilError),
|
||||
SetInterfaceAltSetting(UsbUtilError),
|
||||
ClearHalt(UsbUtilError),
|
||||
GetEndpointType,
|
||||
CreateControlSock(std::io::Error),
|
||||
SetupControlSock(std::io::Error),
|
||||
ReadControlSock(MsgError),
|
||||
WriteControlSock(MsgError),
|
||||
GetXhciTransferType(XhciTransferError),
|
||||
TransferComplete(XhciTransferError),
|
||||
ReadBuffer(BufferError),
|
||||
WriteBuffer(BufferError),
|
||||
BufferLen(BufferError),
|
||||
/// Cannot get interface descriptor for (interface, altsetting).
|
||||
GetInterfaceDescriptor((i32, u16)),
|
||||
GetEndpointDescriptor(u8),
|
||||
GetRequestSetupType,
|
||||
BadXhciTransferState,
|
||||
BadBackendProviderState,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
AddToEventLoop(e) => write!(f, "failed to add to event loop: {}", e),
|
||||
StartAsyncJobQueue(e) => write!(f, "failed to start async job queue: {}", e),
|
||||
QueueAsyncJob(e) => write!(f, "failed to queue async job: {}", e),
|
||||
CreateLibUsbContext(e) => write!(f, "failed to create libusb context: {:?}", e),
|
||||
GetActiveConfig(e) => write!(f, "failed to get active config: {:?}", e),
|
||||
SetActiveConfig(e) => write!(f, "failed to set active config: {:?}", e),
|
||||
SetInterfaceAltSetting(e) => write!(f, "failed to set interface alt setting: {:?}", e),
|
||||
ClearHalt(e) => write!(f, "failed to clear halt: {:?}", e),
|
||||
GetEndpointType => write!(f, "failed to get endpoint type"),
|
||||
CreateControlSock(e) => write!(f, "failed to create contro sock: {}", e),
|
||||
SetupControlSock(e) => write!(f, "failed to setup control sock: {}", e),
|
||||
ReadControlSock(e) => write!(f, "failed to read control sock: {}", e),
|
||||
WriteControlSock(e) => write!(f, "failed to write control sock: {}", e),
|
||||
GetXhciTransferType(e) => write!(f, "failed to get xhci transfer type: {}", e),
|
||||
TransferComplete(e) => write!(f, "xhci transfer completed: {}", e),
|
||||
ReadBuffer(e) => write!(f, "failed to read buffer: {}", e),
|
||||
WriteBuffer(e) => write!(f, "failed to write buffer: {}", e),
|
||||
BufferLen(e) => write!(f, "failed to get buffer length: {}", e),
|
||||
GetInterfaceDescriptor((i, alt_setting)) => write!(
|
||||
f,
|
||||
"failed to get interface descriptor for interface {}, alt setting {}",
|
||||
i, alt_setting
|
||||
),
|
||||
GetEndpointDescriptor(ep_idx) => {
|
||||
write!(f, "failed to get endpoint descriptor for ep: {}", ep_idx)
|
||||
}
|
||||
GetRequestSetupType => write!(f, "failed to get request setup"),
|
||||
BadXhciTransferState => write!(f, "xhci transfer is in a bad state"),
|
||||
BadBackendProviderState => write!(f, "backend provider is in a bad state"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
324
devices/src/usb/host_backend/host_backend_device_provider.rs
Normal file
324
devices/src/usb/host_backend/host_backend_device_provider.rs
Normal file
|
@ -0,0 +1,324 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::context::Context;
|
||||
use super::error::*;
|
||||
use super::host_device::HostDevice;
|
||||
use super::hotplug::HotplugHandler;
|
||||
use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::time::Duration;
|
||||
use sys_util::net::UnixSeqpacket;
|
||||
use sys_util::WatchingEvents;
|
||||
use usb::xhci::usb_hub::UsbHub;
|
||||
use usb::xhci::xhci_backend_device_provider::XhciBackendDeviceProvider;
|
||||
use utils::AsyncJobQueue;
|
||||
use utils::{EventHandler, EventLoop, FailHandle};
|
||||
use vm_control::{UsbControlCommand, UsbControlResult, UsbControlSocket};
|
||||
|
||||
const SOCKET_TIMEOUT_MS: u64 = 2000;
|
||||
|
||||
/// Host backend device provider is a xhci backend device provider that would provide pass through
|
||||
/// devices.
|
||||
pub enum HostBackendDeviceProvider {
|
||||
// The provider is created but not yet started.
|
||||
Created {
|
||||
sock: MsgSocket<UsbControlResult, UsbControlCommand>,
|
||||
},
|
||||
// The provider is started on an event loop.
|
||||
Started {
|
||||
inner: Arc<ProviderInner>,
|
||||
},
|
||||
// The provider has failed.
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl HostBackendDeviceProvider {
|
||||
pub fn new() -> Result<(UsbControlSocket, HostBackendDeviceProvider)> {
|
||||
let (child_sock, control_sock) = UnixSeqpacket::pair().map_err(Error::CreateControlSock)?;
|
||||
control_sock
|
||||
.set_write_timeout(Some(Duration::from_millis(SOCKET_TIMEOUT_MS)))
|
||||
.map_err(Error::SetupControlSock)?;
|
||||
control_sock
|
||||
.set_read_timeout(Some(Duration::from_millis(SOCKET_TIMEOUT_MS)))
|
||||
.map_err(Error::SetupControlSock)?;
|
||||
|
||||
let provider = HostBackendDeviceProvider::Created {
|
||||
sock: MsgSocket::new(child_sock),
|
||||
};
|
||||
Ok((MsgSocket::new(control_sock), provider))
|
||||
}
|
||||
|
||||
fn start_helper(
|
||||
&mut self,
|
||||
fail_handle: Arc<FailHandle>,
|
||||
event_loop: Arc<EventLoop>,
|
||||
hub: Arc<UsbHub>,
|
||||
) -> Result<()> {
|
||||
match mem::replace(self, HostBackendDeviceProvider::Failed) {
|
||||
HostBackendDeviceProvider::Created { sock } => {
|
||||
let ctx = Context::new(event_loop.clone())?;
|
||||
let hotplug_handler = HotplugHandler::new(hub.clone());
|
||||
ctx.set_hotplug_handler(hotplug_handler);
|
||||
let job_queue =
|
||||
AsyncJobQueue::init(&event_loop).map_err(Error::StartAsyncJobQueue)?;
|
||||
let inner = Arc::new(ProviderInner::new(fail_handle, job_queue, ctx, sock, hub));
|
||||
let handler: Arc<EventHandler> = inner.clone();
|
||||
event_loop
|
||||
.add_event(
|
||||
&inner.sock,
|
||||
WatchingEvents::empty().set_read(),
|
||||
Arc::downgrade(&handler),
|
||||
)
|
||||
.map_err(Error::AddToEventLoop)?;
|
||||
*self = HostBackendDeviceProvider::Started { inner };
|
||||
Ok(())
|
||||
}
|
||||
HostBackendDeviceProvider::Started { inner: _ } => {
|
||||
error!("Host backend provider has already started");
|
||||
Err(Error::BadBackendProviderState)
|
||||
}
|
||||
HostBackendDeviceProvider::Failed => {
|
||||
error!("Host backend provider has already failed");
|
||||
Err(Error::BadBackendProviderState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XhciBackendDeviceProvider for HostBackendDeviceProvider {
|
||||
fn start(
|
||||
&mut self,
|
||||
fail_handle: Arc<FailHandle>,
|
||||
event_loop: Arc<EventLoop>,
|
||||
hub: Arc<UsbHub>,
|
||||
) -> std::result::Result<(), ()> {
|
||||
self.start_helper(fail_handle, event_loop, hub)
|
||||
.map_err(|e| {
|
||||
error!("failed to start host backend device provider: {}", e);
|
||||
()
|
||||
})
|
||||
}
|
||||
|
||||
fn keep_fds(&self) -> Vec<RawFd> {
|
||||
match self {
|
||||
HostBackendDeviceProvider::Created { sock } => vec![sock.as_raw_fd()],
|
||||
_ => {
|
||||
error!(
|
||||
"Trying to get keepfds when HostBackendDeviceProvider is not in created state"
|
||||
);
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ProviderInner listens to control socket.
|
||||
pub struct ProviderInner {
|
||||
fail_handle: Arc<FailHandle>,
|
||||
job_queue: Arc<AsyncJobQueue>,
|
||||
ctx: Context,
|
||||
sock: MsgSocket<UsbControlResult, UsbControlCommand>,
|
||||
usb_hub: Arc<UsbHub>,
|
||||
}
|
||||
|
||||
impl ProviderInner {
|
||||
fn new(
|
||||
fail_handle: Arc<FailHandle>,
|
||||
job_queue: Arc<AsyncJobQueue>,
|
||||
ctx: Context,
|
||||
sock: MsgSocket<UsbControlResult, UsbControlCommand>,
|
||||
usb_hub: Arc<UsbHub>,
|
||||
) -> ProviderInner {
|
||||
ProviderInner {
|
||||
fail_handle,
|
||||
job_queue,
|
||||
ctx,
|
||||
sock,
|
||||
usb_hub,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event_helper(&self, _fd: RawFd) -> Result<()> {
|
||||
let cmd = self.sock.recv().map_err(Error::ReadControlSock)?;
|
||||
match cmd {
|
||||
UsbControlCommand::AttachDevice {
|
||||
bus,
|
||||
addr,
|
||||
vid,
|
||||
pid,
|
||||
fd: usb_fd,
|
||||
} => {
|
||||
let _ = usb_fd;
|
||||
#[cfg(not(feature = "sandboxed-libusb"))]
|
||||
let device = match self.ctx.get_device(bus, addr, vid, pid) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
error!(
|
||||
"cannot get device bus: {}, addr: {}, vid: {}, pid: {}",
|
||||
bus, addr, vid, pid
|
||||
);
|
||||
// The send failure will be logged, but event loop still think the event is
|
||||
// handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::NoSuchDevice)
|
||||
.map_err(Error::WriteControlSock)?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "sandboxed-libusb")]
|
||||
let (device, device_handle) = {
|
||||
use vm_control::MaybeOwnedFd;
|
||||
|
||||
let usb_file = match usb_fd {
|
||||
Some(MaybeOwnedFd::Owned(file)) => file,
|
||||
_ => {
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::FailedToOpenDevice)
|
||||
.map_err(Error::WriteControlSock);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let device_fd = usb_file.as_raw_fd();
|
||||
|
||||
let device = match self.ctx.get_device(usb_file) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
error!(
|
||||
"cannot get device bus: {}, addr: {}, vid: {}, pid: {}",
|
||||
bus, addr, vid, pid
|
||||
);
|
||||
// The send failure will be logged, but event loop still think the event
|
||||
// is handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::NoSuchDevice)
|
||||
.map_err(Error::WriteControlSock);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let device_handle = {
|
||||
// This is safe only when fd is an fd of the current device.
|
||||
match unsafe { device.open_fd(device_fd) } {
|
||||
Ok(handle) => handle,
|
||||
Err(e) => {
|
||||
error!("fail to open device: {:?}", e);
|
||||
// The send failure will be logged, but event loop still think
|
||||
// the event is handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::FailedToOpenDevice)
|
||||
.map_err(Error::WriteControlSock);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
};
|
||||
(device, device_handle)
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "sandboxed-libusb"))]
|
||||
let device_handle = match device.open() {
|
||||
Ok(handle) => handle,
|
||||
Err(e) => {
|
||||
error!("fail to open device: {:?}", e);
|
||||
// The send failure will be logged, but event loop still think the event is
|
||||
// handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::FailedToOpenDevice)
|
||||
.map_err(Error::WriteControlSock);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let device = Box::new(HostDevice::new(
|
||||
self.fail_handle.clone(),
|
||||
self.job_queue.clone(),
|
||||
device,
|
||||
device_handle,
|
||||
));
|
||||
let port = self.usb_hub.connect_backend(device);
|
||||
match port {
|
||||
Ok(port) => {
|
||||
// The send failure will be logged, but event loop still think the event is
|
||||
// handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::Ok { port })
|
||||
.map_err(Error::WriteControlSock);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("failed to connect device to hub: {}", e);
|
||||
// The send failure will be logged, but event loop still think the event is
|
||||
// handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::NoAvailablePort)
|
||||
.map_err(Error::WriteControlSock);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
UsbControlCommand::DetachDevice { port } => {
|
||||
match self.usb_hub.disconnect_port(port) {
|
||||
Ok(()) => {
|
||||
// The send failure will be logged, but event loop still think the event is
|
||||
// handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::Ok { port })
|
||||
.map_err(Error::WriteControlSock);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("failed to disconnect device from port {}: {}", port, e);
|
||||
// The send failure will be logged, but event loop still think the event is
|
||||
// handled.
|
||||
let _ = self
|
||||
.sock
|
||||
.send(&UsbControlResult::NoSuchDevice)
|
||||
.map_err(Error::WriteControlSock);
|
||||
}
|
||||
}
|
||||
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() {
|
||||
Some(ref device) => {
|
||||
let vid = device.get_vid();
|
||||
let pid = device.get_pid();
|
||||
UsbControlResult::Device {
|
||||
port: port_number,
|
||||
vid,
|
||||
pid,
|
||||
}
|
||||
}
|
||||
None => UsbControlResult::NoSuchDevice,
|
||||
},
|
||||
None => UsbControlResult::NoSuchPort,
|
||||
};
|
||||
// The send failure will be logged, but event loop still think the event is
|
||||
// handled.
|
||||
let _ = self.sock.send(&result).map_err(Error::WriteControlSock);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventHandler for ProviderInner {
|
||||
fn on_event(&self, fd: RawFd) -> std::result::Result<(), ()> {
|
||||
self.on_event_helper(fd).map_err(|e| {
|
||||
error!("host backend device provider failed: {}", e);
|
||||
()
|
||||
})
|
||||
}
|
||||
}
|
541
devices/src/usb/host_backend/host_device.rs
Normal file
541
devices/src/usb/host_backend/host_device.rs
Normal file
|
@ -0,0 +1,541 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::mem::drop;
|
||||
use std::sync::Arc;
|
||||
use sync::Mutex;
|
||||
|
||||
use super::error::*;
|
||||
use super::usb_endpoint::UsbEndpoint;
|
||||
use super::utils::{submit_transfer, update_transfer_state};
|
||||
use std::collections::HashMap;
|
||||
use usb::usb_util::device_handle::DeviceHandle;
|
||||
use usb::usb_util::error::Error as LibUsbError;
|
||||
use usb::usb_util::libusb_device::LibUsbDevice;
|
||||
use usb::usb_util::types::{
|
||||
ControlRequestDataPhaseTransferDirection, ControlRequestRecipient, ControlRequestType,
|
||||
StandardControlRequest, UsbRequestSetup,
|
||||
};
|
||||
use usb::usb_util::usb_transfer::{
|
||||
control_transfer, ControlTransferBuffer, TransferStatus, UsbTransfer,
|
||||
};
|
||||
use usb::xhci::scatter_gather_buffer::ScatterGatherBuffer;
|
||||
use usb::xhci::xhci_backend_device::{BackendType, UsbDeviceAddress, XhciBackendDevice};
|
||||
use usb::xhci::xhci_transfer::{XhciTransfer, XhciTransferState, XhciTransferType};
|
||||
use utils::AsyncJobQueue;
|
||||
use utils::FailHandle;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ControlEndpointState {
|
||||
/// Control endpoint should receive setup stage next.
|
||||
SetupStage,
|
||||
/// Control endpoint should receive data stage next.
|
||||
DataStage,
|
||||
/// Control endpoint should receive status stage next.
|
||||
StatusStage,
|
||||
}
|
||||
|
||||
// Types of host to device control requests. We want to handle it use libusb functions instead of
|
||||
// control transfers.
|
||||
enum HostToDeviceControlRequest {
|
||||
SetAddress,
|
||||
SetConfig,
|
||||
SetInterface,
|
||||
ClearFeature,
|
||||
// It could still be some standard control request.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl HostToDeviceControlRequest {
|
||||
/// Analyze request setup.
|
||||
pub fn analyze_request_setup(
|
||||
request_setup: &UsbRequestSetup,
|
||||
) -> Result<HostToDeviceControlRequest> {
|
||||
match request_setup.get_type().ok_or(Error::GetRequestSetupType)? {
|
||||
ControlRequestType::Standard => {}
|
||||
_ => return Ok(HostToDeviceControlRequest::Other),
|
||||
};
|
||||
|
||||
let recipient = request_setup.get_recipient();
|
||||
let standard_request = request_setup.get_standard_request();
|
||||
|
||||
match (recipient, standard_request) {
|
||||
(ControlRequestRecipient::Device, Some(StandardControlRequest::SetAddress)) => {
|
||||
Ok(HostToDeviceControlRequest::SetAddress)
|
||||
}
|
||||
|
||||
(ControlRequestRecipient::Device, Some(StandardControlRequest::SetConfiguration)) => {
|
||||
Ok(HostToDeviceControlRequest::SetConfig)
|
||||
}
|
||||
|
||||
(ControlRequestRecipient::Interface, Some(StandardControlRequest::SetInterface)) => {
|
||||
Ok(HostToDeviceControlRequest::SetInterface)
|
||||
}
|
||||
|
||||
(ControlRequestRecipient::Endpoint, Some(StandardControlRequest::ClearFeature)) => {
|
||||
Ok(HostToDeviceControlRequest::ClearFeature)
|
||||
}
|
||||
_ => Ok(HostToDeviceControlRequest::Other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Host device is a device connected to host.
|
||||
pub struct HostDevice {
|
||||
fail_handle: Arc<FailHandle>,
|
||||
// Endpoints only contains data endpoints (1 to 30). Control transfers are handled at device
|
||||
// level.
|
||||
endpoints: Vec<UsbEndpoint>,
|
||||
device: LibUsbDevice,
|
||||
device_handle: Arc<Mutex<DeviceHandle>>,
|
||||
ctl_ep_state: ControlEndpointState,
|
||||
alt_settings: HashMap<u16, u16>,
|
||||
claimed_interfaces: Vec<i32>,
|
||||
control_request_setup: UsbRequestSetup,
|
||||
buffer: Option<ScatterGatherBuffer>,
|
||||
job_queue: Arc<AsyncJobQueue>,
|
||||
}
|
||||
|
||||
impl Drop for HostDevice {
|
||||
fn drop(&mut self) {
|
||||
self.release_interfaces();
|
||||
}
|
||||
}
|
||||
|
||||
impl HostDevice {
|
||||
/// Create a new host device.
|
||||
pub fn new(
|
||||
fail_handle: Arc<FailHandle>,
|
||||
job_queue: Arc<AsyncJobQueue>,
|
||||
device: LibUsbDevice,
|
||||
device_handle: DeviceHandle,
|
||||
) -> HostDevice {
|
||||
let device = HostDevice {
|
||||
fail_handle,
|
||||
endpoints: vec![],
|
||||
device,
|
||||
device_handle: Arc::new(Mutex::new(device_handle)),
|
||||
ctl_ep_state: ControlEndpointState::SetupStage,
|
||||
alt_settings: HashMap::new(),
|
||||
claimed_interfaces: vec![],
|
||||
control_request_setup: UsbRequestSetup::new(0, 0, 0, 0, 0),
|
||||
buffer: None,
|
||||
job_queue,
|
||||
};
|
||||
device
|
||||
}
|
||||
|
||||
fn get_interface_number_of_active_config(&self) -> i32 {
|
||||
match self.device.get_active_config_descriptor() {
|
||||
Err(LibUsbError::NotFound) => {
|
||||
usb_debug!("device is in unconfigured state");
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
// device might be disconnected now.
|
||||
error!("unexpected error: {:?}", e);
|
||||
0
|
||||
}
|
||||
Ok(descriptor) => descriptor.bNumInterfaces as i32,
|
||||
}
|
||||
}
|
||||
|
||||
fn release_interfaces(&mut self) {
|
||||
for i in &self.claimed_interfaces {
|
||||
if let Err(e) = self.device_handle.lock().release_interface(*i) {
|
||||
error!("could not release interface: {:?}", e);
|
||||
}
|
||||
}
|
||||
self.claimed_interfaces = Vec::new();
|
||||
}
|
||||
|
||||
fn handle_control_transfer(&mut self, transfer: XhciTransfer) -> Result<()> {
|
||||
let xhci_transfer = Arc::new(transfer);
|
||||
match xhci_transfer
|
||||
.get_transfer_type()
|
||||
.map_err(Error::GetXhciTransferType)?
|
||||
{
|
||||
XhciTransferType::SetupStage(setup) => {
|
||||
if self.ctl_ep_state != ControlEndpointState::SetupStage {
|
||||
error!("Control endpoint is in an inconsistant state");
|
||||
return Ok(());
|
||||
}
|
||||
usb_debug!("setup stage setup buffer: {:?}", setup);
|
||||
self.control_request_setup = setup;
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Completed, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
self.ctl_ep_state = ControlEndpointState::DataStage;
|
||||
}
|
||||
XhciTransferType::DataStage(buffer) => {
|
||||
if self.ctl_ep_state != ControlEndpointState::DataStage {
|
||||
error!("Control endpoint is in an inconsistant state");
|
||||
return Ok(());
|
||||
}
|
||||
self.buffer = Some(buffer);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Completed, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
self.ctl_ep_state = ControlEndpointState::StatusStage;
|
||||
}
|
||||
XhciTransferType::StatusStage => {
|
||||
if self.ctl_ep_state == ControlEndpointState::SetupStage {
|
||||
error!("Control endpoint is in an inconsistant state");
|
||||
return Ok(());
|
||||
}
|
||||
let buffer = self.buffer.take();
|
||||
match self.control_request_setup.get_direction() {
|
||||
Some(ControlRequestDataPhaseTransferDirection::HostToDevice) => {
|
||||
match HostToDeviceControlRequest::analyze_request_setup(
|
||||
&self.control_request_setup,
|
||||
)? {
|
||||
HostToDeviceControlRequest::Other => {
|
||||
let mut control_transfer = control_transfer(0);
|
||||
control_transfer
|
||||
.buffer_mut()
|
||||
.set_request_setup(&self.control_request_setup);
|
||||
if let Some(buffer) = buffer {
|
||||
buffer
|
||||
.read(&mut control_transfer.buffer_mut().data_buffer)
|
||||
.map_err(Error::ReadBuffer)?;
|
||||
}
|
||||
let tmp_transfer = xhci_transfer.clone();
|
||||
let callback = move |t: UsbTransfer<ControlTransferBuffer>| {
|
||||
update_transfer_state(&xhci_transfer, &t)?;
|
||||
let state = xhci_transfer.state().lock();
|
||||
match *state {
|
||||
XhciTransferState::Cancelled => {
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Cancelled, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
XhciTransferState::Completed => {
|
||||
let status = t.status();
|
||||
let actual_length = t.actual_length();
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&status, actual_length as u32)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
_ => {
|
||||
// update_transfer_state is already invoked before
|
||||
// match. This transfer could only be `Cancelled` or
|
||||
// `Completed`. Any other states means there is a bug
|
||||
// in crosvm implementation.
|
||||
error!("should not take this branch");
|
||||
return Err(Error::BadXhciTransferState);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
let fail_handle = self.fail_handle.clone();
|
||||
control_transfer.set_callback(
|
||||
move |t: UsbTransfer<ControlTransferBuffer>| match callback(t) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("control transfer callback failed {:?}", e);
|
||||
fail_handle.fail();
|
||||
}
|
||||
},
|
||||
);
|
||||
submit_transfer(
|
||||
self.fail_handle.clone(),
|
||||
&self.job_queue,
|
||||
tmp_transfer,
|
||||
&self.device_handle,
|
||||
control_transfer,
|
||||
)?;
|
||||
}
|
||||
HostToDeviceControlRequest::SetAddress => {
|
||||
usb_debug!("host device handling set address");
|
||||
let addr = self.control_request_setup.value as u32;
|
||||
self.set_address(addr);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Completed, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
HostToDeviceControlRequest::SetConfig => {
|
||||
usb_debug!("host device handling set config");
|
||||
let status = self.set_config()?;
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&status, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
HostToDeviceControlRequest::SetInterface => {
|
||||
usb_debug!("host device handling set interface");
|
||||
let status = self.set_interface()?;
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&status, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
HostToDeviceControlRequest::ClearFeature => {
|
||||
usb_debug!("host device handling clear feature");
|
||||
let status = self.clear_feature()?;
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&status, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(ControlRequestDataPhaseTransferDirection::DeviceToHost) => {
|
||||
let mut control_transfer = control_transfer(0);
|
||||
control_transfer
|
||||
.buffer_mut()
|
||||
.set_request_setup(&self.control_request_setup);
|
||||
let tmp_transfer = xhci_transfer.clone();
|
||||
let callback = move |t: UsbTransfer<ControlTransferBuffer>| {
|
||||
usb_debug!("setup token control transfer callback invoked");
|
||||
update_transfer_state(&xhci_transfer, &t)?;
|
||||
let state = xhci_transfer.state().lock();
|
||||
match *state {
|
||||
XhciTransferState::Cancelled => {
|
||||
usb_debug!("transfer cancelled");
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Cancelled, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
XhciTransferState::Completed => {
|
||||
let status = t.status();
|
||||
if let Some(ref buffer) = buffer {
|
||||
let _bytes = buffer
|
||||
.write(&t.buffer().data_buffer)
|
||||
.map_err(Error::WriteBuffer)?
|
||||
as u32;
|
||||
let _actual_length = t.actual_length();
|
||||
usb_debug!(
|
||||
"transfer completed bytes: {} actual length {}",
|
||||
_bytes,
|
||||
_actual_length
|
||||
);
|
||||
}
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&status, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
_ => {
|
||||
// update_transfer_state is already invoked before this match.
|
||||
// Any other states indicates a bug in crosvm.
|
||||
error!("should not take this branch");
|
||||
return Err(Error::BadXhciTransferState);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
let fail_handle = self.fail_handle.clone();
|
||||
control_transfer.set_callback(
|
||||
move |t: UsbTransfer<ControlTransferBuffer>| match callback(t) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("control transfer callback failed {:?}", e);
|
||||
fail_handle.fail();
|
||||
}
|
||||
},
|
||||
);
|
||||
submit_transfer(
|
||||
self.fail_handle.clone(),
|
||||
&self.job_queue,
|
||||
tmp_transfer,
|
||||
&self.device_handle,
|
||||
control_transfer,
|
||||
)?;
|
||||
}
|
||||
None => error!("Unknown transfer direction!"),
|
||||
}
|
||||
|
||||
self.ctl_ep_state = ControlEndpointState::SetupStage;
|
||||
}
|
||||
_ => {
|
||||
// Non control transfer should not be handled in this function.
|
||||
error!("Non control (could be noop) transfer sent to control endpoint.");
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Completed, 0)
|
||||
.map_err(Error::TransferComplete)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_config(&mut self) -> Result<TransferStatus> {
|
||||
// It's a standard, set_config, device request.
|
||||
let config = (self.control_request_setup.value & 0xff) as i32;
|
||||
usb_debug!(
|
||||
"Set config control transfer is received with config: {}",
|
||||
config
|
||||
);
|
||||
self.release_interfaces();
|
||||
let cur_config = self
|
||||
.device_handle
|
||||
.lock()
|
||||
.get_active_configuration()
|
||||
.map_err(Error::GetActiveConfig)?;
|
||||
usb_debug!("current config is: {}", cur_config);
|
||||
if config != cur_config {
|
||||
self.device_handle
|
||||
.lock()
|
||||
.set_active_configuration(config)
|
||||
.map_err(Error::SetActiveConfig)?;
|
||||
}
|
||||
self.claim_interfaces();
|
||||
self.create_endpoints()?;
|
||||
Ok(TransferStatus::Completed)
|
||||
}
|
||||
|
||||
fn set_interface(&mut self) -> Result<TransferStatus> {
|
||||
usb_debug!("set interface");
|
||||
// It's a standard, set_interface, interface request.
|
||||
let interface = self.control_request_setup.index;
|
||||
let alt_setting = self.control_request_setup.value;
|
||||
self.device_handle
|
||||
.lock()
|
||||
.set_interface_alt_setting(interface as i32, alt_setting as i32)
|
||||
.map_err(Error::SetInterfaceAltSetting)?;
|
||||
self.alt_settings.insert(interface, alt_setting);
|
||||
self.create_endpoints()?;
|
||||
Ok(TransferStatus::Completed)
|
||||
}
|
||||
|
||||
fn clear_feature(&mut self) -> Result<TransferStatus> {
|
||||
usb_debug!("clear feature");
|
||||
let request_setup = &self.control_request_setup;
|
||||
// It's a standard, clear_feature, endpoint request.
|
||||
const STD_FEATURE_ENDPOINT_HALT: u16 = 0;
|
||||
if request_setup.value == STD_FEATURE_ENDPOINT_HALT {
|
||||
self.device_handle
|
||||
.lock()
|
||||
.clear_halt(request_setup.index as u8)
|
||||
.map_err(Error::ClearHalt)?;
|
||||
}
|
||||
Ok(TransferStatus::Completed)
|
||||
}
|
||||
|
||||
fn claim_interfaces(&mut self) {
|
||||
for i in 0..self.get_interface_number_of_active_config() {
|
||||
match self.device_handle.lock().claim_interface(i) {
|
||||
Ok(()) => {
|
||||
usb_debug!("claimed interface {}", i);
|
||||
self.claimed_interfaces.push(i);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("unable to claim interface {}: {:?}", i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_endpoints(&mut self) -> Result<()> {
|
||||
self.endpoints = Vec::new();
|
||||
let config_descriptor = match self.device.get_active_config_descriptor() {
|
||||
Err(e) => {
|
||||
error!("device might be disconnected: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(descriptor) => descriptor,
|
||||
};
|
||||
for i in &self.claimed_interfaces {
|
||||
let alt_setting = self.alt_settings.get(&(*i as u16)).unwrap_or(&0);
|
||||
let interface = config_descriptor
|
||||
.get_interface_descriptor(*i as u8, *alt_setting as i32)
|
||||
.ok_or(Error::GetInterfaceDescriptor((*i, *alt_setting)))?;
|
||||
for ep_idx in 0..interface.bNumEndpoints {
|
||||
let ep_dp = interface
|
||||
.endpoint_descriptor(ep_idx)
|
||||
.ok_or(Error::GetEndpointDescriptor(ep_idx))?;
|
||||
let ep_num = ep_dp.get_endpoint_number();
|
||||
if ep_num == 0 {
|
||||
usb_debug!("endpoint 0 in endpoint descriptors");
|
||||
continue;
|
||||
}
|
||||
let direction = ep_dp.get_direction();
|
||||
let ty = ep_dp.get_endpoint_type().ok_or(Error::GetEndpointType)?;
|
||||
self.endpoints.push(UsbEndpoint::new(
|
||||
self.fail_handle.clone(),
|
||||
self.job_queue.clone(),
|
||||
self.device_handle.clone(),
|
||||
ep_num,
|
||||
direction,
|
||||
ty,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn submit_transfer_helper(&mut self, transfer: XhciTransfer) -> Result<()> {
|
||||
if transfer.get_endpoint_number() == 0 {
|
||||
return self.handle_control_transfer(transfer);
|
||||
}
|
||||
for ep in &self.endpoints {
|
||||
if ep.match_ep(transfer.get_endpoint_number(), transfer.get_transfer_dir()) {
|
||||
return ep.handle_transfer(transfer);
|
||||
}
|
||||
}
|
||||
warn!("Could not find endpoint for transfer");
|
||||
transfer
|
||||
.on_transfer_complete(&TransferStatus::Error, 0)
|
||||
.map_err(Error::TransferComplete)
|
||||
}
|
||||
}
|
||||
|
||||
impl XhciBackendDevice for HostDevice {
|
||||
fn get_backend_type(&self) -> BackendType {
|
||||
let d = match self.device.get_device_descriptor() {
|
||||
Ok(d) => d,
|
||||
Err(_) => return BackendType::Usb2,
|
||||
};
|
||||
|
||||
// See definition of bcdUsb.
|
||||
const USB3_MASK: u16 = 0x0300;
|
||||
match d.bcdUSB & USB3_MASK {
|
||||
USB3_MASK => BackendType::Usb3,
|
||||
_ => BackendType::Usb2,
|
||||
}
|
||||
}
|
||||
|
||||
fn host_bus(&self) -> u8 {
|
||||
self.device.get_bus_number()
|
||||
}
|
||||
|
||||
fn host_address(&self) -> u8 {
|
||||
self.device.get_address()
|
||||
}
|
||||
|
||||
fn get_vid(&self) -> u16 {
|
||||
match self.device.get_device_descriptor() {
|
||||
Ok(d) => d.idVendor,
|
||||
Err(e) => {
|
||||
error!("cannot get device descriptor: {:?}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pid(&self) -> u16 {
|
||||
match self.device.get_device_descriptor() {
|
||||
Ok(d) => d.idProduct,
|
||||
Err(e) => {
|
||||
error!("cannot get device descriptor: {:?}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn submit_transfer(&mut self, transfer: XhciTransfer) -> std::result::Result<(), ()> {
|
||||
self.submit_transfer_helper(transfer).map_err(|e| {
|
||||
error!("failed to submit transfer: {}", e);
|
||||
()
|
||||
})
|
||||
}
|
||||
|
||||
fn set_address(&mut self, _address: UsbDeviceAddress) {
|
||||
// It's a standard, set_address, device request. We do nothing here. As described in XHCI
|
||||
// spec. See set address command ring trb.
|
||||
usb_debug!(
|
||||
"Set address control transfer is received with address: {}",
|
||||
_address
|
||||
);
|
||||
}
|
||||
}
|
45
devices/src/usb/host_backend/hotplug.rs
Normal file
45
devices/src/usb/host_backend/hotplug.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use usb::usb_util::hotplug::{HotplugEvent, UsbHotplugHandler};
|
||||
use usb::usb_util::libusb_device::LibUsbDevice;
|
||||
use usb::xhci::usb_hub::UsbHub;
|
||||
|
||||
pub struct HotplugHandler {
|
||||
hub: Arc<UsbHub>,
|
||||
}
|
||||
|
||||
impl HotplugHandler {
|
||||
pub fn new(hub: Arc<UsbHub>) -> Self {
|
||||
HotplugHandler { hub }
|
||||
}
|
||||
}
|
||||
|
||||
impl UsbHotplugHandler for HotplugHandler {
|
||||
fn hotplug_event(&self, device: LibUsbDevice, event: HotplugEvent) {
|
||||
match event {
|
||||
HotplugEvent::DeviceLeft => {
|
||||
let bus = device.get_bus_number();
|
||||
let address = device.get_address();
|
||||
let descriptor = match device.get_device_descriptor() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
error!("cannot get device descriptor: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let vid = descriptor.idVendor;
|
||||
let pid = descriptor.idProduct;
|
||||
|
||||
if let Err(e) = self.hub.try_detach(bus, address, vid, pid) {
|
||||
error!("device left event triggered failed detach from hub: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
11
devices/src/usb/host_backend/mod.rs
Normal file
11
devices/src/usb/host_backend/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
pub mod context;
|
||||
mod error;
|
||||
pub mod host_backend_device_provider;
|
||||
pub mod host_device;
|
||||
mod hotplug;
|
||||
pub mod usb_endpoint;
|
||||
mod utils;
|
253
devices/src/usb/host_backend/usb_endpoint.rs
Normal file
253
devices/src/usb/host_backend/usb_endpoint.rs
Normal file
|
@ -0,0 +1,253 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use sync::Mutex;
|
||||
|
||||
use super::error::*;
|
||||
use super::utils::{submit_transfer, update_transfer_state};
|
||||
use usb::usb_util::device_handle::DeviceHandle;
|
||||
use usb::usb_util::types::{EndpointDirection, EndpointType, ENDPOINT_DIRECTION_OFFSET};
|
||||
use usb::usb_util::usb_transfer::{
|
||||
bulk_transfer, interrupt_transfer, BulkTransferBuffer, TransferStatus, UsbTransfer,
|
||||
};
|
||||
use usb::xhci::scatter_gather_buffer::ScatterGatherBuffer;
|
||||
use usb::xhci::xhci_transfer::{
|
||||
TransferDirection, XhciTransfer, XhciTransferState, XhciTransferType,
|
||||
};
|
||||
use utils::AsyncJobQueue;
|
||||
use utils::FailHandle;
|
||||
|
||||
/// Isochronous, Bulk or Interrupt endpoint.
|
||||
pub struct UsbEndpoint {
|
||||
fail_handle: Arc<FailHandle>,
|
||||
job_queue: Arc<AsyncJobQueue>,
|
||||
device_handle: Arc<Mutex<DeviceHandle>>,
|
||||
endpoint_number: u8,
|
||||
direction: EndpointDirection,
|
||||
ty: EndpointType,
|
||||
}
|
||||
|
||||
impl UsbEndpoint {
|
||||
/// Create new endpoint. This function will panic if endpoint type is control.
|
||||
pub fn new(
|
||||
fail_handle: Arc<FailHandle>,
|
||||
job_queue: Arc<AsyncJobQueue>,
|
||||
device_handle: Arc<Mutex<DeviceHandle>>,
|
||||
endpoint_number: u8,
|
||||
direction: EndpointDirection,
|
||||
ty: EndpointType,
|
||||
) -> UsbEndpoint {
|
||||
assert!(ty != EndpointType::Control);
|
||||
UsbEndpoint {
|
||||
fail_handle,
|
||||
job_queue,
|
||||
device_handle,
|
||||
endpoint_number,
|
||||
direction,
|
||||
ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn ep_addr(&self) -> u8 {
|
||||
self.endpoint_number | ((self.direction as u8) << ENDPOINT_DIRECTION_OFFSET)
|
||||
}
|
||||
|
||||
/// Returns true is this endpoint matches number and direction.
|
||||
pub fn match_ep(&self, endpoint_number: u8, dir: TransferDirection) -> bool {
|
||||
let self_dir = match self.direction {
|
||||
EndpointDirection::HostToDevice => TransferDirection::Out,
|
||||
EndpointDirection::DeviceToHost => TransferDirection::In,
|
||||
};
|
||||
self.endpoint_number == endpoint_number && self_dir == dir
|
||||
}
|
||||
|
||||
/// Handle a xhci transfer.
|
||||
pub fn handle_transfer(&self, transfer: XhciTransfer) -> Result<()> {
|
||||
let buffer = match transfer
|
||||
.get_transfer_type()
|
||||
.map_err(Error::GetXhciTransferType)?
|
||||
{
|
||||
XhciTransferType::Normal(buffer) => buffer,
|
||||
XhciTransferType::Noop => {
|
||||
return transfer
|
||||
.on_transfer_complete(&TransferStatus::Completed, 0)
|
||||
.map_err(Error::TransferComplete);
|
||||
}
|
||||
_ => {
|
||||
error!("unhandled xhci transfer type by usb endpoint");
|
||||
return transfer
|
||||
.on_transfer_complete(&TransferStatus::Error, 0)
|
||||
.map_err(Error::TransferComplete);
|
||||
}
|
||||
};
|
||||
|
||||
match self.ty {
|
||||
EndpointType::Bulk => {
|
||||
self.handle_bulk_transfer(transfer, buffer)?;
|
||||
}
|
||||
EndpointType::Interrupt => {
|
||||
self.handle_interrupt_transfer(transfer, buffer)?;
|
||||
}
|
||||
_ => {
|
||||
return transfer
|
||||
.on_transfer_complete(&TransferStatus::Error, 0)
|
||||
.map_err(Error::TransferComplete);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_bulk_transfer(
|
||||
&self,
|
||||
xhci_transfer: XhciTransfer,
|
||||
buffer: ScatterGatherBuffer,
|
||||
) -> Result<()> {
|
||||
let usb_transfer =
|
||||
bulk_transfer(self.ep_addr(), 0, buffer.len().map_err(Error::BufferLen)?);
|
||||
self.do_handle_transfer(xhci_transfer, usb_transfer, buffer)
|
||||
}
|
||||
|
||||
fn handle_interrupt_transfer(
|
||||
&self,
|
||||
xhci_transfer: XhciTransfer,
|
||||
buffer: ScatterGatherBuffer,
|
||||
) -> Result<()> {
|
||||
let usb_transfer =
|
||||
interrupt_transfer(self.ep_addr(), 0, buffer.len().map_err(Error::BufferLen)?);
|
||||
self.do_handle_transfer(xhci_transfer, usb_transfer, buffer)
|
||||
}
|
||||
|
||||
fn do_handle_transfer(
|
||||
&self,
|
||||
xhci_transfer: XhciTransfer,
|
||||
mut usb_transfer: UsbTransfer<BulkTransferBuffer>,
|
||||
buffer: ScatterGatherBuffer,
|
||||
) -> Result<()> {
|
||||
let xhci_transfer = Arc::new(xhci_transfer);
|
||||
let tmp_transfer = xhci_transfer.clone();
|
||||
match self.direction {
|
||||
EndpointDirection::HostToDevice => {
|
||||
// Read data from ScatterGatherBuffer to a continuous memory.
|
||||
buffer
|
||||
.read(usb_transfer.buffer_mut().as_mut_slice())
|
||||
.map_err(Error::ReadBuffer)?;
|
||||
usb_debug!(
|
||||
"out transfer ep_addr {:#x}, buffer len {:?}, data {:#x?}",
|
||||
self.ep_addr(),
|
||||
buffer.len(),
|
||||
usb_transfer.buffer_mut().as_mut_slice()
|
||||
);
|
||||
let callback = move |t: UsbTransfer<BulkTransferBuffer>| {
|
||||
usb_debug!("out transfer callback");
|
||||
update_transfer_state(&xhci_transfer, &t)?;
|
||||
let state = xhci_transfer.state().lock();
|
||||
match *state {
|
||||
XhciTransferState::Cancelled => {
|
||||
usb_debug!("transfer has been cancelled");
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Cancelled, 0)
|
||||
.map_err(Error::TransferComplete)
|
||||
}
|
||||
XhciTransferState::Completed => {
|
||||
let status = t.status();
|
||||
let actual_length = t.actual_length();
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&status, actual_length as u32)
|
||||
.map_err(Error::TransferComplete)
|
||||
}
|
||||
_ => {
|
||||
error!("xhci trasfer state (host to device) is invalid");
|
||||
Err(Error::BadXhciTransferState)
|
||||
}
|
||||
}
|
||||
};
|
||||
let fail_handle = self.fail_handle.clone();
|
||||
usb_transfer.set_callback(
|
||||
move |t: UsbTransfer<BulkTransferBuffer>| match callback(t) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("bulk transfer callback failed: {:?}", e);
|
||||
fail_handle.fail();
|
||||
}
|
||||
},
|
||||
);
|
||||
submit_transfer(
|
||||
self.fail_handle.clone(),
|
||||
&self.job_queue,
|
||||
tmp_transfer,
|
||||
&self.device_handle,
|
||||
usb_transfer,
|
||||
)?;
|
||||
}
|
||||
EndpointDirection::DeviceToHost => {
|
||||
usb_debug!(
|
||||
"in transfer ep_addr {:#x}, buffer len {:?}",
|
||||
self.ep_addr(),
|
||||
buffer.len()
|
||||
);
|
||||
let _addr = self.ep_addr();
|
||||
let callback = move |t: UsbTransfer<BulkTransferBuffer>| {
|
||||
usb_debug!(
|
||||
"ep {:#x} in transfer data {:?}",
|
||||
_addr,
|
||||
t.buffer().as_slice()
|
||||
);
|
||||
update_transfer_state(&xhci_transfer, &t)?;
|
||||
let state = xhci_transfer.state().lock();
|
||||
match *state {
|
||||
XhciTransferState::Cancelled => {
|
||||
usb_debug!("transfer has been cancelled");
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&TransferStatus::Cancelled, 0)
|
||||
.map_err(Error::TransferComplete)
|
||||
}
|
||||
XhciTransferState::Completed => {
|
||||
let status = t.status();
|
||||
let actual_length = t.actual_length() as usize;
|
||||
let copied_length = buffer
|
||||
.write(t.buffer().as_slice())
|
||||
.map_err(Error::WriteBuffer)?;
|
||||
let actual_length = cmp::min(actual_length, copied_length);
|
||||
drop(state);
|
||||
xhci_transfer
|
||||
.on_transfer_complete(&status, actual_length as u32)
|
||||
.map_err(Error::TransferComplete)
|
||||
}
|
||||
_ => {
|
||||
// update state is already invoked. This match should not be in any
|
||||
// other state.
|
||||
error!("xhci trasfer state (device to host) is invalid");
|
||||
Err(Error::BadXhciTransferState)
|
||||
}
|
||||
}
|
||||
};
|
||||
let fail_handle = self.fail_handle.clone();
|
||||
|
||||
usb_transfer.set_callback(
|
||||
move |t: UsbTransfer<BulkTransferBuffer>| match callback(t) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("bulk transfer callback {:?}", e);
|
||||
fail_handle.fail();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
submit_transfer(
|
||||
self.fail_handle.clone(),
|
||||
&self.job_queue,
|
||||
tmp_transfer,
|
||||
&self.device_handle,
|
||||
usb_transfer,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
109
devices/src/usb/host_backend/utils.rs
Normal file
109
devices/src/usb/host_backend/utils.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use sync::Mutex;
|
||||
|
||||
use super::error::*;
|
||||
use usb::usb_util::device_handle::DeviceHandle;
|
||||
use usb::usb_util::usb_transfer::{TransferStatus, UsbTransfer, UsbTransferBuffer};
|
||||
use usb::xhci::xhci_transfer::{XhciTransfer, XhciTransferState};
|
||||
use utils::AsyncJobQueue;
|
||||
use utils::FailHandle;
|
||||
|
||||
/// Helper function to update xhci_transfer state.
|
||||
pub fn update_transfer_state<T: UsbTransferBuffer>(
|
||||
xhci_transfer: &Arc<XhciTransfer>,
|
||||
usb_transfer: &UsbTransfer<T>,
|
||||
) -> Result<()> {
|
||||
let status = usb_transfer.status();
|
||||
let mut state = xhci_transfer.state().lock();
|
||||
|
||||
if status == TransferStatus::Cancelled {
|
||||
*state = XhciTransferState::Cancelled;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match *state {
|
||||
XhciTransferState::Cancelling => {
|
||||
*state = XhciTransferState::Cancelled;
|
||||
}
|
||||
XhciTransferState::Submitted { cancel_callback: _ } => {
|
||||
*state = XhciTransferState::Completed;
|
||||
}
|
||||
_ => {
|
||||
error!("xhci trasfer state is invalid");
|
||||
*state = XhciTransferState::Completed;
|
||||
return Err(Error::BadXhciTransferState);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to submit usb_transfer to device handle.
|
||||
pub fn submit_transfer<T: UsbTransferBuffer>(
|
||||
fail_handle: Arc<FailHandle>,
|
||||
job_queue: &Arc<AsyncJobQueue>,
|
||||
xhci_transfer: Arc<XhciTransfer>,
|
||||
device_handle: &Arc<Mutex<DeviceHandle>>,
|
||||
usb_transfer: UsbTransfer<T>,
|
||||
) -> Result<()> {
|
||||
let transfer_status = {
|
||||
// We need to hold the lock to avoid race condition.
|
||||
// While we are trying to submit the transfer, another thread might want to cancel the same
|
||||
// transfer. Holding the lock here makes sure one of them is cancelled.
|
||||
let mut state = xhci_transfer.state().lock();
|
||||
match mem::replace(&mut *state, XhciTransferState::Cancelled) {
|
||||
XhciTransferState::Created => {
|
||||
let canceller = usb_transfer.get_canceller();
|
||||
// TODO(jkwang) refactor canceller to return Cancel::Ok or Cancel::Err.
|
||||
let cancel_callback = Box::new(move || match canceller.try_cancel() {
|
||||
true => {
|
||||
usb_debug!("cancel issued to libusb backend");
|
||||
}
|
||||
false => {
|
||||
usb_debug!("fail to cancel");
|
||||
}
|
||||
});
|
||||
*state = XhciTransferState::Submitted { cancel_callback };
|
||||
match device_handle.lock().submit_async_transfer(usb_transfer) {
|
||||
Err(e) => {
|
||||
error!("fail to submit transfer {:?}", e);
|
||||
*state = XhciTransferState::Completed;
|
||||
TransferStatus::NoDevice
|
||||
}
|
||||
// If it's submitted, we don't need to send on_transfer_complete now.
|
||||
Ok(_) => return Ok(()),
|
||||
}
|
||||
}
|
||||
XhciTransferState::Cancelled => {
|
||||
warn!("Transfer is already cancelled");
|
||||
TransferStatus::Cancelled
|
||||
}
|
||||
_ => {
|
||||
// The transfer could not be in the following states:
|
||||
// Submitted: A transfer should only be submitted once.
|
||||
// Cancelling: Transfer is cancelling only when it's submitted and someone is
|
||||
// trying to cancel it.
|
||||
// Completed: A completed transfer should not be submitted again.
|
||||
error!("xhci trasfer state is invalid");
|
||||
return Err(Error::BadXhciTransferState);
|
||||
}
|
||||
}
|
||||
};
|
||||
// We are holding locks to of backends, we want to call on_transfer_complete
|
||||
// without any lock.
|
||||
job_queue
|
||||
.queue_job(
|
||||
move || match xhci_transfer.on_transfer_complete(&transfer_status, 0) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("transfer complete failed: {:?}", e);
|
||||
fail_handle.fail();
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(Error::QueueAsyncJob)
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
extern crate usb_util;
|
||||
|
||||
#[macro_use]
|
||||
mod log;
|
||||
mod host_backend;
|
||||
pub mod xhci;
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
extern crate usb_util;
|
||||
|
||||
mod command_ring_controller;
|
||||
mod device_slot;
|
||||
mod event_ring;
|
||||
|
@ -12,12 +10,12 @@ mod intr_resample_handler;
|
|||
mod ring_buffer;
|
||||
mod ring_buffer_controller;
|
||||
mod ring_buffer_stop_cb;
|
||||
mod scatter_gather_buffer;
|
||||
pub mod scatter_gather_buffer;
|
||||
mod transfer_ring_controller;
|
||||
mod usb_hub;
|
||||
pub mod usb_hub;
|
||||
mod xhci_abi;
|
||||
mod xhci_abi_schema;
|
||||
mod xhci_backend_device;
|
||||
mod xhci_backend_device_provider;
|
||||
pub mod xhci_backend_device;
|
||||
pub mod xhci_backend_device_provider;
|
||||
mod xhci_regs;
|
||||
mod xhci_transfer;
|
||||
pub mod xhci_transfer;
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
use super::interrupter::{Error as InterrupterError, Interrupter};
|
||||
use super::scatter_gather_buffer::{Error as BufferError, ScatterGatherBuffer};
|
||||
use super::usb_hub::{Error as HubError, UsbPort};
|
||||
use super::usb_util::types::UsbRequestSetup;
|
||||
use super::usb_util::usb_transfer::TransferStatus;
|
||||
use super::xhci_abi::{
|
||||
AddressedTrb, Error as TrbError, EventDataTrb, SetupStageTrb, TransferDescriptor, TrbCast,
|
||||
TrbCompletionCode, TrbType,
|
||||
|
@ -18,6 +16,8 @@ use std::mem;
|
|||
use std::sync::{Arc, Weak};
|
||||
use sync::Mutex;
|
||||
use sys_util::{Error as SysError, EventFd, GuestMemory};
|
||||
use usb::usb_util::types::UsbRequestSetup;
|
||||
use usb::usb_util::usb_transfer::TransferStatus;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
|
|
@ -3,6 +3,9 @@ name = "vm_control"
|
|||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[features]
|
||||
sandboxed-libusb = []
|
||||
|
||||
[dependencies]
|
||||
byteorder = "*"
|
||||
data_model = { path = "../data_model" }
|
||||
|
|
|
@ -23,7 +23,7 @@ use std::fs::File;
|
|||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
|
||||
use libc::{EINVAL, ENODEV};
|
||||
use libc::{EINVAL, EIO, ENODEV};
|
||||
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use kvm::{Datamatch, IoeventAddress, Vm};
|
||||
|
@ -34,6 +34,7 @@ use sys_util::{
|
|||
};
|
||||
|
||||
/// A file descriptor either borrowed or owned by this.
|
||||
#[derive(Debug)]
|
||||
pub enum MaybeOwnedFd {
|
||||
/// Owned by this enum variant, and will be destructed automatically if not moved out.
|
||||
Owned(File),
|
||||
|
@ -98,10 +99,54 @@ impl Default for VmRunMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(MsgOnSocket, Debug)]
|
||||
pub enum UsbControlCommand {
|
||||
AttachDevice {
|
||||
bus: u8,
|
||||
addr: u8,
|
||||
vid: u16,
|
||||
pid: u16,
|
||||
fd: Option<MaybeOwnedFd>,
|
||||
},
|
||||
DetachDevice {
|
||||
port: u8,
|
||||
},
|
||||
ListDevice {
|
||||
port: u8,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(MsgOnSocket, Debug)]
|
||||
pub enum UsbControlResult {
|
||||
Ok { port: u8 },
|
||||
NoAvailablePort,
|
||||
NoSuchDevice,
|
||||
NoSuchPort,
|
||||
FailedToOpenDevice,
|
||||
Device { port: u8, vid: u16, pid: u16 },
|
||||
}
|
||||
|
||||
impl Display for UsbControlResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::UsbControlResult::*;
|
||||
|
||||
match self {
|
||||
Ok { port } => write!(f, "ok {}", port),
|
||||
NoAvailablePort => write!(f, "no_available_port"),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type UsbControlSocket = MsgSocket<UsbControlCommand, UsbControlResult>;
|
||||
|
||||
/// A request to the main process to perform some operation on the VM.
|
||||
///
|
||||
/// Unless otherwise noted, each request should expect a `VmResponse::Ok` to be received on success.
|
||||
#[derive(MsgOnSocket)]
|
||||
#[derive(MsgOnSocket, Debug)]
|
||||
pub enum VmRequest {
|
||||
/// Set the size of the VM's balloon in bytes.
|
||||
BalloonAdjust(u64),
|
||||
|
@ -130,6 +175,8 @@ pub enum VmRequest {
|
|||
/// Resize a disk chosen by `disk_index` to `new_size` in bytes.
|
||||
/// `disk_index` is a 0-based count of `--disk`, `--rwdisk`, and `-r` command-line options.
|
||||
DiskResize { disk_index: usize, new_size: u64 },
|
||||
/// Command to use controller.
|
||||
UsbCommand(UsbControlCommand),
|
||||
}
|
||||
|
||||
fn register_memory(
|
||||
|
@ -268,6 +315,10 @@ impl VmRequest {
|
|||
VmResponse::Err(SysError::new(ENODEV))
|
||||
}
|
||||
}
|
||||
VmRequest::UsbCommand(ref cmd) => {
|
||||
error!("not implemented yet");
|
||||
VmResponse::Ok
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,7 +326,7 @@ impl VmRequest {
|
|||
/// Indication of success or failure of a `VmRequest`.
|
||||
///
|
||||
/// Success is usually indicated `VmResponse::Ok` unless there is data associated with the response.
|
||||
#[derive(MsgOnSocket)]
|
||||
#[derive(MsgOnSocket, Debug)]
|
||||
pub enum VmResponse {
|
||||
/// Indicates the request was executed successfully.
|
||||
Ok,
|
||||
|
@ -292,6 +343,8 @@ pub enum VmResponse {
|
|||
slot: u32,
|
||||
desc: GpuMemoryDesc,
|
||||
},
|
||||
/// Results of usb control commands.
|
||||
UsbResponse(UsbControlResult),
|
||||
}
|
||||
|
||||
impl Display for VmResponse {
|
||||
|
@ -311,6 +364,7 @@ impl Display for VmResponse {
|
|||
"gpu memory allocated and registered to page frame number {:#x} and memory slot {}",
|
||||
pfn, slot
|
||||
),
|
||||
UsbResponse(result) => write!(f, "usb control request get result {:?}", result),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue