您的位置:首页 > 其它

mini2440 usb host device controller驱动分析(三)-----root hub驱动的分析(一)

2013-10-17 10:40 453 查看
    在我看来,roothub的功能主要有两个:一是感应设备的插拔,在一个是对新插入的设备进行配置。 

     root hub 是怎么感应到设备的插拔的?

     这里开始分析root hub的驱动。之前已经提到过每个host controller都集成有一个root hub。通过看2440的datasheet中的host controller部分,我们可以看到root hub是有相应的物理电路的,并不是host controller虚拟了一个root hub。2440的root hub共有两个port,但是在mini2440上面只连了一个host的接口。


从旁边的图上可以看出,DN0和DP0是第一个host port的引脚。DN1和DP1是第二个host
port的引脚,但是这两个引脚是和udc 的引脚PDN0\PDP0复用的,在mini2440中这个复用的引脚用作了udc的port。但是这里有个问题,是怎么设定这两个复用的引脚的功能的?

root hub的一个很重要的功能就是检测到设备的插拔,有usb device插入时,对设备进行一定部分的初始化。但是有一个需要注意的是,读写usb device,并不需要通过usb root hub,都是hcd直接读写寄存器进行的,不需要通过root hub的传递。root hub对插入的设备进行读写也是通过submit
urb来实现的。

先看root hub的表示。与root hub相关的结构体有两个usb_hub和hub_driver。

struct 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		disconnected:1;

unsigned		has_indicators:1;
u8			indicator[USB_MAXCHILDREN];
struct delayed_work	leds;
struct delayed_work	init_work;
void			**port_owners;
};

在内核中实现了专门针对usb hub的驱动,叫hub_driver。

static struct usb_driver hub_driver = {
.name =		"hub",
.probe =	hub_probe,
.disconnect =	hub_disconnect,
.suspend =	hub_suspend,
.resume =	hub_resume,
.reset_resume =	hub_reset_resume,
.pre_reset =	hub_pre_reset,
.post_reset =	hub_post_reset,
.ioctl =	hub_ioctl,
.id_table =	hub_id_table,
.supports_autosuspend =	1,
};


 之前在hcd的初始化驱动中有register_root_hub,这个函数经过device_add等一系列调用,最终会调用到usb hub的probe函数,即hub_probe。(调用到hub_probe时候,可以看到这个函数参数就已经变成了usb_interface,这个过程还需要以后的分析)。

 

static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_host_interface *desc;
struct usb_endpoint_descriptor *endpoint;
struct usb_device *hdev;
struct usb_hub *hub;

desc = intf->cur_altsetting;
hdev = interface_to_usbdev(intf);

endpoint = &desc->endpoint[0].desc;

/* We found a hub */
dev_info (&intf->dev, "USB hub found\n");

hub = kzalloc(sizeof(*hub), GFP_KERNEL);
if (!hub) {
dev_dbg (&intf->dev, "couldn't kmalloc hub struct\n");
return -ENOMEM;
}

kref_init(&hub->kref);
INIT_LIST_HEAD(&hub->event_list);
hub->intfdev = &intf->dev;
hub->hdev = hdev;
INIT_DELAYED_WORK(&hub->leds, led_work);
INIT_DELAYED_WORK(&hub->init_work, NULL);
usb_get_intf(intf);

usb_set_intfdata (intf, hub);
intf->needs_remote_wakeup = 1;

if (hdev->speed == USB_SPEED_HIGH)
highspeed_hubs++;

if (hub_configure(hub, endpoint) >= 0)
return 0;
}

以上就是hub的初始化函数。到probe函数的时候,root hub 就已经用usb_interface来表示了。这个interface有一个interrupt endpoint:ep0,用来进行interrupt传输。hcd是怎么和root hub进行通信的?直接读寄存器应该就可以了,但是看代码觉得是hcd和root hub也用了和其他外部device通信同样的途径。

另外,usb hub还有一个驱动注册的过程,是按照这条线来的:usb_init   ---- usb_hub_init  ---- usb_register(&hub_driver)  、 kthread_run(hub_thread)。

也就是说,usb_init的时候, 不仅注册了hub_driver, 还启动了一个线程,叫hub_thread。后面我看到这个线程是与设备插拔相关的。后面在分析这个线程。

