USB Forwarding utility
This utility will provide USB forwarding while we work on VSoC implementation
BUG=64121751
Change-Id: I17159053ea971a6e1f69cd6af8e8e280b8337447
diff --git a/guest/usbforward/Android.mk b/guest/usbforward/Android.mk
new file mode 100644
index 0000000..77b14ed
--- /dev/null
+++ b/guest/usbforward/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_MODULE := usbforward
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := \
+ main.cpp \
+ usb_server.cpp
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ device/google/cuttlefish_kernel
+
+LOCAL_SHARED_LIBRARIES := \
+ libcuttlefish_auto_resources \
+ libcuttlefish_fs \
+ libusb \
+ libbase \
+ liblog
+
+LOCAL_MULTILIB := first
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_EXECUTABLE)
+
diff --git a/guest/usbforward/BUILD b/guest/usbforward/BUILD
new file mode 100644
index 0000000..91d968b
--- /dev/null
+++ b/guest/usbforward/BUILD
@@ -0,0 +1,7 @@
+cc_library(
+ name = "protocol",
+ hdrs = [
+ "protocol.h"
+ ],
+ visibility = ["//visibility:public"],
+)
\ No newline at end of file
diff --git a/guest/usbforward/main.cpp b/guest/usbforward/main.cpp
new file mode 100644
index 0000000..3f05812
--- /dev/null
+++ b/guest/usbforward/main.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "UsbForward"
+
+#include <cutils/log.h>
+#include <stdio.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "guest/usbforward/usb_server.h"
+
+int main(int argc, char* argv[]) {
+ if (argc == 1) {
+ printf("Usage: %s <virtio_channel>\n", argv[0]);
+ return 1;
+ }
+
+ avd::SharedFD fd =
+ avd::SharedFD::Open(argv[1], O_RDWR | O_NOCTTY);
+ if (!fd->IsOpen()) {
+ ALOGE("Could not open %s: %s", argv[1], fd->StrError());
+ return 1;
+ }
+
+ USBServer server(fd);
+ if (server.Init()) {
+ server.Serve();
+ }
+ ALOGE("Terminated.");
+ return 1;
+}
diff --git a/guest/usbforward/protocol.h b/guest/usbforward/protocol.h
new file mode 100644
index 0000000..68c514e
--- /dev/null
+++ b/guest/usbforward/protocol.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stdint.h>
+
+// Commands that can be executed over serial port.
+// Use magic value to avoid accidental interpretation of commonly seen numbers.
+enum : uint32_t {
+ // Get device list.
+ // Response format:
+ // - int32_t(num_devices)
+ // - num_devices times:
+ // - DeviceInfo{}
+ // - DeviceInfo.num_interfaces times:
+ // - InterfaceInfo{}
+ CmdDeviceList = 0xcfad0001,
+
+ // Attach specified device.
+ // Request format:
+ // - AttachRequest{}
+ // Response format:
+ // - status (0 = success).
+ CmdAttach,
+
+ // Execute command on attached USB device.
+ // Request format:
+ // - ExecuteRequest{}
+ // - if transfer direction is host -> device
+ // - uint8_t[ExecuteRequest.length]
+ // Response format:
+ // - int32_t(status)
+ // - if transfer direction is device -> host
+ // - int32_t(actual length)
+ // - uint8_t[actual length] bytes
+ CmdExecute
+};
+
+// DeviceInfo describes individual USB device that was found attached to the
+// bus.
+struct DeviceInfo {
+ uint16_t vendor_id;
+ uint16_t product_id;
+ uint16_t dev_version;
+ uint8_t dev_class;
+ uint8_t dev_subclass;
+ uint8_t dev_protocol;
+ uint8_t bus_id;
+ uint8_t dev_id;
+ uint8_t speed;
+ uint8_t num_configurations;
+ uint8_t num_interfaces;
+ uint8_t cur_configuration;
+} __attribute__((packed));
+
+// InterfaceInfo describes individual interface attached to a USB device.
+struct InterfaceInfo {
+ uint8_t if_class;
+ uint8_t if_subclass;
+ uint8_t if_protocol;
+ uint8_t if_reserved;
+} __attribute__((packed));
+
+// AttachRequest specifies which device on which bus needs to be attached.
+struct AttachRequest {
+ uint8_t bus_id;
+ uint8_t dev_id;
+} __attribute__((packed));
+
+// ExecuteRequest specifies target bus and device along with USB request.
+struct ExecuteRequest {
+ uint8_t bus_id;
+ uint8_t dev_id;
+ uint8_t type;
+ uint8_t cmd;
+ uint16_t value;
+ uint16_t index;
+ uint16_t length;
+ uint32_t timeout;
+} __attribute__((packed));
diff --git a/guest/usbforward/usb_server.cpp b/guest/usbforward/usb_server.cpp
new file mode 100644
index 0000000..50af2f7
--- /dev/null
+++ b/guest/usbforward/usb_server.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "guest/usbforward/usb_server.h"
+
+#include <string>
+#include <vector>
+#include <strings.h>
+#include <cutils/log.h>
+#include <libusb/libusb.h>
+#include "common/libs/fs/shared_select.h"
+#include "guest/usbforward/protocol.h"
+
+namespace {
+void GetDeviceInfo(libusb_device* dev, DeviceInfo* info,
+ std::vector<InterfaceInfo>* ifaces) {
+ libusb_device_descriptor desc;
+ libusb_config_descriptor* conf;
+
+ memset(info, 0, sizeof(*info));
+
+ int res = libusb_get_device_descriptor(dev, &desc);
+ if (res < 0) {
+ // This shouldn't really happen.
+ ALOGE("libusb_get_device_descriptor failed %d", res);
+ return;
+ }
+
+ res = libusb_get_active_config_descriptor(dev, &conf);
+ if (res < 0) {
+ // This shouldn't really happen.
+ ALOGE("libusb_get_active_config_descriptor failed %d", res);
+ libusb_free_config_descriptor(conf);
+ return;
+ }
+
+ info->vendor_id = desc.idVendor;
+ info->product_id = desc.idProduct;
+ info->dev_version = desc.bcdDevice;
+ info->dev_class = desc.bDeviceClass;
+ info->dev_subclass = desc.bDeviceSubClass;
+ info->dev_protocol = desc.bDeviceProtocol;
+ info->speed = libusb_get_device_speed(dev);
+ info->num_configurations = desc.bNumConfigurations;
+ info->num_interfaces = conf->bNumInterfaces;
+ info->cur_configuration = conf->bConfigurationValue;
+ info->bus_id = libusb_get_bus_number(dev);
+ info->dev_id = libusb_get_device_address(dev);
+
+ if (ifaces != nullptr) {
+ for (int ifidx = 0; ifidx < conf->bNumInterfaces; ++ifidx) {
+ const libusb_interface& iface = conf->interface[ifidx];
+ for (int altidx = 0; altidx < iface.num_altsetting; ++altidx) {
+ const libusb_interface_descriptor& alt = iface.altsetting[altidx];
+ ifaces->push_back(InterfaceInfo{alt.bInterfaceClass,
+ alt.bInterfaceSubClass,
+ alt.bInterfaceProtocol, 0});
+ }
+ }
+ }
+ libusb_free_config_descriptor(conf);
+}
+
+uint16_t MakeDeviceKey(uint8_t bus_id, uint8_t dev_id) {
+ return bus_id << 8 | dev_id;
+}
+} // anonymous namespace
+
+USBServer::~USBServer() {
+ for (const auto& dev : attached_devices_) {
+ libusb_close(dev.second);
+ }
+
+ for (const auto& dev : devices_) {
+ libusb_unref_device(dev.second);
+ }
+}
+
+bool USBServer::Init() {
+ auto res = libusb_init(nullptr);
+ if (res < 0) {
+ // res is LIBUSB_ERROR in this context.
+ ALOGE("libusb_init failed %d", res);
+ return false;
+ }
+
+ libusb_device** devices;
+ int32_t cnt = libusb_get_device_list(nullptr, &devices);
+ if (cnt <= 0) {
+ ALOGE("libusb_get_device_list failed %d", cnt);
+ return false;
+ }
+
+ for (int index = 0; index < cnt; index++) {
+ auto key = MakeDeviceKey(libusb_get_bus_number(devices[index]),
+ libusb_get_device_address(devices[index]));
+
+ libusb_ref_device(devices[index]);
+ devices_[key] = devices[index];
+ }
+
+ return true;
+}
+
+void USBServer::HandleDeviceList() {
+ // Iterate all devices and send structure for every found device.
+ DeviceInfo info;
+ int32_t size = devices_.size();
+
+ // Write header: number of devices.
+ fd_->Write(&size, sizeof(size));
+
+ for (const auto& dev : devices_) {
+ std::vector<InterfaceInfo> ifaces;
+ GetDeviceInfo(dev.second, &info, &ifaces);
+ fd_->Write(&info, sizeof(info));
+ fd_->Write(ifaces.data(), ifaces.size() * sizeof(InterfaceInfo));
+ }
+}
+
+void USBServer::HandleAttach() {
+ AttachRequest req;
+ // If disconnected prematurely, don't send response.
+ if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return;
+ // To simplify our lives, let's use status similar to USB/IP.
+ int32_t status = 1;
+
+ // Force nul-terminate path.
+ const auto key = MakeDeviceKey(req.bus_id, req.dev_id);
+ auto iter = devices_.find(key);
+ if (iter == devices_.end()) {
+ ALOGE("No device found for %x-%x", req.bus_id, req.dev_id);
+ fd_->Write(&status, sizeof(status));
+ return;
+ }
+
+ libusb_device_handle* handle;
+ auto res = libusb_open(iter->second, &handle);
+ if (res < 0) {
+ ALOGE("libusb_open failed %d", res);
+ fd_->Write(&status, sizeof(status));
+ return;
+ }
+
+ if (handle) attached_devices_[key] = handle;
+ // Indicate failure if we don't have the handle.
+ status = (handle == nullptr);
+ fd_->Write(&status, sizeof(status));
+}
+
+void USBServer::HandleExecute() {
+ ExecuteRequest req;
+ // If disconnected prematurely, don't send response.
+ if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return;
+
+ std::vector<uint8_t> data;
+ data.resize(req.length);
+
+ bool req_out = ((req.type & 0x80) == 0);
+
+ if (req_out && req.length) {
+ // If disconnected prematurely, don't send response.
+ if (fd_->Read(data.data(), req.length) != req.length) return;
+ }
+
+ int32_t status = 1;
+ int32_t len = 0;
+ auto handle_iter =
+ attached_devices_.find(MakeDeviceKey(req.bus_id, req.dev_id));
+
+ if (handle_iter != attached_devices_.end()) {
+ // Now that we read the whole request we are good to check if device's
+ len = libusb_control_transfer(handle_iter->second, req.type, req.cmd,
+ req.value, req.index, data.data(), req.length,
+ req.timeout);
+
+ status = (len < 0);
+ if (status) {
+ ALOGE("USB request failed %d", len);
+ }
+ }
+
+ fd_->Write(&status, sizeof(status));
+
+ if (!status && !req_out) {
+ fd_->Write(&len, sizeof(len));
+ if (len > 0) {
+ fd_->Write(data.data(), len);
+ }
+ }
+}
+
+void USBServer::Serve() {
+ avd::SharedFDSet rset;
+ while (true) {
+ rset.Zero();
+ rset.Set(fd_);
+ int ret = avd::Select(&rset, nullptr, nullptr, nullptr);
+
+ if (ret < 0) continue;
+
+ if (rset.IsSet(fd_)) {
+ uint32_t cmd;
+ char data;
+ if (fd_->Read(&cmd, sizeof(cmd)) < int(sizeof(cmd))) {
+ ALOGE("Could not read data from input stream: %s", fd_->StrError());
+ continue;
+ }
+
+ switch (cmd) {
+ case CmdDeviceList:
+ HandleDeviceList();
+ break;
+
+ case CmdAttach:
+ HandleAttach();
+
+ case CmdExecute:
+ HandleExecute();
+ break;
+
+ default:
+ ALOGE("Discarding unknown command %08x", cmd);
+ }
+ }
+ }
+}
diff --git a/guest/usbforward/usb_server.h b/guest/usbforward/usb_server.h
new file mode 100644
index 0000000..030613b
--- /dev/null
+++ b/guest/usbforward/usb_server.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#define LOG_TAG "UsbForward"
+
+#include <map>
+#include <string>
+#include <libusb/libusb.h>
+
+#include "common/libs/fs/shared_fd.h"
+
+// USBServer exposes access to USB devices over pipe (virtio channel etc).
+// Usage:
+//
+// avd::SharedFD pipe = avd::SharedFD::Open(pipe_path, O_RDWR);
+// USBServer server(pipe);
+// CHECK(server.Init());
+// server.Serve();
+class USBServer final {
+ public:
+ USBServer(const avd::SharedFD& fd) : fd_{fd} {}
+ ~USBServer();
+
+ // Populate list of USB devices.
+ bool Init();
+
+ // Serve incoming USB requests.
+ void Serve();
+
+ private:
+ // Handle CmdDeviceList request.
+ void HandleDeviceList();
+
+ // Handle CmdAttach request.
+ void HandleAttach();
+
+ // Handle CmdExecute request.
+ void HandleExecute();
+
+ avd::SharedFD fd_;
+ std::map<uint16_t, libusb_device*> devices_;
+ std::map<uint16_t, libusb_device_handle*> attached_devices_;
+
+ USBServer(const USBServer& other) = delete;
+ USBServer& operator=(const USBServer& other) = delete;
+};