]>
Commit | Line | Data |
---|---|---|
bb36d470 FB |
1 | /* |
2 | * Linux host USB redirector | |
3 | * | |
4 | * Copyright (c) 2005 Fabrice Bellard | |
5 | * | |
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 FB |
43 | typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id, |
44 | int vendor_id, int product_id, | |
45 | const char *product_name, int speed); | |
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 | ||
2e5d83bb | 60 | static void usb_host_handle_reset(USBDevice *dev, int destroy) |
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 | |
68 | } | |
69 | ||
70 | static int usb_host_handle_control(USBDevice *dev, | |
71 | int request, | |
72 | int value, | |
73 | int index, | |
74 | int length, | |
75 | uint8_t *data) | |
76 | { | |
77 | USBHostDevice *s = (USBHostDevice *)dev; | |
78 | struct usb_ctrltransfer ct; | |
79 | int ret; | |
80 | ||
81 | if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) { | |
82 | /* specific SET_ADDRESS support */ | |
83 | dev->addr = value; | |
84 | return 0; | |
85 | } else { | |
86 | ct.bRequestType = request >> 8; | |
87 | ct.bRequest = request; | |
88 | ct.wValue = value; | |
89 | ct.wIndex = index; | |
90 | ct.wLength = length; | |
91 | ct.timeout = 50; | |
92 | ct.data = data; | |
93 | ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); | |
94 | if (ret < 0) { | |
95 | switch(errno) { | |
96 | case ETIMEDOUT: | |
97 | return USB_RET_NAK; | |
98 | default: | |
99 | return USB_RET_STALL; | |
100 | } | |
101 | } else { | |
102 | return ret; | |
103 | } | |
104 | } | |
105 | } | |
106 | ||
107 | static int usb_host_handle_data(USBDevice *dev, int pid, | |
108 | uint8_t devep, | |
109 | uint8_t *data, int len) | |
110 | { | |
111 | USBHostDevice *s = (USBHostDevice *)dev; | |
112 | struct usbdevfs_bulktransfer bt; | |
113 | int ret; | |
114 | ||
115 | /* XXX: optimize and handle all data types by looking at the | |
116 | config descriptor */ | |
117 | if (pid == USB_TOKEN_IN) | |
118 | devep |= 0x80; | |
119 | bt.ep = devep; | |
120 | bt.len = len; | |
121 | bt.timeout = 50; | |
122 | bt.data = data; | |
123 | ret = ioctl(s->fd, USBDEVFS_BULK, &bt); | |
124 | if (ret < 0) { | |
125 | switch(errno) { | |
126 | case ETIMEDOUT: | |
127 | return USB_RET_NAK; | |
128 | case EPIPE: | |
129 | default: | |
130 | #ifdef DEBUG | |
131 | printf("handle_data: errno=%d\n", errno); | |
132 | #endif | |
133 | return USB_RET_STALL; | |
134 | } | |
135 | } else { | |
136 | return ret; | |
137 | } | |
138 | } | |
139 | ||
bb36d470 | 140 | /* XXX: exclude high speed devices or implement EHCI */ |
a594cfbf | 141 | USBDevice *usb_host_device_open(const char *devname) |
bb36d470 FB |
142 | { |
143 | int fd, interface, ret, i; | |
144 | USBHostDevice *dev; | |
145 | struct usbdevfs_connectinfo ci; | |
146 | uint8_t descr[1024]; | |
a594cfbf | 147 | char buf[1024]; |
bb36d470 | 148 | int descr_len, dev_descr_len, config_descr_len, nb_interfaces; |
a594cfbf | 149 | int bus_num, addr; |
1f6e24e7 | 150 | char product_name[PRODUCT_NAME_SZ]; |
bb36d470 | 151 | |
1f6e24e7 FB |
152 | if (usb_host_find_device(&bus_num, &addr, |
153 | product_name, sizeof(product_name), | |
154 | devname) < 0) | |
a594cfbf FB |
155 | return NULL; |
156 | ||
157 | snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d", | |
158 | bus_num, addr); | |
159 | fd = open(buf, O_RDWR); | |
bb36d470 | 160 | if (fd < 0) { |
a594cfbf FB |
161 | perror(buf); |
162 | return NULL; | |
bb36d470 FB |
163 | } |
164 | ||
165 | /* read the config description */ | |
166 | descr_len = read(fd, descr, sizeof(descr)); | |
167 | if (descr_len <= 0) { | |
168 | perror("read descr"); | |
169 | goto fail; | |
170 | } | |
171 | ||
172 | i = 0; | |
173 | dev_descr_len = descr[0]; | |
174 | if (dev_descr_len > descr_len) | |
175 | goto fail; | |
176 | i += dev_descr_len; | |
177 | config_descr_len = descr[i]; | |
178 | if (i + config_descr_len > descr_len) | |
179 | goto fail; | |
180 | nb_interfaces = descr[i + 4]; | |
181 | if (nb_interfaces != 1) { | |
182 | /* NOTE: currently we grab only one interface */ | |
a594cfbf FB |
183 | fprintf(stderr, "usb_host: only one interface supported\n"); |
184 | goto fail; | |
185 | } | |
186 | ||
187 | #ifdef USBDEVFS_DISCONNECT | |
188 | /* earlier Linux 2.4 do not support that */ | |
868bfe2b FB |
189 | { |
190 | struct usbdevfs_ioctl ctrl; | |
191 | ctrl.ioctl_code = USBDEVFS_DISCONNECT; | |
192 | ctrl.ifno = 0; | |
193 | ret = ioctl(fd, USBDEVFS_IOCTL, &ctrl); | |
194 | if (ret < 0 && errno != ENODATA) { | |
195 | perror("USBDEVFS_DISCONNECT"); | |
196 | goto fail; | |
197 | } | |
bb36d470 | 198 | } |
a594cfbf FB |
199 | #endif |
200 | ||
bb36d470 FB |
201 | /* XXX: only grab if all interfaces are free */ |
202 | interface = 0; | |
203 | ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface); | |
204 | if (ret < 0) { | |
205 | if (errno == EBUSY) { | |
a594cfbf | 206 | fprintf(stderr, "usb_host: device already grabbed\n"); |
bb36d470 FB |
207 | } else { |
208 | perror("USBDEVFS_CLAIMINTERFACE"); | |
209 | } | |
210 | fail: | |
211 | close(fd); | |
a594cfbf | 212 | return NULL; |
bb36d470 FB |
213 | } |
214 | ||
215 | ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); | |
216 | if (ret < 0) { | |
217 | perror("USBDEVFS_CONNECTINFO"); | |
218 | goto fail; | |
219 | } | |
220 | ||
221 | #ifdef DEBUG | |
a594cfbf | 222 | printf("host USB device %d.%d grabbed\n", bus_num, addr); |
bb36d470 FB |
223 | #endif |
224 | ||
bb36d470 FB |
225 | dev = qemu_mallocz(sizeof(USBHostDevice)); |
226 | if (!dev) | |
227 | goto fail; | |
228 | dev->fd = fd; | |
229 | if (ci.slow) | |
230 | dev->dev.speed = USB_SPEED_LOW; | |
231 | else | |
232 | dev->dev.speed = USB_SPEED_HIGH; | |
a594cfbf | 233 | dev->dev.handle_packet = usb_generic_handle_packet; |
bb36d470 FB |
234 | |
235 | dev->dev.handle_reset = usb_host_handle_reset; | |
236 | dev->dev.handle_control = usb_host_handle_control; | |
237 | dev->dev.handle_data = usb_host_handle_data; | |
1f6e24e7 FB |
238 | |
239 | if (product_name[0] == '\0') | |
240 | snprintf(dev->dev.devname, sizeof(dev->dev.devname), | |
241 | "host:%s", devname); | |
242 | else | |
243 | pstrcpy(dev->dev.devname, sizeof(dev->dev.devname), | |
244 | product_name); | |
245 | ||
a594cfbf FB |
246 | return (USBDevice *)dev; |
247 | } | |
bb36d470 | 248 | |
a594cfbf FB |
249 | static int get_tag_value(char *buf, int buf_size, |
250 | const char *str, const char *tag, | |
251 | const char *stopchars) | |
252 | { | |
253 | const char *p; | |
254 | char *q; | |
255 | p = strstr(str, tag); | |
256 | if (!p) | |
257 | return -1; | |
258 | p += strlen(tag); | |
259 | while (isspace(*p)) | |
260 | p++; | |
261 | q = buf; | |
262 | while (*p != '\0' && !strchr(stopchars, *p)) { | |
263 | if ((q - buf) < (buf_size - 1)) | |
264 | *q++ = *p; | |
265 | p++; | |
266 | } | |
267 | *q = '\0'; | |
268 | return q - buf; | |
bb36d470 FB |
269 | } |
270 | ||
a594cfbf | 271 | static int usb_host_scan(void *opaque, USBScanFunc *func) |
bb36d470 | 272 | { |
a594cfbf FB |
273 | FILE *f; |
274 | char line[1024]; | |
bb36d470 | 275 | char buf[1024]; |
a594cfbf FB |
276 | int bus_num, addr, speed, device_count, class_id, product_id, vendor_id; |
277 | int ret; | |
278 | char product_name[512]; | |
279 | ||
280 | f = fopen(USBDEVFS_PATH "/devices", "r"); | |
281 | if (!f) { | |
282 | term_printf("Could not open %s\n", USBDEVFS_PATH "/devices"); | |
283 | return 0; | |
284 | } | |
285 | device_count = 0; | |
286 | bus_num = addr = speed = class_id = product_id = vendor_id = 0; | |
287 | ret = 0; | |
bb36d470 | 288 | for(;;) { |
a594cfbf | 289 | if (fgets(line, sizeof(line), f) == NULL) |
bb36d470 | 290 | break; |
a594cfbf FB |
291 | if (strlen(line) > 0) |
292 | line[strlen(line) - 1] = '\0'; | |
293 | if (line[0] == 'T' && line[1] == ':') { | |
38ca0f6d PB |
294 | if (device_count && (vendor_id || product_id)) { |
295 | /* New device. Add the previously discovered device. */ | |
a594cfbf FB |
296 | ret = func(opaque, bus_num, addr, class_id, vendor_id, |
297 | product_id, product_name, speed); | |
298 | if (ret) | |
299 | goto the_end; | |
300 | } | |
301 | if (get_tag_value(buf, sizeof(buf), line, "Bus=", " ") < 0) | |
302 | goto fail; | |
303 | bus_num = atoi(buf); | |
304 | if (get_tag_value(buf, sizeof(buf), line, "Dev#=", " ") < 0) | |
305 | goto fail; | |
306 | addr = atoi(buf); | |
307 | if (get_tag_value(buf, sizeof(buf), line, "Spd=", " ") < 0) | |
308 | goto fail; | |
309 | if (!strcmp(buf, "480")) | |
310 | speed = USB_SPEED_HIGH; | |
311 | else if (!strcmp(buf, "1.5")) | |
312 | speed = USB_SPEED_LOW; | |
313 | else | |
314 | speed = USB_SPEED_FULL; | |
315 | product_name[0] = '\0'; | |
316 | class_id = 0xff; | |
317 | device_count++; | |
318 | product_id = 0; | |
319 | vendor_id = 0; | |
320 | } else if (line[0] == 'P' && line[1] == ':') { | |
321 | if (get_tag_value(buf, sizeof(buf), line, "Vendor=", " ") < 0) | |
322 | goto fail; | |
323 | vendor_id = strtoul(buf, NULL, 16); | |
324 | if (get_tag_value(buf, sizeof(buf), line, "ProdID=", " ") < 0) | |
325 | goto fail; | |
326 | product_id = strtoul(buf, NULL, 16); | |
327 | } else if (line[0] == 'S' && line[1] == ':') { | |
328 | if (get_tag_value(buf, sizeof(buf), line, "Product=", "") < 0) | |
329 | goto fail; | |
330 | pstrcpy(product_name, sizeof(product_name), buf); | |
331 | } else if (line[0] == 'D' && line[1] == ':') { | |
332 | if (get_tag_value(buf, sizeof(buf), line, "Cls=", " (") < 0) | |
333 | goto fail; | |
334 | class_id = strtoul(buf, NULL, 16); | |
bb36d470 | 335 | } |
a594cfbf FB |
336 | fail: ; |
337 | } | |
38ca0f6d PB |
338 | if (device_count && (vendor_id || product_id)) { |
339 | /* Add the last device. */ | |
a594cfbf FB |
340 | ret = func(opaque, bus_num, addr, class_id, vendor_id, |
341 | product_id, product_name, speed); | |
bb36d470 | 342 | } |
a594cfbf FB |
343 | the_end: |
344 | fclose(f); | |
345 | return ret; | |
bb36d470 FB |
346 | } |
347 | ||
a594cfbf FB |
348 | typedef struct FindDeviceState { |
349 | int vendor_id; | |
350 | int product_id; | |
351 | int bus_num; | |
352 | int addr; | |
1f6e24e7 | 353 | char product_name[PRODUCT_NAME_SZ]; |
a594cfbf FB |
354 | } FindDeviceState; |
355 | ||
356 | static int usb_host_find_device_scan(void *opaque, int bus_num, int addr, | |
357 | int class_id, | |
358 | int vendor_id, int product_id, | |
359 | const char *product_name, int speed) | |
bb36d470 | 360 | { |
a594cfbf | 361 | FindDeviceState *s = opaque; |
1f6e24e7 FB |
362 | if ((vendor_id == s->vendor_id && |
363 | product_id == s->product_id) || | |
364 | (bus_num == s->bus_num && | |
365 | addr == s->addr)) { | |
366 | pstrcpy(s->product_name, PRODUCT_NAME_SZ, product_name); | |
a594cfbf FB |
367 | s->bus_num = bus_num; |
368 | s->addr = addr; | |
369 | return 1; | |
370 | } else { | |
371 | return 0; | |
372 | } | |
373 | } | |
bb36d470 | 374 | |
a594cfbf FB |
375 | /* the syntax is : |
376 | 'bus.addr' (decimal numbers) or | |
377 | 'vendor_id:product_id' (hexa numbers) */ | |
378 | static int usb_host_find_device(int *pbus_num, int *paddr, | |
1f6e24e7 | 379 | char *product_name, int product_name_size, |
a594cfbf FB |
380 | const char *devname) |
381 | { | |
382 | const char *p; | |
383 | int ret; | |
384 | FindDeviceState fs; | |
385 | ||
386 | p = strchr(devname, '.'); | |
387 | if (p) { | |
388 | *pbus_num = strtoul(devname, NULL, 0); | |
389 | *paddr = strtoul(p + 1, NULL, 0); | |
1f6e24e7 FB |
390 | fs.bus_num = *pbus_num; |
391 | fs.addr = *paddr; | |
392 | ret = usb_host_scan(&fs, usb_host_find_device_scan); | |
393 | if (ret) | |
394 | pstrcpy(product_name, product_name_size, fs.product_name); | |
a594cfbf FB |
395 | return 0; |
396 | } | |
397 | p = strchr(devname, ':'); | |
398 | if (p) { | |
399 | fs.vendor_id = strtoul(devname, NULL, 16); | |
400 | fs.product_id = strtoul(p + 1, NULL, 16); | |
401 | ret = usb_host_scan(&fs, usb_host_find_device_scan); | |
402 | if (ret) { | |
403 | *pbus_num = fs.bus_num; | |
404 | *paddr = fs.addr; | |
1f6e24e7 | 405 | pstrcpy(product_name, product_name_size, fs.product_name); |
a594cfbf | 406 | return 0; |
bb36d470 FB |
407 | } |
408 | } | |
a594cfbf | 409 | return -1; |
bb36d470 FB |
410 | } |
411 | ||
a594cfbf FB |
412 | /**********************/ |
413 | /* USB host device info */ | |
414 | ||
415 | struct usb_class_info { | |
416 | int class; | |
417 | const char *class_name; | |
418 | }; | |
419 | ||
420 | static const struct usb_class_info usb_class_info[] = { | |
421 | { USB_CLASS_AUDIO, "Audio"}, | |
422 | { USB_CLASS_COMM, "Communication"}, | |
423 | { USB_CLASS_HID, "HID"}, | |
424 | { USB_CLASS_HUB, "Hub" }, | |
425 | { USB_CLASS_PHYSICAL, "Physical" }, | |
426 | { USB_CLASS_PRINTER, "Printer" }, | |
427 | { USB_CLASS_MASS_STORAGE, "Storage" }, | |
428 | { USB_CLASS_CDC_DATA, "Data" }, | |
429 | { USB_CLASS_APP_SPEC, "Application Specific" }, | |
430 | { USB_CLASS_VENDOR_SPEC, "Vendor Specific" }, | |
431 | { USB_CLASS_STILL_IMAGE, "Still Image" }, | |
432 | { USB_CLASS_CSCID, "Smart Card" }, | |
433 | { USB_CLASS_CONTENT_SEC, "Content Security" }, | |
434 | { -1, NULL } | |
435 | }; | |
436 | ||
437 | static const char *usb_class_str(uint8_t class) | |
bb36d470 | 438 | { |
a594cfbf FB |
439 | const struct usb_class_info *p; |
440 | for(p = usb_class_info; p->class != -1; p++) { | |
441 | if (p->class == class) | |
442 | break; | |
bb36d470 | 443 | } |
a594cfbf FB |
444 | return p->class_name; |
445 | } | |
446 | ||
447 | void usb_info_device(int bus_num, int addr, int class_id, | |
448 | int vendor_id, int product_id, | |
449 | const char *product_name, | |
450 | int speed) | |
451 | { | |
452 | const char *class_str, *speed_str; | |
453 | ||
454 | switch(speed) { | |
455 | case USB_SPEED_LOW: | |
456 | speed_str = "1.5"; | |
457 | break; | |
458 | case USB_SPEED_FULL: | |
459 | speed_str = "12"; | |
460 | break; | |
461 | case USB_SPEED_HIGH: | |
462 | speed_str = "480"; | |
463 | break; | |
464 | default: | |
465 | speed_str = "?"; | |
466 | break; | |
467 | } | |
468 | ||
469 | term_printf(" Device %d.%d, speed %s Mb/s\n", | |
470 | bus_num, addr, speed_str); | |
471 | class_str = usb_class_str(class_id); | |
472 | if (class_str) | |
473 | term_printf(" %s:", class_str); | |
474 | else | |
475 | term_printf(" Class %02x:", class_id); | |
476 | term_printf(" USB device %04x:%04x", vendor_id, product_id); | |
477 | if (product_name[0] != '\0') | |
478 | term_printf(", %s", product_name); | |
479 | term_printf("\n"); | |
480 | } | |
481 | ||
482 | static int usb_host_info_device(void *opaque, int bus_num, int addr, | |
483 | int class_id, | |
484 | int vendor_id, int product_id, | |
485 | const char *product_name, | |
486 | int speed) | |
487 | { | |
488 | usb_info_device(bus_num, addr, class_id, vendor_id, product_id, | |
489 | product_name, speed); | |
490 | return 0; | |
491 | } | |
492 | ||
493 | void usb_host_info(void) | |
494 | { | |
495 | usb_host_scan(NULL, usb_host_info_device); | |
bb36d470 FB |
496 | } |
497 | ||
498 | #else | |
499 | ||
a594cfbf FB |
500 | void usb_host_info(void) |
501 | { | |
502 | term_printf("USB host devices not supported\n"); | |
503 | } | |
504 | ||
bb36d470 | 505 | /* XXX: modify configure to compile the right host driver */ |
a594cfbf | 506 | USBDevice *usb_host_device_open(const char *devname) |
bb36d470 FB |
507 | { |
508 | return NULL; | |
509 | } | |
510 | ||
511 | #endif |