Skip to content
Snippets Groups Projects
usb.c 36.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    		wait_ms(200);
    	}
    
    	if (tries==MAX_TRIES) {
    		USB_HUB_PRINTF("Cannot enable port %i after %i retries, disabling port.\n", port+1, MAX_TRIES);
    		USB_HUB_PRINTF("Maybe the USB cable is bad?\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	}
    
    	usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_RESET);
    
    	*portstat = portstatus;
    	return 0;
    
    }
    
    
    void usb_hub_port_connect_change(struct usb_device *dev, int port)
    {
    	struct usb_device *usb;
    	struct usb_port_status portsts;
    	unsigned short portstatus, portchange;
    
    	/* Check status */
    	if (usb_get_port_status(dev, port + 1, &portsts)<0) {
    		USB_HUB_PRINTF("get_port_status failed\n");
    		return;
    	}
    
    
    	portstatus = le16_to_cpu(portsts.wPortStatus);
    	portchange = le16_to_cpu(portsts.wPortChange);
    
    	USB_HUB_PRINTF("portstatus %x, change %x, %s\n", portstatus, portchange,
    		portstatus&(1<<USB_PORT_FEAT_LOWSPEED) ? "Low Speed" : "High Speed");
    
    	/* Clear the connection change status */
    	usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_CONNECTION);
    
    	/* Disconnect any existing devices under this port */
    	if (((!(portstatus & USB_PORT_STAT_CONNECTION)) &&
    	     (!(portstatus & USB_PORT_STAT_ENABLE)))|| (dev->children[port])) {
    		USB_HUB_PRINTF("usb_disconnect(&hub->children[port]);\n");
    		/* Return now if nothing is connected */
    		if (!(portstatus & USB_PORT_STAT_CONNECTION))
    			return;
    	}
    	wait_ms(200);
    
    	/* Reset the port */
    	if (hub_port_reset(dev, port, &portstatus) < 0) {
    		printf("cannot reset port %i!?\n", port + 1);
    		return;
    	}
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	wait_ms(200);
    
    	/* Allocate a new device struct for it */
    	usb=usb_alloc_new_device();
    	usb->slow = (portstatus & USB_PORT_STAT_LOW_SPEED) ? 1 : 0;
    
    	dev->children[port] = usb;
    	usb->parent=dev;
    	/* Run it through the hoops (find a driver, etc) */
    	if (usb_new_device(usb)) {
    		/* Woops, disable the port */
    		USB_HUB_PRINTF("hub: disabling port %d\n", port + 1);
    		usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE);
    	}
    }
    
    
    int usb_hub_configure(struct usb_device *dev)
    {
    
    	unsigned char buffer[USB_BUFSIZ], *bitmap;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	struct usb_hub_descriptor *descriptor;
    	struct usb_hub_status *hubsts;
    	int i;
    	struct usb_hub_device *hub;
    
    	/* "allocate" Hub device */
    	hub=usb_hub_allocate();
    	if(hub==NULL)
    		return -1;
    	hub->pusb_dev=dev;
    	/* Get the the hub descriptor */
    	if (usb_get_hub_descriptor(dev, buffer, 4) < 0) {
    		USB_HUB_PRINTF("usb_hub_configure: failed to get hub descriptor, giving up %lX\n",dev->status);
    		return -1;
    	}
    	descriptor = (struct usb_hub_descriptor *)buffer;
    
    	/* silence compiler warning if USB_BUFSIZ is > 256 [= sizeof(char)] */
    	i = descriptor->bLength;
    	if (i > USB_BUFSIZ) {
    
    		USB_HUB_PRINTF("usb_hub_configure: failed to get hub descriptor - too long: %d\n",
    
    			descriptor->bLength);
    		return -1;
    	}
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (usb_get_hub_descriptor(dev, buffer, descriptor->bLength) < 0) {
    		USB_HUB_PRINTF("usb_hub_configure: failed to get hub descriptor 2nd giving up %lX\n",dev->status);
    		return -1;
    	}
    	memcpy((unsigned char *)&hub->desc,buffer,descriptor->bLength);
    	/* adjust 16bit values */
    
    	hub->desc.wHubCharacteristics = le16_to_cpu(descriptor->wHubCharacteristics);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	/* set the bitmap */
    	bitmap=(unsigned char *)&hub->desc.DeviceRemovable[0];
    	memset(bitmap,0xff,(USB_MAXCHILDREN+1+7)/8); /* devices not removable by default */
    	bitmap=(unsigned char *)&hub->desc.PortPowerCtrlMask[0];
    	memset(bitmap,0xff,(USB_MAXCHILDREN+1+7)/8); /* PowerMask = 1B */
    	for(i=0;i<((hub->desc.bNbrPorts + 1 + 7)/8);i++) {
    		hub->desc.DeviceRemovable[i]=descriptor->DeviceRemovable[i];
    	}
    	for(i=0;i<((hub->desc.bNbrPorts + 1 + 7)/8);i++) {
    		hub->desc.DeviceRemovable[i]=descriptor->PortPowerCtrlMask[i];
    	}
    	dev->maxchild = descriptor->bNbrPorts;
    	USB_HUB_PRINTF("%d ports detected\n", dev->maxchild);
    
    	switch (hub->desc.wHubCharacteristics & HUB_CHAR_LPSM) {
    		case 0x00:
    			USB_HUB_PRINTF("ganged power switching\n");
    			break;
    		case 0x01:
    			USB_HUB_PRINTF("individual port power switching\n");
    			break;
    		case 0x02:
    		case 0x03:
    			USB_HUB_PRINTF("unknown reserved power switching mode\n");
    			break;
    	}
    
    	if (hub->desc.wHubCharacteristics & HUB_CHAR_COMPOUND)
    		USB_HUB_PRINTF("part of a compound device\n");
    	else
    		USB_HUB_PRINTF("standalone hub\n");
    
    	switch (hub->desc.wHubCharacteristics & HUB_CHAR_OCPM) {
    		case 0x00:
    			USB_HUB_PRINTF("global over-current protection\n");
    			break;
    		case 0x08:
    			USB_HUB_PRINTF("individual port over-current protection\n");
    			break;
    		case 0x10:
    		case 0x18:
    			USB_HUB_PRINTF("no over-current protection\n");
          break;
    	}
    	USB_HUB_PRINTF("power on to power good time: %dms\n", descriptor->bPwrOn2PwrGood * 2);
    	USB_HUB_PRINTF("hub controller current requirement: %dmA\n", descriptor->bHubContrCurrent);
    	for (i = 0; i < dev->maxchild; i++)
    		USB_HUB_PRINTF("port %d is%s removable\n", i + 1,
    			hub->desc.DeviceRemovable[(i + 1)/8] & (1 << ((i + 1)%8)) ? " not" : "");
    
    	if (sizeof(struct usb_hub_status) > USB_BUFSIZ) {
    		USB_HUB_PRINTF("usb_hub_configure: failed to get Status - too long: %d\n",
    			descriptor->bLength);
    		return -1;
    	}
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (usb_get_hub_status(dev, buffer) < 0) {
    		USB_HUB_PRINTF("usb_hub_configure: failed to get Status %lX\n",dev->status);
    		return -1;
    	}
    	hubsts = (struct usb_hub_status *)buffer;
    	USB_HUB_PRINTF("get_hub_status returned status %X, change %X\n",
    
    		le16_to_cpu(hubsts->wHubStatus),le16_to_cpu(hubsts->wHubChange));
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	USB_HUB_PRINTF("local power source is %s\n",
    
    		(le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_LOCAL_POWER) ? "lost (inactive)" : "good");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	USB_HUB_PRINTF("%sover-current condition exists\n",
    
    		(le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? "" : "no ");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	usb_hub_power_on(hub);
    	for (i = 0; i < dev->maxchild; i++) {
    		struct usb_port_status portsts;
    		unsigned short portstatus, portchange;
    
    		if (usb_get_port_status(dev, i + 1, &portsts) < 0) {
    			USB_HUB_PRINTF("get_port_status failed\n");
    			continue;
    		}
    
    		portstatus = le16_to_cpu(portsts.wPortStatus);
    		portchange = le16_to_cpu(portsts.wPortChange);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		USB_HUB_PRINTF("Port %d Status %X Change %X\n",i+1,portstatus,portchange);
    		if (portchange & USB_PORT_STAT_C_CONNECTION) {
    			USB_HUB_PRINTF("port %d connection change\n", i + 1);
    			usb_hub_port_connect_change(dev, i);
    		}
    		if (portchange & USB_PORT_STAT_C_ENABLE) {
    			USB_HUB_PRINTF("port %d enable change, status %x\n", i + 1, portstatus);
    			usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_ENABLE);
    
    			/* EM interference sometimes causes bad shielded USB devices to
    			 * be shutdown by the hub, this hack enables them again.
    			 * Works at least with mouse driver */
    			if (!(portstatus & USB_PORT_STAT_ENABLE) &&
    				(portstatus & USB_PORT_STAT_CONNECTION) && (dev->children[i])) {
    				USB_HUB_PRINTF("already running port %i disabled by hub (EMI?), re-enabling...\n",
    					i + 1);
    					usb_hub_port_connect_change(dev, i);
    			}
    		}
    		if (portstatus & USB_PORT_STAT_SUSPEND) {
    			USB_HUB_PRINTF("port %d suspend change\n", i + 1);
    			usb_clear_port_feature(dev, i + 1,  USB_PORT_FEAT_SUSPEND);
    		}
    
    		if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
    			USB_HUB_PRINTF("port %d over-current change\n", i + 1);
    			usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_OVER_CURRENT);
    			usb_hub_power_on(hub);
    		}
    
    		if (portchange & USB_PORT_STAT_C_RESET) {
    			USB_HUB_PRINTF("port %d reset change\n", i + 1);
    			usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_RESET);
    		}
    	} /* end for i all ports */
    
    	return 0;
    }
    
    int usb_hub_probe(struct usb_device *dev, int ifnum)
    {
    	struct usb_interface_descriptor *iface;
    	struct usb_endpoint_descriptor *ep;
    	int ret;
    
    	iface = &dev->config.if_desc[ifnum];
    	/* Is it a hub? */
    	if (iface->bInterfaceClass != USB_CLASS_HUB)
    		return 0;
    	/* Some hubs have a subclass of 1, which AFAICT according to the */
    	/*  specs is not defined, but it works */
    	if ((iface->bInterfaceSubClass != 0) &&
    	    (iface->bInterfaceSubClass != 1))
    		return 0;
    	/* Multiple endpoints? What kind of mutant ninja-hub is this? */
    	if (iface->bNumEndpoints != 1)
    		return 0;
    	ep = &iface->ep_desc[0];
    	/* Output endpoint? Curiousier and curiousier.. */
    	if (!(ep->bEndpointAddress & USB_DIR_IN))
    		return 0;
    	/* If it's not an interrupt endpoint, we'd better punt! */
    	if ((ep->bmAttributes & 3) != 3)
    		return 0;
    	/* We found a hub */
    	USB_HUB_PRINTF("USB hub found\n");
    	ret=usb_hub_configure(dev);
    	return ret;
    }
    
    /* EOF */