=============  本系列参考  =============

《圈圈教你玩USB》、《Linux那些事儿之我是USB》

协议文档:https://www.usb.org/document-library/usb-20-specification  usb_20_20190524/usb_20.pdf

调试工具:Beagle USB 480 逻辑分析仪、sys/kernel/debug/usb/usbmon/

代码:linux-3.10.65/drivers/usb/core/hub.c

====================================

前言

  由于USB设备是先被hub识别的, 所以这次先分析hub代码, 与前面两篇博文连贯起来

一、 hub加载

  在USB子系统核心模块被调用:

/* drivers/usb/core/usb.c */
static int __init usb_init(void)
{
int retval;
if (nousb) {
pr_info("%s: USB support disabled\n", usbcore_name);
return ;
} retval = usb_debugfs_init();
if (retval)
goto out; usb_acpi_register();
retval = bus_register(&usb_bus_type);
if (retval)
goto bus_register_failed;
retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
if (retval)
goto bus_notifier_failed;
retval = usb_major_init();
if (retval)
goto major_init_failed;
retval = usb_register(&usbfs_driver);
if (retval)
goto driver_register_failed;
retval = usb_devio_init();
if (retval)
goto usb_devio_init_failed;
retval = usb_hub_init();
if (retval)
goto hub_init_failed;
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
if (!retval)
goto out; usb_hub_cleanup();
hub_init_failed:
usb_devio_cleanup();
usb_devio_init_failed:
usb_deregister(&usbfs_driver);
driver_register_failed:
usb_major_cleanup();
major_init_failed:
bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_notifier_failed:
bus_unregister(&usb_bus_type);
bus_register_failed:
usb_acpi_unregister();
usb_debugfs_cleanup();
out:
return retval;
} /*
* Cleanup
*/
static void __exit usb_exit(void)
{
/* This will matter if shutdown/reboot does exitcalls. */
if (nousb)
return; usb_deregister_device_driver(&usb_generic_driver);
usb_major_cleanup();
usb_deregister(&usbfs_driver);
usb_devio_cleanup();
usb_hub_cleanup();
bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_unregister(&usb_bus_type);
usb_acpi_unregister();
usb_debugfs_cleanup();
} subsys_initcall(usb_init);
module_exit(usb_exit);
MODULE_LICENSE("GPL");
/* drivers/usb/core/hub.c */
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,
.unlocked_ioctl = hub_ioctl,
.id_table = hub_id_table,
.supports_autosuspend = ,
}; int usb_hub_init(void)
{
if (usb_register(&hub_driver) < ) {
printk(KERN_ERR "%s: can't register hub driver\n",
usbcore_name);
return -;
} khubd_task = kthread_run(hub_thread, NULL, "khubd");
if (!IS_ERR(khubd_task))
return ; /* Fall through if kernel_thread failed */
usb_deregister(&hub_driver);
printk(KERN_ERR "%s: can't start khubd\n", usbcore_name); return -;
}

  

  usb_hub_init()就做两件事, 一是注册驱动--针对hub接口设备的驱动, 二是创建内核线程“khubd”, 我们后续会详解  

  记住hub本省也是个usb设备, 跟普通的U盘使用的都是同样的结构体, 当有hub设备被创建时,hub驱动的probe()将会match调用, 那问题来了,一个普通设备是被hub创建的, 那hub设备是谁创建的呢?

很显然最初的root hub设备必须是静态创建的, 且这部分代码没放在hub.c, 而是放到了hcd.c, 可以看出一个Host必然有一个root hub, 是绑定的!

int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags)
{
int retval;
struct usb_device *rhdev; /* 1. 创建一个root hub设备 */
if ((rhdev = usb_alloc_dev(NULL, &hcd->self, )) == NULL) {
dev_err(hcd->self.controller, "unable to allocate root hub\n");
retval = -ENOMEM;
goto err_allocate_root_hub;
}
/* 2. 让hcd与root hub 紧紧地绑在一起! */
hcd->self.root_hub = rhdev; /* 3. 注册usb设备 */
if ((retval = register_root_hub(hcd)) != )
goto err_register_root_hub; return retval; }
EXPORT_SYMBOL_GPL(usb_add_hcd); =========================================
/* root hub 设备默认就接在Host, 不是热拔插 */
static int register_root_hub(struct usb_hcd *hcd)
{
struct device *parent_dev = hcd->self.controller;
struct usb_device *usb_dev = hcd->self.root_hub;
int retval; /* 4. 有效设备地址1~127, root hub默认使用地址1 */
usb_dev->devnum = ;
/* 5. 直接进入地址阶段 */
usb_set_device_state(usb_dev, USB_STATE_ADDRESS); /* 6. 直接设置ep0 size=64, 看来是协议规定的了 */
usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16();
/* 7. root hub 也是设备, 也要获取各种描述符 */
retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);
retval = usb_get_bos_descriptor(usb_dev);
/* 8. 注册设备(是注册usb_device, 不是usb_interface) */
retval = usb_new_device (usb_dev); return retval;
}

  

着重说明usb_generic_driver会与所有的usb_device进行match, 然后选择合适的配置描述符,设置配置描述符时自然就设置interace, 也即创建usb_interface, 这个接口设备才是各个驱动对应的设备, 比如我们现在讨论的hub_driver

就是针对hub的usb_interface, 不是hub的usb_device,  usb_device代表是这个设备整体抽象, usb_interface代表是具体的某样功能, 需要具体的驱动操作。

  当注册一个USB Host时就静态创建root hub设备, 经过简单初始化后注册就会与usb_generic_driver 进行match创建具体的usb_interface, 当注册接口设备时就会match到hub_driver.probe(), 我们继续看看probe做了啥

二、hub驱动probe()

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;
/* 1. 要求除了ep0, 必须有且只有一个ep 还是INT型 */
if (desc->desc.bNumEndpoints != )
goto descriptor_error; endpoint = &desc->endpoint[].desc;
if (!usb_endpoint_is_int_in(endpoint))
goto descriptor_error; /* 2. 这是和普通设备的区别, 除了用usb_device描述外,hub还会创建usb_hub和usb_port */
hub = kzalloc(sizeof(*hub), GFP_KERNEL); dev_info (&intf->dev, "USB hub found\n");
if (hub_configure(hub, endpoint) >= )
return ;
} static int hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint)
{
/* 3. 主要获取hub有多少个port */
ret = get_hub_descriptor(hdev, hub->descriptor);
hdev->maxchild = hub->descriptor->bNbrPorts;
/* 4. 这也是和普通外设的区别, 会创建port, 这里只是创建指针, 真正创建在后面 */
hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *), GFP_KERNEL); /* 5. hub内部高速与全/低速不可兼容, 所以需要两部分传输电路, 根据需要进行切换
SINGLE_TT指的是hub只有一个转换电路针对整个hub,MULTI_TT表示每个port都有个TT转换电路可以针对每个port(土豪)
TTTT指的是转换电路后需要多少时间才稳定, 只有稳定了才可以传输数据*/
switch (hdev->descriptor.bDeviceProtocol) {
case USB_HUB_PR_FS:
break;
case USB_HUB_PR_HS_SINGLE_TT:
dev_dbg(hub_dev, "Single TT\n");
hub->tt.hub = hdev;
break;
case USB_HUB_PR_HS_MULTI_TT:
ret = usb_set_interface(hdev, , );
if (ret == ) {
dev_dbg(hub_dev, "TT per port\n");
hub->tt.multi = ;
} else
dev_err(hub_dev, "Using single TT (err %d)\n",
ret);
hub->tt.hub = hdev;
break;
case USB_HUB_PR_SS:
/* USB 3.0 hubs don't have a TT */
break;
default:
dev_dbg(hub_dev, "Unrecognized hub protocol %d\n",
hdev->descriptor.bDeviceProtocol);
break;
} /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */
switch (wHubCharacteristics & HUB_CHAR_TTTT) {
case HUB_TTTT_8_BITS:
if (hdev->descriptor.bDeviceProtocol != ) {
hub->tt.think_time = ;
dev_dbg(hub_dev, "TT requires at most %d "
"FS bit times (%d ns)\n",
, hub->tt.think_time);
}
break;
case HUB_TTTT_16_BITS:
hub->tt.think_time = * ;
dev_dbg(hub_dev, "TT requires at most %d "
"FS bit times (%d ns)\n",
, hub->tt.think_time);
break;
case HUB_TTTT_24_BITS:
hub->tt.think_time = * ;
dev_dbg(hub_dev, "TT requires at most %d "
"FS bit times (%d ns)\n",
, hub->tt.think_time);
break;
case HUB_TTTT_32_BITS:
hub->tt.think_time = * ;
dev_dbg(hub_dev, "TT requires at most %d "
"FS bit times (%d ns)\n",
, hub->tt.think_time);
break;
} /* 7. 申请一个urb并填充, 后续在hub_activate会调用usb_submit_urb */
hub->urb = usb_alloc_urb(, GFP_KERNEL);
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval); /* 8. 真正创建usb_port设备 */
for (i = ; i < hdev->maxchild; i++) {
ret = usb_hub_create_port_device(hub, i + );
if (ret < ) {
dev_err(hub->intfdev,
"couldn't create port%d device.\n", i + );
hdev->maxchild = i;
goto fail_keep_maxchild;
}
} /* 9. 激活 */
hub_activate(hub, HUB_INIT);
return ;
} static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
struct usb_device *hdev = hub->hdev;
for (port1 = ; port1 <= hdev->maxchild; ++port1) {
struct usb_device *udev = hub->ports[port1 - ]->child;
u16 portstatus, portchange; portstatus = portchange = ;
status = hub_port_status(hub, port1, &portstatus, &portchange); if (udev || (portstatus & USB_PORT_STAT_CONNECTION) || (portstatus & USB_PORT_STAT_OVERCURRENT))
/* 10. 前面读取portstatus&portchange 就是为了标志change_bits, 因为内核线程会读取看有没有变化
其实内核线程也会调用hub_port_status,我觉得可以不需要change_bits, 之所以存在是线程khubd
一运行就第一时间知道有没有设备插入, 或者线程读之前是不是状态又发生了变化*/
set_bit(port1, hub->change_bits); } /* 11. 第一次提交urb,后续的提交就在urb的回调函数hub_irq()里调用 */
status = usb_submit_urb(hub->urb, GFP_NOIO); /* 12. 其实submit后就会调用回调函数hub_irq(), 里面就会调用kick_khubd(hub),不知道为何这里重复调用一次 */
kick_khubd(hub);
}

  整个核心就是创建usb_hub结构体、获取hub描述符知道多少个port后又创建usb_port结构体、初始化TT转换电路、然后顺便读取状态看有没有外设插入, 之所以说是顺便是因为内核线程khubd才是主业做这个查询状态的, 无论是这次顺带读还是

khubd读总得有个urb, 所以就申请一个urb,采用中断传输模式, 并触发第一次submit提交, 然后在回调函数再次提交urb

三、 内核线程khubd

  上面probe()是针对每个hub设备都会有的行为, 创建usb_hub、usb_port,申请一个urb, 但有一个共同操作, 就是内核线程khubd, 所以它放在usb_hub_init()

hub_thread()
-> hub_events():
/* 1. 处理每个hub, 并从链表删除这个hub */
while () {
/* 2. 如果链表空了就退出 */
if (list_empty(&hub_event_list)) {
spin_unlock_irq(&hub_event_lock);
break;
} tmp = hub_event_list.next;
list_del_init(tmp); hub = list_entry(tmp, struct usb_hub, event_list);
hub_dev = hub->intfdev;
intf = to_usb_interface(hub_dev); /* 3. 处理每个hub的每个port */
for (i = ; i <= hub->descriptor->bNbrPorts; i++) {
/* 4. 早前提到的顺带读取状态会操作change_bits */
connect_change = test_bit(i, hub->change_bits);
ret = hub_port_status(hub, i, &portstatus, &portchange);
printk("ret=%d, portstatus=0x%x, portchange=0x%x\n", ret, portstatus, portchange); if (portchange & USB_PORT_STAT_C_CONNECTION) {
usb_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_CONNECTION);
connect_change = ;
} /* 5. 如果有设备插入, 则创建新的设备 */
if (connect_change)
hub_port_connect_change(hub, i, portstatus, portchange);
} /* end for i */
} /* end while (1) */

  线程就是不断从链表取出hub, 然后扫描hub的所有port看有没有外设插入, 有的话就通过hub_port_connect_change()创建, 这样所有的hub,所有的port就都被访问到了, 那链表的hub是什么时候挂上去的呢?

这就是上面提到的urb, 每个hub外设都会申请一个urb, 采用中断传输, 传输的内容就是读取hub的状态, 如果状态改变, 则将这个hub挂到链表上去, 同时启动内核线程, 线程自然就会处理这些hub了!

  

static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange)
{
udev = usb_alloc_dev(hdev, hdev->bus, port1);
choose_devnum(udev);
hub_port_init(hub, udev, port1, i);
-> hub_port_reset()
-> usb_get_device_descriptor
-> hub_port_reset
-> usb_get_device_descriptor
usb_new_device(udev);
}

USB之hub3的更多相关文章

  1. Linux自动共享USB设备:udev+Samba

    一.概述 公司最近要我实现USB设备插入Ubuntu后,自动共享到网络上,能像Windows共享一样(如\\192.168.1.10)访问里面的内容,不需要写入权限.当时听完这需求,我这新人表示惊呆了 ...

  2. OpenWrt中开启usb存储和samba服务

    在从官网安装的WNDR3800 15.05.1版本OpenWrt中, 不带usb存储支持以及samba, 需要另外安装 1. 启用usb支持 USB Basic Support https://wik ...

  3. USB设备(移动硬盘、鼠标)掉电掉驱动的两种解决方案

    症状: 当你发现"移动硬盘图标"经常无故消失,又自己出现时. 你可以把这个现象称之为"掉电" or "掉驱动". 遇到这种情况,相当不爽. ...

  4. 【.NET MF】.NET Micro Framework USB移植

    1.开发环境 windows 7  32位 MDK 4.54 .Net Micro Framework Porting Kit 4.2(RTM QFE2) .Net Micro Framework   ...

  5. USB Host的上拉下拉电阻

      关于USB的上下拉电阻,不是随便接个任意阻值的电阻就ok了. 当你的USB为主设备的时候,D+.D-上分别接一个15K的下拉电阻,这样可以使得在没有设备插入的时候,D+.D-上始终保持低电平:当为 ...

  6. stm32 usb error : identifier "bool" is undefined

    .\usb\USB\usb_pwr.h(54): error:  #20: identifier "bool" is undefinedusb\USB\usb_pwr.h(54): ...

  7. 如果mac电脑的usb转接器连接wlan时不显示,也就是不识别usb此时的网络连接没有,解决办法就是如下

    1.接上电源   关机 先按下shift +ctrl + opt + 开机键    ,等待10秒,这10秒是没有反应的,屏幕不会亮,系统不会跑起来,  10秒之后松开所有键,再按下opt + cmd ...

  8. UP Board USB无线网卡一贴通

    前言 原创文章,转载引用务必注明链接,水平有限,欢迎指正. 本文环境:ubilinux 3.0 kernel 4.4.0 本文使用Markdown写成,为获得更好的阅读体验和正常的图片.链接,请访问我 ...

  9. AD域控制器通过组策略禁止USB设备

    问题:域环境下如何禁用USB口设备? 第一种:用传统的办法,在Bios中禁用USB. 第二种: 微软技术支持回答:根据您的需求, Windows识别USB设备主要通过两个文件,一个是Usbstor.p ...

随机推荐

  1. SQL 对比,移动累计

    数据对比 两种常用模型 1.对比下一行,判断增长.减少.维持现状 -- 建表 drop table sales create table sales( num int, soc int ); inse ...

  2. 研发的困境----DEVOPS

    1.研发的困境 互联网的环境 互联网这个环境比较特别,包括现在不只是互联网,就算是被互联网赋能的这些“互联网+”的企业也在改变,用户在发生变化,用户构成的群体在发生变化,群体造成场景的变化,场景营造新 ...

  3. (转)Intellij Idea工具栏添加打开选中文件的资源管理器位置

    背景:在idea的view>toolbar上面添加工具按钮,能够简化操作,现在添加打开资源管理按钮,后续功能待研究 Intellij Idea工具栏添加打开选中文件的资源管理器位置 工具栏-右击 ...

  4. LeetCode 394. 字符串解码(Decode String) 44

    394. 字符串解码 394. Decode String 题目描述 给定一个经过编码的字符串,返回它解码后的字符串. 编码规则为: k[encoded_string],表示其中方括号内部的 enco ...

  5. C++ 每日一题 参数分析 (vector)

    首先给出原题地址: https://www.nowcoder.com/practice/668603dc307e4ef4bb07bcd0615ea677?tpId 以下是代码解析: #include& ...

  6. Delphi RSA加解密【 (RSA公钥加密,私钥解密)、(RSA私钥加密,公钥解密)、MD5加密、SHA加密】

    作者QQ:(648437169) 点击下载➨delphi RSA加解密 [Delphi RSA加解密]支持 (RSA公钥加密,私钥解密).(RSA私钥加密,公钥解密).MD5加密.SHA1加密.SHA ...

  7. 深度学习-Wasserstein GAN论文理解笔记

    GAN存在问题 训练困难,G和D多次尝试没有稳定性,Loss无法知道能否优化,生成样本单一,改进方案靠暴力尝试 WGAN GAN的Loss函数选择不合适,使模型容易面临梯度消失,梯度不稳定,优化目标不 ...

  8. idea中的调试按键(f5,f6,f7,f8,f9)

    f5: 如果断点处存在方法,f5 则强制进入方法内部,然后一步一步执行方法体, 如果再遇到方法,则继续进入方法体,如此循环,直到执行到断点开始处: f6: 从断点处一步步执行以后的代码,会跳出断点所在 ...

  9. ansible debugger 模块

    在搞TF(tungstenfabric)时遇到了一些错误,TF通过ansible playbook 来部署的.通常情况下遇到错误都是通过ansibale xxxx –vvv 来详细输出一下.出错的类型 ...

  10. C# vb .net实现相机视图效果滤镜

    在.net中,如何简单快捷地实现Photoshop滤镜组中的相机视图效果呢?答案是调用SharpImage!专业图像特效滤镜和合成类库.下面开始演示关键代码,您也可以在文末下载全部源码: 设置授权 第 ...