aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/usb.h1
-rw-r--r--usb-linux.c203
-rw-r--r--vl.c3
3 files changed, 176 insertions, 31 deletions
diff --git a/hw/usb.h b/hw/usb.h
index 997e69fa0e..1a353bbc44 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -240,6 +240,7 @@ USBDevice *usb_hub_init(int nb_ports);
/* usb-linux.c */
USBDevice *usb_host_device_open(const char *devname);
+int usb_host_device_close(const char *devname);
void usb_host_info(void);
/* usb-hid.c */
diff --git a/usb-linux.c b/usb-linux.c
index b82c4f65e1..937ff12867 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -5,7 +5,7 @@
*
* Copyright (c) 2008 Max Krasnyansky
* Support for host device auto connect & disconnect
- * Magor rewrite to support fully async operation
+ * Major rewrite to support fully async operation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -951,23 +951,52 @@ fail:
return NULL;
}
+static int usb_host_auto_add(const char *spec);
+static int usb_host_auto_del(const char *spec);
+
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)
+ if (strstr(devname, "auto:")) {
+ usb_host_auto_add(devname);
return NULL;
+ }
- if (hostdev_find(bus_num, addr)) {
- term_printf("husb: host usb device %d.%d is already open\n", bus_num, addr);
+ if (usb_host_find_device(&bus_num, &addr, product_name, sizeof(product_name),
+ devname) < 0)
return NULL;
- }
+
+ if (hostdev_find(bus_num, addr)) {
+ term_printf("husb: 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);
}
+
+int usb_host_device_close(const char *devname)
+{
+ char product_name[PRODUCT_NAME_SZ];
+ int bus_num, addr;
+ USBHostDevice *s;
+
+ if (strstr(devname, "auto:"))
+ return usb_host_auto_del(devname);
+
+ if (usb_host_find_device(&bus_num, &addr, product_name, sizeof(product_name),
+ devname) < 0)
+ return -1;
+
+ s = hostdev_find(bus_num, addr);
+ if (s) {
+ usb_device_del_addr(0, s->dev.addr);
+ return 0;
+ }
+
+ return -1;
+}
static int get_tag_value(char *buf, int buf_size,
const char *str, const char *tag,
@@ -1126,21 +1155,76 @@ static void usb_host_auto_timer(void *unused)
}
/*
- * Add autoconnect filter
- * -1 means 'any' (device, vendor, etc)
+ * Autoconnect filter
+ * Format:
+ * auto:bus:dev[:vid:pid]
+ * auto:bus.dev[:vid:pid]
+ *
+ * bus - bus number (dec, * means any)
+ * dev - device number (dec, * means any)
+ * vid - vendor id (hex, * means any)
+ * pid - product id (hex, * means any)
+ *
+ * See 'lsusb' output.
*/
-static void usb_host_auto_add(int bus_num, int addr, int vendor_id, int product_id)
+static int parse_filter(const char *spec, struct USBAutoFilter *f)
{
- struct USBAutoFilter *f = qemu_mallocz(sizeof(*f));
+ enum { BUS, DEV, VID, PID, DONE };
+ const char *p = spec;
+ int i;
+
+ f->bus_num = -1;
+ f->addr = -1;
+ f->vendor_id = -1;
+ f->product_id = -1;
+
+ for (i = BUS; i < DONE; i++) {
+ p = strpbrk(p, ":.");
+ if (!p) break;
+ p++;
+
+ if (*p == '*')
+ continue;
+
+ switch(i) {
+ case BUS: f->bus_num = strtol(p, NULL, 10); break;
+ case DEV: f->addr = strtol(p, NULL, 10); break;
+ case VID: f->vendor_id = strtol(p, NULL, 16); break;
+ case PID: f->product_id = strtol(p, NULL, 16); break;
+ }
+ }
+
+ if (i < DEV) {
+ fprintf(stderr, "husb: invalid auto filter spec %s\n", spec);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int match_filter(const struct USBAutoFilter *f1,
+ const struct USBAutoFilter *f2)
+{
+ return f1->bus_num == f2->bus_num &&
+ f1->addr == f2->addr &&
+ f1->vendor_id == f2->vendor_id &&
+ f1->product_id == f2->product_id;
+}
+
+static int usb_host_auto_add(const char *spec)
+{
+ struct USBAutoFilter filter, *f;
+
+ if (parse_filter(spec, &filter) < 0)
+ return -1;
+
+ f = qemu_mallocz(sizeof(*f));
if (!f) {
fprintf(stderr, "husb: failed to allocate auto filter\n");
- return;
+ return -1;
}
- f->bus_num = bus_num;
- f->addr = addr;
- f->vendor_id = vendor_id;
- f->product_id = product_id;
+ *f = filter;
if (!usb_auto_filter) {
/*
@@ -1153,18 +1237,52 @@ static void usb_host_auto_add(int bus_num, int addr, int vendor_id, int product_
if (!usb_auto_timer) {
fprintf(stderr, "husb: failed to allocate auto scan timer\n");
qemu_free(f);
- return;
+ return -1;
}
/* Check for new devices every two seconds */
qemu_mod_timer(usb_auto_timer, qemu_get_clock(rt_clock) + 2000);
}
- dprintf("husb: auto filter: bus_num %d addr %d vid %d pid %d\n",
- bus_num, addr, vendor_id, product_id);
+ dprintf("husb: added auto filter: bus_num %d addr %d vid %d pid %d\n",
+ f->bus_num, f->addr, f->vendor_id, f->product_id);
f->next = usb_auto_filter;
usb_auto_filter = f;
+
+ return 0;
+}
+
+static int usb_host_auto_del(const char *spec)
+{
+ struct USBAutoFilter *pf = usb_auto_filter;
+ struct USBAutoFilter **prev = &usb_auto_filter;
+ struct USBAutoFilter filter;
+
+ if (parse_filter(spec, &filter) < 0)
+ return -1;
+
+ while (pf) {
+ if (match_filter(pf, &filter)) {
+ dprintf("husb: removed auto filter: bus_num %d addr %d vid %d pid %d\n",
+ pf->bus_num, pf->addr, pf->vendor_id, pf->product_id);
+
+ *prev = pf->next;
+
+ if (!usb_auto_filter) {
+ /* No more filters. Stop scanning. */
+ qemu_del_timer(usb_auto_timer);
+ qemu_free_timer(usb_auto_timer);
+ }
+
+ return 0;
+ }
+
+ prev = &pf->next;
+ pf = pf->next;
+ }
+
+ return -1;
}
typedef struct FindDeviceState {
@@ -1208,12 +1326,6 @@ 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;
@@ -1222,15 +1334,10 @@ static int usb_host_find_device(int *pbus_num, int *paddr,
pstrcpy(product_name, product_name_size, fs.product_name);
return 0;
}
+
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) {
@@ -1324,9 +1431,38 @@ static int usb_host_info_device(void *opaque, int bus_num, int addr,
return 0;
}
+static void dec2str(int val, char *str)
+{
+ if (val == -1)
+ strcpy(str, "*");
+ else
+ sprintf(str, "%d", val);
+}
+
+static void hex2str(int val, char *str)
+{
+ if (val == -1)
+ strcpy(str, "*");
+ else
+ sprintf(str, "%x", val);
+}
+
void usb_host_info(void)
{
+ struct USBAutoFilter *f;
+
usb_host_scan(NULL, usb_host_info_device);
+
+ if (usb_auto_filter)
+ term_printf(" Auto filters:\n");
+ for (f = usb_auto_filter; f; f = f->next) {
+ char bus[10], addr[10], vid[10], pid[10];
+ dec2str(f->bus_num, bus);
+ dec2str(f->addr, addr);
+ hex2str(f->vendor_id, vid);
+ hex2str(f->product_id, pid);
+ term_printf(" Device %s.%s ID %s:%s\n", bus, addr, vid, pid);
+ }
}
#else
@@ -1344,4 +1480,9 @@ USBDevice *usb_host_device_open(const char *devname)
return NULL;
}
+int usb_host_device_close(const char *devname)
+{
+ return 0;
+}
+
#endif
diff --git a/vl.c b/vl.c
index 18bcc1f311..0619f1c612 100644
--- a/vl.c
+++ b/vl.c
@@ -5866,6 +5866,9 @@ static int usb_device_del(const char *devname)
int bus_num, addr;
const char *p;
+ if (strstart(devname, "host:", &p))
+ return usb_host_device_close(p);
+
if (!used_usb_ports)
return -1;