本文共 12815 字,大约阅读时间需要 42 分钟。
本文讲述的USB驱动,是基于X86架构下的PCI-USB总线下的USB设备驱动,侧重函数调用。架构如下:
涉及的几个modules:
arch/x86/pci/legacy.c
drivers/usb/host/uhci-hcd.c
drivers/usb/core/usb.c
其中arch/x86/pci/legacy.c和drivers/usb/core/usb.c使用了subsys_initcall()来申明初始化的函数存放在initcall4这个段,结合makefile可知,drivers/下的先放,arch/x86/下的后放。
故在内核初始化时do_basic_setup时,首先调用的时drivers/下申明的函数,之后才是arch/x86/下的。对于本文来说,是先调用drivers/usb/core/usb.c中申明的usb_init()
drivers/usb/core/usb.c
之后才调用arch/x86/pci/legacy.c下申明的pci_subsys_init()
arch/x86/pci/legacy.c
drivers/usb/host/uhci-hcd.c中使用了module_init()来申明初始化函数,module_init()对应initcall6段,故它的初始化在另外两个之后。
我们按顺序来看,首先是drivers/usb/core/usb.c
drivers/usb/core/usb.c
1059行,注册一种总线类型usb bus。
drivers/usb/core/driver.c
drivers/base/bus.c
注册完成后,会在’/sys/bus/’下创建对应总线类型的目录,这里为’/sys/bus/usb’
并且在该目录下创建下列文件:
1065行,调用usb_major_init()注册字符设备的设备号:主设备号180, 次设备号0-255。
且注册了字符设备chrdev, 可通过cat /proc/devices查看,见下面的图示。
drivers/usb/core/file.c
include/linux/fs.h
fs/char_dev.c
1068行,注册usb fs驱动,该驱动设置for_device为0,即不用于匹配usb device。
drivers/usb/core/devio.c
include/linux/usb.h
drivers/usb/core/driver.c
1071行调用usb_devio_init()注册字符设备usb_devices,主设备号189,可通过cat /proc/devices查看。
/drivers/usb/core/devio.c
至此,我们已经创建了两个字符设备cdev,一个主设备号180(usb), 另一个主设备号189(usb_device), 这个信息可以通过/proc/devices导出到用户空间。
我们在/dev/下看到的字符设备文件,是通过mknod创建的。
1074行调用usb_hub_init(),注册hub_driver,这个driver也不是用于usb device的(for_devices = 0),然后启动一个内核线程hub_thread. 这个hub thread用于处理hub 事件的,后面再讲。
drivers/usb/core/hub.c
1077行调用usb_register_device_driver()注册通用的usb设备驱动。
drivers/usb/core/generic.c
drivers/usb/core/driver.c
在bus_add_driver()中,首先在/sys/bus/usb/drivers/下创建当前驱动的目录(kobject), 然后将驱动插入usb_bus_type的驱动链表klist_drivers中,并且尝试通过driver_attach()去匹配当前已经瓜子在usb_bus_type的devices链表klist_devices中的设备,如果有匹配的设备,则调用驱动的probe()函数。由于我们这个drivers/usb/core/usb.c是第一跑的,故此时还没有usb device挂在usb_bus_type的klist_devices上。
bus_add_driver()最后调用driver_create_file()在/sys/bus/usb/$(driver_name)/下创建uevent文件。
这个uevent文件,用于处理usb事件,比如插拔usb设备等(据说是在2.6版本之前使用的,2.6版本后不用了)。
当向这个文件写入”ADD”, “REMOVE”等,会触发内核广播netlink消息(<ACTION>@<DEVPATH>)给所有感兴趣的sock,调用/sbin/hotplug程序去处理事件.
内核调用hotplug的参数为: /sbin/hotplug usb
同时设置环境变量:
ACTION=
DEVPATH=/sys/bus/usb/drivers/usb
SUBSYSTEM=usb
SEQNUM=
HOME=/
PATH= /sbin:/bin:/usr/sbin:/usr/bin
最后,总结一下/sys这个路径的含义:
至此,我们将完了drivers/usb/core/usb.c的初始化函数usb_init()
接下去是arch/x86/pci/legacy.c
arch/x86/pci/legacy.c
arch/x86/pci/common.c
drivers/pci/probe.c
通过上述调用栈, pci_subsys_init()最终调用了pci_scan_single_device()来扫描pci总线上的设备,以确定某个pci设备是否存在。
pci_scan_single_device()调用pci_scan_device()通过读取vendor_id来判断是否存在对应的pci设备,如果存在,则分配pci_dev内存, 并设置pci_dev的bus type为pci_bus_type。
drivers/pci/probe.c
这个pci_bus_type是在drivers/pci/pci-driver.c中注册的。pci_driver_init使用postcore_initcall()声明,故其存放在initcall2, 更早的时候被调用。
pci_bus_type被注册后,系统中就有/sys/bus/pci, /sys/bus/pci/drivers/, /sys/bus/pci/<pci_driver>等文件或目录了,具体的,在usb_bus_type注册的时候讲过了。
pci_scan_single_device()调用pci_scan_device()扫描到设备后,调用pci_device_add()向pci总线注册设备。
pci_device_add()通过device_initialize()设置pci_dev的kobj.kset为devices_kset,这个devices_kset为所有pci_dev的“始祖”,这个devices_kset对应的目录为/sys/devices
drivers/base/core.c
最后,调用device_add(),添加这个pci设备。
drivers/base/core.c
device_add()调用kobject_add()在/sys/devices/创建pci设备对应的目录,并且在该设备目录下创建uevent等文件或目录。
添加完kobject后,调用bus_add_device()将设备挂到pci_bus_type的klist_devices上。
drivers/base/bus.c
device_add()最后调用bus_probe_device()去关联驱动和其对应的设备。
drivers/base/bus.c
drivers/base/dd.c
通过上述调用栈,在device_attach()中,通过bus_for_each_drv()遍历pci_bus_type上的所有的驱动,找到pci_dev匹配的驱动,并调用这个驱动的probe函数。由于此时,我们的usb hcd驱动还没有注册,所以,这里device_attach()什么也不做。
drivers/base/bus.c
到此,pci也初始化部分也讲完了,来看最后一个。
最后一个是drivers/usb/host/uhci-hcd.c
drivers/usb/host/uhci-pci.c
hcd_name为”uhci_hcd”
drivers/usb/host/uhci-hcd.c
include/linux/pci.h
drivers/pci/pci-driver.c
drivers/base/driver.c
drivers/base/bus.c
bus_add_driver()调用kobject_init_and_add()在/sys/bus/pci/drivers/下创建了该驱动对应的目录(uhci_hcd)。
然后将驱动挂到pci_bus_type的klist_drivers上,并调用driver_attach()去找该驱动匹配的pci设备。
drivers/base/dd.c
drivers/base/bus.c
由于之前PCI驱动已经将扫描到的PCI设备挂到了pci_bus_type的klist_devices上了。故这里klist_devices上有dev, __driver_attach()会被调用。
drivers/base/dd.c
driver_match_device()调用pci_bus_type的match函数,即pci_bus_match().
drivers/base/base.h
drivers/pci/pci-driver.c
drivers/pci/pci.h
通过匹配pci驱动的id_table中的vendor,device,subvendor,subdevice和pci总线扫描出来的设备上的vendor,device,subvendor,subdevice是否一致来判断该驱动和该device是否match。
当当前device是pci-usb-bridge时,且是该驱动支持的厂商设备时,match返回1,即match。
uhci_pci_driver是个通用driver,匹配any_id。
drivers/usb/host/uhci-pci.c
include/linux/pci.h
驱动和设备匹配后,由于该设备之前是没有对应的驱动的,即dev->driver为NULL, 所以__driver_attach()调用driver_probe_device()函数。
drivers/base/dd.c
really_probe()调用driver_sysfs_add()在驱动目录下创建一个link,link到对应的设备文件。
并在设备文件目录下创建一个link,link到这个驱动。
然后,调用pci_bus_type的probe函数,即pci_device_probe().
drivers/pci/pci-driver.c
最终,调用pci驱动的probe()函数,我们这里,即usb_hcd_pci_probe()
drivers/usb/host/uhci-hcd.c
drivers/usb/host/uhci-pci.c
drivers/usb/core/hcd-pci.c
189行的id->driver_data为uhci_driver,定义如下:
drivers/usb/core/hcd-pci.c
212行调用usb_create_hcd()创建hcd(host control device).
drivers/usb/core/hcd.c
hcd的结构为struct usb_hcd + struct uhci_hcd. dev->p-driver_data = hcd; hcd->driver为id_table中的driver_data, 即uhci_pci_ids[0].driver_data.
249行将pci-usb-bridge的io资源注册到系统中。
275行调用usb_add_hcd(hcd, hcd_irq, IRQF_SHARED)注册这个hcd,hcd是作为usb总线的。, 其中hcd_irq为pci中断号。
再往下将之前,先来介绍下linux kernel对usb的管理结构,如下图:
所有的usb设备都是挂载virtual Root Hub下的,hub可以级联。而virtual Root Hub是属于Host Controller的。上面说的root hub就是上图中的virtual Root Hub, 它是第一个usb device.
drivers/usb/core/hcd.c
2627行,注册hcd为usb_bus, 总线号为1. 并调用usb_notifier_list上的notify函数发送USB_BUS_ADD信息。
drivers/usb/core/hcd.c
drivers/usb/core/notify.c
这个usb_notifier_list为driver/usb/core/usb.c中通过usb_devio_init()注册的。
driver/usb/core/usb.c
2630行调用usb_alloc_dev()申请usb_device内存,这个usb_device是用作virtual Root Hub的。
drivers/usb/core/usb.c
这个usb_device将会设置kobject的parent为’/sys/devices’, 由于pci-usb-bridge是作为pci设备的,故在pci目录下:
另外,这个usb_device将会被挂载到usb_bus_type的klist_devices上。
并且设置usb_device的devpath.对于virtual Root Hub, 其为’0’,对于它的child,则为port号,再低一层级则为port1.port2
2670行调用id_table[].driver_data.reset()即uhci_pci_init()。
drivers/usb/host/uhci-pci.c
uhci_pci_init()调用uhci_count_ports(),向uhci->io_addr发送port status get命令,来判断这个端口是否可用,通过这样的方式来获取port的数目,该数目就是virtual Root Hub的port数目,它决定了virtual Root Hub下面可以挂多少usb_device.
2689行,调用usb_hcd_request_irqs()来设置中断处理程序usb_hcd_irq()。
drivers/usb/core/hcd.c
include/linux/interrupt.h
drivers/usb/core/hcd.c
这里的hcd->driver->irq为uhci_irq()。
drivers/usb/host/uhci-hcd.c
中断程序先不将,后面合适的时候再来将。
2695行调用hcd->driver->start(),即uhci_start()。
drivers/usb/host/uhci-hcd.c
uhci_start()给hcd做一些初始化配置,比如queue, dma pool等, 之后,调用configure_hc设置host controller寄存器,比如配置SOF,frame_number,设置framd dma物理地址等。
configure_hc之后又调用start_rh让host controller run起来。
drivers/usb/host/uhci_hcd.c
2702行调用register_root_hub()注册usb virtual Root Hub.
drivers/usb/core/hcd.c
register_root_hub()首先获取virtual root hub的设备描述符(device descriptor), 然后调用usb_new_device()。
usb_new_device()调用usb_enumerate_device()获取这个virtual root hub的配置描述符(config descriptor), 接口描述符(interface descriptor), 端点描述符(ep descriptor), 并解析描述符,写入udev->config.
virtual root hub作为第一个usb device,其描述符是固定的,如下:
drivers/usb/core/hcd.c
设备描述符
配置描述符 + 接口描述符 + endpoint描述符
之后调用device_add()将这个hcd设备添加到系统中(可以通过/sys/devices/来查询), 这个device_add()将触发hcd关联的bus type上匹配的驱动的probe()。
从前面的代码,我们直到hcd的bus type为usb_bus_type,我们先来分析它的match函数。
drivers/usb/core/driver.c
drivers/usb/core/usb.h
所以,对于usb device而言,只要有驱动的for_devices为1的,就可以match。
在讲解drivers/usb/core/usb.c的初始化函数时,我们知道有且只有usb generic的驱动设置了这个。故usb generic驱动的probe()将被调用来处理hcd device。
drivers/usb/core/generic.c
generic_probe()调用usb_choose_configuration()从usb设备的多个配置描述符中选择一个合适的。然后调用usb_set_configuration()激活设备的选中的配置描述符的配置。
drivers/usb/core/message.c
同时,创建这个设备的interface, 以及interface下的endpoint.
interface object的命名为<busno>-<parent_devpath>.<config_index>.<interfacenumber>,比如1-0:1.0
endpoint的命令为ep_<endpointaddr>.
这里两种device,我们分开介绍:usb_if_device和usb_ep_device。
先介绍usb_if_device.
drivers/usb/core/message.c
首先给root hub创建了interface device, 注意上述中的dev.type为usb_if_device_type.当调用device_add()添加该设备时,
drivers/base/core.c
drivers/base/bus.c
添加interface device会触发usb bus驱动匹配。我们来看usb bus type的match函数。
drivers/base/dd.c
drivers/base/base.h
drv->bus->match为usb_bus_type的match。
drivers/usb/core/driver.c
由于当前添加的是interface,故走788行分支。
drivers/usb/core/driver.c
usb_bus_type.match,遍历所有注册的usb_driver,调用usb_match_device()匹配root hub中的设备描述符,即匹配usb11_rh_dev_descriptor[].
然后调用usb_match_one_id_intf()匹配root_hub中的一个interface描述符。
这里会匹配上hub_driver。我们来看hub_driver。
drivers/usb/core/hub.c
匹配root hub设备描述符时,id为hub_driver的hub_id_table[2],match_flag为USB_DEVICE_ID_MATCH_INT_CLASS,device_class为USB_CLASS_HUB(9), 不会命中任何if branch,最后返回1. 然后去匹配interface.
匹配interface描述符时,id为同一个id,为hub_driver的hub_id_table[2],match_flag为USB_DEVICE_ID_MATCH_INT_CLASS,interfaceclass为USB_CLASS_HUB(9)。root hub 对应的interface描述符的值为:
所以,root hub的interface这个usb device会匹配hub_driver这个usb_driver,匹配的id为hub_driver的hub_id_table[2]。
匹配之后,调用driver_probe_device(), 第一个参数为hub_driver的device_driver, 第二个参数为root hub的interface.
drivers/base/dd.c
usb_bus_type没有probe函数,故这里直接调用了usb_driver的device_driver的probe()函数。
对于hub_driver而言,其device_driver的probe函数为:usb_probe_interface
drivers/usb/core/hub.c
include/linux/usb.h
drivers/usb/core/driver.c
usb_probe_interface()最终去调用usb_driver的probe()。
drivers/usb/core/driver.c
即,最终调用了hub_driver的probe函数hub_probe().
drivers/usb/core/hub.c
hub_probe分配usb_hub设备内存,然后调用hub_configure(), 参数2endpoint为root hub设备的endpoint, 不再是root hub的ep0了。
hub_configure通过get_hub_descriptor获取hub descriptor, hub descriptor定义如下:
drivers/usb/host/uhci-hub.c
之后,根据hub descriptor的bNbrPorts申请usb_port内存。
接着调用usb_get_status(),获取hub device status,hub device status的值如下:
drivers/usb/core/hcd.c
然后,调用hub_hub_status() -> get_hub_status()获取hub status, 如下:
drivers/usb/host/uhci_hub.c
之后,给hub申请urb, 并设置pipe为endpoint descriptor描述的address。
再调用usb_hub_create_port_device(),创建hub下的usb_port设备。
最后调用hub_activate(hub, HUB_INIT)。
drivers/usb/core/hub.c
hub_active()调用hub_port_status,通过pci io addr + req去获取硬件hub上指定的Port的状态信息,如下(266行):
drivers/usb/host/uhci_hub.c
portstatus & USB_PORT_STAT_CONNECTION== 1表示当前port1上有设备连着。
1166行设置port change位,标识当前hub哪个port状态发生了变化。
当usb接口上连接了usb设备,则port信息会变化。
以ast2500evt为例,来看一下port 寄存器的定义。
最后,hub_active()进入INIT3阶段时,调用usb_submit_urb(),递交了一个之前设置好的urb(usb request block)
//hub_configure()
//hub_active()
drivers/usb/core/urb.c
drivers/usb/core/hcd.c
usb_submit_urb()最终调用rh_queue_status()。
rh_queue_status()调用usb_hcd_link_urb_to_ep()将usb挂到ep urb_list上。
然后将hcd的status_urb指向这个urb。
hub_active()最后调用kick_khubd()。
drivers/usb/core/hub.c
kick_khubd()将当前hub添加到hub_event_list(572行),并唤醒等待队列khubd_wait。
这个等待队列会将hub thread唤醒。
我们接着来看这个hub thread.
drivers/usb/core/hub.c
hub_thread为这个hub thread的内核线程的入口函数。
hub_thread刚运行时,hub_event_list为空,hub_events()会马上返回,然后这个线程就sleep在这个等待队列khubd_wait上。当hub_active()唤醒这个khubd_wait后,hub_event_list不为空,hub_thread又循环回到hub_events()处理。
hub_events循环扫描hub的所有ports,如果Port连接状态改变了,即portchange & USB_PORT_STAT_C_CONNECTION, 则先清除这个register对应的bit,并设置connect_change为1.
之后,再调用hub_port_connect_change()来处理当前port的连接状态的变化。
drivers/usb/core/hub.c
hub_port_connect_change()申请usb_device内存(udev),这个udev代表了连接在hub port上的usb设备,然后调用hub_port_init(), hub_port_init()去reset外设(USB外设),并获取该usb外设的设备描述符。
最后hub_port_connect_change()调用usb_new_device()注册这个udev。
这个udev再usb_alloc_dev时,有如下设置:
即该udev会被挂到usb_bus_type的device_klist上,且type为usb_device_type。
于是,当调用usb_new_device()注册该udev设备时,会触发以下actions:
遍历usb_bus_type的drivers_klist上的驱动;
调用其match函数,匹配驱动和注册的设备udev;
如果驱动匹配设备udev,则调用驱动的probe()函数。
由于type为usb_device_type,所以匹配的驱动会是usb_generic_driver, 故这里的probe函数为generic_probe,generic_probe()扫描获取的配置,选择一个配置(usb_choose_configuration),并设置它(usb_set_configuration)。
drivers/usb/core/hub.c
usb_set_configuration()按interface numbers创建对应的usb interface device, 然后扫描usb bus type上的所有usb_if_device_type驱动,匹配驱动的id,匹配后调用相应的probe()函数(usb_probe_interface() -> usb_driver.probe())。
至此,我们的这部分的也结束了。
以上部分,我们讲解了对于pci-usb bridge架构下,系统如何检测usb设备连入,以及查找对应的驱动,并调用驱动的probe()函数。接下去,那就是驱动程序做的事了。
有个问题,既然usb是标准规范的,为什么要查找usb外设驱动?难道不是一个驱动就能搞定的吗?
答:usb外设有各种设备,比如打印机,摄像头,鼠标,键盘等。这些设备有各自不同的feature和要求,比如有只有input的或只有output的,或要求大吞吐的,或要求同步的,每种设备的电气或配置要求有差异。故需要特别的驱动。
下面,我们接着在hub port插入usb设备后,调用对应驱动来讲。
以f81232为例,这是一个USB转串口UART的设备。
drivers/usb/serial/f81232.c
include/linux/usb/serial.h
drivers/usb/serial/usb-serial.c
f81232通过module_usb_serial_driver()调用了usb_serial_register_drivers()。
usb_serial_register_drivers()又调用usb_register()注册了一个usb interface驱动。
include/linux/usb.h
drivers/usb/core/driver.c
所以,当root hub端口上插上该f81232设备后,通过root hub对应port获取该设备的描述符,然后调用generic_driver.probe() -> usb_set_configuration() 创建f81232设备的interface设备,并匹配id_table后,调用它的device_driver的probe()函数,即usb_probe_interface(),这个usb_probe_interface()最终又调用usb_driver的probe()函数,即usb_serial_probe()。
drivers/usb/serial/usb-serial.c
usb_serial_probe()取出注册的usb_serial_driver,设置该usb interface的每个Port的read/write urb,并注册ttyUSBx设备。
然后,我们就可以通过/dev/ttyUSBx设备文件来操作了,比如open, ioctl, close等操作。这些操作对应f81232的函数为:
这个驱动具体的操作函数,本文不作深究。
转载地址:http://dtlci.baihongyu.com/