5.2.2:接口驱动中的hub_thread()函数

我们之前在分析usb_hub_init()的代码的时候,忽略掉了一部份.

代码片段如下所示:

int usb_hub_init(void)

{

……

khubd_task = kthread_run(hub_thread, NULL, "khubd");

……

}

Kthread_run()是kernel中用来启动一个新kernel线程的接口,它所要执行的函数就是后面跟的第一个参数.在这里,也就是hub_thread().另外,顺带提一句,要终止kthread_run()创建的线程,可以调用kthread_stop().

Hub_thread()的代码如下:

static int hub_thread(void *__unused)

{

set_freezable();

do {

hub_events();

wait_event_freezable(khubd_wait,

!list_empty(&hub_event_list) ||

kthread_should_stop());

} while (!kthread_should_stop() || !list_empty(&hub_event_list));

pr_debug("%s: khubd exiting\n", usbcore_name);

return 0;

}

在上面的代码中, kthread_should_stop()用来判断是否有kthread_stop()将其终止.

在这里,我们终止看到,我们在前面要唤醒的等待队列khubd_wait,也就是在这个地方了.

这个函数的核心处理是hub_events().分段分析代码,如下:

static void hub_events(void)

{

struct list_head *tmp;

struct usb_device *hdev;

struct usb_interface *intf;

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 */

//如果hub_event_list为空,退出

spin_lock_irq(&hub_event_lock);

if (list_empty(&hub_event_list)) {

spin_unlock_irq(&hub_event_lock);

break;

}

//取hub_event_list中的后一个元素,并将其断链

tmp = hub_event_list.next;

list_del_init(tmp);

hub = list_entry(tmp, struct usb_hub, event_list);

kref_get(&hub->kref);

spin_unlock_irq(&hub_event_lock);

hdev = hub->hdev;

hub_dev = hub->intfdev;

intf = to_usb_interface(hub_dev);

dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",

hdev->state, hub->descriptor

? hub->descriptor->bNbrPorts

: 0,

/* NOTE: expects max 15 ports... */

(u16) hub->change_bits[0],

(u16) hub->event_bits[0]);

/* Lock the device, then check to see if we were

* disconnected while waiting for the lock to succeed. */

usb_lock_device(hdev);

//如果hub断开了,继续hub_event_list中的下一个

if (unlikely(hub->disconnected))

goto loop;

/* If the hub has died, clean up after it */

//设备没有连接上

if (hdev->state == USB_STATE_NOTATTACHED) {

hub->error = -ENODEV;

//将下面的子设备全部disable

hub_pre_reset(intf);

goto loop;

}

/* Autoresume */

ret = usb_autopm_get_interface(intf);

if (ret) {

dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);

goto loop;

}

/* If this is an inactive hub, do nothing */

//hub 暂停

if (hub->quiescing)

goto loop_autopm;

//hub 有错误发生?

if (hub->error) {

dev_dbg (hub_dev, "resetting for error %d\n",

hub->error);

ret = usb_reset_composite_device(hdev, intf);

if (ret) {

dev_dbg (hub_dev,

"error resetting hub: %d\n", ret);

goto loop_autopm;

}

hub->nerrors = 0;

hub->error = 0;

}

首先,从hub_event_list摘下第一个元素,根据我们之前在接口驱动probe过程的kick_khubd()函数分析中,有将hub->event_list添加到hub_event_list.因此,就可以顺藤摸瓜找到hub,再根据hub结构,找到接口结构和所属的usb 设备结构.

然后,进行第一个重要的判断.如果hub被断开了,则,断开hub下面所连接的所有端口,这是在hub_pre_reset()中完成的.

最后,进行第二个重要的判断,如果hub发生了错误,则reset它下面的所有端口,这是在usb_reset_composite_device()中完成的.

/* deal with port status changes */

//遍历hub中的每一个port

for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {

{

if (test_bit(i, hub->busy_bits))

continue;

connect_change = test_bit(i, hub->change_bits);

if (!test_and_clear_bit(i, hub->event_bits) &&

!connect_change && !hub->activating)

continue;

//Get_Port_Status:取得端口状态.

//会取得port的改变值和状态值

ret = hub_port_status(hub, i,

&portstatus, &portchange);

if (ret < 0)

continue;

//如果对应端口没有在设备树上,且端口显示已经连接上

//将connect_change置为1

if (hub->activating && !hdev->children[i-1] &&

(portstatus &

USB_PORT_STAT_CONNECTION))

connect_change = 1;

//端口的连接状态发生了改变.需要发送Clear_Feature

if (portchange & USB_PORT_STAT_C_CONNECTION) {

clear_port_feature(hdev, i,

USB_PORT_FEAT_C_CONNECTION);

connect_change = 1;

}

//端口的状态从enable 变为了disable

if (portchange & USB_PORT_STAT_C_ENABLE) {

if (!connect_change)

dev_dbg (hub_dev,

"port %d enable change, "

"status %08x\n",

i, portstatus);

clear_port_feature(hdev, i,

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-1]) {

dev_err (hub_dev,

"port %i "

"disabled by hub (EMI?), "

"re-enabling...\n",

i);

connect_change = 1;

}

}

//Resume完成

if (portchange & USB_PORT_STAT_C_SUSPEND) {

clear_port_feature(hdev, i,

USB_PORT_FEAT_C_SUSPEND);

//如果端口连接了设备,就将设备唤醒

if (hdev->children[i-1]) {

ret = remote_wakeup(hdev->

children[i-1]);

if (ret < 0)

connect_change = 1;

}

//如果端口没有连接设备,就将端口禁用

else {

ret = -ENODEV;

hub_port_disable(hub, i, 1);

}

dev_dbg (hub_dev,

"resume on port %d, status %d\n",

i, ret);

}

//有过流保护,需要对hub power on

if (portchange & USB_PORT_STAT_C_OVERCURRENT) {

dev_err (hub_dev,

"over-current change on port %d\n",

i);

clear_port_feature(hdev, i,

USB_PORT_FEAT_C_OVER_CURRENT);

hub_power_on(hub);

}

//Reset状态已经完成了

if (portchange & USB_PORT_STAT_C_RESET) {

dev_dbg (hub_dev,

"reset change on port %d\n",

i);

clear_port_feature(hdev, i,

USB_PORT_FEAT_C_RESET);

}

if (connect_change)

hub_port_connect_change(hub, i,

portstatus, portchange);

}

