aboutsummaryrefslogtreecommitdiff
path: root/usb-linux.c
diff options
context:
space:
mode:
authorbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2005-11-05 14:22:28 +0000
committerbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2005-11-05 14:22:28 +0000
commitbb36d4708bc93874af95db54da2a9cee0d30a84d (patch)
tree5a57b4daada78670aefb208b9514fa0b47f10db9 /usb-linux.c
parent1aff381f59b508a422f6fe03965fbc3728d3c45a (diff)
initial USB support
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1597 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'usb-linux.c')
-rw-r--r--usb-linux.c309
1 files changed, 309 insertions, 0 deletions
diff --git a/usb-linux.c b/usb-linux.c
new file mode 100644
index 0000000000..87f3d120b9
--- /dev/null
+++ b/usb-linux.c
@@ -0,0 +1,309 @@
+/*
+ * Linux host USB redirector
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "vl.h"
+
+#if defined(__linux__)
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+#include <linux/version.h>
+
+/* We redefine it to avoid version problems */
+struct usb_ctrltransfer {
+ uint8_t bRequestType;
+ uint8_t bRequest;
+ uint16_t wValue;
+ uint16_t wIndex;
+ uint16_t wLength;
+ uint32_t timeout;
+ void *data;
+};
+
+//#define DEBUG
+
+#define MAX_DEVICES 8
+
+#define USBDEVFS_PATH "/proc/bus/usb"
+
+typedef struct USBHostDevice {
+ USBDevice dev;
+ int fd;
+} USBHostDevice;
+
+typedef struct USBHostHubState {
+ USBDevice *hub_dev;
+ USBPort *hub_ports[MAX_DEVICES];
+ USBDevice *hub_devices[MAX_DEVICES];
+} USBHostHubState;
+
+static void usb_host_handle_reset(USBDevice *dev)
+{
+#if 0
+ USBHostDevice *s = (USBHostDevice *)dev;
+ /* USBDEVFS_RESET, but not the first time as it has already be
+ done by the host OS */
+ ioctl(s->fd, USBDEVFS_RESET);
+#endif
+}
+
+static int usb_host_handle_control(USBDevice *dev,
+ int request,
+ int value,
+ int index,
+ int length,
+ uint8_t *data)
+{
+ USBHostDevice *s = (USBHostDevice *)dev;
+ struct usb_ctrltransfer ct;
+ int ret;
+
+ if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) {
+ /* specific SET_ADDRESS support */
+ dev->addr = value;
+ return 0;
+ } else {
+ ct.bRequestType = request >> 8;
+ ct.bRequest = request;
+ ct.wValue = value;
+ ct.wIndex = index;
+ ct.wLength = length;
+ ct.timeout = 50;
+ ct.data = data;
+ ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+ if (ret < 0) {
+ switch(errno) {
+ case ETIMEDOUT:
+ return USB_RET_NAK;
+ default:
+ return USB_RET_STALL;
+ }
+ } else {
+ return ret;
+ }
+ }
+}
+
+static int usb_host_handle_data(USBDevice *dev, int pid,
+ uint8_t devep,
+ uint8_t *data, int len)
+{
+ USBHostDevice *s = (USBHostDevice *)dev;
+ struct usbdevfs_bulktransfer bt;
+ int ret;
+
+ /* XXX: optimize and handle all data types by looking at the
+ config descriptor */
+ if (pid == USB_TOKEN_IN)
+ devep |= 0x80;
+ bt.ep = devep;
+ bt.len = len;
+ bt.timeout = 50;
+ bt.data = data;
+ ret = ioctl(s->fd, USBDEVFS_BULK, &bt);
+ if (ret < 0) {
+ switch(errno) {
+ case ETIMEDOUT:
+ return USB_RET_NAK;
+ case EPIPE:
+ default:
+#ifdef DEBUG
+ printf("handle_data: errno=%d\n", errno);
+#endif
+ return USB_RET_STALL;
+ }
+ } else {
+ return ret;
+ }
+}
+
+static int usb_host_handle_packet(USBDevice *dev, int pid,
+ uint8_t devaddr, uint8_t devep,
+ uint8_t *data, int len)
+{
+ return usb_generic_handle_packet(dev, pid, devaddr, devep, data, len);
+}
+
+/* XXX: exclude high speed devices or implement EHCI */
+static void scan_host_device(USBHostHubState *s, const char *filename)
+{
+ int fd, interface, ret, i;
+ USBHostDevice *dev;
+ struct usbdevfs_connectinfo ci;
+ uint8_t descr[1024];
+ int descr_len, dev_descr_len, config_descr_len, nb_interfaces;
+
+#ifdef DEBUG
+ printf("scanning %s\n", filename);
+#endif
+ fd = open(filename, O_RDWR);
+ if (fd < 0) {
+ perror(filename);
+ return;
+ }
+
+ /* read the config description */
+ descr_len = read(fd, descr, sizeof(descr));
+ if (descr_len <= 0) {
+ perror("read descr");
+ goto fail;
+ }
+
+ i = 0;
+ dev_descr_len = descr[0];
+ if (dev_descr_len > descr_len)
+ goto fail;
+ i += dev_descr_len;
+ config_descr_len = descr[i];
+ if (i + config_descr_len > descr_len)
+ goto fail;
+ nb_interfaces = descr[i + 4];
+ if (nb_interfaces != 1) {
+ /* NOTE: currently we grab only one interface */
+ goto fail;
+ }
+ /* XXX: only grab if all interfaces are free */
+ interface = 0;
+ ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface);
+ if (ret < 0) {
+ if (errno == EBUSY) {
+#ifdef DEBUG
+ printf("%s already grabbed\n", filename);
+#endif
+ } else {
+ perror("USBDEVFS_CLAIMINTERFACE");
+ }
+ fail:
+ close(fd);
+ return;
+ }
+
+ ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci);
+ if (ret < 0) {
+ perror("USBDEVFS_CONNECTINFO");
+ goto fail;
+ }
+
+#ifdef DEBUG
+ printf("%s grabbed\n", filename);
+#endif
+
+ /* find a free slot */
+ for(i = 0; i < MAX_DEVICES; i++) {
+ if (!s->hub_devices[i])
+ break;
+ }
+ if (i == MAX_DEVICES) {
+#ifdef DEBUG
+ printf("too many host devices\n");
+ goto fail;
+#endif
+ }
+
+ dev = qemu_mallocz(sizeof(USBHostDevice));
+ if (!dev)
+ goto fail;
+ dev->fd = fd;
+ if (ci.slow)
+ dev->dev.speed = USB_SPEED_LOW;
+ else
+ dev->dev.speed = USB_SPEED_HIGH;
+ dev->dev.handle_packet = usb_host_handle_packet;
+
+ dev->dev.handle_reset = usb_host_handle_reset;
+ dev->dev.handle_control = usb_host_handle_control;
+ dev->dev.handle_data = usb_host_handle_data;
+
+ s->hub_devices[i] = (USBDevice *)dev;
+
+ /* activate device on hub */
+ usb_attach(s->hub_ports[i], s->hub_devices[i]);
+}
+
+static void scan_host_devices(USBHostHubState *s, const char *bus_path)
+{
+ DIR *d;
+ struct dirent *de;
+ char buf[1024];
+
+ d = opendir(bus_path);
+ if (!d)
+ return;
+ for(;;) {
+ de = readdir(d);
+ if (!de)
+ break;
+ if (de->d_name[0] != '.') {
+ snprintf(buf, sizeof(buf), "%s/%s", bus_path, de->d_name);
+ scan_host_device(s, buf);
+ }
+ }
+ closedir(d);
+}
+
+static void scan_host_buses(USBHostHubState *s)
+{
+ DIR *d;
+ struct dirent *de;
+ char buf[1024];
+
+ d = opendir(USBDEVFS_PATH);
+ if (!d)
+ return;
+ for(;;) {
+ de = readdir(d);
+ if (!de)
+ break;
+ if (isdigit(de->d_name[0])) {
+ snprintf(buf, sizeof(buf), "%s/%s", USBDEVFS_PATH, de->d_name);
+ scan_host_devices(s, buf);
+ }
+ }
+ closedir(d);
+}
+
+/* virtual hub containing the USB devices of the host */
+USBDevice *usb_host_hub_init(void)
+{
+ USBHostHubState *s;
+ s = qemu_mallocz(sizeof(USBHostHubState));
+ if (!s)
+ return NULL;
+ s->hub_dev = usb_hub_init(s->hub_ports, MAX_DEVICES);
+ if (!s->hub_dev) {
+ free(s);
+ return NULL;
+ }
+ scan_host_buses(s);
+ return s->hub_dev;
+}
+
+#else
+
+/* XXX: modify configure to compile the right host driver */
+USBDevice *usb_host_hub_init(void)
+{
+ return NULL;
+}
+
+#endif