看完了roothub的初始化,那么root hub是怎么感应到设备的插拔的?

从上面probe函数中的hub_configure函数看起,这个函数很重要。

static int hub_configure(struct usb_hub *hub,
struct usb_endpoint_descriptor *endpoint)
{
struct usb_hcd *hcd;
struct usb_device *hdev = hub->hdev;
struct device *hub_dev = hub->intfdev;
u16 hubstatus, hubchange;
u16 wHubCharacteristics;
unsigned int pipe;
int maxp, ret;
char *message = "out of memory";

hub->buffer = usb_buffer_alloc(hdev, sizeof(*hub->buffer), GFP_KERNEL,
&hub->buffer_dma);

hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL);
mutex_init(&hub->status_mutex);

hub->descriptor = kmalloc(sizeof(*hub->descriptor), GFP_KERNEL);
ret = get_hub_descriptor(hdev, hub->descriptor,
sizeof(*hub->descriptor));
hdev->maxchild = hub->descriptor->bNbrPorts;
hub->port_owners = kzalloc(hdev->maxchild * sizeof(void *), GFP_KERNEL);
wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
spin_lock_init (&hub->tt.lock);
INIT_LIST_HEAD (&hub->tt.clear_list);
INIT_WORK(&hub->tt.clear_work, hub_tt_work);
switch (hdev->descriptor.bDeviceProtocol) {
case 0:
break;
}

/* probe() zeroes hub->indicator[] */
ret = usb_get_status(hdev, USB_RECIP_DEVICE, 0, &hubstatus);
hcd = bus_to_hcd(hdev->bus);
ret = hub_hub_status(hub, &hubstatus, &hubchange);
/* set up the interrupt endpoint
* We use the EP's maxpacket size instead of (PORTS+1+7)/8
* bytes as USB2.0[11.12.3] says because some hubs are known
* to send more data (and thus cause overflow). For root hubs,
* maxpktsize is defined in hcd.c's fake endpoint descriptors
* to be big enough for at least USB_MAXCHILDREN ports. */
pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe));

hub->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!hub->urb) {
ret = -ENOMEM;
goto fail;
}

usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
hub, endpoint->bInterval);
hub->urb->transfer_dma = hub->buffer_dma;
hub->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

/* maybe cycle the hub leds */
if (hub->has_indicators && blinkenlights)
hub->indicator [0] = INDICATOR_CYCLE;

hub_activate(hub, HUB_INIT);
return 0;
}


上面这个函数中重要的是函数的后面部分(前面就是一些初始化过程)。 我们看到usb_hub结构体中有一个 urb成员,在上面程中 用usb_alloc_urb 以及 usb_fill_int_urb对这个成员进行了初始化,后面在hub_active中调用usb_sumbit_urb对该urb进行了提交。初始化时,我们看到这个urb的complete回调函数时hub_irq。也就是说这个urb提交后,udc
core 再把这个urb处理完之后,中断函数再调用时,会调用到hub_irq函数。这个回调的过程我们后面分析。

先继续看hub_active函数。

static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
struct usb_device *hdev = hub->hdev;
int port1;
int status;
bool need_debounce_delay = false;
unsigned delay;

/* Continue a partial initialization */
if (type == HUB_INIT2)
goto init2;
if (type == HUB_INIT3)
goto init3;

/* After a resume, port power should still be on.
* For any other type of activation, turn it on.
*/
if (type != HUB_RESUME) {

if (type == HUB_INIT) {
delay = hub_power_on(hub, false);
PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2);
schedule_delayed_work(&hub->init_work,
msecs_to_jiffies(delay));

/* Suppress autosuspend until init is done */
atomic_set(&to_usb_interface(hub->intfdev)->
pm_usage_cnt, 1);
return;		/* Continues at init2: below */
} else {
hub_power_on(hub, true);
}
}
init2:

/* Check each port and set hub->change_bits to let khubd know
* which ports need attention.
*/
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
struct usb_device *udev = hdev->children[port1-1];
u16 portstatus, portchange;

portstatus = portchange = 0;
status = hub_port_status(hub, port1, &portstatus, &portchange);
if ((portstatus & USB_PORT_STAT_ENABLE) && (
type != HUB_RESUME ||
!(portstatus & USB_PORT_STAT_CONNECTION) ||
!udev ||
udev->state == USB_STATE_NOTATTACHED)) {
clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
portstatus &= ~USB_PORT_STAT_ENABLE;
}

/* Clear status-change flags; we'll debounce later */
if (portchange & USB_PORT_STAT_C_CONNECTION) {
need_debounce_delay = true;
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_CONNECTION);
}
if (portchange & USB_PORT_STAT_C_ENABLE) {
need_debounce_delay = true;
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_ENABLE);
}

if (!udev || udev->state == USB_STATE_NOTATTACHED) {
/* Tell khubd to disconnect the device or
* check for a new connection
*/
if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
set_bit(port1, hub->change_bits);

} else if (portstatus & USB_PORT_STAT_ENABLE) {
/* The power session apparently survived the resume.
* If there was an overcurrent or suspend change
* (i.e., remote wakeup request), have khubd
* take care of it.
*/
if (portchange)
set_bit(port1, hub->change_bits);

} else if (udev->persist_enabled) {
set_bit(port1, hub->change_bits);

} else {
/* The power session is gone; tell khubd */
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
set_bit(port1, hub->change_bits);
}
}

if (need_debounce_delay) {
delay = HUB_DEBOUNCE_STABLE;

/* Don't do a long sleep inside a workqueue routine */
if (type == HUB_INIT2) {
PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func3);
schedule_delayed_work(&hub->init_work,
msecs_to_jiffies(delay));
return;		/* Continues at init3: below */
} else {
msleep(delay);
}
}
init3:
hub->quiescing = 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 that need attention */
kick_khubd(hub);
}

/* Implement the continuations for the delays above */
static void hub_init_func2(struct work_struct *ws)
{
struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work);

hub_activate(hub, HUB_INIT2);
}

static void hub_init_func3(struct work_struct *ws)
{
struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work);

hub_activate(hub, HUB_INIT3);
}

上面这个函数怪的一点就是把hub_active这个过程 分成了三个不连续的部分去执行。本来是一个函数,却通过shedule_delayed_work这个函数,把这个hub_active进行了分割,使得这个函数 的执行过程变慢。第一阶段将hub power on, 第二阶段 获取各个port的状态。 第三阶段 在submit urb。我觉得是因为这三个阶段之间需要进行延时,而且也不需要精确的延时,所以用schedule_delayed_work来实现。至此,我们已经看到
hub acitve中进行的工作。其中,submit urb是与我们后面 感应设备插拔最相关的。

 

至此,前期的准备过程已经结束。现在,假设有一个device插入了root hub。那么这个会引起中断,进入ohci_irq函数的处理。在前面的章节中,我们已经看过了这个中断函数。在ohci_irq中,判断usb中断类型,如果是RHSC(root hub status change),就调用usb_hcd_poll_rh_status,获取port的状态, 再调用usb_hcd_giveback_urb,最终可以调用到hub_irq函数。

来看这个函数:

static void hub_irq(struct urb *urb)
{
struct usb_hub *hub = urb->context;
int status = urb->status;
unsigned i;
unsigned long bits;

switch (status) {
case -ENOENT:		/* synchronous unlink */
case -ECONNRESET:	/* async unlink */
case -ESHUTDOWN:	/* hardware going away */
return;

/* 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);
}

在hub_irq中,通过kick_khubd就会给之前启动的hub_thread发送消息,相当于是进程间通信。 在hub_thread中会继续对事件进行处理。 另外,在hub_irq中会再次提交hub->urb。相当于hub->urb就是负责感应到设备的插拔的。每次感应到之后,hub->urb会再次被submit。

看hub_thread中对事件的处理。hub_thread中会调用hub_events,hub_events 又对不同的事件有不同的处理,如果是设备的插拔,最终会调用到hub_port_connect_change。

/* Handle physical or logical connection change events.
* This routine is called when:
* 	a port connection-change occurs;
*	a port enable-change occurs (often caused by EMI);
*	usb_reset_and_verify_device() encounters changed descriptors (as from
*		a firmware download)
* caller already locked the hub
*/
static void hub_port_connect_change(struct usb_hub *hub, int port1,
u16 portstatus, u16 portchange)
{
struct usb_device *hdev = hub->hdev;
struct device *hub_dev = hub->intfdev;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
unsigned wHubCharacteristics =
le16_to_cpu(hub->descriptor->wHubCharacteristics);
struct usb_device *udev;
int status, i;

dev_dbg (hub_dev,
"port %d, status %04x, change %04x, %s\n",
port1, portstatus, portchange, portspeed (portstatus));

if (hub->has_indicators) {
set_port_led(hub, port1, HUB_LED_AUTO);
hub->indicator[port1-1] = INDICATOR_AUTO;
}

/* Try to resuscitate an existing device */
udev = hdev->children[port1-1];
if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
udev->state != USB_STATE_NOTATTACHED) {
usb_lock_device(udev);
if (portstatus & USB_PORT_STAT_ENABLE) {
status = 0;		/* Nothing to do */

} else {
status = -ENODEV;	/* Don't resuscitate */
}
usb_unlock_device(udev);

if (status == 0) {
clear_bit(port1, hub->change_bits);
return;
}
}

/* Disconnect any existing devices under this port */
if (udev)
usb_disconnect(&hdev->children[port1-1]);
clear_bit(port1, hub->change_bits);

if (portchange & (USB_PORT_STAT_C_CONNECTION |
USB_PORT_STAT_C_ENABLE)) {
status = hub_port_debounce(hub, port1);
if (status < 0) {
if (printk_ratelimit())
dev_err(hub_dev, "connect-debounce failed, "
"port %d disabled\n", port1);
portstatus &= ~USB_PORT_STAT_CONNECTION;
} else {
portstatus = status;
}
}

/* Return now if debouncing failed or nothing is connected */
if (!(portstatus & USB_PORT_STAT_CONNECTION)) {

/* maybe switch power back on (e.g. root hub was reset) */
if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
&& !(portstatus & (1 << USB_PORT_FEAT_POWER)))
set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);

if (portstatus & USB_PORT_STAT_ENABLE)
goto done;
return;
}

for (i = 0; i < SET_CONFIG_TRIES; i++) {

/* reallocate for each attempt, since references
* to the previous one can escape in various ways
*/
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) {
dev_err (hub_dev,
"couldn't allocate port %d usb_device\n",
port1);
goto done;
}

usb_set_device_state(udev, USB_STATE_POWERED);
udev->bus_mA = hub->mA_per_port;
udev->level = hdev->level + 1;

else if ((hdev->parent == NULL) &&
(portstatus & (1 << USB_PORT_FEAT_SUPERSPEED)))
udev->speed = USB_SPEED_SUPER;
else
udev->speed = USB_SPEED_UNKNOWN;

/*
* xHCI needs to issue an address device command later
* in the hub_port_init sequence for SS/HS/FS/LS devices.
*/
if (!(hcd->driver->flags & HCD_USB3)) {
/* set the address */
choose_address(udev);
if (udev->devnum <= 0) {
status = -ENOTCONN;	/* Don't retry */
goto loop;
}
}

/* reset (non-USB 3.0 devices) and get descriptor */
status = hub_port_init(hub, udev, port1, i);
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
&& udev->bus_mA <= 100) {
u16	devstat;

status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
&devstat);
if (status < 2) {
dev_dbg(&udev->dev, "get status %d ?\n", status);
goto loop_disable;
}
le16_to_cpus(&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[port1-1] =
INDICATOR_AMBER_BLINK;
schedule_delayed_work (&hub->leds, 0);
}
status = -ENOTCONN;	/* Don't retry */
goto loop_disable;
}
}

/* check for devices running slower than they could */
if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
&& udev->speed == USB_SPEED_FULL
&& highspeed_hubs != 0)
check_highspeed (hub, udev, port1);

/* 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.
*/
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[port1-1] = 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[port1-1] = NULL;
spin_unlock_irq(&device_state_lock);
}
}

if (status)
goto loop_disable;

status = hub_power_remaining(hub);
if (status)
dev_dbg(hub_dev, "%dmA power budget left\n", status);

return;
}

可以看到在这个函数中对设备的插拔进行的了处理。具体调用的函数有usb_new_device 以及 usb_disconnect。

至此,我们就看完了 root hub 感应设备插拔的过程。

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  host usb root hub