]>
Commit | Line | Data |
---|---|---|
bb36d470 FB |
1 | /* |
2 | * Linux host USB redirector | |
3 | * | |
4 | * Copyright (c) 2005 Fabrice Bellard | |
5fafdf24 | 5 | * |
bb36d470 FB |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
24 | #include "vl.h" | |
25 | ||
26 | #if defined(__linux__) | |
27 | #include <dirent.h> | |
28 | #include <sys/ioctl.h> | |
29 | #include <linux/usbdevice_fs.h> | |
30 | #include <linux/version.h> | |
31 | ||
32 | /* We redefine it to avoid version problems */ | |
33 | struct usb_ctrltransfer { | |
34 | uint8_t bRequestType; | |
35 | uint8_t bRequest; | |
36 | uint16_t wValue; | |
37 | uint16_t wIndex; | |
38 | uint16_t wLength; | |
39 | uint32_t timeout; | |
40 | void *data; | |
41 | }; | |
42 | ||
a594cfbf | 43 | typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id, |
5fafdf24 | 44 | int vendor_id, int product_id, |
a594cfbf | 45 | const char *product_name, int speed); |
5fafdf24 | 46 | static int usb_host_find_device(int *pbus_num, int *paddr, |
1f6e24e7 | 47 | char *product_name, int product_name_size, |
a594cfbf | 48 | const char *devname); |
bb36d470 | 49 | |
a594cfbf | 50 | //#define DEBUG |
bb36d470 FB |
51 | |
52 | #define USBDEVFS_PATH "/proc/bus/usb" | |
1f6e24e7 | 53 | #define PRODUCT_NAME_SZ 32 |
bb36d470 FB |
54 | |
55 | typedef struct USBHostDevice { | |
56 | USBDevice dev; | |
57 | int fd; | |
58 | } USBHostDevice; | |
59 | ||
059809e4 | 60 | static void usb_host_handle_reset(USBDevice *dev) |
bb36d470 FB |
61 | { |
62 | #if 0 | |
63 | USBHostDevice *s = (USBHostDevice *)dev; | |
64 | /* USBDEVFS_RESET, but not the first time as it has already be | |
65 | done by the host OS */ | |
66 | ioctl(s->fd, USBDEVFS_RESET); | |
67 | #endif | |
5fafdf24 | 68 | } |
bb36d470 | 69 | |
059809e4 FB |
70 | static void usb_host_handle_destroy(USBDevice *dev) |
71 | { | |
72 | USBHostDevice *s = (USBHostDevice *)dev; | |
73 | ||
74 | if (s->fd >= 0) | |
75 | close(s->fd); | |
76 | qemu_free(s); | |
77 | } | |
78 | ||
bb36d470 FB |
79 | static int usb_host_handle_control(USBDevice *dev, |
80 | int request, | |
81 | int value, | |
82 | int index, | |
83 | int length, | |
84 | uint8_t *data) | |
85 | { | |
86 | USBHostDevice *s = (USBHostDevice *)dev; | |
87 | struct usb_ctrltransfer ct; | |
88 | int ret; | |
89 | ||
90 | if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) { | |
91 | /* specific SET_ADDRESS support */ | |
92 | dev->addr = value; | |
93 | return 0; | |
94 | } else { | |
95 | ct.bRequestType = request >> 8; | |
96 | ct.bRequest = request; | |
97 | ct.wValue = value; | |
98 | ct.wIndex = index; | |
99 | ct.wLength = length; | |
100 | ct.timeout = 50; | |
101 | ct.data = data; | |
102 | ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); | |
103 | if (ret < 0) { | |
104 | switch(errno) { | |
105 | case ETIMEDOUT: | |
106 | return USB_RET_NAK; | |
107 | default: | |
108 | return USB_RET_STALL; | |
109 | } | |
110 | } else { | |
111 | return ret; | |
112 | } | |
113 | } | |
114 | } | |
115 | ||
4d611c9a | 116 | static int usb_host_handle_data(USBDevice *dev, USBPacket *p) |
bb36d470 FB |
117 | { |
118 | USBHostDevice *s = (USBHostDevice *)dev; | |
119 | struct usbdevfs_bulktransfer bt; | |
120 | int ret; | |
4d611c9a | 121 | uint8_t devep = p->devep; |
bb36d470 FB |
122 | |
123 | /* XXX: optimize and handle all data types by looking at the | |
124 | config descriptor */ | |
4d611c9a | 125 | if (p->pid == USB_TOKEN_IN) |
bb36d470 FB |
126 | devep |= 0x80; |
127 | bt.ep = devep; | |
4d611c9a | 128 | bt.len = p->len; |
bb36d470 | 129 | bt.timeout = 50; |
4d611c9a | 130 | bt.data = p->data; |
bb36d470 FB |
131 | ret = ioctl(s->fd, USBDEVFS_BULK, &bt); |
132 | if (ret < 0) { | |
133 | switch(errno) { | |
134 | case ETIMEDOUT: | |
135 | return USB_RET_NAK; | |
136 | case EPIPE: | |
137 | default: | |
138 | #ifdef DEBUG | |
139 | printf("handle_data: errno=%d\n", errno); | |
140 | #endif | |
141 | return USB_RET_STALL; | |
142 | } | |
143 | } else { | |
144 | return ret; | |
145 | } | |
146 | } | |
147 | ||
bb36d470 | 148 | /* XXX: exclude high speed devices or implement EHCI */ |
a594cfbf | 149 | USBDevice *usb_host_device_open(const char *devname) |
bb36d470 FB |
150 | { |
151 | int fd, interface, ret, i; | |
152 | USBHostDevice *dev; | |
153 | struct usbdevfs_connectinfo ci; | |
154 | uint8_t descr[1024]; | |
a594cfbf | 155 | char buf[1024]; |
bb36d470 | 156 | int descr_len, dev_descr_len, config_descr_len, nb_interfaces; |
a594cfbf | 157 | int bus_num, addr; |
1f6e24e7 | 158 | char product_name[PRODUCT_NAME_SZ]; |
bb36d470 | 159 | |
5fafdf24 | 160 | if (usb_host_find_device(&bus_num, &addr, |
1f6e24e7 | 161 | product_name, sizeof(product_name), |
5fafdf24 | 162 | devname) < 0) |
a594cfbf | 163 | return NULL; |
3b46e624 | 164 | |
5fafdf24 | 165 | snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d", |
a594cfbf FB |
166 | bus_num, addr); |
167 | fd = open(buf, O_RDWR); | |
bb36d470 | 168 | if (fd < 0) { |
a594cfbf FB |
169 | perror(buf); |
170 | return NULL; | |
bb36d470 FB |
171 | } |
172 | ||
173 | /* read the config description */ | |
174 | descr_len = read(fd, descr, sizeof(descr)); | |
175 | if (descr_len <= 0) { | |
176 | perror("read descr"); | |
177 | goto fail; | |
178 | } | |
3b46e624 | 179 | |
bb36d470 FB |
180 | i = 0; |
181 | dev_descr_len = descr[0]; | |
182 | if (dev_descr_len > descr_len) | |
183 | goto fail; | |
184 | i += dev_descr_len; | |
185 | config_descr_len = descr[i]; | |
186 | if (i + config_descr_len > descr_len) | |
187 | goto fail; | |
188 | nb_interfaces = descr[i + 4]; | |
189 | if (nb_interfaces != 1) { | |
190 | /* NOTE: currently we grab only one interface */ | |
a594cfbf FB |
191 | fprintf(stderr, "usb_host: only one interface supported\n"); |
192 | goto fail; | |
193 | } | |
194 | ||
195 | #ifdef USBDEVFS_DISCONNECT | |
196 | /* earlier Linux 2.4 do not support that */ | |
868bfe2b FB |
197 | { |
198 | struct usbdevfs_ioctl ctrl; | |
199 | ctrl.ioctl_code = USBDEVFS_DISCONNECT; | |
200 | ctrl.ifno = 0; | |
201 | ret = ioctl(fd, USBDEVFS_IOCTL, &ctrl); | |
202 | if (ret < 0 && errno != ENODATA) { | |
203 | perror("USBDEVFS_DISCONNECT"); | |
204 | goto fail; | |
205 | } | |
bb36d470 | 206 | } |
a594cfbf FB |
207 | #endif |
208 | ||
bb36d470 FB |
209 | /* XXX: only grab if all interfaces are free */ |
210 | interface = 0; | |
211 | ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface); | |
212 | if (ret < 0) { | |
213 | if (errno == EBUSY) { | |
a594cfbf | 214 | fprintf(stderr, "usb_host: device already grabbed\n"); |
bb36d470 FB |
215 | } else { |
216 | perror("USBDEVFS_CLAIMINTERFACE"); | |
217 | } | |
218 | fail: | |
219 | close(fd); | |
a594cfbf | 220 | return NULL; |
bb36d470 FB |
221 | } |
222 | ||
223 | ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); | |
224 | if (ret < 0) { | |
225 | perror("USBDEVFS_CONNECTINFO"); | |
226 | goto fail; | |
227 | } | |
228 | ||
229 | #ifdef DEBUG | |
a594cfbf | 230 | printf("host USB device %d.%d grabbed\n", bus_num, addr); |
3b46e624 | 231 | #endif |
bb36d470 | 232 | |
bb36d470 FB |
233 | dev = qemu_mallocz(sizeof(USBHostDevice)); |
234 | if (!dev) | |
235 | goto fail; | |
236 | dev->fd = fd; | |
237 | if (ci.slow) | |
238 | dev->dev.speed = USB_SPEED_LOW; | |
239 | else | |
240 | dev->dev.speed = USB_SPEED_HIGH; | |
a594cfbf | 241 | dev->dev.handle_packet = usb_generic_handle_packet; |
bb36d470 FB |
242 | |
243 | dev->dev.handle_reset = usb_host_handle_reset; | |
244 | dev->dev.handle_control = usb_host_handle_control; | |
245 | dev->dev.handle_data = usb_host_handle_data; | |
059809e4 | 246 | dev->dev.handle_destroy = usb_host_handle_destroy; |
1f6e24e7 FB |
247 | |
248 | if (product_name[0] == '\0') | |
249 | snprintf(dev->dev.devname, sizeof(dev->dev.devname), | |
250 | "host:%s", devname); | |
251 | else | |
252 | pstrcpy(dev->dev.devname, sizeof(dev->dev.devname), | |
253 | product_name); | |
254 | ||
a594cfbf FB |
255 | return (USBDevice *)dev; |
256 | } | |
bb36d470 | 257 | |
a594cfbf | 258 | static int get_tag_value(char *buf, int buf_size, |
5fafdf24 | 259 | const char *str, const char *tag, |
a594cfbf FB |
260 | const char *stopchars) |
261 | { | |
262 | const char *p; | |
263 | char *q; | |
264 | p = strstr(str, tag); | |
265 | if (!p) | |
266 | return -1; | |
267 | p += strlen(tag); | |
268 | while (isspace(*p)) | |
269 | p++; | |
270 | q = buf; | |
271 | while (*p != '\0' && !strchr(stopchars, *p)) { | |
272 | if ((q - buf) < (buf_size - 1)) | |
273 | *q++ = *p; | |
274 | p++; | |
275 | } | |
276 | *q = '\0'; | |
277 | return q - buf; | |
bb36d470 FB |
278 | } |
279 | ||
a594cfbf | 280 | static int usb_host_scan(void *opaque, USBScanFunc *func) |
bb36d470 | 281 | { |
a594cfbf FB |
282 | FILE *f; |
283 | char line[1024]; | |
bb36d470 | 284 | char buf[1024]; |
a594cfbf FB |
285 | int bus_num, addr, speed, device_count, class_id, product_id, vendor_id; |
286 | int ret; | |
287 | char product_name[512]; | |
3b46e624 | 288 | |
a594cfbf FB |
289 | f = fopen(USBDEVFS_PATH "/devices", "r"); |
290 | if (!f) { | |
291 | term_printf("Could not open %s\n", USBDEVFS_PATH "/devices"); | |
292 | return 0; | |
293 | } | |
294 | device_count = 0; | |
295 | bus_num = addr = speed = class_id = product_id = vendor_id = 0; | |
296 | ret = 0; | |
bb36d470 | 297 | for(;;) { |
a594cfbf | 298 | if (fgets(line, sizeof(line), f) == NULL) |
bb36d470 | 299 | break; |
a594cfbf FB |
300 | if (strlen(line) > 0) |
301 | line[strlen(line) - 1] = '\0'; | |
302 | if (line[0] == 'T' && line[1] == ':') { | |
38ca0f6d PB |
303 | if (device_count && (vendor_id || product_id)) { |
304 | /* New device. Add the previously discovered device. */ | |
5fafdf24 | 305 | ret = func(opaque, bus_num, addr, class_id, vendor_id, |
a594cfbf FB |
306 | product_id, product_name, speed); |
307 | if (ret) | |
308 | goto the_end; | |
309 | } | |
310 | if (get_tag_value(buf, sizeof(buf), line, "Bus=", " ") < 0) | |
311 | goto fail; | |
312 | bus_num = atoi(buf); | |
313 | if (get_tag_value(buf, sizeof(buf), line, "Dev#=", " ") < 0) | |
314 | goto fail; | |
315 | addr = atoi(buf); | |
316 | if (get_tag_value(buf, sizeof(buf), line, "Spd=", " ") < 0) | |
317 | goto fail; | |
318 | if (!strcmp(buf, "480")) | |
319 | speed = USB_SPEED_HIGH; | |
320 | else if (!strcmp(buf, "1.5")) | |
321 | speed = USB_SPEED_LOW; | |
322 | else | |
323 | speed = USB_SPEED_FULL; | |
324 | product_name[0] = '\0'; | |
325 | class_id = 0xff; | |
326 | device_count++; | |
327 | product_id = 0; | |
328 | vendor_id = 0; | |
329 | } else if (line[0] == 'P' && line[1] == ':') { | |
330 | if (get_tag_value(buf, sizeof(buf), line, "Vendor=", " ") < 0) | |
331 | goto fail; | |
332 | vendor_id = strtoul(buf, NULL, 16); | |
333 | if (get_tag_value(buf, sizeof(buf), line, "ProdID=", " ") < 0) | |
334 | goto fail; | |
335 | product_id = strtoul(buf, NULL, 16); | |
336 | } else if (line[0] == 'S' && line[1] == ':') { | |
337 | if (get_tag_value(buf, sizeof(buf), line, "Product=", "") < 0) | |
338 | goto fail; | |
339 | pstrcpy(product_name, sizeof(product_name), buf); | |
340 | } else if (line[0] == 'D' && line[1] == ':') { | |
341 | if (get_tag_value(buf, sizeof(buf), line, "Cls=", " (") < 0) | |
342 | goto fail; | |
343 | class_id = strtoul(buf, NULL, 16); | |
bb36d470 | 344 | } |
a594cfbf FB |
345 | fail: ; |
346 | } | |
38ca0f6d PB |
347 | if (device_count && (vendor_id || product_id)) { |
348 | /* Add the last device. */ | |
5fafdf24 | 349 | ret = func(opaque, bus_num, addr, class_id, vendor_id, |
a594cfbf | 350 | product_id, product_name, speed); |
bb36d470 | 351 | } |
a594cfbf FB |
352 | the_end: |
353 | fclose(f); | |
354 | return ret; | |
bb36d470 FB |
355 | } |
356 | ||
a594cfbf FB |
357 | typedef struct FindDeviceState { |
358 | int vendor_id; | |
359 | int product_id; | |
360 | int bus_num; | |
361 | int addr; | |
1f6e24e7 | 362 | char product_name[PRODUCT_NAME_SZ]; |
a594cfbf FB |
363 | } FindDeviceState; |
364 | ||
5fafdf24 | 365 | static int usb_host_find_device_scan(void *opaque, int bus_num, int addr, |
a594cfbf | 366 | int class_id, |
5fafdf24 | 367 | int vendor_id, int product_id, |
a594cfbf | 368 | const char *product_name, int speed) |
bb36d470 | 369 | { |
a594cfbf | 370 | FindDeviceState *s = opaque; |
1f6e24e7 FB |
371 | if ((vendor_id == s->vendor_id && |
372 | product_id == s->product_id) || | |
373 | (bus_num == s->bus_num && | |
374 | addr == s->addr)) { | |
375 | pstrcpy(s->product_name, PRODUCT_NAME_SZ, product_name); | |
a594cfbf FB |
376 | s->bus_num = bus_num; |
377 | s->addr = addr; | |
378 | return 1; | |
379 | } else { | |
380 | return 0; | |
381 | } | |
382 | } | |
bb36d470 | 383 | |
5fafdf24 TS |
384 | /* the syntax is : |
385 | 'bus.addr' (decimal numbers) or | |
a594cfbf | 386 | 'vendor_id:product_id' (hexa numbers) */ |
5fafdf24 | 387 | static int usb_host_find_device(int *pbus_num, int *paddr, |
1f6e24e7 | 388 | char *product_name, int product_name_size, |
a594cfbf FB |
389 | const char *devname) |
390 | { | |
391 | const char *p; | |
392 | int ret; | |
393 | FindDeviceState fs; | |
394 | ||
395 | p = strchr(devname, '.'); | |
396 | if (p) { | |
397 | *pbus_num = strtoul(devname, NULL, 0); | |
398 | *paddr = strtoul(p + 1, NULL, 0); | |
1f6e24e7 FB |
399 | fs.bus_num = *pbus_num; |
400 | fs.addr = *paddr; | |
401 | ret = usb_host_scan(&fs, usb_host_find_device_scan); | |
402 | if (ret) | |
403 | pstrcpy(product_name, product_name_size, fs.product_name); | |
a594cfbf FB |
404 | return 0; |
405 | } | |
406 | p = strchr(devname, ':'); | |
407 | if (p) { | |
408 | fs.vendor_id = strtoul(devname, NULL, 16); | |
409 | fs.product_id = strtoul(p + 1, NULL, 16); | |
410 | ret = usb_host_scan(&fs, usb_host_find_device_scan); | |
411 | if (ret) { | |
412 | *pbus_num = fs.bus_num; | |
413 | *paddr = fs.addr; | |
1f6e24e7 | 414 | pstrcpy(product_name, product_name_size, fs.product_name); |
a594cfbf | 415 | return 0; |
bb36d470 FB |
416 | } |
417 | } | |
a594cfbf | 418 | return -1; |
bb36d470 FB |
419 | } |
420 | ||
a594cfbf FB |
421 | /**********************/ |
422 | /* USB host device info */ | |
423 | ||
424 | struct usb_class_info { | |
425 | int class; | |
426 | const char *class_name; | |
427 | }; | |
428 | ||
429 | static const struct usb_class_info usb_class_info[] = { | |
430 | { USB_CLASS_AUDIO, "Audio"}, | |
431 | { USB_CLASS_COMM, "Communication"}, | |
432 | { USB_CLASS_HID, "HID"}, | |
433 | { USB_CLASS_HUB, "Hub" }, | |
434 | { USB_CLASS_PHYSICAL, "Physical" }, | |
435 | { USB_CLASS_PRINTER, "Printer" }, | |
436 | { USB_CLASS_MASS_STORAGE, "Storage" }, | |
437 | { USB_CLASS_CDC_DATA, "Data" }, | |
438 | { USB_CLASS_APP_SPEC, "Application Specific" }, | |
439 | { USB_CLASS_VENDOR_SPEC, "Vendor Specific" }, | |
440 | { USB_CLASS_STILL_IMAGE, "Still Image" }, | |
441 | { USB_CLASS_CSCID, "Smart Card" }, | |
442 | { USB_CLASS_CONTENT_SEC, "Content Security" }, | |
443 | { -1, NULL } | |
444 | }; | |
445 | ||
446 | static const char *usb_class_str(uint8_t class) | |
bb36d470 | 447 | { |
a594cfbf FB |
448 | const struct usb_class_info *p; |
449 | for(p = usb_class_info; p->class != -1; p++) { | |
450 | if (p->class == class) | |
451 | break; | |
bb36d470 | 452 | } |
a594cfbf FB |
453 | return p->class_name; |
454 | } | |
455 | ||
456 | void usb_info_device(int bus_num, int addr, int class_id, | |
5fafdf24 | 457 | int vendor_id, int product_id, |
a594cfbf FB |
458 | const char *product_name, |
459 | int speed) | |
460 | { | |
461 | const char *class_str, *speed_str; | |
462 | ||
463 | switch(speed) { | |
5fafdf24 TS |
464 | case USB_SPEED_LOW: |
465 | speed_str = "1.5"; | |
a594cfbf | 466 | break; |
5fafdf24 TS |
467 | case USB_SPEED_FULL: |
468 | speed_str = "12"; | |
a594cfbf | 469 | break; |
5fafdf24 TS |
470 | case USB_SPEED_HIGH: |
471 | speed_str = "480"; | |
a594cfbf FB |
472 | break; |
473 | default: | |
5fafdf24 | 474 | speed_str = "?"; |
a594cfbf FB |
475 | break; |
476 | } | |
477 | ||
5fafdf24 | 478 | term_printf(" Device %d.%d, speed %s Mb/s\n", |
a594cfbf FB |
479 | bus_num, addr, speed_str); |
480 | class_str = usb_class_str(class_id); | |
5fafdf24 | 481 | if (class_str) |
a594cfbf FB |
482 | term_printf(" %s:", class_str); |
483 | else | |
484 | term_printf(" Class %02x:", class_id); | |
485 | term_printf(" USB device %04x:%04x", vendor_id, product_id); | |
486 | if (product_name[0] != '\0') | |
487 | term_printf(", %s", product_name); | |
488 | term_printf("\n"); | |
489 | } | |
490 | ||
5fafdf24 | 491 | static int usb_host_info_device(void *opaque, int bus_num, int addr, |
a594cfbf | 492 | int class_id, |
5fafdf24 | 493 | int vendor_id, int product_id, |
a594cfbf FB |
494 | const char *product_name, |
495 | int speed) | |
496 | { | |
497 | usb_info_device(bus_num, addr, class_id, vendor_id, product_id, | |
498 | product_name, speed); | |
499 | return 0; | |
500 | } | |
501 | ||
502 | void usb_host_info(void) | |
503 | { | |
504 | usb_host_scan(NULL, usb_host_info_device); | |
bb36d470 FB |
505 | } |
506 | ||
507 | #else | |
508 | ||
a594cfbf FB |
509 | void usb_host_info(void) |
510 | { | |
511 | term_printf("USB host devices not supported\n"); | |
512 | } | |
513 | ||
bb36d470 | 514 | /* XXX: modify configure to compile the right host driver */ |
a594cfbf | 515 | USBDevice *usb_host_device_open(const char *devname) |
bb36d470 FB |
516 | { |
517 | return NULL; | |
518 | } | |
519 | ||
520 | #endif |