📄 hub.c
字号:
/* * USB hub driver. * * (C) Copyright 1999 Linus Torvalds * (C) Copyright 1999 Johannes Erdfelt * (C) Copyright 1999 Gregory P. Smith * (C) Copyright 2001 Brad Hards (bhards@bigpond.net.au) * */#include <linux/config.h>#include <linux/kernel.h>#include <linux/errno.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/completion.h>#include <linux/sched.h>#include <linux/list.h>#include <linux/slab.h>#include <linux/smp_lock.h>#include <linux/ioctl.h>#include <linux/usb.h>#include <linux/usbdevice_fs.h>#include <linux/kthread.h>#include <asm/semaphore.h>#include <asm/uaccess.h>#include <asm/byteorder.h>#include "usb.h"#include "hcd.h"#include "hub.h"/* Protect struct usb_device->state and ->children members * Note: Both are also protected by ->serialize, except that ->state can * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */static DEFINE_SPINLOCK(device_state_lock);/* khubd's worklist and its lock */static DEFINE_SPINLOCK(hub_event_lock);static LIST_HEAD(hub_event_list); /* List of hubs needing servicing *//* Wakes up khubd */static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);static struct task_struct *khubd_task;/* cycle leds on hubs that aren't blinking for attention */static int blinkenlights = 0;module_param (blinkenlights, bool, S_IRUGO);MODULE_PARM_DESC (blinkenlights, "true to cycle leds on hubs");/* * As of 2.6.10 we introduce a new USB device initialization scheme which * closely resembles the way Windows works. Hopefully it will be compatible * with a wider range of devices than the old scheme. However some previously * working devices may start giving rise to "device not accepting address" * errors; if that happens the user can try the old scheme by adjusting the * following module parameters. * * For maximum flexibility there are two boolean parameters to control the * hub driver's behavior. On the first initialization attempt, if the * "old_scheme_first" parameter is set then the old scheme will be used, * otherwise the new scheme is used. If that fails and "use_both_schemes" * is set, then the driver will make another attempt, using the other scheme. */static int old_scheme_first = 0;module_param(old_scheme_first, bool, S_IRUGO | S_IWUSR);MODULE_PARM_DESC(old_scheme_first, "start with the old device initialization scheme");static int use_both_schemes = 1;module_param(use_both_schemes, bool, S_IRUGO | S_IWUSR);MODULE_PARM_DESC(use_both_schemes, "try the other device initialization scheme if the " "first one fails");#ifdef DEBUGstatic inline char *portspeed (int portstatus){ if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED)) return "480 Mb/s"; else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED)) return "1.5 Mb/s"; else return "12 Mb/s";}#endif/* Note that hdev or one of its children must be locked! */static inline struct usb_hub *hdev_to_hub(struct usb_device *hdev){ return usb_get_intfdata(hdev->actconfig->interface[0]);}/* USB 2.0 spec Section 11.24.4.5 */static int get_hub_descriptor(struct usb_device *hdev, void *data, int size){ int i, ret; for (i = 0; i < 3; i++) { ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, USB_DT_HUB << 8, 0, data, size, USB_CTRL_GET_TIMEOUT); if (ret >= (USB_DT_HUB_NONVAR_SIZE + 2)) return ret; } return -EINVAL;}/* * USB 2.0 spec Section 11.24.2.1 */static int clear_hub_feature(struct usb_device *hdev, int feature){ return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), USB_REQ_CLEAR_FEATURE, USB_RT_HUB, feature, 0, NULL, 0, 1000);}/* * USB 2.0 spec Section 11.24.2.2 */static int clear_port_feature(struct usb_device *hdev, int port1, int feature){ return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1, NULL, 0, 1000);}/* * USB 2.0 spec Section 11.24.2.13 */static int set_port_feature(struct usb_device *hdev, int port1, int feature){ return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, NULL, 0, 1000);}/* * USB 2.0 spec Section 11.24.2.7.1.10 and table 11-7 * for info about using port indicators */static void set_port_led( struct usb_hub *hub, int port1, int selector){ int status = set_port_feature(hub->hdev, (selector << 8) | port1, USB_PORT_FEAT_INDICATOR); if (status < 0) dev_dbg (hub->intfdev, "port %d indicator %s status %d\n", port1, ({ char *s; switch (selector) { case HUB_LED_AMBER: s = "amber"; break; case HUB_LED_GREEN: s = "green"; break; case HUB_LED_OFF: s = "off"; break; case HUB_LED_AUTO: s = "auto"; break; default: s = "??"; break; }; s; }), status);}#define LED_CYCLE_PERIOD ((2*HZ)/3)static void led_work (void *__hub){ struct usb_hub *hub = __hub; struct usb_device *hdev = hub->hdev; unsigned i; unsigned changed = 0; int cursor = -1; if (hdev->state != USB_STATE_CONFIGURED || hub->quiescing) return; for (i = 0; i < hub->descriptor->bNbrPorts; i++) { unsigned selector, mode; /* 30%-50% duty cycle */ switch (hub->indicator[i]) { /* cycle marker */ case INDICATOR_CYCLE: cursor = i; selector = HUB_LED_AUTO; mode = INDICATOR_AUTO; break; /* blinking green = sw attention */ case INDICATOR_GREEN_BLINK: selector = HUB_LED_GREEN; mode = INDICATOR_GREEN_BLINK_OFF; break; case INDICATOR_GREEN_BLINK_OFF: selector = HUB_LED_OFF; mode = INDICATOR_GREEN_BLINK; break; /* blinking amber = hw attention */ case INDICATOR_AMBER_BLINK: selector = HUB_LED_AMBER; mode = INDICATOR_AMBER_BLINK_OFF; break; case INDICATOR_AMBER_BLINK_OFF: selector = HUB_LED_OFF; mode = INDICATOR_AMBER_BLINK; break; /* blink green/amber = reserved */ case INDICATOR_ALT_BLINK: selector = HUB_LED_GREEN; mode = INDICATOR_ALT_BLINK_OFF; break; case INDICATOR_ALT_BLINK_OFF: selector = HUB_LED_AMBER; mode = INDICATOR_ALT_BLINK; break; default: continue; } if (selector != HUB_LED_AUTO) changed = 1; set_port_led(hub, i + 1, selector); hub->indicator[i] = mode; } if (!changed && blinkenlights) { cursor++; cursor %= hub->descriptor->bNbrPorts; set_port_led(hub, cursor + 1, HUB_LED_GREEN); hub->indicator[cursor] = INDICATOR_CYCLE; changed++; } if (changed) schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);}/* use a short timeout for hub/port status fetches */#define USB_STS_TIMEOUT 1000#define USB_STS_RETRIES 5/* * USB 2.0 spec Section 11.24.2.6 */static int get_hub_status(struct usb_device *hdev, struct usb_hub_status *data){ int i, status = -ETIMEDOUT; for (i = 0; i < USB_STS_RETRIES && status == -ETIMEDOUT; i++) { status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0, data, sizeof(*data), USB_STS_TIMEOUT); } return status;}/* * USB 2.0 spec Section 11.24.2.7 */static int get_port_status(struct usb_device *hdev, int port1, struct usb_port_status *data){ int i, status = -ETIMEDOUT; for (i = 0; i < USB_STS_RETRIES && status == -ETIMEDOUT; i++) { status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1, data, sizeof(*data), USB_STS_TIMEOUT); } return status;}static void kick_khubd(struct usb_hub *hub){ unsigned long flags; spin_lock_irqsave(&hub_event_lock, flags); if (list_empty(&hub->event_list)) { list_add_tail(&hub->event_list, &hub_event_list); wake_up(&khubd_wait); } spin_unlock_irqrestore(&hub_event_lock, flags);}void usb_kick_khubd(struct usb_device *hdev){ kick_khubd(hdev_to_hub(hdev));}/* completion function, fires on port status changes and various faults */static void hub_irq(struct urb *urb, struct pt_regs *regs){ struct usb_hub *hub = (struct usb_hub *)urb->context; int status; int i; unsigned long bits; switch (urb->status) { case -ENOENT: /* synchronous unlink */ case -ECONNRESET: /* async unlink */ case -ESHUTDOWN: /* hardware going away */ return; default: /* presumably an error */ /* Cause a hub reset after 10 consecutive errors */ dev_dbg (hub->intfdev, "transfer --> %d\n", urb->status); if ((++hub->nerrors < 10) || hub->error) goto resubmit; hub->error = urb->status; /* FALL THROUGH */ /* let khubd handle things */ case 0: /* we got data: port status changed */ bits = 0; for (i = 0; i < urb->actual_length; ++i) bits |= ((unsigned long) ((*hub->buffer)[i])) << (i*8); hub->event_bits[0] = bits; break; } hub->nerrors = 0; /* Something happened, let khubd figure it out */ kick_khubd(hub);resubmit: if (hub->quiescing) return; if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0 && status != -ENODEV && status != -EPERM) dev_err (hub->intfdev, "resubmit --> %d\n", status);}/* USB 2.0 spec Section 11.24.2.3 */static inline inthub_clear_tt_buffer (struct usb_device *hdev, u16 devinfo, u16 tt){ return usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), HUB_CLEAR_TT_BUFFER, USB_RT_PORT, devinfo, tt, NULL, 0, 1000);}/* * enumeration blocks khubd for a long time. we use keventd instead, since * long blocking there is the exception, not the rule. accordingly, HCDs * talking to TTs must queue control transfers (not just bulk and iso), so * both can talk to the same hub concurrently. */static void hub_tt_kevent (void *arg){ struct usb_hub *hub = arg; unsigned long flags; spin_lock_irqsave (&hub->tt.lock, flags); while (!list_empty (&hub->tt.clear_list)) { struct list_head *temp; struct usb_tt_clear *clear; struct usb_device *hdev = hub->hdev; int status; temp = hub->tt.clear_list.next; clear = list_entry (temp, struct usb_tt_clear, clear_list); list_del (&clear->clear_list); /* drop lock so HCD can concurrently report other TT errors */ spin_unlock_irqrestore (&hub->tt.lock, flags); status = hub_clear_tt_buffer (hdev, clear->devinfo, clear->tt); spin_lock_irqsave (&hub->tt.lock, flags); if (status) dev_err (&hdev->dev, "clear tt %d (%04x) error %d\n", clear->tt, clear->devinfo, status); kfree(clear); } spin_unlock_irqrestore (&hub->tt.lock, flags);}/** * usb_hub_tt_clear_buffer - clear control/bulk TT state in high speed hub * @udev: the device whose split transaction failed * @pipe: identifies the endpoint of the failed transaction * * High speed HCDs use this to tell the hub driver that some split control or * bulk transaction failed in a way that requires clearing internal state of * a transaction translator. This is normally detected (and reported) from * interrupt context. * * It may not be possible for that hub to handle additional full (or low) * speed transactions until that state is fully cleared out. */void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe){ struct usb_tt *tt = udev->tt; unsigned long flags; struct usb_tt_clear *clear; /* we've got to cope with an arbitrary number of pending TT clears, * since each TT has "at least two" buffers that can need it (and * there can be many TTs per hub). even if they're uncommon. */ if ((clear = kmalloc (sizeof *clear, SLAB_ATOMIC)) == NULL) { dev_err (&udev->dev, "can't save CLEAR_TT_BUFFER state\n"); /* FIXME recover somehow ... RESET_TT? */ return; } /* info that CLEAR_TT_BUFFER needs */ clear->tt = tt->multi ? udev->ttport : 1; clear->devinfo = usb_pipeendpoint (pipe); clear->devinfo |= udev->devnum << 4; clear->devinfo |= usb_pipecontrol (pipe) ? (USB_ENDPOINT_XFER_CONTROL << 11) : (USB_ENDPOINT_XFER_BULK << 11); if (usb_pipein (pipe)) clear->devinfo |= 1 << 15; /* tell keventd to clear state for this TT */ spin_lock_irqsave (&tt->lock, flags); list_add_tail (&clear->clear_list, &tt->clear_list); schedule_work (&tt->kevent); spin_unlock_irqrestore (&tt->lock, flags);}static void hub_power_on(struct usb_hub *hub){ int port1; unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2; u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); /* if hub supports power switching, enable power on each port */ if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2) { dev_dbg(hub->intfdev, "enabling power on all ports\n"); for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++) set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); } /* Wait at least 100 msec for power to become stable */ msleep(max(pgood_delay, (unsigned) 100));}static inline void __hub_quiesce(struct usb_hub *hub){ /* (nonblocking) khubd and related activity won't re-trigger */ hub->quiescing = 1; hub->activating = 0; hub->resume_root_hub = 0;}static void hub_quiesce(struct usb_hub *hub){ /* (blocking) stop khubd and related activity */ __hub_quiesce(hub); usb_kill_urb(hub->urb); if (hub->has_indicators) cancel_delayed_work(&hub->leds); if (hub->has_indicators || hub->tt.hub) flush_scheduled_work();}static void hub_activate(struct usb_hub *hub){ int status; hub->quiescing = 0; hub->activating = 1; hub->resume_root_hub = 0; status = usb_submit_urb(hub->urb, GFP_NOIO); if (status < 0) dev_err(hub->intfdev, "activate --> %d\n", status); if (hub->has_indicators && blinkenlights) schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); /* scan all ports ASAP */ kick_khubd(hub);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -