⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 hub.c

📁 linux-2.6.15.6
💻 C
📖 第 1 页 / 共 5 页
字号:
/* * 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 + -