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