|
@@ -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)
|