这段代码就是最核心的操作了,首先要说明的是,在struct usb_dev中,有一个struct usb_device *children[USB_MAXCHILDREN]的成员,它是表示对应端口序号上所连接的usb设备.

在这里,它遍历hub上的每一个端口,如果端口的连接会生了改变(connect_change等于1)的情况,就会调用hub_port_connect_change().我们来看一下,什么情况下, hub_port_connect_change才会被设为1.

1:端口在hub->change_bits中被置位.搜索整个代码树,发生在设置hub->change_bits的地方,只有在hub_port_logical_disconnect()中手动将端口禁用,会将对应位置1.

2:hub上没有这个设备树上没有这个端口上的设备.但显示端口已经连上了设备

3:hub这个端口上的连接发生了改变,从端口有设备连接变为无设备连接,或者从无设备连接变为有设备连接.

4:hub的端口变为了disable,此时这个端口上连接了设备,但被显示该端口已经变禁用,需要将connect_change设为1.

5:端口状态从SUSPEND变成了RESUME,远程唤醒端口上的设备失败,就需要将connect_change设为1.

另外hub_port_connect_change()函数我们放在后面再来讨论

//对HUB的处理

/* deal with hub status changes */

//如果hub状态末变化,不需要做任何处理

if (test_and_clear_bit(0, hub->event_bits) == 0)

;   /* do nothing */

//Get_hub_status 失败?

else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)

dev_err (hub_dev, "get_hub_status failed\n");

else {

//这里是对应hub 状态发生了改变,且Get_hub_status正常返回的情况

//如果hub的本地电源供电发生了改变

if (hubchange & HUB_CHANGE_LOCAL_POWER) {

dev_dbg (hub_dev, "power change\n");

clear_hub_feature(hdev, C_HUB_LOCAL_POWER);

//如果是本地电源供电

if (hubstatus & HUB_STATUS_LOCAL_POWER)

/* FIXME: Is this always true? */

hub->limited_power = 1;

//如果本电源不供电

else

hub->limited_power = 0;

}

//如果hub 发生过电源保护,需要对hub power on

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);

}

}

hub->activating = 0;

/* If this is a root hub, tell the HCD it's okay to

* re-enable port-change interrupts now. */

if (!hdev->parent && !hub->busy_bits[0])

usb_enable_root_hub_irq(hdev->bus);

loop_autopm:

/* Allow autosuspend if we're not going to run again */

if (list_empty(&hub->event_list))

usb_autopm_enable(intf);

loop:

usb_unlock_device(hdev);

kref_put(&hub->kref, hub_release);

} /* end while (1) */

}

处理完hub上的port之后,就要来处理hub本身的状态改变了,结合代码中的注释应该很容易看懂,在这里主要是清除hub的对应Feature.

之后,将  hub->activating设为了0,如果hub是root hub,需要重新打开root hub的中断.

这个函数到这里就完成了.不过,其中的几个子函数,涉及到的操作很重要,现分析如下:

1:hub_pre_reset()函数.

该函数在设备断开连接的时候,将其下挂载的所有子设备全部注销掉,代码如下所示:

static int hub_pre_reset(struct usb_interface *intf)

{

struct usb_hub *hub = usb_get_intfdata(intf);

struct usb_device *hdev = hub->hdev;

int i;

/* Disconnect all the children */

for (i = 0; i < hdev->maxchild; ++i) {

if (hdev->children[i])

usb_disconnect(&hdev->children[i]);

}

hub_quiesce(hub);

return 0;

}

它将设备上所挂载的所有设备全部都调用usb_disconnect()来断开联接.之后,再对hub调用hub_quiesce().

hub_quiesce()是和hub_activate()相对应的一个函数,hub_activate()在前面已经分析过了,现在来对hub_quiesce()进行分析.

代码如下:

static void hub_quiesce(struct usb_hub *hub)

{

/* (nonblocking) khubd and related activity won't re-trigger */

hub->quiescing = 1;

hub->activating = 0;

/* (blocking) stop khubd and related activity */

usb_kill_urb(hub->urb);

if (hub->has_indicators)

cancel_delayed_work_sync(&hub->leds);

if (hub->tt.hub)

cancel_work_sync(&hub->tt.kevent);

}

首先,它调hub->quiescing置为1,而activating置为0.这和hub_activate()刚好是相反的动作.之后,取消hub的中断传输出URB.取得TT和LED的工作队列.

我们在后面分析的HUB中断URB传输,可以知道,如果将这个URB禁用,那么,就不会将hub->event_list添加到hub_event_list.因此,也不会进入到hub_events()函数.

usb_disconnect()用来断开某个设备,代码如下:

void usb_disconnect(struct usb_device **pdev)

{

struct usb_device   *udev = *pdev;

int         i;

if (!udev) {

pr_debug ("%s nodev\n", __FUNCTION__);

return;

}

/* mark the device as inactive, so any further urb submissions for

* this device (and any of its children) will fail immediately.

* this quiesces everyting except pending urbs.

*/

usb_set_device_state(udev, USB_STATE_NOTATTACHED);

dev_info (&udev->dev, "USB disconnect, address %d\n", udev->devnum);

usb_lock_device(udev);

/* Free up all the children before we remove this device */

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

if (udev->children[i])

usb_disconnect(&udev->children[i]);

}

/* deallocate hcd/hardware state ... nuking all pending urbs and

* cleaning up all state associated with the current configuration

* so that the hardware is now fully quiesced.

*/

dev_dbg (&udev->dev, "unregistering device\n");

usb_disable_device(udev, 0);

usb_unlock_device(udev);

/* Unregister the device.  The device driver is responsible

* for removing the device files from usbfs and sysfs and for

* de-configuring the device.

*/

device_del(&udev->dev);

/* Free the device number and delete the parent's children[]

* (or root_hub) pointer.

*/

release_address(udev);

/* Avoid races with recursively_mark_NOTATTACHED() */

spin_lock_irq(&device_state_lock);

*pdev = NULL;

spin_unlock_irq(&device_state_lock);

usb_stop_pm(udev);

put_device(&udev->dev);

}

很容易看出.这个函数采用深度遍历算法,它依次遍历udev->children[]下的子设备,然后依然调用usb_disconnect().

这个函数中的另外几个子函数有的在前面已经分析过,有的是设备模型中的基础函数.很有是跟PM相关的,在这里就不做详细分析,来看一下release_address()函数,顾名思意,它用来释放设备的地址,如下示:

static void release_address(struct usb_device *udev)

{

if (udev->devnum > 0) {

clear_bit(udev->devnum, udev->bus->devmap.devicemap);

udev->devnum = -1;

}

}

我们在分析UHCI中,有关root hub的初始化时说明,设各号都是保存在bus->devmap数组中的.在这里,只需要将该设备号在数组中的某位清了即可.

hub_pre_reset()函数就分析到这里了.

注意到这里调用的put_device(&udev->dev)没.根据Linux设备模型的分析,这时它会调用跟它绑定的driver的remove()接口,对应的,这个函数会将操作回溯到usb_driver->disconnect().可以自行查阅这个过程.

或许,有人的疑问又来了?要是这个usb_dev没有跟usb_driver绑定怎么办呢?

不要忘记我们之前的分析了,对于usb_generic_driver这个驱动是会适用所有的usb_dev的.^_^,也是说,无论如何,usb_dev都会绑定到usb_generic_driver.

2:hub_port_connect_change()函数

这个函数是一个很核心的操作,它的代码如下:

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);

u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);

int status, i;

dev_dbg (hub_dev,

"port %d, status %04x, change %04x, %s\n",

port1, portstatus, portchange, portspeed (portstatus));

//HUB LED

if (hub->has_indicators) {

set_port_led(hub, port1, HUB_LED_AUTO);

hub->indicator[port1-1] = INDICATOR_AUTO;

}

/* Disconnect any existing devices under this port */

//如果对应端口已经有设备连接,先将其断开

if (hdev->children[port1-1])

usb_disconnect(&hdev->children[port1-1]);

//将hub_change_bits中的对应位清零,以免下次进来的时候,还会检测到

//hub_port_logical_disconnect()对该值的设置

clear_bit(port1, hub->change_bits);

#ifdef  CONFIG_USB_OTG

/* during HNP, don't repeat the debounce */

if (hdev->bus->is_b_host)

portchange &= ~USB_PORT_STAT_C_CONNECTION;

#endif

//连接发生改变

//连接反弹的处理,实际上就是除抖动

if (portchange & USB_PORT_STAT_C_CONNECTION) {

status = hub_port_debounce(hub, port1);

if (status < 0) {

if (printk_ratelimit())

dev_err (hub_dev, "connect-debounce failed, "

"port %d disabled\n", port1);

goto done;

}

portstatus = status;

}

在这里,我们忽略掉HUB LED灯的操作,然后,将HUB对应端口下面挂载的设备断开.经过前面的分析,进入到这个函数的可能有多种情况(在hub_events()中分析的五种情况).可以分为三大类:

一类是之前有连接之后没联接的,在这里,将hub 对应端口下的设备全部断开是无可非议的.

第二类是之前没有,之后有连接的,在这里,if(hdev->children[port-1])的判断是不会满足的.

第三类是需要重置的端口,在这里先将设备断开,然后再将它联连上去好了.

接下来,将hub->change_bits的对应位清掉,该位是在函数hub_port_logical_disconnect()中被置的,在这里将其清除,免得下次在进入hub_events()的时候,再次检测到这个位发生改变.

忽略掉CONFIG_USB_OTG的处理,这个宏我们在前面分析过很多次了,这里不再赘述.

如果该端口的连接发生改变(从有连接到无接接,或者从无连接到有连接),就有一个除抖动的过程,usb2.0 spec上规定,除抖动的时间为100ms.

也许有人会有这样的想法: 那检测到移除了一个设备,但它在100ms又插上去了,这里适不适合这里的抖动检测的情况呢?

我们先从代码的流程看,检测到连接发生改变,进入到hub_port_connect_change(),它首先就会将端口上的设备移除.这样,就算你在100ms上连接上去了,也得要再次建立.

从usb2.0的协议看来,设备移除后,usb设备里保存的信息(例如选择的配置,给它分配的地址)全部都丢失了,必须要重新进行配置过程才能够使用.

在这里,顺便将hub_port_debounce()列出,来看一下具体的除抖过程是怎么样实现的.

static int hub_port_debounce(struct usb_hub *hub, int port1)

