📄 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/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/ioctl.h>#include <linux/usb.h>#include <linux/usbdevice_fs.h>#include <linux/kthread.h>#include <linux/mutex.h>#include <linux/freezer.h>#include <asm/semaphore.h>#include <asm/uaccess.h>#include <asm/byteorder.h>#include "usb.h"#include "hcd.h"#include "hub.h"#ifdef CONFIG_USB_PERSIST#define USB_PERSIST 1#else#define USB_PERSIST 0#endifstruct usb_hub { struct device *intfdev; /* the "interface" device */ struct usb_device *hdev; struct kref kref; struct urb *urb; /* for interrupt polling pipe */ /* buffer for urb ... with extra space in case of babble */ char (*buffer)[8]; dma_addr_t buffer_dma; /* DMA address for buffer */ union { struct usb_hub_status hub; struct usb_port_status port; } *status; /* buffer for status reports */ struct mutex status_mutex; /* for the status buffer */ int error; /* last reported error */ int nerrors; /* track consecutive errors */ struct list_head event_list; /* hubs w/data or errs ready */ unsigned long event_bits[1]; /* status change bitmask */ unsigned long change_bits[1]; /* ports with logical connect status change */ unsigned long busy_bits[1]; /* ports being reset or resumed */#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */#error event_bits[] is too short!#endif struct usb_hub_descriptor *descriptor; /* class descriptor */ struct usb_tt tt; /* Transaction Translator */ unsigned mA_per_port; /* current for each child */ unsigned limited_power:1; unsigned quiescing:1; unsigned activating:1; unsigned disconnected:1; unsigned has_indicators:1; u8 indicator[USB_MAXCHILDREN]; struct delayed_work leds;};/* Protect struct usb_device->state and ->children members * Note: Both are also protected by ->dev.sem, 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");/* Mutual exclusion for EHCI CF initialization. This interferes with * port reset on some companion controllers. */DECLARE_RWSEM(ehci_cf_port_reset_rwsem);EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);static 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";}/* 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 (struct work_struct *work){ struct usb_hub *hub = container_of(work, struct usb_hub, leds.work); 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; /* Suppress autosuspend until khubd runs */ to_usb_interface(hub->intfdev)->pm_usage_cnt = 1; spin_lock_irqsave(&hub_event_lock, flags); if (!hub->disconnected && 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){ /* FIXME: What if hdev isn't bound to the hub driver? */ kick_khubd(hdev_to_hub(hdev));}/* completion function, fires on port status changes and various faults */static void hub_irq(struct urb *urb){ struct usb_hub *hub = urb->context; int status = urb->status; int i; unsigned long bits; switch (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", status); if ((++hub->nerrors < 10) || hub->error) goto resubmit; hub->error = 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 (struct work_struct *work){ struct usb_hub *hub = container_of(work, struct usb_hub, tt.kevent); unsigned long flags; int limit = 100; spin_lock_irqsave (&hub->tt.lock, flags); while (--limit && !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)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -