|
|
1.1 root 1: /*
2: * QEMU USB HUB emulation
3: *
4: * Copyright (c) 2005 Fabrice Bellard
1.1.1.4 root 5: *
1.1 root 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: */
1.1.1.4 root 24: #include "qemu-common.h"
25: #include "usb.h"
1.1.1.8 ! root 26: #include "usb-desc.h"
1.1 root 27:
28: //#define DEBUG
29:
1.1.1.8 ! root 30: #define NUM_PORTS 8
1.1 root 31:
32: typedef struct USBHubPort {
33: USBPort port;
34: uint16_t wPortStatus;
35: uint16_t wPortChange;
36: } USBHubPort;
37:
38: typedef struct USBHubState {
39: USBDevice dev;
1.1.1.8 ! root 40: USBHubPort ports[NUM_PORTS];
1.1 root 41: } USBHubState;
42:
43: #define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE)
44: #define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE)
45: #define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR)
46: #define GetHubStatus (0xa000 | USB_REQ_GET_STATUS)
47: #define GetPortStatus (0xa300 | USB_REQ_GET_STATUS)
48: #define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE)
49: #define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE)
50:
51: #define PORT_STAT_CONNECTION 0x0001
52: #define PORT_STAT_ENABLE 0x0002
53: #define PORT_STAT_SUSPEND 0x0004
54: #define PORT_STAT_OVERCURRENT 0x0008
55: #define PORT_STAT_RESET 0x0010
56: #define PORT_STAT_POWER 0x0100
57: #define PORT_STAT_LOW_SPEED 0x0200
58: #define PORT_STAT_HIGH_SPEED 0x0400
59: #define PORT_STAT_TEST 0x0800
60: #define PORT_STAT_INDICATOR 0x1000
61:
62: #define PORT_STAT_C_CONNECTION 0x0001
63: #define PORT_STAT_C_ENABLE 0x0002
64: #define PORT_STAT_C_SUSPEND 0x0004
65: #define PORT_STAT_C_OVERCURRENT 0x0008
66: #define PORT_STAT_C_RESET 0x0010
67:
68: #define PORT_CONNECTION 0
69: #define PORT_ENABLE 1
70: #define PORT_SUSPEND 2
71: #define PORT_OVERCURRENT 3
72: #define PORT_RESET 4
73: #define PORT_POWER 8
74: #define PORT_LOWSPEED 9
75: #define PORT_HIGHSPEED 10
76: #define PORT_C_CONNECTION 16
77: #define PORT_C_ENABLE 17
78: #define PORT_C_SUSPEND 18
79: #define PORT_C_OVERCURRENT 19
80: #define PORT_C_RESET 20
81: #define PORT_TEST 21
82: #define PORT_INDICATOR 22
83:
84: /* same as Linux kernel root hubs */
85:
1.1.1.8 ! root 86: enum {
! 87: STR_MANUFACTURER = 1,
! 88: STR_PRODUCT,
! 89: STR_SERIALNUMBER,
! 90: };
! 91:
! 92: static const USBDescStrings desc_strings = {
! 93: [STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
! 94: [STR_PRODUCT] = "QEMU USB Hub",
! 95: [STR_SERIALNUMBER] = "314159",
! 96: };
! 97:
! 98: static const USBDescIface desc_iface_hub = {
! 99: .bInterfaceNumber = 0,
! 100: .bNumEndpoints = 1,
! 101: .bInterfaceClass = USB_CLASS_HUB,
! 102: .eps = (USBDescEndpoint[]) {
! 103: {
! 104: .bEndpointAddress = USB_DIR_IN | 0x01,
! 105: .bmAttributes = USB_ENDPOINT_XFER_INT,
! 106: .wMaxPacketSize = 1 + (NUM_PORTS + 7) / 8,
! 107: .bInterval = 0xff,
! 108: },
! 109: }
! 110: };
! 111:
! 112: static const USBDescDevice desc_device_hub = {
! 113: .bcdUSB = 0x0110,
! 114: .bDeviceClass = USB_CLASS_HUB,
! 115: .bMaxPacketSize0 = 8,
! 116: .bNumConfigurations = 1,
! 117: .confs = (USBDescConfig[]) {
! 118: {
! 119: .bNumInterfaces = 1,
! 120: .bConfigurationValue = 1,
! 121: .bmAttributes = 0xe0,
! 122: .ifs = &desc_iface_hub,
! 123: },
! 124: },
! 125: };
! 126:
! 127: static const USBDesc desc_hub = {
! 128: .id = {
! 129: .idVendor = 0,
! 130: .idProduct = 0,
! 131: .bcdDevice = 0x0101,
! 132: .iManufacturer = STR_MANUFACTURER,
! 133: .iProduct = STR_PRODUCT,
! 134: .iSerialNumber = STR_SERIALNUMBER,
! 135: },
! 136: .full = &desc_device_hub,
! 137: .str = desc_strings,
! 138: };
! 139:
1.1 root 140: static const uint8_t qemu_hub_dev_descriptor[] = {
141: 0x12, /* u8 bLength; */
142: 0x01, /* u8 bDescriptorType; Device */
143: 0x10, 0x01, /* u16 bcdUSB; v1.1 */
144:
145: 0x09, /* u8 bDeviceClass; HUB_CLASSCODE */
146: 0x00, /* u8 bDeviceSubClass; */
147: 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
148: 0x08, /* u8 bMaxPacketSize0; 8 Bytes */
149:
150: 0x00, 0x00, /* u16 idVendor; */
151: 0x00, 0x00, /* u16 idProduct; */
152: 0x01, 0x01, /* u16 bcdDevice */
153:
154: 0x03, /* u8 iManufacturer; */
155: 0x02, /* u8 iProduct; */
156: 0x01, /* u8 iSerialNumber; */
157: 0x01 /* u8 bNumConfigurations; */
158: };
159:
160: /* XXX: patch interrupt size */
161: static const uint8_t qemu_hub_config_descriptor[] = {
162:
163: /* one configuration */
164: 0x09, /* u8 bLength; */
165: 0x02, /* u8 bDescriptorType; Configuration */
166: 0x19, 0x00, /* u16 wTotalLength; */
167: 0x01, /* u8 bNumInterfaces; (1) */
168: 0x01, /* u8 bConfigurationValue; */
169: 0x00, /* u8 iConfiguration; */
1.1.1.7 root 170: 0xe0, /* u8 bmAttributes;
1.1 root 171: Bit 7: must be set,
172: 6: Self-powered,
173: 5: Remote wakeup,
174: 4..0: resvd */
175: 0x00, /* u8 MaxPower; */
1.1.1.4 root 176:
1.1 root 177: /* USB 1.1:
178: * USB 2.0, single TT organization (mandatory):
179: * one interface, protocol 0
180: *
181: * USB 2.0, multiple TT organization (optional):
182: * two interfaces, protocols 1 (like single TT)
183: * and 2 (multiple TT mode) ... config is
184: * sometimes settable
185: * NOT IMPLEMENTED
186: */
187:
188: /* one interface */
189: 0x09, /* u8 if_bLength; */
190: 0x04, /* u8 if_bDescriptorType; Interface */
191: 0x00, /* u8 if_bInterfaceNumber; */
192: 0x00, /* u8 if_bAlternateSetting; */
193: 0x01, /* u8 if_bNumEndpoints; */
194: 0x09, /* u8 if_bInterfaceClass; HUB_CLASSCODE */
195: 0x00, /* u8 if_bInterfaceSubClass; */
196: 0x00, /* u8 if_bInterfaceProtocol; [usb1.1 or single tt] */
197: 0x00, /* u8 if_iInterface; */
1.1.1.4 root 198:
1.1 root 199: /* one endpoint (status change endpoint) */
200: 0x07, /* u8 ep_bLength; */
201: 0x05, /* u8 ep_bDescriptorType; Endpoint */
202: 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */
203: 0x03, /* u8 ep_bmAttributes; Interrupt */
204: 0x02, 0x00, /* u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */
205: 0xff /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */
206: };
207:
208: static const uint8_t qemu_hub_hub_descriptor[] =
209: {
1.1.1.2 root 210: 0x00, /* u8 bLength; patched in later */
1.1 root 211: 0x29, /* u8 bDescriptorType; Hub-descriptor */
212: 0x00, /* u8 bNbrPorts; (patched later) */
213: 0x0a, /* u16 wHubCharacteristics; */
214: 0x00, /* (per-port OC, no power switching) */
215: 0x01, /* u8 bPwrOn2pwrGood; 2ms */
216: 0x00 /* u8 bHubContrCurrent; 0 mA */
217:
218: /* DeviceRemovable and PortPwrCtrlMask patched in later */
219: };
220:
1.1.1.8 ! root 221: static void usb_hub_attach(USBPort *port1)
1.1 root 222: {
223: USBHubState *s = port1->opaque;
224: USBHubPort *port = &s->ports[port1->index];
1.1.1.4 root 225:
1.1.1.8 ! root 226: port->wPortStatus |= PORT_STAT_CONNECTION;
! 227: port->wPortChange |= PORT_STAT_C_CONNECTION;
! 228: if (port->port.dev->speed == USB_SPEED_LOW) {
! 229: port->wPortStatus |= PORT_STAT_LOW_SPEED;
1.1 root 230: } else {
1.1.1.8 ! root 231: port->wPortStatus &= ~PORT_STAT_LOW_SPEED;
! 232: }
! 233: }
! 234:
! 235: static void usb_hub_detach(USBPort *port1)
! 236: {
! 237: USBHubState *s = port1->opaque;
! 238: USBHubPort *port = &s->ports[port1->index];
! 239:
! 240: port->wPortStatus &= ~PORT_STAT_CONNECTION;
! 241: port->wPortChange |= PORT_STAT_C_CONNECTION;
! 242: if (port->wPortStatus & PORT_STAT_ENABLE) {
! 243: port->wPortStatus &= ~PORT_STAT_ENABLE;
! 244: port->wPortChange |= PORT_STAT_C_ENABLE;
! 245: }
! 246: }
! 247:
! 248: static void usb_hub_wakeup(USBDevice *dev)
! 249: {
! 250: USBHubState *s = dev->port->opaque;
! 251: USBHubPort *port = &s->ports[dev->port->index];
! 252:
! 253: if (port->wPortStatus & PORT_STAT_SUSPEND) {
! 254: port->wPortChange |= PORT_STAT_C_SUSPEND;
! 255: usb_wakeup(&s->dev);
! 256: }
! 257: }
! 258:
! 259: static void usb_hub_handle_attach(USBDevice *dev)
! 260: {
! 261: USBHubState *s = DO_UPCAST(USBHubState, dev, dev);
! 262: int i;
! 263:
! 264: for (i = 0; i < NUM_PORTS; i++) {
! 265: usb_port_location(&s->ports[i].port, dev->port, i+1);
1.1 root 266: }
267: }
268:
269: static void usb_hub_handle_reset(USBDevice *dev)
270: {
271: /* XXX: do it */
272: }
273:
274: static int usb_hub_handle_control(USBDevice *dev, int request, int value,
275: int index, int length, uint8_t *data)
276: {
277: USBHubState *s = (USBHubState *)dev;
278: int ret;
279:
1.1.1.8 ! root 280: ret = usb_desc_handle_control(dev, request, value, index, length, data);
! 281: if (ret >= 0) {
! 282: return ret;
! 283: }
! 284:
1.1 root 285: switch(request) {
286: case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
287: if (value == 0 && index != 0x81) { /* clear ep halt */
288: goto fail;
289: }
290: ret = 0;
291: break;
292: case DeviceRequest | USB_REQ_GET_INTERFACE:
293: data[0] = 0;
294: ret = 1;
295: break;
296: case DeviceOutRequest | USB_REQ_SET_INTERFACE:
297: ret = 0;
298: break;
299: /* usb specific requests */
300: case GetHubStatus:
301: data[0] = 0;
302: data[1] = 0;
303: data[2] = 0;
304: data[3] = 0;
305: ret = 4;
306: break;
307: case GetPortStatus:
308: {
309: unsigned int n = index - 1;
310: USBHubPort *port;
1.1.1.8 ! root 311: if (n >= NUM_PORTS) {
1.1 root 312: goto fail;
1.1.1.8 ! root 313: }
1.1 root 314: port = &s->ports[n];
315: data[0] = port->wPortStatus;
316: data[1] = port->wPortStatus >> 8;
317: data[2] = port->wPortChange;
318: data[3] = port->wPortChange >> 8;
319: ret = 4;
320: }
321: break;
322: case SetHubFeature:
323: case ClearHubFeature:
324: if (value == 0 || value == 1) {
325: } else {
326: goto fail;
327: }
328: ret = 0;
329: break;
330: case SetPortFeature:
331: {
332: unsigned int n = index - 1;
333: USBHubPort *port;
334: USBDevice *dev;
1.1.1.8 ! root 335: if (n >= NUM_PORTS) {
1.1 root 336: goto fail;
1.1.1.8 ! root 337: }
1.1 root 338: port = &s->ports[n];
339: dev = port->port.dev;
340: switch(value) {
341: case PORT_SUSPEND:
342: port->wPortStatus |= PORT_STAT_SUSPEND;
343: break;
344: case PORT_RESET:
345: if (dev) {
1.1.1.3 root 346: usb_send_msg(dev, USB_MSG_RESET);
1.1 root 347: port->wPortChange |= PORT_STAT_C_RESET;
348: /* set enable bit */
349: port->wPortStatus |= PORT_STAT_ENABLE;
350: }
351: break;
352: case PORT_POWER:
353: break;
354: default:
355: goto fail;
356: }
357: ret = 0;
358: }
359: break;
360: case ClearPortFeature:
361: {
362: unsigned int n = index - 1;
363: USBHubPort *port;
1.1.1.7 root 364:
1.1.1.8 ! root 365: if (n >= NUM_PORTS) {
1.1 root 366: goto fail;
1.1.1.8 ! root 367: }
1.1 root 368: port = &s->ports[n];
369: switch(value) {
370: case PORT_ENABLE:
371: port->wPortStatus &= ~PORT_STAT_ENABLE;
372: break;
373: case PORT_C_ENABLE:
374: port->wPortChange &= ~PORT_STAT_C_ENABLE;
375: break;
376: case PORT_SUSPEND:
377: port->wPortStatus &= ~PORT_STAT_SUSPEND;
378: break;
379: case PORT_C_SUSPEND:
380: port->wPortChange &= ~PORT_STAT_C_SUSPEND;
381: break;
382: case PORT_C_CONNECTION:
383: port->wPortChange &= ~PORT_STAT_C_CONNECTION;
384: break;
385: case PORT_C_OVERCURRENT:
386: port->wPortChange &= ~PORT_STAT_C_OVERCURRENT;
387: break;
388: case PORT_C_RESET:
389: port->wPortChange &= ~PORT_STAT_C_RESET;
390: break;
391: default:
392: goto fail;
393: }
394: ret = 0;
395: }
396: break;
397: case GetHubDescriptor:
398: {
399: unsigned int n, limit, var_hub_size = 0;
1.1.1.4 root 400: memcpy(data, qemu_hub_hub_descriptor,
1.1 root 401: sizeof(qemu_hub_hub_descriptor));
1.1.1.8 ! root 402: data[2] = NUM_PORTS;
1.1 root 403:
404: /* fill DeviceRemovable bits */
1.1.1.8 ! root 405: limit = ((NUM_PORTS + 1 + 7) / 8) + 7;
1.1 root 406: for (n = 7; n < limit; n++) {
407: data[n] = 0x00;
408: var_hub_size++;
409: }
410:
411: /* fill PortPwrCtrlMask bits */
1.1.1.8 ! root 412: limit = limit + ((NUM_PORTS + 7) / 8);
1.1 root 413: for (;n < limit; n++) {
414: data[n] = 0xff;
415: var_hub_size++;
416: }
417:
418: ret = sizeof(qemu_hub_hub_descriptor) + var_hub_size;
1.1.1.2 root 419: data[0] = ret;
1.1 root 420: break;
421: }
422: default:
423: fail:
424: ret = USB_RET_STALL;
425: break;
426: }
427: return ret;
428: }
429:
1.1.1.3 root 430: static int usb_hub_handle_data(USBDevice *dev, USBPacket *p)
1.1 root 431: {
432: USBHubState *s = (USBHubState *)dev;
433: int ret;
434:
1.1.1.3 root 435: switch(p->pid) {
1.1 root 436: case USB_TOKEN_IN:
1.1.1.3 root 437: if (p->devep == 1) {
1.1 root 438: USBHubPort *port;
439: unsigned int status;
440: int i, n;
1.1.1.8 ! root 441: n = (NUM_PORTS + 1 + 7) / 8;
1.1.1.3 root 442: if (p->len == 1) { /* FreeBSD workaround */
1.1 root 443: n = 1;
1.1.1.3 root 444: } else if (n > p->len) {
1.1 root 445: return USB_RET_BABBLE;
446: }
447: status = 0;
1.1.1.8 ! root 448: for(i = 0; i < NUM_PORTS; i++) {
1.1 root 449: port = &s->ports[i];
450: if (port->wPortChange)
451: status |= (1 << (i + 1));
452: }
453: if (status != 0) {
454: for(i = 0; i < n; i++) {
1.1.1.3 root 455: p->data[i] = status >> (8 * i);
1.1 root 456: }
457: ret = n;
458: } else {
459: ret = USB_RET_NAK; /* usb11 11.13.1 */
460: }
461: } else {
462: goto fail;
463: }
464: break;
465: case USB_TOKEN_OUT:
466: default:
467: fail:
468: ret = USB_RET_STALL;
469: break;
470: }
471: return ret;
472: }
473:
1.1.1.3 root 474: static int usb_hub_broadcast_packet(USBHubState *s, USBPacket *p)
1.1 root 475: {
476: USBHubPort *port;
477: USBDevice *dev;
478: int i, ret;
479:
1.1.1.8 ! root 480: for(i = 0; i < NUM_PORTS; i++) {
1.1 root 481: port = &s->ports[i];
482: dev = port->port.dev;
483: if (dev && (port->wPortStatus & PORT_STAT_ENABLE)) {
1.1.1.6 root 484: ret = dev->info->handle_packet(dev, p);
1.1 root 485: if (ret != USB_RET_NODEV) {
486: return ret;
487: }
488: }
489: }
490: return USB_RET_NODEV;
491: }
492:
1.1.1.3 root 493: static int usb_hub_handle_packet(USBDevice *dev, USBPacket *p)
1.1 root 494: {
495: USBHubState *s = (USBHubState *)dev;
496:
497: #if defined(DEBUG) && 0
498: printf("usb_hub: pid=0x%x\n", pid);
499: #endif
500: if (dev->state == USB_STATE_DEFAULT &&
501: dev->addr != 0 &&
1.1.1.3 root 502: p->devaddr != dev->addr &&
1.1.1.4 root 503: (p->pid == USB_TOKEN_SETUP ||
504: p->pid == USB_TOKEN_OUT ||
1.1.1.3 root 505: p->pid == USB_TOKEN_IN)) {
1.1 root 506: /* broadcast the packet to the devices */
1.1.1.3 root 507: return usb_hub_broadcast_packet(s, p);
1.1 root 508: }
1.1.1.3 root 509: return usb_generic_handle_packet(dev, p);
1.1 root 510: }
511:
1.1.1.2 root 512: static void usb_hub_handle_destroy(USBDevice *dev)
513: {
514: USBHubState *s = (USBHubState *)dev;
1.1.1.6 root 515: int i;
1.1.1.2 root 516:
1.1.1.8 ! root 517: for (i = 0; i < NUM_PORTS; i++) {
1.1.1.6 root 518: usb_unregister_port(usb_bus_from_device(dev),
519: &s->ports[i].port);
520: }
1.1.1.2 root 521: }
522:
1.1.1.8 ! root 523: static USBPortOps usb_hub_port_ops = {
! 524: .attach = usb_hub_attach,
! 525: .detach = usb_hub_detach,
! 526: .wakeup = usb_hub_wakeup,
! 527: };
! 528:
1.1.1.6 root 529: static int usb_hub_initfn(USBDevice *dev)
1.1 root 530: {
1.1.1.6 root 531: USBHubState *s = DO_UPCAST(USBHubState, dev, dev);
1.1 root 532: USBHubPort *port;
533: int i;
534:
1.1.1.8 ! root 535: usb_desc_init(dev);
! 536: for (i = 0; i < NUM_PORTS; i++) {
1.1 root 537: port = &s->ports[i];
1.1.1.6 root 538: usb_register_port(usb_bus_from_device(dev),
1.1.1.8 ! root 539: &port->port, s, i, &usb_hub_port_ops,
! 540: USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
1.1 root 541: port->wPortStatus = PORT_STAT_POWER;
542: port->wPortChange = 0;
543: }
1.1.1.6 root 544: return 0;
545: }
546:
1.1.1.8 ! root 547: static const VMStateDescription vmstate_usb_hub_port = {
! 548: .name = "usb-hub-port",
! 549: .version_id = 1,
! 550: .minimum_version_id = 1,
! 551: .fields = (VMStateField []) {
! 552: VMSTATE_UINT16(wPortStatus, USBHubPort),
! 553: VMSTATE_UINT16(wPortChange, USBHubPort),
! 554: VMSTATE_END_OF_LIST()
! 555: }
! 556: };
! 557:
! 558: static const VMStateDescription vmstate_usb_hub = {
! 559: .name = "usb-hub",
! 560: .version_id = 1,
! 561: .minimum_version_id = 1,
! 562: .fields = (VMStateField []) {
! 563: VMSTATE_USB_DEVICE(dev, USBHubState),
! 564: VMSTATE_STRUCT_ARRAY(ports, USBHubState, NUM_PORTS, 0,
! 565: vmstate_usb_hub_port, USBHubPort),
! 566: VMSTATE_END_OF_LIST()
! 567: }
! 568: };
! 569:
1.1.1.6 root 570: static struct USBDeviceInfo hub_info = {
571: .product_desc = "QEMU USB Hub",
572: .qdev.name = "usb-hub",
1.1.1.8 ! root 573: .qdev.fw_name = "hub",
1.1.1.6 root 574: .qdev.size = sizeof(USBHubState),
1.1.1.8 ! root 575: .qdev.vmsd = &vmstate_usb_hub,
! 576: .usb_desc = &desc_hub,
1.1.1.6 root 577: .init = usb_hub_initfn,
578: .handle_packet = usb_hub_handle_packet,
1.1.1.8 ! root 579: .handle_attach = usb_hub_handle_attach,
1.1.1.6 root 580: .handle_reset = usb_hub_handle_reset,
581: .handle_control = usb_hub_handle_control,
582: .handle_data = usb_hub_handle_data,
583: .handle_destroy = usb_hub_handle_destroy,
584: };
585:
586: static void usb_hub_register_devices(void)
587: {
588: usb_qdev_register(&hub_info);
1.1 root 589: }
1.1.1.6 root 590: device_init(usb_hub_register_devices)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.