{

int ret;

int total_time, stable_time = 0;

u16 portchange, portstatus;

unsigned connection = 0xffff;

for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {

ret = hub_port_status(hub, port1, &portstatus, &portchange);

if (ret < 0)

return ret;

if (!(portchange & USB_PORT_STAT_C_CONNECTION) &&

(portstatus & USB_PORT_STAT_CONNECTION) == connection) {

stable_time += HUB_DEBOUNCE_STEP;

if (stable_time >= HUB_DEBOUNCE_STABLE)

break;

} else {

stable_time = 0;

connection = portstatus & USB_PORT_STAT_CONNECTION;

}

if (portchange & USB_PORT_STAT_C_CONNECTION) {

clear_port_feature(hub->hdev, port1,

USB_PORT_FEAT_C_CONNECTION);

}

if (total_time >= HUB_DEBOUNCE_TIMEOUT)

break;

msleep(HUB_DEBOUNCE_STEP);

}

dev_dbg (hub->intfdev,

"debounce: port %d: total %dms stable %dms status 0x%x\n",

port1, total_time, stable_time, portstatus);

if (stable_time < HUB_DEBOUNCE_STABLE)

return -ETIMEDOUT;

return portstatus;

}

函数中的stable_time表示隐定的时间.在hub_events()的代码分析时,我们看到了,在检测到连接状态发生改变的时候,会发送Clear_Feature.因此,如果在这里检测到有USB_PORT_STAT_C_CONNECTION,就说明之后又有一次连接状态发生改变了.

分析这个函数的时候,要注意有这样的情况,端口的连接状态,一直在波动,即时有连接,时末有连接.

还有注意,connection的初始值是0xffff,所以(portstatus & USB_PORT_STAT_CONNECTION) == connection这个判断是肯定不会满足的,因为hub_port_status()取得的portstatus里面还有一些保留位.所以,在第一次进入这个循环的时候,就会进入到else中,就会将stable_time置0,而connection也保存了这一次的连接信息.

如果端口维持前一个状态,那循环中的流程就会满足第一个if,在这个if的操作里,会增加stable_time的值.

如果端口的状态发生了改变,那循环中的流程就会满足else,又将stable_time和connection初始化了.另外,要记得在状态发生改变的时候,要发送Clear_Feature,将状态清除.

在函数里,定义的测试时间是1500ms.如果在这个时间内,端口还末处于稳定状态,就会返回-ETIMEDOUT.

如果已经处于稳定状态了,就会返回稳定状态下的portstatus.

/* Return now if 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;

}

经过去抖后,端口稳定的处于断开连接状态.说明端口已经没有设备了.然后,再判断hub是否有电源开关((wHubCharacteristics & HUB_CHAR_LPSM) < 2),portstatus 的 USB_PORT_FEAT_POWER位是否被设置,如果没有被设置,则说明该端口断电了.如果hub有电源开关,且端口没有上电,则需要发送POWER的Set_Feature来为之上电.

如果端口依然处理enable状态,就会跳转到标号done处,就端口disalbe.

//如果接口上面有了联接,需要为联接在端口上设备建立连接

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

struct usb_device *udev;

/* 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->speed = USB_SPEED_UNKNOWN;

udev->bus_mA = hub->mA_per_port;

udev->level = hdev->level + 1;

/* 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(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;

loop_disable:

hub_port_disable(hub, port1, 1);

loop:

ep0_reinit(udev);

release_address(udev);

usb_put_dev(udev);

if ((status == -ENOTCONN) || (status == -ENOTSUPP))

break;

}

如果端口隐定处于连接状态,那就需要连接端口下的设备了.首先看到的是一个for循环,是用来配置设备的两种方式.我们知道,在配置设备的时候,首先要去取设备的描述符,这个过程是在ep0上完成的.而这个ep0支持的最大传输出数据又是在设备描述符的bMaxPacketSize0中所定义的.

因此就对应有两种处理方式:

第一种是传输8个字节,取得描述符的前面一部份,从而就可以取得bMaxPacketSize0.此后再reset设备,再根据这个bMaxPacketSize0的长度去取它的设备描述符.

第二种是一次传输64字节,取得设备描述符的bMaxPacketSize0字段

关于这两种方式的描述,详见fudan_abc的<< Linux那些事儿之我是Hub>>.

有关这个for循环的作用就解释到这里.

在这段代码里,它首先分配一个usb_dev的结构,然后将其置为USB_STATE_POWERED状态.接着,为设备指定一个地址.

然后就调用hub_port_init()对这个usb_dev结构进行一系的初始化,在这个函数中会处理:Get_Description,Set_address.等操作,这个函数接下来我们再详细分析.

接着,将分配的struct usb_dev结构跟他的父结构关联起来,也就是说添加到它的父结构的usb_dev->children[]数组.

最后再调用usb_new_device()来取这个设备的配置项.这个函数我们在分析UHCI的时候已经分析过了.

中间是关于一些电流的判断处理,这部份比较简单,自行查看就可以看懂,这里不再分析.

注意,这里在分配usb_dev结构的时候,跟root hub是不相同的,如下示:

udev = usb_alloc_dev(hdev, hdev->bus, port1)

在为root hub分配struct usb_dev的时候,它的第一个参数,也就是它的父结点是为NULL.

我们来观察一下它在sysfs中的命名方式:

如下所示:

在没有插入U盘之前:

[root@localhost devices]# pwd

/sys/bus/usb/devices

[root@localhost devices]# ls

1-0:1.0  usb1

[root@localhost devices]#

插入U盘之后:

[root@localhost devices]# ls

1-0:1.0  1-1  1-1:1.0  usb1

增加的两个目是:

1-1和1-1:1.0

表示,U盘对应的设备目录是1-1.结合之前UHCI分析中,对usb_alloc_dev()应该很容易理解.

1-1:1.0 :只有这样的目录,表示该U盘只有一个接口,当前选取的是第0号设置项.

done:

hub_port_disable(hub, port1, 1);

if (hcd->driver->relinquish_port && !hub->hdev->parent)

hcd->driver->relinquish_port(hcd, port1);

}

Done标号是对应上述处理失败的处理,它禁用掉该端口(因为该端口没有连接设备或者是端口上的设备配置失败),如果是root hub,且USB控制器器驱动中又定义了relinquish_port.调用它.

照例,还是分析一下这个函数中涉及到的重要的子函数.

第一个要分析的函数是choose_address()

该函数用来为设备选择一个地址,代码如下所示:

static void choose_address(struct usb_device *udev)

{

int     devnum;

struct usb_bus  *bus = udev->bus;

/* If khubd ever becomes multithreaded, this will need a lock */

/* Try to allocate the next devnum beginning at bus->devnum_next. */

//从bus->devnum_next开始找到一个末被使用的位

devnum = find_next_zero_bit(bus->devmap.devicemap, 128,

bus->devnum_next);

//如果搜索到了最末尾,(128是不能被占用的),则从1起开始搜索

if (devnum >= 128)

devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);

//更新bus->devnum_next

bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);

//如果找到了合适位,将该位设为占用,然后更新udev->devnum为找到的设备号

if (devnum < 128) {

set_bit(devnum, bus->devmap.devicemap);

udev->devnum = devnum;

}

}

这个函数的原理我们在之前说过了多次,它是到所属的usb bus的bus->devmap中找到没有使用的那一位.在这里设置bus->devnum_next项是一个搜索的优化,它不必每次都从第1位起开始搜索.最后将找到的值存放在udev->devnum中.

第二个要分析的函数是hub_port_disable().

这个函数将hub对应的端口禁用,代码如下:

static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)

{

struct usb_device *hdev = hub->hdev;

int ret = 0;

//将接在该端口下的设备设为末连接

if (hdev->children[port1-1] && set_state)

usb_set_device_state(hdev->children[port1-1],

USB_STATE_NOTATTACHED);

//发送enable 的Clear_Feature请求.

if (!hub->error)

ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);

if (ret)

dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",

port1, ret);

return ret;

}

该函数的逻辑很简单,就是该端点下的联接设备断开,如果端口有设备连接的话.然后清除端口的enable.

第三个要分析的函数是hub_port_init().

将它列到最后,并不是因为它最轻微,而是因为它太复杂.^_^

代码分段分析如下:

static int

hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,

int retry_counter)

{

static DEFINE_MUTEX(usb_address0_mutex);

struct usb_device   *hdev = hub->hdev;

int         i, j, retval;

unsigned        delay = HUB_SHORT_RESET_TIME;

enum usb_device_speed   oldspeed = udev->speed;

char            *speed, *type;

int         devnum = udev->devnum;

/* root hub ports have a slightly longer reset period

* (from USB 2.0 spec, section 7.1.7.5)

*/

//设置port 的重置等待时间

if (!hdev->parent) {

delay = HUB_ROOT_RESET_TIME;

if (port1 == hdev->bus->otg_port)

hdev->bus->b_hnp_enable = 0;

}

/* Some low speed devices have problems with the quick delay, so */

/*  be a bit pessimistic with those devices. RHbug #23670 */

if (oldspeed == USB_SPEED_LOW)

delay = HUB_LONG_RESET_TIME;

mutex_lock(&usb_address0_mutex);

/* Reset the device; full speed may morph to high speed */

//将port reset

retval = hub_port_reset(hub, port1, udev, delay);

if (retval < 0)     /* error or disconnect */

goto fail;

/* success, speed is known */

retval = -ENODEV;

//在设备之前的设速已经确定的情况下

//如果设备的速度发生了改变,肯定是发生了错误

if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed) {

dev_dbg(&udev->dev, "device reset changed speed!\n");

goto fail;

}

oldspeed = udev->speed;

首先为端口重置选择一个合适的延时,即在这个延时过后,端口的Reset应该完成了.usb2.0 spec上规定,root hub的延时值是50ms,高速设备是10ms,而低速设备是100ms.从代码上看,这个延时都是从udev参数中来的,这个参数就是表示在端口上连接的设备.其实,所谓的Reset端口,就是Reset端口上连接的设备.

由于我们现在要对这个设备进行配置,因此,先将它复原成初始值.

另外,如果重置之后,设备的speed发生了变化,这肯定是错误的.

/* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...

* it's fixed size except for full speed devices.

* For Wireless USB devices, ep0 max packet is always 512 (tho

* reported as 0xff in the device descriptor). WUSB1.0[4.8.1].

*/

switch (udev->speed) {

case USB_SPEED_VARIABLE:    /* fixed at 512 */

udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(512);

break;

case USB_SPEED_HIGH:        /* fixed at 64 */

udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64);

break;

case USB_SPEED_FULL:        /* 8, 16, 32, or 64 */

/* to determine the ep0 maxpacket size, try to read

* the device descriptor to get bMaxPacketSize0 and

* then correct our initial guess.

*/

udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64);

break;

case USB_SPEED_LOW:     /* fixed at 8 */

udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(8);

break;

default:

goto fail;

}

根据设备的speed来设定ep0的MaxPacketSize.这个只是spec上规定的值.另外对于Full Speed的设来说,它的MaxPacketSize有四种情况,即8.16.32和64实际的值要在设备描述符的bMaxPacketSize0字段才能知道.

type = "";

switch (udev->speed) {

case USB_SPEED_LOW: speed = "low";  break;

case USB_SPEED_FULL:    speed = "full"; break;

case USB_SPEED_HIGH:    speed = "high"; break;

case USB_SPEED_VARIABLE:

speed = "variable";

type = "Wireless ";

break;

default:        speed = "?";    break;

}

dev_info (&udev->dev,

"%s %s speed %sUSB device using %s and address %d\n",

(udev->config) ? "reset" : "new", speed, type,

udev->bus->controller->driver->name, devnum);

这段代码无关紧要,只是打印出了一个Debug信息,

/* Set up TT records, if needed  */

if (hdev->tt) {

udev->tt = hdev->tt;

udev->ttport = hdev->ttport;

} else if (udev->speed != USB_SPEED_HIGH

&& hdev->speed == USB_SPEED_HIGH) {

udev->tt = &hub->tt;

udev->ttport = port1;

}

/* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?

* Because device hardware and firmware is sometimes buggy in

* this area, and this is how Linux has done it for ages.

* Change it cautiously.

*

* NOTE:  If USE_NEW_SCHEME() is true we will start by issuing

* a 64-byte GET_DESCRIPTOR request.  This is what Windows does,

* so it may help with some non-standards-compliant devices.

* Otherwise we start with SET_ADDRESS and then try to read the

* first 8 bytes of the device descriptor to get the ep0 maxpacket

* value.

*/

for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) {

if (USE_NEW_SCHEME(retry_counter)) {

struct usb_device_descriptor *buf;

int r = 0;

#define GET_DESCRIPTOR_BUFSIZE  64

buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);

if (!buf) {

retval = -ENOMEM;

continue;

}

/* Retry on all errors; some devices are flakey.

* 255 is for WUSB devices, we actually need to use

* 512 (WUSB1.0[4.8.1]).

*/

for (j = 0; j < 3; ++j) {

buf->bMaxPacketSize0 = 0;

r = usb_control_msg(udev, usb_rcvaddr0pipe(),

USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,

USB_DT_DEVICE << 8, 0,

buf, GET_DESCRIPTOR_BUFSIZE,

USB_CTRL_GET_TIMEOUT);

switch (buf->bMaxPacketSize0) {

case 8: case 16: case 32: case 64: case 255:

if (buf->bDescriptorType ==

USB_DT_DEVICE) {

r = 0;

break;

}

/* FALL THROUGH */

default:

if (r == 0)

r = -EPROTO;

break;

}

if (r == 0)

break;

}

udev->descriptor.bMaxPacketSize0 =

buf->bMaxPacketSize0;

kfree(buf);

retval = hub_port_reset(hub, port1, udev, delay);

if (retval < 0)     /* error or disconnect */

goto fail;

if (oldspeed != udev->speed) {

dev_dbg(&udev->dev,

"device reset changed speed!\n");

retval = -ENODEV;

goto fail;

}

if (r) {

dev_err(&udev->dev, "device descriptor "

"read/%s, error %d\n",

"64", r);

retval = -EMSGSIZE;

continue;

}

#undef GET_DESCRIPTOR_BUFSIZE

}

for (j = 0; j < SET_ADDRESS_TRIES; ++j) {

retval = hub_set_address(udev, devnum);

if (retval >= 0)

break;

msleep(200);

}

if (retval < 0) {

dev_err(&udev->dev,

"device not accepting address %d, error %d\n",

devnum, retval);

goto fail;

}

/* cope with hardware quirkiness:

*  - let SET_ADDRESS settle, some device hardware wants it

*  - read ep0 maxpacket even for high and low speed,

*/

msleep(10);

if (USE_NEW_SCHEME(retry_counter))

break;

retval = usb_get_device_descriptor(udev, 8);

if (retval < 8) {

dev_err(&udev->dev, "device descriptor "

"read/%s, error %d\n",

"8", retval);

if (retval >= 0)

retval = -EMSGSIZE;

} else {

retval = 0;

break;

}

}

这个for循环是一个很重要的操作,首先,我们来看一下USE_NEW_SCHEME宏的定义.如下示:

((i) / 2 == old_scheme_first),old_scheme_first默认为0,也就是说,当i为0,1的时候,这个宏会返回1.那就是说,对于之前分析的两种机制,每种机制尝试两次.

区分一下这两种机制的不同:

对于第一种机制,它先用64的buffer去取设备描述符.而第二种机制,是以长度8的缓存区,取设备描述符的前半部份.

另外,第一种机制,去取设备描述符之前没有设置设备的地址,因此使用地址0来表示设备的地址,在代码中,用usb_rcvaddr0pipe()表示.而在第二种机制中,它在取设备描述符之前已经设置了设备的地址.

疑问:可能有人就有这样的疑问,既然地址0可以表示没有设置地址的设备地址,那如果有多个没有set address的设备,这个地址0到底是表示那个设备呢?

实际上,从代码上看,Linux是每打开一个hub的端口就初始连在这个端口上的设备.之后这连接上的设备设置好地址之后再打开hub的另外的端口进行配置,因此,在同一条usb bus上,不会出现多个末配置的活动设备.

if (retval)

goto fail;

i = udev->descriptor.bMaxPacketSize0 == 0xff?

512 : udev->descriptor.bMaxPacketSize0;

if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) {

if (udev->speed != USB_SPEED_FULL ||

!(i == 8 || i == 16 || i == 32 || i == 64)) {

dev_err(&udev->dev, "ep0 maxpacket = %d\n", i);

retval = -EMSGSIZE;

goto fail;

}

dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);

udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);

ep0_reinit(udev);

}

retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);

if (retval < (signed)sizeof(udev->descriptor)) {

dev_err(&udev->dev, "device descriptor read/%s, error %d\n",

"all", retval);

if (retval >= 0)

retval = -ENOMSG;

goto fail;

}

retval = 0;

fail:

if (retval) {

hub_port_disable(hub, port1, 0);

udev->devnum = devnum;  /* for disconnect processing */

}

mutex_unlock(&usb_address0_mutex);

return retval;

}

在上面获得的设备描述符的bMaxPacketSize0字段,也就是ep0的MaxPacketSize.但如果这个值不和我们之前根据spec为ep0设定的MaxPacketSize值相等,且不是Full speed的话,就会有错误了.因为只有Full Speed的设备的ep0 的MaxPacketSize在spec上并没有一个明确的定义值.

有了确定的ep0 的MaxPacketSize值,就可以取得完整的设备描述符了.

第四个要分析的函数是hub_port_reset().

这个函数将端口重置并等待端口重置完成.代码如下:

static int hub_port_reset(struct usb_hub *hub, int port1,

struct usb_device *udev, unsigned int delay)

{

int i, status;

/* Block EHCI CF initialization during the port reset.

* Some companion controllers don't like it when they mix.

*/

down_read(&ehci_cf_port_reset_rwsem);

/* Reset the port */

//尝试5次

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

//发送Reset 的Set_Feature

status = set_port_feature(hub->hdev,

port1, USB_PORT_FEAT_RESET);

//发送错误

if (status)

dev_err(hub->intfdev,

"cannot reset port %d (err = %d)\n",

port1, status);

else {

//发送Clear_Feature成功,等待端口重置完成

status = hub_port_wait_reset(hub, port1, udev, delay);

if (status && status != -ENOTCONN)

dev_dbg(hub->intfdev,

"port_wait_reset: err = %d\n",

status);

}

/* return on disconnect or reset */

switch (status) {

//成功

case 0:

/* TRSTRCY = 10 ms; plus some extra */

msleep(10 + 40);

udev->devnum = 0;   /* Device now at address 0 */

/* FALL THROUGH */

//端口没有连接

case -ENOTCONN:

//要发送的设备不存在

case -ENODEV:

clear_port_feature(hub->hdev,

port1, USB_PORT_FEAT_C_RESET);

/* FIXME need disconnect() for NOTATTACHED device */

usb_set_device_state(udev, status

? USB_STATE_NOTATTACHED

: USB_STATE_DEFAULT);

goto done;

}

dev_dbg (hub->intfdev,

"port %d not enabled, trying reset again...\n",

port1);

//将延迟设至最长,再试一次

delay = HUB_LONG_RESET_TIME;

}

dev_err (hub->intfdev,

"Cannot enable port %i.  Maybe the USB cable is bad?\n",

port1);

done:

up_read(&ehci_cf_port_reset_rwsem);

return status;

}

这个函数的代码看清淅,首先将端口重置,然后等待端口重置完成.在成功返回或者是发错致命错误的时候就会在清除掉RESET Feature,设置设备状态之后返回.这个所谓的致命包括:

1:发送Clear_Feature时,返回-ENODEV,表示设备不存在

2:在hub_port_wait_reset()后返回的-ENOTCONN,表示端口上末连接设备.

另外,在这里哆嗦的重复一句,只有在设备有这个Feature的时候,才能Clear_Feature.在上面的代码中,只有代码中,如果Reset不成功,是不需要Clear USB_PORT_FEAT_C_RESET 这个Feature的.只有在已经设置成功的情况,才能将其Clear(-ENODEV的情况,无所谓,这个错误在submit urb前期就能测检出来,不会跟硬件交互,而-ENOTCONN则表示端口Reset完成,但尚末检测到连接设备,这种情况下,也是需要Clear_Feature的).

另外,里面还调用了一个子函数,hub_port_wait_reset().代码如下:

static int hub_port_wait_reset(struct usb_hub *hub, int port1,

struct usb_device *udev, unsigned int delay)

{

int delay_time, ret;

u16 portstatus;

u16 portchange;

//最长等待时间是500

for (delay_time = 0;

delay_time < HUB_RESET_TIMEOUT;

delay_time += delay) {

/* wait to give the device a chance to reset */

msleep(delay);

/* read and decode port status */

ret = hub_port_status(hub, port1, &portstatus, &portchange);

if (ret < 0)

return ret;

/* Device went away? */

//端口已经没有连接了,说明连接的设备在某个时刻被拨下来了

if (!(portstatus & USB_PORT_STAT_CONNECTION))

return -ENOTCONN;

/* bomb out completely if the connection bounced */

//连接状态发生了改变,则说明连接状态不稳定.因为断开之后,再联上是需要重新配置的

//退出

if ((portchange & USB_PORT_STAT_C_CONNECTION))

return -ENOTCONN;

/* if we`ve finished resetting, then break out of the loop */

//如果Reset已经完成,且端口处于enable状态,设置speed成员就可以返回了

if (!(portstatus & USB_PORT_STAT_RESET) &&

(portstatus & USB_PORT_STAT_ENABLE)) {

if (hub_is_wusb(hub))

udev->speed = USB_SPEED_VARIABLE;

else if (portstatus & USB_PORT_STAT_HIGH_SPEED)

udev->speed = USB_SPEED_HIGH;

else if (portstatus & USB_PORT_STAT_LOW_SPEED)

udev->speed = USB_SPEED_LOW;

else

udev->speed = USB_SPEED_FULL;

return 0;

}

/* switch to the long delay after two short delay failures */

//失败两次,将延时时间设为最长的时间

if (delay_time >= 2 * HUB_SHORT_RESET_TIME)

delay = HUB_LONG_RESET_TIME;

dev_dbg (hub->intfdev,

"port %d not reset yet, waiting %dms\n",

port1, delay);

}

return -EBUSY;

}

注意到在上面为speed成员赋值的时候,出现了一个hub_is_wusb().该宏用来判断hcd是否是一个无线的USB主机控制器.如果hcd 是一个无线的,那其下的所有设备的speed均为USB_SPEED_VARIABLE.这个是属于usb2.5 spec里面定义的.

到这里,hub_thread()函数已经分析完了.它已经将hub下连接的所有新设备都初始化并添加进了设备模型.

5.2.3:HUB中断URB传输完成的处理

在之前分析中断URB初始化的时候,曾分析到,如果中断URB传输完成,就会调用hub_irq().在分析这个函数之前,我们先从spec上了解一下,对于hub的中断传输到底会传些什么样的东西:

如下图所示:

Bit0表示hub的连接状态发生了改变,而bit1~bitN表示的是各端口连接状态的改变.如果1表示改变,为0表示末改变.

现在可以看该函数的代码了,如下:

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);

}

从上面的代码可以看出,就将是设HUB中断传输的信息保存在hub->event_bits中,然后又将此URB再次提交,再次提交的结果是,可以轮询获得hub的状态,另外,还会调用kick_khubd().这样,hub_events()就又会调用,又可以处理HUB端口的状态改变.

六:小结

在本小结里,对HUB的处理过程做了一个详尽的分析,在这一节里,也了解到了USB的驱动架构以及USB设备的枚举过程.

在下一节里,我们以特定的USB设备分例,来分析USB驱动程序的架构.

linux hub设备,Linux设备驱动之USB hub驱动(续)相关推荐

  1. linux网卡驱动rtl8188cu,linux下编译基于rtl8188cu控制芯片的USB无线网卡驱动 -电脑资料...

    本人因为实验的需要,需要在嵌入式linux环境下添加wifi功能,通过在网上调研,选择基于rtl8188cu控制芯片的水星NW150UM无线USB网卡,现简单记录下配置过程, 1.下载最新的rtl81 ...

  2. 十五、Linux驱动之USB鼠标驱动

    1. 如何编写USB鼠标驱动 结合十四.Linux驱动之USB驱动分析中的分析,我们开始写一个USB鼠标驱动.      USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动, ...

  3. “复合设备(Compound Device)”可以占用多个地址。所谓复合设备其实就是把多个功能设备 通过内置的USB HUB 组合而成的设备,比如带录音话筒的USB 摄像头

    ROOT HUB 是一个特殊的USB HUB,它集成在主机控制器里,不占用地址.ROOT HUB 不但 实现了普通USB HUB 的功能,还包括其他一些功能,具体在增强型主机控制器的规范中有详细的介 ...

  4. USB 3G驱动和USB HOST驱动加载

    ********************************LoongEmbedded******************************** 作者:LoongEmbedded(kandi ...

  5. linux设备驱动之USB主机控制器驱动分析

    http://www.cnblogs.com/sdphome/archive/2011/09/29/2195791.html 一:前言 Usb是一个很复杂的系统.在usb2.0规范中,将其定义成了一个 ...

  6. linux设备驱动之USB主机控制器驱动分析 【转】

    转自:http://blog.chinaunix.net/uid-20543183-id-1930831.html ------------------------------------------ ...

  7. usb控制linux关机,linux设备驱动之USB主机控制器驱动分析

    一:前言 Usb是一个很复杂的系统.在usb2.0规范中,将其定义成了一个分层模型.linux中的代码也是按照这个分层模型来设计的.具体的分为 usb设备,hub和主机控制器三部份.在阅读代码的时候, ...

  8. Linux下的USB总线驱动(04)——USB键盘驱动 usbkbd.c

    原文链接地址:http://www.linuxidc.com/Linux/2012-12/76197p9.htm 跟USB鼠标类型一样,USB键盘也属于HID类型,代码在/dirver/hid/usb ...

  9. ok6410如何驱动NW336无线网卡.更新zd1211b到kernel 2.6.15上.成功在Linux下安装TP-LINK TL-WN322G+ 54M无线USB网卡驱动

    WN322G+ 使用ZD1211b芯片,BT3内核已包含该芯片的驱动,可还是有不少人反映用lsusb能够看到网卡的信息,但Ifconfig -a 却没有该网卡,既然有驱动为什么用不了? 在网上搜寻多时 ...

最新文章

  1. 外网远程桌面连接设置
  2. linux 源码安装e1000e,linux安装网卡e1000e
  3. 网站SEO优化该如何提高用户粘度?
  4. 阿里数据库内核月报:2015年11月
  5. 如何正确、高效地阅读源代码?
  6. 2011年度中国地区网络安全威胁大事记
  7. 洛谷P1634、P1888、P3954、P5660题解(Java语言描述)
  8. mysql查找前几条数据类型_MySQL与SQLServer的语法区别
  9. 小白成长建议(9)-苞丁解牛
  10. java中no enclosing instance of type * is accessible的解决方法
  11. 股票资金建仓分仓补仓计算器
  12. 揭秘Apple Watch心率监测技术
  13. 彻底弄懂 Nginx location 匹配
  14. 拼音速度测试软件,在线五笔打字练习
  15. 如何将大硬盘对拷到小硬盘
  16. 区块链医疗平台搭建 高效的医疗管理生态服务体系
  17. 超好用的卸载软件 —— Geek
  18. 让TQ2440也用上设备树 (device tree 操作实例3_重要,对于移植很有参考价值_code)
  19. 微信小程序-仿智行火车票12306
  20. 微信挪车功能成功上线,祝贺一下自己

热门文章

  1. 读Training Strategies for Improved Lip-Reading论文
  2. [Unity]利用Mesh在Unity中绘制扇形图片
  3. 个人整理的淘宝正品鞋店
  4. 利用腾讯云进行上传文件
  5. 2010计算机录制宏步骤,Excel中2010版进行宏录制和执行的操作方法
  6. latex转word python_分分钟甩Word几条街,Python编辑公式竟可以如此简单!
  7. USB C端口说明-USB Type C的不同之处和速度
  8. 单元测试框架之Junit
  9. zblog php 手机模板,Zblog主题模板自适应手机响应式ZblogPHP简洁博客主题
  10. z-blog php免费主题,免费ZBLOG PHP主题 - AZure简单个人博客响应主题