📄 hub.c
字号:
udev = usb_alloc_dev(hdev, hdev->bus, port); if (!udev) { dev_err (hub_dev, "couldn't allocate port %d usb_device\n", port+1); goto done; } usb_set_device_state(udev, USB_STATE_POWERED); udev->speed = USB_SPEED_UNKNOWN; /* set the address */ choose_address(udev); if (udev->devnum <= 0) { status = -ENOTCONN; /* Don't retry */ goto loop; } /* reset and get descriptor */ status = hub_port_init(hdev, udev, port); if (status < 0) goto loop; /* consecutive bus-powered hubs aren't reliable; they can * violate the voltage drop budget. if the new child has * a "powered" LED, users should notice we didn't enable it * (without reading syslog), even without per-port LEDs * on the parent. */ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB && hub->power_budget) { u16 devstat; status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstat); if (status < 0) { dev_dbg(&udev->dev, "get status %d ?\n", status); goto loop; } cpu_to_le16s(&devstat); if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) { dev_err(&udev->dev, "can't connect bus-powered hub " "to this port\n"); if (hub->has_indicators) { hub->indicator[port] = INDICATOR_AMBER_BLINK; schedule_work (&hub->leds); } status = -ENOTCONN; /* Don't retry */ goto loop; } } /* check for devices running slower than they could */ if (udev->descriptor.bcdUSB >= 0x0200 && udev->speed == USB_SPEED_FULL && highspeed_hubs != 0) check_highspeed (hub, udev, port); /* Store the parent's children[] pointer. At this point * udev becomes globally accessible, although presumably * no one will look at it until hdev is unlocked. */ down (&udev->serialize); status = 0; /* We mustn't add new devices if the parent hub has * been disconnected; we would race with the * recursively_mark_NOTATTACHED() routine. */ spin_lock_irq(&device_state_lock); if (hdev->state == USB_STATE_NOTATTACHED) status = -ENOTCONN; else hdev->children[port] = udev; spin_unlock_irq(&device_state_lock); /* Run it through the hoops (find a driver, etc) */ if (!status) { status = usb_new_device(udev); if (status) { spin_lock_irq(&device_state_lock); hdev->children[port] = NULL; spin_unlock_irq(&device_state_lock); } } up (&udev->serialize); if (status) goto loop; status = hub_power_remaining(hub); if (status) dev_dbg(hub_dev, "%dmA power budget left\n", 2 * status); return;loop: hub_port_disable(hdev, port); usb_disable_endpoint(udev, 0 + USB_DIR_IN); usb_disable_endpoint(udev, 0 + USB_DIR_OUT); release_address(udev); usb_put_dev(udev); if (status == -ENOTCONN) break; } done: hub_port_disable(hdev, port);}static void hub_events(void){ struct list_head *tmp; struct usb_device *hdev; struct usb_hub *hub; struct device *hub_dev; u16 hubstatus; u16 hubchange; u16 portstatus; u16 portchange; int i, ret; int connect_change; /* * We restart the list every time to avoid a deadlock with * deleting hubs downstream from this one. This should be * safe since we delete the hub from the event list. * Not the most efficient, but avoids deadlocks. */ while (1) { /* Grab the first entry at the beginning of the list */ spin_lock_irq(&hub_event_lock); if (list_empty(&hub_event_list)) { spin_unlock_irq(&hub_event_lock); break; } tmp = hub_event_list.next; list_del_init(tmp); hub = list_entry(tmp, struct usb_hub, event_list); hdev = hub->hdev; hub_dev = &hub->intf->dev; usb_get_dev(hdev); spin_unlock_irq(&hub_event_lock); /* Lock the device, then check to see if we were * disconnected while waiting for the lock to succeed. */ down(&hdev->serialize); if (hdev->state != USB_STATE_CONFIGURED || !hdev->actconfig || hub != usb_get_intfdata( hdev->actconfig->interface[0])) goto loop; if (hub->error) { dev_dbg (hub_dev, "resetting for error %d\n", hub->error); if (hub_reset(hub)) { dev_dbg (hub_dev, "can't reset; disconnecting\n"); hub_start_disconnect(hdev); goto loop; } hub->nerrors = 0; hub->error = 0; } /* deal with port status changes */ for (i = 0; i < hub->descriptor->bNbrPorts; i++) { connect_change = test_bit(i, hub->change_bits); if (!test_and_clear_bit(i+1, hub->event_bits) && !connect_change) continue; ret = hub_port_status(hdev, i, &portstatus, &portchange); if (ret < 0) continue; if (portchange & USB_PORT_STAT_C_CONNECTION) { clear_port_feature(hdev, i + 1, USB_PORT_FEAT_C_CONNECTION); connect_change = 1; } if (portchange & USB_PORT_STAT_C_ENABLE) { if (!connect_change) dev_dbg (hub_dev, "port %d enable change, " "status %08x\n", i + 1, portstatus); clear_port_feature(hdev, i + 1, USB_PORT_FEAT_C_ENABLE); /* * EM interference sometimes causes badly * 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) && !connect_change && hdev->children[i]) { dev_err (hub_dev, "port %i " "disabled by hub (EMI?), " "re-enabling...\n", i + 1); connect_change = 1; } } if (portchange & USB_PORT_STAT_C_SUSPEND) { clear_port_feature(hdev, i + 1, USB_PORT_FEAT_C_SUSPEND); if (hdev->children[i]) ret = remote_wakeup(hdev->children[i]); else ret = -ENODEV; dev_dbg (hub_dev, "resume on port %d, status %d\n", i + 1, ret); if (ret < 0) ret = hub_port_disable(hdev, i); } if (portchange & USB_PORT_STAT_C_OVERCURRENT) { dev_err (hub_dev, "over-current change on port %d\n", i + 1); clear_port_feature(hdev, i + 1, USB_PORT_FEAT_C_OVER_CURRENT); hub_power_on(hub); } if (portchange & USB_PORT_STAT_C_RESET) { dev_dbg (hub_dev, "reset change on port %d\n", i + 1); clear_port_feature(hdev, i + 1, USB_PORT_FEAT_C_RESET); } if (connect_change) hub_port_connect_change(hub, i, portstatus, portchange); } /* end for i */ /* deal with hub status changes */ if (test_and_clear_bit(0, hub->event_bits) == 0) ; /* do nothing */ else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0) dev_err (hub_dev, "get_hub_status failed\n"); else { if (hubchange & HUB_CHANGE_LOCAL_POWER) { dev_dbg (hub_dev, "power change\n"); clear_hub_feature(hdev, C_HUB_LOCAL_POWER); } if (hubchange & HUB_CHANGE_OVERCURRENT) { dev_dbg (hub_dev, "overcurrent change\n"); msleep(500); /* Cool down */ clear_hub_feature(hdev, C_HUB_OVER_CURRENT); hub_power_on(hub); } }loop: up(&hdev->serialize); usb_put_dev(hdev); } /* end while (1) */}static int hub_thread(void *__unused){ /* * This thread doesn't need any user-level access, * so get rid of all our resources */ daemonize("khubd"); allow_signal(SIGKILL); /* Send me a signal to get me die (for debugging) */ do { hub_events(); wait_event_interruptible(khubd_wait, !list_empty(&hub_event_list)); if (current->flags & PF_FREEZE) refrigerator(PF_FREEZE); } while (!signal_pending(current)); pr_debug ("%s: khubd exiting\n", usbcore_name); complete_and_exit(&khubd_exited, 0);}static struct usb_device_id hub_id_table [] = { { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, .bDeviceClass = USB_CLASS_HUB}, { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, .bInterfaceClass = USB_CLASS_HUB}, { } /* Terminating entry */};MODULE_DEVICE_TABLE (usb, hub_id_table);static struct usb_driver hub_driver = { .owner = THIS_MODULE, .name = "hub", .probe = hub_probe, .disconnect = hub_disconnect, .suspend = hub_suspend, .resume = hub_resume, .ioctl = hub_ioctl, .id_table = hub_id_table,};int usb_hub_init(void){ pid_t pid; if (usb_register(&hub_driver) < 0) { printk(KERN_ERR "%s: can't register hub driver\n", usbcore_name); return -1; } pid = kernel_thread(hub_thread, NULL, CLONE_KERNEL); if (pid >= 0) { khubd_pid = pid; return 0; } /* Fall through if kernel_thread failed */ usb_deregister(&hub_driver); printk(KERN_ERR "%s: can't start khubd\n", usbcore_name); return -1;}void usb_hub_cleanup(void){ int ret; /* Kill the thread */ ret = kill_proc(khubd_pid, SIGKILL, 1); wait_for_completion(&khubd_exited); /* * Hub resources are freed for us by usb_deregister. It calls * usb_driver_purge on every device which in turn calls that * devices disconnect function if it is using this driver. * The hub_disconnect function takes care of releasing the * individual hub resources. -greg */ usb_deregister(&hub_driver);} /* usb_hub_cleanup() */static int config_descriptors_changed(struct usb_device *udev){ unsigned index; unsigned len = 0; struct usb_config_descriptor *buf; for (index = 0; index < udev->descriptor.bNumConfigurations; index++) { if (len < udev->config[index].desc.wTotalLength) len = udev->config[index].desc.wTotalLength; } buf = kmalloc (len, SLAB_KERNEL); if (buf == 0) { dev_err(&udev->dev, "no mem to re-read configs after reset\n"); /* assume the worst */ return 1; } for (index = 0; index < udev->descriptor.bNumConfigurations; index++) { int length; int old_length = udev->config[index].desc.wTotalLength; length = usb_get_descriptor(udev, USB_DT_CONFIG, index, buf, old_length); if (length < old_length) { dev_dbg(&udev->dev, "config index %d, error %d\n", index, length); break; } if (memcmp (buf, udev->rawdescriptors[index], old_length) != 0) { dev_dbg(&udev->dev, "config index %d changed (#%d)\n", index, buf->bConfigurationValue); break; } } kfree(buf); return index != udev->descriptor.bNumConfigurations;}/** * usb_reset_devce - perform a USB port reset to reinitialize a device * @udev: device to reset (not in SUSPENDED or NOTATTACHED state) * * WARNING - don't reset any device unless drivers for all of its * interfaces are expecting that reset! Maybe some driver->reset() * method should eventually help ensure sufficient cooperation. * * Do a port reset, reassign the device's address, and establish its * former operating configuration. If the reset fails, or the device's * descriptors change from their values before the reset, or the original * configuration and altsettings cannot be restored, a flag will be set * telling khubd to pretend the device has been disconnected and then * re-connected. All drivers will be unbound, and the device will be * re-enumerated and probed all over again. * * Returns 0 if the reset succeeded, -ENODEV if the device has been * flagged for logical disconnection, or some other negative error code * if the reset wasn't even attempted. * * The caller must own the device lock. For example, it's safe to use * this from a driver probe() routine after downloading new firmware. */int __usb_reset_device(struct usb_device *udev){ struct usb_device *parent = udev->parent; struct usb_device_descriptor descriptor = udev->descriptor; int i, ret, port = -1; struct usb_hub *hub; if (udev->state == USB_STATE_NOTATTACHED || udev->state == USB_STATE_SUSPENDED) { dev_dbg(&udev->dev, "device reset not allowed in state %d\n", udev->state); return -EINVAL; } /* FIXME: This should be legal for regular hubs. Root hubs may * have special requirements. */ if (udev->maxchild) { /* this requires hub- or hcd-specific logic; * see hub_reset() and OHCI hc_restart() */ dev_dbg(&udev->dev, "%s for hub!\n", __FUNCTION__); return -EISDIR; } for (i = 0; i < parent->maxchild; i++) if (parent->children[i] == udev) { port = i; break; } if (port < 0) { /* If this ever happens, it's very bad */ dev_err(&udev->dev, "Can't locate device's port!\n"); return -ENOENT; } ret = hub_port_init(parent, udev, port); if (ret < 0) goto re_enumerate; /* Device might have changed firmware (DFU or similar) */ if (memcmp(&udev->descriptor, &descriptor, sizeof descriptor) || config_descriptors_changed (udev)) { dev_info(&udev->dev, "device firmware changed\n"); udev->descriptor = descriptor; /* for disconnect() calls */ goto re_enumerate; } if (!udev->actconfig) return 0; ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), USB_REQ_SET_CONFIGURATION, 0, udev->actconfig->desc.bConfigurationValue, 0, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); if (ret < 0) { dev_err(&udev->dev, "can't restore configuration #%d (error=%d)\n", udev->actconfig->desc.bConfigurationValue, ret); goto re_enumerate; } usb_set_device_state(udev, USB_STATE_CONFIGURED); for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { struct usb_interface *intf = udev->actconfig->interface[i]; struct usb_interface_descriptor *desc; /* set_interface resets host side toggle and halt status even * for altsetting zero. the interface may have no driver. */ desc = &intf->cur_altsetting->desc; ret = usb_set_interface(udev, desc->bInterfaceNumber, desc->bAlternateSetting); if (ret < 0) { dev_err(&udev->dev, "failed to restore interface %d " "altsetting %d (error=%d)\n", desc->bInterfaceNumber, desc->bAlternateSetting, ret); goto re_enumerate; } } return 0; re_enumerate: hub_port_disable(parent, port); hub = usb_get_intfdata(parent->actconfig->interface[0]); set_bit(port, hub->change_bits); spin_lock_irq(&hub_event_lock); if (list_empty(&hub->event_list)) { list_add_tail(&hub->event_list, &hub_event_list); wake_up(&khubd_wait); } spin_unlock_irq(&hub_event_lock); return -ENODEV;}EXPORT_SYMBOL(__usb_reset_device);int usb_reset_device(struct usb_device *udev){ int r; down(&udev->serialize); r = __usb_reset_device(udev); up(&udev->serialize); return r;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -