aboutsummaryrefslogtreecommitdiff
path: root/usb-linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'usb-linux.c')
-rw-r--r--usb-linux.c209
1 files changed, 195 insertions, 14 deletions
diff --git a/usb-linux.c b/usb-linux.c
index 0023c1dfd5..622255c9e2 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -3,6 +3,9 @@
*
* Copyright (c) 2005 Fabrice Bellard
*
+ * Support for host device auto connect & disconnect
+ * Copyright (c) 2008 Max Krasnyansky
+ *
* 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
@@ -67,6 +70,8 @@ struct endp_data {
uint8_t type;
};
+
+
/* FIXME: move USBPacket to PendingURB */
typedef struct USBHostDevice {
USBDevice dev;
@@ -78,9 +83,51 @@ typedef struct USBHostDevice {
uint8_t descr[1024];
int descr_len;
int urbs_ready;
+
QEMUTimer *timer;
+
+ /* Host side address */
+ int bus_num;
+ int addr;
+
+ struct USBHostDevice *next;
} USBHostDevice;
+static USBHostDevice *hostdev_list;
+
+static void hostdev_link(USBHostDevice *dev)
+{
+ dev->next = hostdev_list;
+ hostdev_list = dev;
+}
+
+static void hostdev_unlink(USBHostDevice *dev)
+{
+ USBHostDevice *pdev = hostdev_list;
+ USBHostDevice **prev = &hostdev_list;
+
+ while (pdev) {
+ if (pdev == dev) {
+ *prev = dev->next;
+ return;
+ }
+
+ prev = &pdev->next;
+ pdev = pdev->next;
+ }
+}
+
+static USBHostDevice *hostdev_find(int bus_num, int addr)
+{
+ USBHostDevice *s = hostdev_list;
+ while (s) {
+ if (s->bus_num == bus_num && s->addr == addr)
+ return s;
+ s = s->next;
+ }
+ return NULL;
+}
+
typedef struct PendingURB {
struct usbdevfs_urb *urb;
int status;
@@ -238,6 +285,8 @@ static void usb_host_handle_destroy(USBDevice *dev)
qemu_del_timer(s->timer);
+ hostdev_unlink(s);
+
if (s->fd >= 0)
close(s->fd);
@@ -619,32 +668,26 @@ static void usb_host_device_check(void *priv)
qemu_mod_timer(s->timer, qemu_get_clock(rt_clock) + 1000);
}
-/* XXX: exclude high speed devices or implement EHCI */
-USBDevice *usb_host_device_open(const char *devname)
+static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *prod_name)
{
int fd = -1, ret;
USBHostDevice *dev = NULL;
struct usbdevfs_connectinfo ci;
char buf[1024];
- int bus_num, addr;
- char product_name[PRODUCT_NAME_SZ];
-
- if (usb_host_find_device(&bus_num, &addr,
- product_name, sizeof(product_name),
- devname) < 0)
- return NULL;
-
dev = qemu_mallocz(sizeof(USBHostDevice));
if (!dev)
goto fail;
+ dev->bus_num = bus_num;
+ dev->addr = addr;
+
dev->timer = qemu_new_timer(rt_clock, usb_host_device_check, (void *) dev);
if (!dev->timer)
goto fail;
#ifdef DEBUG
- printf("usb_host_device_open %s\n", devname);
+ printf("usb_host_device_open %d.%d\n", bus_num, addr);
#endif
snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d",
@@ -704,12 +747,12 @@ USBDevice *usb_host_device_open(const char *devname)
dev->dev.handle_data = usb_host_handle_data;
dev->dev.handle_destroy = usb_host_handle_destroy;
- if (product_name[0] == '\0')
+ if (!prod_name || prod_name[0] == '\0')
snprintf(dev->dev.devname, sizeof(dev->dev.devname),
- "host:%s", devname);
+ "host:%d.%d", bus_num, addr);
else
pstrcpy(dev->dev.devname, sizeof(dev->dev.devname),
- product_name);
+ prod_name);
#ifdef USE_ASYNCIO
/* set up the signal handlers */
@@ -735,8 +778,11 @@ USBDevice *usb_host_device_open(const char *devname)
/* Start the timer to detect disconnect */
qemu_mod_timer(dev->timer, qemu_get_clock(rt_clock) + 1000);
+ hostdev_link(dev);
+
dev->urbs_ready = 0;
return (USBDevice *)dev;
+
fail:
if (dev) {
if (dev->timer)
@@ -747,6 +793,24 @@ fail:
return NULL;
}
+USBDevice *usb_host_device_open(const char *devname)
+{
+ int bus_num, addr;
+ char product_name[PRODUCT_NAME_SZ];
+
+ if (usb_host_find_device(&bus_num, &addr,
+ product_name, sizeof(product_name),
+ devname) < 0)
+ return NULL;
+
+ if (hostdev_find(bus_num, addr)) {
+ printf("host usb device %d.%d is already open\n", bus_num, addr);
+ return NULL;
+ }
+
+ return usb_host_device_open_addr(bus_num, addr, product_name);
+}
+
static int get_tag_value(char *buf, int buf_size,
const char *str, const char *tag,
const char *stopchars)
@@ -846,6 +910,108 @@ static int usb_host_scan(void *opaque, USBScanFunc *func)
return ret;
}
+struct USBAutoFilter {
+ struct USBAutoFilter *next;
+ int bus_num;
+ int addr;
+ int vendor_id;
+ int product_id;
+};
+
+static QEMUTimer *usb_auto_timer;
+static struct USBAutoFilter *usb_auto_filter;
+
+static int usb_host_auto_scan(void *opaque, int bus_num, int addr,
+ int class_id, int vendor_id, int product_id,
+ const char *product_name, int speed)
+{
+ struct USBAutoFilter *f;
+ struct USBDevice *dev;
+
+ /* Ignore hubs */
+ if (class_id == 9)
+ return 0;
+
+ for (f = usb_auto_filter; f; f = f->next) {
+ // printf("Auto match: bus_num %d addr %d vid %d pid %d\n",
+ // bus_num, addr, vendor_id, product_id);
+
+ if (f->bus_num >= 0 && f->bus_num != bus_num)
+ continue;
+
+ if (f->addr >= 0 && f->addr != addr)
+ continue;
+
+ if (f->vendor_id >= 0 && f->vendor_id != vendor_id)
+ continue;
+
+ if (f->product_id >= 0 && f->product_id != product_id)
+ continue;
+
+ /* We got a match */
+
+ /* Allredy attached ? */
+ if (hostdev_find(bus_num, addr))
+ return 0;
+
+ printf("Auto open: bus_num %d addr %d\n", bus_num, addr);
+
+ dev = usb_host_device_open_addr(bus_num, addr, product_name);
+ if (dev)
+ usb_device_add_dev(dev);
+ }
+
+ return 0;
+}
+
+static void usb_host_auto_timer(void *unused)
+{
+ usb_host_scan(NULL, usb_host_auto_scan);
+ qemu_mod_timer(usb_auto_timer, qemu_get_clock(rt_clock) + 2000);
+}
+
+/*
+ * Add autoconnect filter
+ * -1 means 'any' (device, vendor, etc)
+ */
+static void usb_host_auto_add(int bus_num, int addr, int vendor_id, int product_id)
+{
+ struct USBAutoFilter *f = qemu_mallocz(sizeof(*f));
+ if (!f) {
+ printf("Failed to allocate auto filter\n");
+ return;
+ }
+
+ f->bus_num = bus_num;
+ f->addr = addr;
+ f->vendor_id = vendor_id;
+ f->product_id = product_id;
+
+ if (!usb_auto_filter) {
+ /*
+ * First entry. Init and start the monitor.
+ * Right now we're using timer to check for new devices.
+ * If this turns out to be too expensive we can move that into a
+ * separate thread.
+ */
+ usb_auto_timer = qemu_new_timer(rt_clock, usb_host_auto_timer, NULL);
+ if (!usb_auto_timer) {
+ printf("Failed to allocate timer\n");
+ qemu_free(f);
+ return;
+ }
+
+ /* Check for new devices every two seconds */
+ qemu_mod_timer(usb_auto_timer, qemu_get_clock(rt_clock) + 2000);
+ }
+
+ printf("Auto filter: bus_num %d addr %d vid %d pid %d\n",
+ bus_num, addr, vendor_id, product_id);
+
+ f->next = usb_auto_filter;
+ usb_auto_filter = f;
+}
+
typedef struct FindDeviceState {
int vendor_id;
int product_id;
@@ -887,6 +1053,12 @@ static int usb_host_find_device(int *pbus_num, int *paddr,
p = strchr(devname, '.');
if (p) {
*pbus_num = strtoul(devname, NULL, 0);
+
+ if (*(p + 1) == '*') {
+ usb_host_auto_add(*pbus_num, -1, -1, -1);
+ return -1;
+ }
+
*paddr = strtoul(p + 1, NULL, 0);
fs.bus_num = *pbus_num;
fs.addr = *paddr;
@@ -898,6 +1070,12 @@ static int usb_host_find_device(int *pbus_num, int *paddr,
p = strchr(devname, ':');
if (p) {
fs.vendor_id = strtoul(devname, NULL, 16);
+
+ if (*(p + 1) == '*') {
+ usb_host_auto_add(-1, -1, fs.vendor_id, -1);
+ return -1;
+ }
+
fs.product_id = strtoul(p + 1, NULL, 16);
ret = usb_host_scan(&fs, usb_host_find_device_scan);
if (ret) {
@@ -996,6 +1174,9 @@ void usb_host_info(void)
usb_host_scan(NULL, usb_host_info_device);
}
+
+
+
#else
void usb_host_info(void)