USB2.0 软件篇
文章目录
第 1 篇 Linux 那些事儿之我是 USB Core
设备的生命周期(一)
设备也有它的生命周期,从你把它插到 Hub 上开始,到你把它从 Hub 上拔下来结束。
当你把 USB 设备连接到 Hub 的某个端口上,Hub 检测到有设备连接进来,它会为设备分配一个 struct usb_device 结构的对象并初始化,调用设备模型提供的接口将设备添加到 USB 总线的设备列表里,然后 USB 总线会遍历驱动列表里的每个驱动,调用自己的 match 函数看它们和你的设备是否匹配。
- 将设备所在的总线类型设置为 usb_bus_type
- 将设备的设备类型设置为 usb_device_type
- 将设备的状态设置为 attached
- 判断设备是不是直接连到 Root Hub 上的,如果是,将 dev->devpath[0] 赋值为 0,以示特殊,然后父设备设置为 controller,同时把 dev-> bus_id[] 设置为如 usb1/usb2/usb/3/usb4 这样的字符串。如果设备不是直接连到 Root Hub 上的,分两种情况:如果设备连接的 Hub 是直接连到 Root Hub 上的,则 dev->devpath 就等于端口号,否则 dev->devpath 就等于在父 Hub 的 devpath 基础上加一个 “.” 再加一个端口号,最后把 bus_id[] 设置成 1-/2-/3-/4- 这样的字符串后面连接上 devpath。
设备的生命周期(二)
现在设备的 struct usb_device 结构体已经准备好了,只是还不怎么饱满,Hub 接下来就会给它做 “整容手术”,往里边塞点什么,充实一些内容,比如:将设备的状态设置为 Powered。因为此时还不知道设备支持的速度,于是将设备的 speed 成员暂时设置为 USB_SPEED_UNKNOWN;设备的级别 level 当然会被设置为 Hub 的 level 加上 1;还有为设备能够从 Hub 那里获得的电流赋值;为了保证通信畅通,Hub 还会为设备在总线上选择一个独一无二的地址。
驱动的生命周期(一)
设备的生命周期你可以想当然地认为是从你的 USB 设备连接到 Hub 的某个端口时开始,驱动的生命周期就必须得回溯到 USB 子系统的初始化函数 usb_register_device_driver。
- 判断 USB 子系统是不是在你启动内核时就被禁止了,如果是,它的生命也就太短暂了
- 指定 probe 函数和 remove 函数
- 调用设备模型的函数 device_driver 将 usb_generic_driver 添加到 USB 总线的那条驱动链表里。
- usb_generic_driver 和 USB 设备匹配成功后,就会调用之前指定的 probe 函数 usb_probe_device()
- 从设备众多配置中选择一个合适的,然后去配置设备,从而让设备进入期待已久的 Configured 状态。
驱动的生命周期(二)
Core 配置设备使用的是 message.c 里的 usb_set_configuration 函数
驱动的生命周期(三)
驱动的生命周期(四)
设备自从有了 Address,拿到了各种描述符,就在那看 usb_generic_driver 忙活了,不过还算没白忙,设备总算是幸福地进入 “Configured” 了。
Address 有点像你刚买的一套新房子,如果不装修,它对你来说的意义就是可以对别人说你的家庭地址在哪,可实际上你在里面什么也干不了,你还得想办法去装修它。Configured 就像是已经装修好的房子。
- 获得端点地址,端点号
- 将接口的 is_active 标志初始化为 0,表示它还没有与任何驱动绑定,就是还没有找到另一半。
USB gadget
什么是 USB gadget?gadget 说白了就是配件的意思,主要就是一些内部运行 Linux 的嵌入式设备,比如 PDA,设备本身有 USB 设备控制器(USB Device Controller),可以将 PC,也就是我们的主机作为 master 端,将这样的设备作为 slave 端和主机通过 USB 进行通信。
从主机的角度来看,主机系统的 USB 驱动程序控制插入其中的 USB 设备,而 USB gadget 的驱动程序控制外围设备作为一个 USB 设备和主机通信。比如,我们的嵌入式主板上支持 SD 卡,如果我们希望将主板通过 USB 连接到 PC 后,这个 SD 卡被模拟成 U 盘,那么就要通过 USB gadget 架构的驱动。
第 3 篇 Linux 那些事儿之我是 U 盘
小城故事
在 Linux 内核代码目录中,所有与设备驱动程序有关的代码都在 drivers/ 目录下面,其中 drivers/usb 目录包含了所有 USB 设备的驱动
liyongjun@Box:~/project/board/buildroot/OrangePiPC/build/linux-5.10.9/drivers/usb$ ls
atm cdns3 common dwc3 host Kconfig modules.order musb roles typec
built-in.a chipidea core early image Makefile mon phy serial usbip
c67x00 class dwc2 gadget isp1760 misc mtu3 renesas_usbhs storage usb-skeleton.c
注意到,每一个目录下面都有一个 Kconfig 和 Makefile 文件,这很重要。
而我们的故事其实是围绕着 drivers/usb/storage 这个目录来展开的。
liyongjun@Box:~/project/board/buildroot/OrangePiPC/build/linux-5.10.9/drivers/usb/storage$ ls -I "*.o"
alauda.c initializers.h option_ms.h sierra_ms.c unusual_devs.h unusual_sddr55.h
built-in.a isd200.c protocol.c sierra_ms.h unusual_ene_ub6250.h unusual_uas.h
cypress_atacb.c jumpshot.c protocol.h transport.c unusual_freecom.h unusual_usbat.h
datafab.c karma.c realtek_cr.c transport.h unusual_isd200.h usb.c
debug.c Kconfig scsiglue.c uas.c unusual_jumpshot.h usb.h
debug.h Makefile scsiglue.h uas-detect.h unusual_karma.h usual-tables.c
ene_ub6250.c modules.order sddr09.c unusual_alauda.h unusual_onetouch.h
freecom.c onetouch.c sddr55.c unusual_cypress.h unusual_realtek.h
initializers.c option_ms.c shuttle_usbat.c unusual_datafab.h unusual_sddr09.h
实际上,这里的代码清清楚楚地展示了我们日常频繁接触的 U 盘是如何工作的,是如何被驱动起来的。
用 “wc -l *.c *.h” 这个命令统计,22947 行!!!但是,也许,生活中总是充满了跌宕起伏。
认真看了 Makefile 和 Kconfig 之后,心情明显好许多。
Makefile
基本上,Linux 内核中每一个目录下面都有一个 Makefile。
Makefile 和 Kconfig 就像一个城市的地图,地图带领我们去认识一个城市,而 Makefile 和 Kconfig 则可以让我们了解这个目录下面的结构。
drivers/usb/storage/Makefile
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the USB Mass Storage device drivers.
#
# 15 Aug 2000, Christoph Hellwig <hch@infradead.org>
# Rewritten to use lists instead of if-statements.
#
ccflags-y := -I $(srctree)/drivers/scsi
ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE=USB_STORAGE
obj-$(CONFIG_USB_UAS) += uas.o
obj-$(CONFIG_USB_STORAGE) += usb-storage.o
usb-storage-y := scsiglue.o protocol.o transport.o usb.o
usb-storage-y += initializers.o sierra_ms.o option_ms.o
usb-storage-y += usual-tables.o
usb-storage-$(CONFIG_USB_STORAGE_DEBUG) += debug.o
obj-$(CONFIG_USB_STORAGE_ALAUDA) += ums-alauda.o
obj-$(CONFIG_USB_STORAGE_CYPRESS_ATACB) += ums-cypress.o
obj-$(CONFIG_USB_STORAGE_DATAFAB) += ums-datafab.o
obj-$(CONFIG_USB_STORAGE_ENE_UB6250) += ums-eneub6250.o
obj-$(CONFIG_USB_STORAGE_FREECOM) += ums-freecom.o
obj-$(CONFIG_USB_STORAGE_ISD200) += ums-isd200.o
obj-$(CONFIG_USB_STORAGE_JUMPSHOT) += ums-jumpshot.o
obj-$(CONFIG_USB_STORAGE_KARMA) += ums-karma.o
obj-$(CONFIG_USB_STORAGE_ONETOUCH) += ums-onetouch.o
obj-$(CONFIG_USB_STORAGE_REALTEK) += ums-realtek.o
obj-$(CONFIG_USB_STORAGE_SDDR09) += ums-sddr09.o
obj-$(CONFIG_USB_STORAGE_SDDR55) += ums-sddr55.o
obj-$(CONFIG_USB_STORAGE_USBAT) += ums-usbat.o
ums-alauda-y := alauda.o
ums-cypress-y := cypress_atacb.o
ums-datafab-y := datafab.o
ums-eneub6250-y := ene_ub6250.o
ums-freecom-y := freecom.o
ums-isd200-y := isd200.o
ums-jumpshot-y := jumpshot.o
ums-karma-y := karma.o
ums-onetouch-y := onetouch.o
ums-realtek-y := realtek_cr.o
ums-sddr09-y := sddr09.o
ums-sddr55-y := sddr55.o
ums-usbat-y := shuttle_usbat.o
而 Kconfig 文件,其实就是对上面看到的这些 Config 选项进行解释,Kconfig 文件比较长,就不贴出来了。
通过看 Kconfig 文件可以知道,除了 CONFIG_USB_STORAGE 这个编译选项是我们真正需要的以外,别的选项都可以不予理睬。比如,关于 USB_STORAGE_SDDR55,Kconfig 文件中有这么一段
config USB_STORAGE_SDDR55
tristate "SanDisk SDDR-55 SmartMedia support"
help
Say Y here to include additional code to support the Sandisk SDDR-55
SmartMedia reader in the USB Mass Storage driver.
If this driver is compiled as a module, it will be named ums-sddr55.
显然,这是 SanDisk 的产品,并且是针对 SM 卡的,也不是 U 盘设备,所以不予理睬。
只有 CONFIG_USB_STORAGE 这个选项是真正需要关系的,而它所对应的模块叫 usb-storage,Makefile 中也说了
obj-$(CONFIG_USB_STORAGE) += usb-storage.o
usb-storage-y := scsiglue.o protocol.o transport.o usb.o
usb-storage-y += initializers.o sierra_ms.o option_ms.o
usb-storage-y += usual-tables.o
这就意味着我们只需要关注的文件就是 scsiglue.c,protocol.c,transport.c,usb.c,initializers.c,sierra_ms.c,option_ms.c,usual-tables.c 以及它们的同名 .h 头文件。再次使用 wc -l 命令统计这几个文件,发现总长度只有 4437 行了,比最初看到的 22947 行少了许多,顿时信心倍增。一天看 800 行,一周也看完了。
需要注意的是,CONFIG_USB_STORAGE_DEBUG 这个编译选项不是必须的,但是如果真的要自己修改或调试 usb-storage 的代码,那么打开这个选项是很有必要的,因为它会负责打印一些调试信息,以后在源代码中会看到它的作用。
外面的世界很精彩
看代码之前,我曾经认真地思考过这么一个问题,需要关注的仅仅是 drivers/usb/storage 目录下面那相关的 4000 多行代码吗?就是这样几个文件就能让一个个不同的 U 盘在 Linux 下面工作起来吗?像一开始那样把这个目录比作一个小城的话,也许,城里的月光很漂亮,它能够把人的梦照亮,能够温暖人的心房。但我们真的就能厮守在这个城里,一生一世吗?
很不幸,问题远不是这样简单。外面的世界很精彩,作为 U 盘,她需要与 USB Core、SCSI Core、内存管理单元,还有内核中许许多多其它模块打交道。外面的世界很大,远比我们想象的大。
什么是 USB Core?它负责实现一些核心功能,为别的设备驱动程序提供服务,比如申请内存,比如实现一些所有设备都会需要的公共函数。事实上,在 USB 的世界里,要使一个普通的设备正常地工作,除了要有设备本身以外,还要有主机控制器(Host Controller),和这个控制器相连接在一起的是 Root Hub。
USB 主机控制器(Host Controller)本身是干什么用的呢?控制器,顾名思义,用于控制所有 USB 设备的通信。通常,计算机的 CPU 并不是直接和 USB 设备打交道的,而是和控制器打交道,CPU 要对设备做什么,它会告诉控制器,控制器再去负责处理这件事情,会去指挥设备执行命令,而 CPU 就不用管剩下的事情,控制器会替它去完成剩下的事情,事情办完了再通知 CPU。否则,让 CPU 去盯着每一个设备去做每一件事情,那是不现实的。
所以,Linux 内核开发人员们,专门写了一些代码,并美其名曰 USB Core。时代总在发展,早起的 Linux 内核,其结构并不是如今天这般有层次感,远不像今天这般错落有致,那时候 drivers/usb/ 这个目录下面放了很多很多文件,USB Core 与其它各种设备的驱动程序都堆砌在这里。后来,怎奈世间万千的变幻,总爱把有情的人分两端。
于是在 drivers/usb/ 目录下面出来了一个 core 目录,就专门放一些核心的代码,比如初始化整个 USB 系统,初始化 Root Hub,初始化 Host Controller 的代码,在后来甚至把 Host Controller 相关的代码也单独建了一个目录,叫 host 目录(和 PCIe 好像),这是因为 USB Host Controller 随着时代的发展,也开始有了好多种,不在像刚开始那样只有一种,所以呢,设计者们把一些 Host Controller 公共的代码仍然留在 core 目录下,而一些各 Host Controller 单独的代码则移动到 host 目录下面,让负责各种 Host Controller 的人去维护。常见的 Host Controller 有三种:EHCI、UHCI 和 OHCI。
所以,这样出来了三个概念,USB Core、USB Host Controller 和 USB 设备,显示总是很无奈,然而,心若知道灵犀的方向,哪怕不能够朝夕相伴?没错,USB 通信的灵魂就是 USB 协议。USB 协议将是所有 USB 设备和 USB 主机所必须遵循的游戏规则。这种规则也很自然地体现在了代码中。于是我们需要了解的不仅仅是 drivers/usb/storage/ 目录下面的文件,还得去了解外面的世界,虽然,只需要了解一点点。
未曾开始却似结束
从 module_usb_stor_driver() 这个初始化函数开始看,每一个人的心中都有一种莫名的兴奋,因为它太短了,就两行 usb_stor_host_template_init() 和 usb_register(),
#define module_usb_stor_driver(__driver, __sht, __name) \
static int __init __driver##_init(void) \
{ \
usb_stor_host_template_init(&(__sht), __name, THIS_MODULE); \
return usb_register(&(__driver)); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
usb_deregister(&(__driver)); \
} \
module_exit(__driver##_exit)
usb_register() 这个函数有什么用呢?首先,这个函数正是来自 USB Core。凡是 USB 设备驱动,都要调用这个函数来向 USB Core 注册,从而让 USB Core 知道有这个驱动。
不懂设备模型又怎能说自己懂设备驱动呢?读代码的人,写代码的人,都要知道,什么是设备驱动?什么又是设备?设备和驱动之间究竟是什么关系?设备如何与计算机主机联系起来的?
计算机世界里,设备有很多种类,比如 PCI 设备,比如 ISA 设备,再比如 SCSI 设备,在比如我们这里的 USB 设备。为设备联姻的是总线,是它把设备连入了计算机主机。但是与其说设备是嫁给了计算机主机,倒不如说设备是嫁给了设备驱动程序。很显然,在计算机世界里,无论风里雨里,陪伴着设备的正是驱动程序。
唯一遗憾的是,计算机中的设备和驱动程序的关系却并非如可乐和拉环的关系那样,一对一。然而,世上又有多少事情总能如人愿呢。
狂欢是一群人的孤单
Linux 设备模型中三个很重要的概念就是总线、设备和驱动,即 bus、device 和 driver。而实际上内核中也定义了这么一些数据结构,它们是 bus_type、device、device_driver,这三个重要的数据结构都来自同一个地方,即 include/linux/device.h。
我们知道总线有很多种,如 PCI 总线、SCSI 总线、USB 总线,所以我们会看到 Linux 内核代码中出现 pci_bus_type,scsi_bus_type,usb_bus_type,它们都是 struct bus_type 类型的变量。而 struct bus_type 结构中两个非常重要的成员就是 struct kset devices 和 struct kset drivers。kset 和另一个叫做 kobject 正是 2.6 内核中设备模型的基本元素。
这里我们只需要知道,devices 和 drivers 的存在,然 struct bus_type 与两个链表联系了起来,分别是 devices 和 drivers 的链表。也就是说,知道一条总线所对应的数据结构,就可以找到这条总线所关联的设备,及支持这类设备的驱动程序。
而要实现这些目的,就要求每次出现一个设备就要向总线汇报,或者说注册。每次出现一个驱动,也要向总线汇报,或者说注册。比如系统初始化时,会扫描连接了哪些设备,并为每一个设备建立起一个 struct device 的变量,每一次有一个驱动程序,就要准备一个 struct device_driver 结构的变量。把这些变量统统加入相应的链表,设备插入 devices 链表,驱动插入 drivers 链表。这样通过总线就能找到每一个设备、每一个驱动。
假如计算机里只有设备却没有对应的驱动,那么设备无法工作。反过来,倘若只有驱动却没有设备,驱动也起不了任何作用。设备开始多了,驱动开始多了。它们像是来自两个世界,设备们彼此取暖,驱动们一起狂欢,但它们有一点是相同的,都只是在等待属于自己的那个另一半。
总线、设备和驱动(上)
struct bus_type 中为设备和驱动准备了两个链表,而代表设备的结构体 struct device 中又有两个成员,struct bus_type *bus 和 struct device_driver *driver。同样,代表驱动的结构体 struct device_driver 同样有两个成员,struct bus_type *bus 和 struct list_head devices。struct device 和 struct device_driver 的定义和 struct bus_type 一样,在 include/linux/device.h 中。凭直觉,可以知道,struct device 中 bus 记录的是这个设备连接在哪条总线上,driver 记录的是这个设备用的是哪个驱动。反过来,struct device_driver 中的 bus 代表的也是这个驱动属于哪条总线,devices 记录的是这个驱动支持的哪些设备。没错,是 devices(复数),而不是 device(单数),因为一个驱动程序可以支持一个或多个设备,反过来,一个设备则只会绑定一个驱动程序。
于是我们想知道,关于 bus,关于 device,关于 driver,它们是如何建立联系的呢?换而言之,这三个数据结构中的指针是如何被赋值的?绝对不可能发生的事情是,一旦为一条总线申请了一个 struct bus_type 的数据结构之后,它就知道它的 devices 链表和 drivers 链表会包含哪些东西,这些东西一定不会是先天就有的,只能是后来填进来的。
而具体到 USB 系统,完成这个工作的就是 USB Core。USB Core 的代码会进行整个 USB 系统的初始化,比如申请 struct bus_type usb_bus_type,然后会扫描 USB 总线,看线上连接了哪些 USB 设备。或者说 Root Hub 上连了哪些 USB 设备,比如说连接了一个 USB 键盘,那么就为它准备一个 struct device,根据它的实际情况,为这个 struct device 赋值,并插入 devices 链表中来。又比如 Root Hub 上连了一个普通的 Hub,那么除了要为这个 Hub 本身准备一个 struct device 外,还得继续扫描看这个 Hub 上是否又连了别的设备,如果有的话继续重复之前的事情,这样一直进行下去,直到完成整个扫描,最终就把 usb_bus_type 中的 devices 链表给建立了起来。
那 drivers 链表呢?这个就不用 bus 方面主动了,而该由每一个驱动本身去 bus 上面登记,或者说挂牌。具体到 USB 系统,每一个 USB 设备的驱动程序都会有一个 Struct usb_driver 结构体。
static struct usb_driver usb_storage_driver = {
.name = DRV_NAME,
.probe = storage_probe,
.disconnect = usb_stor_disconnect,
.suspend = usb_stor_suspend,
.resume = usb_stor_resume,
.reset_resume = usb_stor_reset_resume,
.pre_reset = usb_stor_pre_reset,
.post_reset = usb_stor_post_reset,
.id_table = usb_storage_usb_ids,
.supports_autosuspend = 1,
.soft_unbind = 1,
};
module_usb_stor_driver(usb_storage_driver, usb_stor_host_template, DRV_NAME);
USB Core 为每一个驱动准备了一个函数,让它把这个 driver 插入到 usb_bus_type 中的 drivers 链表中去,而这个函数正是我们此前看到的 usb_register。
#define module_usb_stor_driver(__driver, __sht, __name) \
static int __init __driver##_init(void) \
{ \
usb_stor_host_template_init(&(__sht), __name, THIS_MODULE); \
return usb_register(&(__driver)); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
usb_deregister(&(__driver)); \
} \
module_exit(__driver##_exit)
而与之对应的 usb_deregister 函数所从事的正是与之相反的工作,把这个结构体变量从 drivers 链表中删除。
可以说,USB Core 的确是用心良苦,为每一个 USB 驱动做足了功课,正因为如此,作为一个实际的 USB 驱动,它在初始化阶段所要做的事情就很少,很简单了,直接调用 usb_register 即可。事实上,没有人是理所应当应该为你做什么的,但 USB Core 这么做了。所以每一个写 USB 设备驱动的人应该铭记,USB 设备驱动绝不是一个人在工作,在它身后,是 USB Core 所提供的默默无闻又不可或缺的支持。
总线、设备和驱动(下)
bus 上的两张链表记录了每一个设备和驱动,那么设备和驱动这两者之间又是如何联系起来的呢?此刻,必须抛出这样一个问题,先有设备还是先有驱动?
在以前,先有的是设备,每一个要用的设备在计算机启动之前就已经插好了,插放在它应该在的位置上,而后计算机启动,然后操作系统开始初始化,总线开始扫描设备。每找到一个设备,就为其申请一个 struct device 结构,并挂入总线的 devices 链表中,然后每一个驱动程序开始初始化,开始注册器 struct device_driver 结构,然后它去总线的 devices 链表中去寻找每一个还没有绑定驱动的设备,即 struct device 中 struct device_driver 指针仍为空的设备,然后它会去观察这种设备的特征,看是否是它所支持的设备,如果是,那么调用一个叫做 device_bind_driver 的函数,然后它们就结为秦晋之好。换句话说,把 struct device 中 struct device_driver driver 指向这个驱动,而 struct device_driver 的 struct list_head devices 链表加入 struct device。就这样,bus、device 和 driver,这三者之间或者说它们中的两两之间,就给联系上了。知道其中之一,就能找到另外两个。一荣俱荣,一损俱损。
但现在情况变了,出现了一种新的名词 “热拔插”。设备可以在计算机启动以后再插入或拔出计算机了。因此,很难再说是先有设备还是先有驱动了。因为都有可能。设备可以在任何时刻出现,而驱动也可以在任何时刻被加载。所以,出现的情况就是,每当一个 struct device 诞生,他就会去 bus 的 drivers 链表中寻找自己的另一半;反之,每当一个 struct device_driver 诞生,它就去 bus 的 devices 链表中寻找它的那些设备。如果找到了合适的,那么和之前那种情况一样,调用 device_bind_driver 绑定好。如果找不到,没有关系,等待吧。
事实上,完善这个三角关系,正是每一个设备驱动初始化阶段所完成的重要使命之一。让我们还是回到代码中来,usb_register 函数调用了是调用了,但是传递给它的参数是什么呢?
是上面提到的 usb_storage_driver,不嫌重复,再把它贴一下吧,为了方便我们接下来分析其成员
static struct usb_driver usb_storage_driver = {
.name = DRV_NAME,
.probe = storage_probe,
.disconnect = usb_stor_disconnect,
.suspend = usb_stor_suspend,
.resume = usb_stor_resume,
.reset_resume = usb_stor_reset_resume,
.pre_reset = usb_stor_pre_reset,
.post_reset = usb_stor_post_reset,
.id_table = usb_storage_usb_ids,
.supports_autosuspend = 1,
.soft_unbind = 1,
};
module_usb_stor_driver(usb_storage_driver, usb_stor_host_template, DRV_NAME);
可以看到,定义了一个 struct usb_driver 的结构体变量 usb_storage_driver,关于 usb_driver 我们上节已经说过了,当时主要说的是其中的成员 driver,而眼下要讲的则是另外几个成员。
首先,name 是这个模块的名字,USB Core 会处理它,所以,如果这个模块正常被加载了的话,使用 lsmod 命令能看到一个叫做 usb-storage 的模块名。下面重点要讲一讲 .probe 和 .disconnect 以及这个 .id_table。
我是谁的他
probe,disconnect 和 id_table,这三个元素中首先要登场亮相的是 id_table,它是干什么用的呢?我们说过,一个设备只能绑定一个驱动,但驱动并非只能支持一种设备。因为一个模块可以被多个设备共用,才会有 “模块计数” 这个说法。
既然一个驱动可以支持多个设备,那么当发现一个设备时,如何知道哪个才是它的驱动呢?这就是 id_table 的用处,让每一个 struct usb_driver 准备一张表,里面注明该驱动支持哪些设备。如果这个设备属于这张表里,那么绑定,如果不属于这张表里的,那么不好意思,您请便。
于是我们知道,一个 usb_driver 会把它的 id 表和每一个 USB 设备的实际情况进行比较。如果该设备的实际情况和这张表里的某个 id 相同,准确地说,只有许多特征都吻合,才能够把一个 USB 设备和这个 USB 驱动进行绑定。
那么 USB 设备的实际情况是什么时候建立起来的?在介绍 .probe 之前,有必要先谈一谈另一个数据结构了,他就是 struct usb_device。
从协议中来,到协议中去(上)
在 struct usb_driver 中,.probe 和 .disconnect 的原型如下:
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);
我们来看其中的参数,struct usb_device_id 这个不用说了,刚才已经介绍过了,那么 struct usb_interface 从何而来?还是让我们先从 struct usb_device 说起。
对于一个 U 盘来说,它就是对应这么一个 struct usb_device 的变量,这个变量由 USB Core 负责申请和赋值。
对于 U 盘设备驱动来说,比这个 struct usb_device 更重要的数据结构是 struct usb_interface。走到这一步,我们不得不去了解一些 USB 设备的规范了,或者专业一点说,USB 协议。因为我们至少要知道什么是 USB 接口(Interface)。
从协议中来,到协议中去(中)
任何事物都有其遵守的规矩。USB 设备要遵循的就是 USB 协议。不管是软件还是硬件,在设计的开始,总是要参考 USB 协议。怎么设计硬件?如何编写软件?不看 USB 协议,谁也不可能凭空想象出来。
USB 协议规定了,每个 USB 设备都得有一些基本的元素,称为描述符。有四类描述符是任何一种 USB 设备都得有的,它们是,设备描述符(Device Descriptor)、配置描述符(Configuration Descriptor)、接口描述符(Interface Descriptor)、端点描述符(Endpoint Descriptor)。描述符里的东西是一个设备出厂时就被厂家给固化在设备中了。这种东西不管怎样也改变不了,比如我有一个 TOSHIBA(东芝)的 U 盘,里面的固有信息肯定是在东芝出厂时就被烙在里面了,厂家早已把它的一切,烙上了东芝的印记。所以当我插入 U 盘,用 cat /proc/scsi/scsi 命令看的话,“Vendor” 一项显示的肯定是 TOSHIBA
# cat /proc/scsi/scsi
Attached devices:
Host: scsi0 Channel: 00 Id: 00 Lun: 00
Vendor: TOSHIBA Model: TransMemory Rev: 1.00
Type: Direct-Access ANSI SCSI revision: 04
关于这几种描述符,USB Core 在总线扫描时就会去读取,获得里面的信息,其中,设备描述符描述的是整个设备。注意了,这个设备和咱们一直讲的设备驱动里的设备是不一样的。因为一个 USB 设备实际上指的是一种宏观上的概念,它可以是一种多功能的设备,改革开放之后,多功能的东西越来越多了,比如外企常见的多功能一体机,就是集打印机、复印机、扫描仪、传真机于一体的设备。当然,这不属于 USB 设备,但是 USB 设备当然也有这种情况,比如电台 DJ 可能会用到的,一个键盘,上边带一个扬声器,它们用两个 USB 接口接到 USB Hub 上去,而设备描述符描述的就是这整个设备的特点。
那么如何配置描述符呢?老实说,对我们了解 U 盘驱动真是没有什么意义,先不说了。
对于 USB 设备驱动程序编写者来说,更为关键的是下面的接口和端点。先说接口。第一句,一个接口对应一个 USB 设备驱动程序。没错,还举前面那个例子。一个 USB 设备,两种功能,一个键盘,上面带一个扬声器,两个接口,那肯定得要两个驱动程序,一个是键盘驱动程序,一个是音频流驱动程序。“道上”的兄弟喜欢把这样两个整合在一起的东西叫做一个设备,我们用接口来区分这两者。于是有了我们前面提到的那个数据结构 struct usb_interface,
struct usb_interface {
/* array of alternate settings for this interface,
* stored in no particular order */
struct usb_host_interface *altsetting;
struct usb_host_interface *cur_altsetting; /* the currently
* active alternate setting */
unsigned num_altsetting; /* number of alternate settings */
/* If there is an interface association descriptor then it will list
* the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;
int minor; /* minor number this interface is
* bound to */
enum usb_interface_condition condition; /* state of binding */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned ep_devs_created:1; /* endpoint "devices" exist */
unsigned unregistering:1; /* unregistration is in progress */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned resetting_device:1; /* true: bandwidth alloc after reset */
unsigned authorized:1; /* used for interface authorization */
struct device dev; /* interface specific device info */
struct device *usb_dev;
struct work_struct reset_ws; /* for resets in atomic context */
};
这个结构体贯穿整个 U 盘驱动程序,所以虽然我们不用去深入了解,但是需要记住,整个 U 盘驱动程序在后面任何一处提到的 struct usb_interface 都是同一个变量,这个变量是在 USB Core 总线扫描时就申请好了的。我们只需要直接用就是了,比如前面说过的 storage_probe(stuct usb_interface *intf, const stuct usb_device_id *id),storage_disconnect(struct usb_interface *intf) 这两个函数中的参数 intf。
从协议中来,到协议中去(下)
如果你是急性子,那这时候你一定很想开始看 storage_probe 函数了,因为整个 U 盘的工作就是从这里开始的。
前面我们已经了解了设备、配置和接口,还剩下最后一个就是端点。
USB 通信的最基本的形式就是通过端点,一个接口有一个或多个端点,而作为像 U 盘这样的存储设备,它至少有一个控制端点,两个批量端点。这些端点都是干什么用的?说来话长,真是一言难尽啊。
USB 协议中规定了,USB 设备有四种通信方式,分别是控制传输、中断传输、批量传输、等时传输。其中,等时传输显然是用于音频和视频一类的设备,这类设备期望能够有一个比较稳定的数据流,比如你在网上微信视频聊天,肯定希望每分钟传输的图像/声音速率是比较稳定的。usb-storage 里面肯定不会用到等时传输,因为我们只管复制一个文件,管它第一秒和第二秒的传输有什么速度区别,只要几十秒内传完就完事了。
相比之下,等时传输是四种传输中最麻烦的,不过,U 盘用不着。中断传输也用不着,对于 U 盘来说,的确用不着,虽然说 USB Mass storage 的协议中间有个叫做 CBI 的传输协议,CBI 就是 Control/Bulk/Interrupt,即控制/批量/中断,这三种传输都会用到,但这种传输协议并不适用于 U 盘,U 盘使用的是叫做 Bulk-Only 的传输协议。使用这种协议的设备只有两种传输方式:批量传输和控制传输,控制传输是任何一种 USB 设备都必须支持的,它专门用于传输一些控制信息。比如我想查询关于这个接口的一些信息,那么就用控制传输。而批量传输,它就是 U 盘的主要工作了,读写数据,这种情况就得用批量传输。
好了,知道了传输方式,就可以来认识端点了。和端点齐名的有一个叫做管道(Pipe)。端点就是通信的发送点或者接收点,要发送数据,那你只要把数据发生到正确的端点那里就可以了。
之所以 U 盘有两个批量端点,是因为端点也是有方向的,一个叫做 Bulk IN,一个叫做 Bulk OUT。从 USB 主机到设备称为 OUT,从设备到主机称为 IN。而管道实际上只是为了让我们能够找到端点,就相当于我们日常说的邮编地址,比如一个国家,为了通信,我们必须给各个地方取名,给各个大大小小的路取名,比如要从某偏僻的小县城出发,要到北京,那你的这个端点就是北京,而你得知道你来北京的线路,那这个从你们县城到北京的路线就算一条管道。有人好奇地问了,管道应该有两个端点吧,一个端点是北京,那另一个端点呢?答案是,这条管道有些特殊,我们只需要知道一端的目的地是北京,而另一端是哪里无所谓,因为不管你在哪里你都得到北京。
严格来说,管道的另一端应该是 USB 主机,USB 协议中也是这么规定的,协议中管道代表着一种能力。怎样一种能力呢?在主机和设备上的端点之间移动数据,听上去挺玄幻的。因为事实上,USB 里面所有的数据传输都是由主机发起的。一切都是以主机为核心,各种设备紧紧围绕在主机周围。所以 USB Core 里面很多函数就是为了让 USB 主机能够正确地完成数据传输或者说传输调度,就得告诉主机这个管道,换而言之,它得知道自己这次调度的数据是传送给谁,或者从谁那里传输过来。不过有人又要问了,比如说要从 U 盘里读一个文件,那告诉 USB 主机某个端点能有用吗?那个文件又不是放在一个端点里面,它不是存放在 U 盘里面的吗?这个就只能在后面的代码里去知道了。
梦开始的地方
对于整个 usb-storage 模块,usb_storage_driver_init() 函数是它的开始,然而,对于 U 盘驱动程序来说,它真正驱使 U 盘工作却始于 storage_probe() 函数。
USB Core 为设备找到了适合它的驱动程序,或者为驱动程序找到了它所支持的设备,但这只表明双方的第一印象还可以,但彼此是否真的合适还需要进一步了解。而 U 盘驱动则会调用函数 storage_probe() 去认识对方,它是一个什么样的设备?这里调用了 4 个函数:get_device_info()、get_transport()、get_protocol()、get_pipes()。
整个 U 盘驱动这部大戏,由 storage_probe 开始,由 storage_disconnect 结束。
先看 storage_probe()
/* The main probe routine for standard devices */
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
const struct us_unusual_dev *unusual_dev;
struct us_data *us;
int result;
int size;
/* If uas is enabled and this device can do uas then ignore it. */
#if IS_ENABLED(CONFIG_USB_UAS)
if (uas_use_uas_driver(intf, id, NULL))
return -ENXIO;
#endif
/*
* If the device isn't standard (is handled by a subdriver
* module) then don't accept it.
*/
if (usb_usual_ignore_device(intf))
return -ENXIO;
/*
* Call the general probe procedures.
*
* The unusual_dev_list array is parallel to the usb_storage_usb_ids
* table, so we use the index of the id entry to find the
* corresponding unusual_devs entry.
*/
size = ARRAY_SIZE(us_unusual_dev_list);
if (id >= usb_storage_usb_ids && id < usb_storage_usb_ids + size) {
unusual_dev = (id - usb_storage_usb_ids) + us_unusual_dev_list;
} else {
unusual_dev = &for_dynamic_ids;
dev_dbg(&intf->dev, "Use Bulk-Only transport with the Transparent SCSI protocol for dynamic id: 0x%04x 0x%04x\n",
id->idVendor, id->idProduct);
}
result = usb_stor_probe1(&us, intf, id, unusual_dev,
&usb_stor_host_template);
if (result)
return result;
/* No special transport or protocol settings in the main module */
result = usb_stor_probe2(us);
return result;
}
usb-storage 模块里面自己定义的数据结构不多,struct us_data 算一个。这个数据结构是跟我们的代码一直走下去的,如影随形,几乎处处都可以看见它的身影。
/* we allocate one of these for every device that we remember */
struct us_data {
/*
* The device we're working with
* It's important to note:
* (o) you must hold dev_mutex to change pusb_dev
*/
struct mutex dev_mutex; /* protect pusb_dev */
struct usb_device *pusb_dev; /* this usb_device */
struct usb_interface *pusb_intf; /* this interface */
const struct us_unusual_dev *unusual_dev;
/* device-filter entry */
unsigned long fflags; /* fixed flags from filter */
unsigned long dflags; /* dynamic atomic bitflags */
unsigned int send_bulk_pipe; /* cached pipe values */
unsigned int recv_bulk_pipe;
unsigned int send_ctrl_pipe;
unsigned int recv_ctrl_pipe;
unsigned int recv_intr_pipe;
/* information about the device */
char *transport_name;
char *protocol_name;
__le32 bcs_signature;
u8 subclass;
u8 protocol;
u8 max_lun;
u8 ifnum; /* interface number */
u8 ep_bInterval; /* interrupt interval */
/* function pointers for this device */
trans_cmnd transport; /* transport function */
trans_reset transport_reset; /* transport device reset */
proto_cmnd proto_handler; /* protocol handler */
/* SCSI interfaces */
struct scsi_cmnd *srb; /* current srb */
unsigned int tag; /* current dCBWTag */
char scsi_name[32]; /* scsi_host name */
/* control and bulk communications data */
struct urb *current_urb; /* USB requests */
struct usb_ctrlrequest *cr; /* control requests */
struct usb_sg_request current_sg; /* scatter-gather req. */
unsigned char *iobuf; /* I/O buffer */
dma_addr_t iobuf_dma; /* buffer DMA addresses */
struct task_struct *ctl_thread; /* the control thread */
/* mutual exclusion and synchronization structures */
struct completion cmnd_ready; /* to sleep thread on */
struct completion notify; /* thread begin/end */
wait_queue_head_t delay_wait; /* wait during reset */
struct delayed_work scan_dwork; /* for async scanning */
/* subdriver information */
void *extra; /* Any extra data */
extra_data_destructor extra_destructor;/* extra data destructor */
#ifdef CONFIG_PM
pm_hook suspend_resume_hook;
#endif
/* hacks for READ CAPACITY bug handling */
int use_last_sector_hacks;
int last_sector_retries;
};
不难发现,Linux 内核中,每一个重要的数据结构都很复杂。总之,这个令人头疼的数据结构是每一个设备都有的。换句话说,我们会为每一个设备申请一个 struct us_data,因为这个结构里的东西我们之后一直会用得着的,日后我们会非常频繁地看到 us。另外,us 什么意思?us,即 usb storage。
static struct scsi_host_template usb_stor_host_template;
/* The main probe routine for standard devices */
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
。。。
result = usb_stor_probe1(&us, intf, id, unusual_dev,
&usb_stor_host_template);
if (result)
return result;
。。。
}
/* First part of general USB mass-storage probing */
int usb_stor_probe1(struct us_data **pus,
struct usb_interface *intf,
const struct usb_device_id *id,
const struct us_unusual_dev *unusual_dev,
struct scsi_host_template *sht)
{
。。。
host = scsi_host_alloc(sht, sizeof(*us));
。。。
}
关于 usb_stor_host_template,必须得认真看,因为这个变量我们以后还会多次碰到。scsi_host_alloc 就是 SCSI 子系统提供的函数,它的作用就是申请一个 SCSI Host 相应的数据结构。
之所以 USB Mass Storage 里面会涉及 SCSI 这一层,是因为我们事实上把一块 U 盘模拟成了一块 SCSI 设备。对于 SCSI 设备来说,要想正常工作,得有一个 SCSI 卡,或者说 SCSI Host。而按照 SCSI 这一层的规矩,要想申请一个 SCSI Host 的结构体,我们就得提供一个 struct scsi_host_template 结构体,这其实从名字可以看出,是一个模板,SCSI 那层把一切都封装好了,只要提交一个模板给它,它就能为你提供一个 struct Scsi_Host 结构体。这个结构体变量的初始化如下
void usb_stor_host_template_init(struct scsi_host_template *sht,
const char *name, struct module *owner)
{
*sht = usb_stor_host_template;
sht->name = name;
sht->proc_name = name;
sht->module = owner;
}
EXPORT_SYMBOL_GPL(usb_stor_host_template_init);
按照 SCSI 层的规矩,要想申请一个 SCSI Host,并且让它工作,我们需要调用三个函数,第 1 个是 scsi_host_alloc(),第 2 个是 scsi_add_host(),第 3 个是 scsi_scan_host()。
scsi_host_alloc() 一调用,就是给 struct Scsi_Host 结构申请了空间,而只有调用了 scsi_add_host() 之后,SCSI 核心层才知道有这个 Host 的存在,然后只有 scsi_scan_host() 被调用了之后,真正的设备才被发现。这些函数的含义和它们的名字吻合的很好,不是吗?
总之咱们这么一折腾,就让 USB 驱动和 SCSI Host 联系起来了。从此以后这个 U 盘既要扮演 U 盘的角色,又要扮演 SCSI 设备的角色。
storage_probe 函数之所以短小,是因为它调用了大量的函数。所以,看起来短短一段代码,实际上却要花费读代码的人好几个小时。
接着,一系列一 init* 命名的函数在此刻被调用,这里涉及了一些锁机制、等待机制,不过只是初始化,暂且不理睬,到后面用到时再细说,不过请记住,这几行每一行都是有用的。后面自然会用得着。
/* First part of general USB mass-storage probing */
int usb_stor_probe1(struct us_data **pus,
struct usb_interface *intf,
const struct usb_device_id *id,
const struct us_unusual_dev *unusual_dev,
struct scsi_host_template *sht)
{
。。。
mutex_init(&(us->dev_mutex));
us_set_lock_class(&us->dev_mutex, intf);
init_completion(&us->cmnd_ready);
init_completion(&(us->notify));
init_waitqueue_head(&us->delay_wait);
INIT_DELAYED_WORK(&us->scan_dwork, usb_stor_scan_dwork);
。。。
result = associate_dev(us, intf);
。。。
result = get_device_info(us, id, unusual_dev);
。。。
}
先往下走,associate_dev() 和 get_device_info() 这两个函数是我们目前需要看的。
/* Associate our private data with the USB device */
static int associate_dev(struct us_data *us, struct usb_interface *intf)
{
/* Fill in the device-related fields */
us->pusb_dev = interface_to_usbdev(intf);
us->pusb_intf = intf;
us->ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
usb_stor_dbg(us, "Vendor: 0x%04x, Product: 0x%04x, Revision: 0x%04x\n",
le16_to_cpu(us->pusb_dev->descriptor.idVendor),
le16_to_cpu(us->pusb_dev->descriptor.idProduct),
le16_to_cpu(us->pusb_dev->descriptor.bcdDevice));
usb_stor_dbg(us, "Interface Subclass: 0x%02x, Protocol: 0x%02x\n",
intf->cur_altsetting->desc.bInterfaceSubClass,
intf->cur_altsetting->desc.bInterfaceProtocol);
/* Store our private data in the interface */
usb_set_intfdata(intf, us);
/* Allocate the control/setup and DMA-mapped buffers */
us->cr = kmalloc(sizeof(*us->cr), GFP_KERNEL);
if (!us->cr)
return -ENOMEM;
us->iobuf = usb_alloc_coherent(us->pusb_dev, US_IOBUF_SIZE,
GFP_KERNEL, &us->iobuf_dma);
if (!us->iobuf) {
usb_stor_dbg(us, "I/O buffer allocation failed\n");
return -ENOMEM;
}
return 0;
}
我们首先来关注函数 associate_dev 的参数,struct us_data *us,传递给它的是 us,这个不用多说了吧,此前刚刚为它申请了内存,并且初始化各成员为0。 这个 us 将一直陪伴我们走下去,直到我们的故事结束。所以其重要性不言而喻。struct usb_interface *intf,这个也不用说,storage_probe() 函数传进来的两个参数之一。总之,此处郑重申明一次,struct us_data 的结构体指针 us、struct usb_interface 结构体的指针 intf、以及 struct usb_device 结构体和 struct usb_device_id 结构体在整个 U 盘驱动的故事中是唯一的,每次提到都是那个。而以后我们会遇上的几个重要的数据结构,struct urb urb、struct scsi_cmnd
srb 这也非常重要,但是它们并不唯一,也许每次遇上都不一样,就像演戏一样。前边这几个数据结构的变量就像那些主角,而之后遇见的 urb 啊、srb 啊,虽然频繁露面,但是只是群众演员,只不过这次是路人甲,下次是路人乙。所以,以后我们将只说 us,不再说 struct us_data *us,struct usb_interface * intf 也将只用 intf 来代替。
us 之所以重要,是因为接下来很多函数都要用到它以及它的各个成员。实际上目前 associate_dev 这个函数所做的事情就是为 us 的各成员赋值,毕竟此刻 us 和我们之前提到的那些 struct usb_device 啊、struct usb_interface 啊,还没有一点关系。因而,这个函数,以及这之后的好几个函数都是为了给 us 的各成员赋上适当的值,之所以如此兴师动众去为它赋值,主要就是因为后面要利用它。所谓天下没有免费的午餐。
pusb_dev,就是 point of usb device 的意思。struct us_data 中的一个成员,按照我们刚才约定的规矩,此刻我将说 us 的一个成员,us->pusb_dev = interface_to_usbdev(intf),interface_to_usbdev 我们前面已经讲过,其含义正如字面表示的那样,把一个 struct interface 结构体的指针转换成一个 struct usb_device 的结构体指针。前面我们说过,struct usb_device 对我们没有什么用,但是 usb core 层的一些函数要求使用这个参数,所以我们不得已而为止,正所谓人在江湖身不由己。
us 的 ifnum,先看 intf 的 cur_altsetting,这个容易令外行混淆。usb 设备有一个 configuration 的概念,这个我们前面讲协议的时候说了,而这里又有一个 setting,咋一看有些奇怪,这两个词不是一回事吗。这时候,就体现出外语水平了,上过新东方没上过新东方,背没背过俞敏洪的 GRE 红宝书,在这时候就体现出差距了。还是拿我们最熟悉的手机来打比方,configuration 不说了,setting,一个手机可能各种配置都确定了,是振动还是铃声已经确定了,各种功能都确定了,但是声音的大小还可以变吧,通常手机的音量是一格一格的变动,大概也就 5、6 格,那么这个可以算一个 setting 吧。
/* host-side wrapper for one interface setting's parsed descriptors */
struct usb_host_interface {
struct usb_interface_descriptor desc;
int extralen;
unsigned char *extra; /* Extra descriptors */
/* array of desc.bNumEndpoints endpoints associated with this
* interface setting. these will be in no particular order.
*/
struct usb_host_endpoint *endpoint;
char *string; /* iInterface string, if present */
};
include/uapi/linux/usb/ch9.h
/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bInterfaceNumber;
__u8 bAlternateSetting;
__u8 bNumEndpoints;
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
__u8 iInterface;
} __attribute__ ((packed));
#define USB_DT_INTERFACE_SIZE 9
usb_host_interface 的成员 desc 是一个 usb_interface_descriptor 类型变量,这个结构体的定义是和 USB 协议直接对应的。定义于 include/uapi/linux/usb/ch9.h,这里取名为 ch9 是因为这个文件中很多东西对应于 USB Spec 2.0 中的第 9 章,chapter 9。
其中我们这里提到的是 bInterfaceNumber,一个设备可以有多个接口,于是每一个接口当然就得用一个编号了,要不然怎么区分啊?所有这些描述符里的东西都是出厂时就固化在设备中的,而我们这里之所以可以用 bInterfaceNumber 来赋值,是因为 USB Core 在为设备初始化时就已经做足了功课,否则的话,我们真是寸步难行。
总之,us->ifnum 就是这样,最终就是等于咱们眼下这个接口的编号。
接下来
us->cr = kmalloc(sizeof(*us->cr), GFP_KERNEL);
us->iobuf = usb_alloc_coherent(us->pusb_dev, US_IOBUF_SIZE,
GFP_KERNEL, &us->iobuf_dma);
就是申请内存了,us->cr 和 us->iobuf 都是指针,这里让它们指向两段内存空间,下面会用得到。参数 GFP_KERNEL 是一个内存申请的 flag,通常内存申请都用这个 flag,除非是中断上下文,不能睡眠,那就得用 GPF_ATOMIC,这里没那么多要求。
而 usb_alloc_coherent()的第四个参数有些意思了,这涉及到 dma 传输。iobuf_dma 是 dma_addr_t 类型的变量,这个数据类型是 Linux 内核中专门为 dma 传输而准备的。为了支持 dma 传输,usb_alloc_coherent 不仅仅是申请了地址,并且建立了 dma 映
射。
每一次申请完内存就要检查成功与否,这是惯例。驱动程序能否驱动设备,关键就是看能否申请到内存空间,任何一处内存空间申请失败,整个驱动程序就没法正常工作。
冬天来了,春天还会远吗?(一)
整个 usb-storage 模块的代码中,其最灵魂的部分在一个叫做 usb_stor_control_thread()的函数中,而那也自然是我们整个故事的高潮。
然而在此之前,有四个函数挡在我们面前,它们就是get_device_info,get_transport,get_protocol,get_pipes。如我前面所说,两个人要走到一起,首先要了解彼此,这四个函数就是让 driver 去认识 device 的。这一点我们从名字上也能看出来。driver 需要知道 device 姓甚名谁,所以有了 get_device_info,driver 需要知道 device 喜欢用什么方式沟通,是用 QQ 还是用 msn 还是只用手机短信,如果是某一种,那么账号是多少,或者手机号是多少,写代码的人把这些工作分配给了 get_transport,get_protocol,get_pipes。
实际上,这四个函数,加上之前刚说过的那个 associate_dev(),是整个故事中最平淡最枯燥的部分,第一次读这部分代码总让人困惑,怎么没看见一点 USB 数据通信啊?完全没有看到 USB host 和 USB device 是如何在交流的,这是 USB 吗?这一刻,这颗浮躁的心,在这个城市,迷失了。但是,我们知道,爱情,并非都与风花雪月有关,友情,并非总与疯斗打闹有关。这几个函数应该说是给后面做铺垫,红花总要有绿叶配,没有这段代码的铺垫,到了后面 USB 设备恐怕也无法正常工作吧。不过,一个利好消息是,这几个函数我们只会遇见这一次,它们在整个故事中就这么一次露脸的机会,像我们每个人的青春,只有一次,无法回头。和我们每个人的青春一样,都是绝版的。所以,让我们享受这段平淡无奇的代码吧。
既然明白了 unusual_devs.h 的作用,那么很显然的一个事情,如果一个厂家推出了一个新的设备,它有一些新的特征,而目前的设备驱动不足以完全支持它,那么厂家首先需要做的事情就是在unusual_devs.h 中添加一个 UNUSUAL_DEV 来定义自己的设备,然后再看是否需要给内核打补丁以及如何打。因此这几年 unusual_devs.h 这个文件的长度也是慢慢在增长。
对于 U 盘,Spec 规定了,它属于 Bulk-only 的传输方式,即它的 us->protocol 就是 USB_PR_BULK,就是我们刚刚在 get_device_info 中确定下来的。于是,在整个 switch 段落中,我们所执行的只是 US_PR_BULK 这一段
/* Get the transport settings */
static void get_transport(struct us_data *us)
{
switch (us->protocol) {
case USB_PR_CB:
us->transport_name = "Control/Bulk";
us->transport = usb_stor_CB_transport;
us->transport_reset = usb_stor_CB_reset;
us->max_lun = 7;
break;
case USB_PR_CBI:
us->transport_name = "Control/Bulk/Interrupt";
us->transport = usb_stor_CB_transport;
us->transport_reset = usb_stor_CB_reset;
us->max_lun = 7;
break;
case USB_PR_BULK:
us->transport_name = "Bulk";
us->transport = usb_stor_Bulk_transport;
us->transport_reset = usb_stor_Bulk_reset;
break;
}
}
us 的 transport_name 被赋值为 “Bulk”,transport 被赋值为 usb_stor_Bulk_transport,transport_reset 被赋值为 usb_stor_Bulk_reset。其中我们最需要记住的是,us 的成员 tansport 和 transport_reset 是两个函数指针。程序员们把这个称作钩子。这两个赋值我们需要牢记,日后我们定会用到它们的,因为这正是我们真正的数据传输的时候调用的东东。
get_protocol() 就做了一件事,根据 us->subclass 来判断,对于 U 盘来说,Spec 里面规定了,它的 subclass 是 US_SC_SCSI,所以这里就是两句赋值语句
case USB_SC_SCSI:
us->protocol_name = "Transparent SCSI";
us->proto_handler = usb_stor_transparent_scsi_command;
break;
传说中的 URB
urb 全称 USB Request Block。USB 设备需要通信,需要传递数据,就需要 urb,确切地说,应该是 USB 设备驱动程序使用 urb。
实际上,作为 USB 设备驱动,它本身并不能直接操纵数据的传输,在 usb 这个大观园里,外接设备永远都是配角,真正的核心只是 USB Core,而真正负责调度的是 USB 主机控制。这个通常看不见的 USB 主机控制器芯片,俨然是 USB 大观园中的大管家。
设备驱动要发送信息,所需要做的是建立一个 urb 数据结构,并把这个数据结构交给核心层,而核心层会为所有设备统一完成调度,而设备在提交了 urb 之后需要做的,只是等待。
设备和主机控制器的分工是如何呢?硬件实现上我们就不说了,说点具体的,Linux 中,设备驱动程序只要为每一次请求准备一个 urb 结构体变量,把它填充好,(就是说赋上该赋的值)然后它调用 USB core提供的函数,把这个 urb 传递给 Host Controller,Host Controller 就会把各个设备驱动程序所提交的 urb 统一规划,去执行每一个操作。而这期间,USB 设备驱动程序通常会进入睡眠,而一旦 Host Controller 把 urb 要做的事情给做完了,它会调用一个函数去唤醒 USB 设备驱动程序,然后 USB 设备驱动程序就可以继续往下走了。
/* Initialize all the dynamic resources we need */
static int usb_stor_acquire_resources(struct us_data *us)
{
。。。
/* Start up our control thread */
th = kthread_run(usb_stor_control_thread, us, "usb-storage");
。。。
}
如果让观众投票的话,usb_stor_control_thread() 这个函数中的代码无疑是整个模块中最为精华的代码。
static int usb_stor_control_thread(void * __us)
{
struct us_data *us = (struct us_data *)__us;
struct Scsi_Host *host = us_to_host(us);
struct scsi_cmnd *srb;
for (;;) {
usb_stor_dbg(us, "*** thread sleeping\n");
if (wait_for_completion_interruptible(&us->cmnd_ready))
break;
usb_stor_dbg(us, "*** thread awakened\n");
/* lock the device pointers */
mutex_lock(&(us->dev_mutex));
/* lock access to the state */
scsi_lock(host);
/* When we are called with no command pending, we're done */
srb = us->srb;
if (srb == NULL) {
scsi_unlock(host);
mutex_unlock(&us->dev_mutex);
usb_stor_dbg(us, "-- exiting\n");
break;
}
/* has the command timed out *already* ? */
if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) {
srb->result = DID_ABORT << 16;
goto SkipForAbort;
}
scsi_unlock(host);
/*
* reject the command if the direction indicator
* is UNKNOWN
*/
if (srb->sc_data_direction == DMA_BIDIRECTIONAL) {
usb_stor_dbg(us, "UNKNOWN data direction\n");
srb->result = DID_ERROR << 16;
}
/*
* reject if target != 0 or if LUN is higher than
* the maximum known LUN
*/
else if (srb->device->id &&
!(us->fflags & US_FL_SCM_MULT_TARG)) {
usb_stor_dbg(us, "Bad target number (%d:%llu)\n",
srb->device->id,
srb->device->lun);
srb->result = DID_BAD_TARGET << 16;
}
else if (srb->device->lun > us->max_lun) {
usb_stor_dbg(us, "Bad LUN (%d:%llu)\n",
srb->device->id,
srb->device->lun);
srb->result = DID_BAD_TARGET << 16;
}
/*
* Handle those devices which need us to fake
* their inquiry data
*/
else if ((srb->cmnd[0] == INQUIRY) &&
(us->fflags & US_FL_FIX_INQUIRY)) {
unsigned char data_ptr[36] = {
0x00, 0x80, 0x02, 0x02,
0x1F, 0x00, 0x00, 0x00};
usb_stor_dbg(us, "Faking INQUIRY command\n");
fill_inquiry_response(us, data_ptr, 36);
srb->result = SAM_STAT_GOOD;
}
/* we've got a command, let's do it! */
else {
US_DEBUG(usb_stor_show_command(us, srb));
us->proto_handler(srb, us);
usb_mark_last_busy(us->pusb_dev);
}
/* lock access to the state */
scsi_lock(host);
/* was the command aborted? */
if (srb->result == DID_ABORT << 16) {
SkipForAbort:
usb_stor_dbg(us, "scsi command aborted\n");
srb = NULL; /* Don't call srb->scsi_done() */
}
/*
* If an abort request was received we need to signal that
* the abort has finished. The proper test for this is
* the TIMED_OUT flag, not srb->result == DID_ABORT, because
* the timeout might have occurred after the command had
* already completed with a different result code.
*/
if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) {
complete(&(us->notify));
/* Allow USB transfers to resume */
clear_bit(US_FLIDX_ABORTING, &us->dflags);
clear_bit(US_FLIDX_TIMED_OUT, &us->dflags);
}
/* finished working on this command */
us->srb = NULL;
scsi_unlock(host);
/* unlock the device pointers */
mutex_unlock(&us->dev_mutex);
/* now that the locks are released, notify the SCSI core */
if (srb) {
usb_stor_dbg(us, "scsi cmd done, result=0x%x\n",
srb->result);
srb->scsi_done(srb);
}
} /* for (;;) */
/* Wait until we are told to stop */
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (kthread_should_stop())
break;
schedule();
}
__set_current_state(TASK_RUNNING);
return 0;
}
我们只需要它中间那个 for( ; ; ) 就知道,这是一个死循环,即使别的代码都执行完了,即使别的函数都退出了,这个函数仍然像永不消逝的电波一般,经典常驻。显然,只有死循环才能代码永恒。才能代表忠诚。这是每一个守护者的职责。
wait_for_completion_interruptible() 函数,事实上,从上下两行的调试信息可以知道,这里就是想获得锁,等待一个函数去释放锁。
我们刚刚跟着 storage_probe() 几乎完整地走了一遍,貌似一切都该结束了,可是你不觉得你到目前为止还根本没有看明白设备究竟怎么工作的吗?
U 盘,不仅仅是 USB 设备,还是 “盘”,它还需要遵守 USB Mass Storage 协议,以及 Transparent SCSI 规范。从驱动程序的角度来看,它和一般的 SCSI 磁盘差不多。正因为如此,所以 U 盘的工作真正需要的是四个模块,usbcore,scsi_mod,sd_mod 以及咱们这里的 usb-storage,其中 sd_mod 恰恰就是 SCSI 硬盘的驱动程序。没有它,你的 SCSI 硬盘就别想在 Linux 下面转起来。
usb_stor_control_thread(),唤醒它的是来自 queuecommand 的 up(&(us->sema)),us->srb 被赋值为 srb,而 srb 是来自 scsi 核心层在调用 queuecommand 时候传递进来的参数。
B.8 驱动开发三件宝
- 协议规范(Spec)
- 硬件的 datasheet
- 驱动的源代码
Spec、datasheet、内核源代码,这三样东西对于每个开发设备驱动的人来说都是再寻常不过了,但正是因为它们的普通,所以在很多人眼里被归为被忽视的群体。于是大家开发驱动的过程中,遇到问题的时候首先想到的可能还是 “问问牛人怎么解决吧”、“旁边要是有个牛人该多好”,因为牛人的稀有,所以知道牛人的价值,而又因为 Spec、datasheet 和内核源代码的唾手可得,所以常常体会不到它们在解决问题时的重要性。
当然我并不是贬低牛人的价值,宣扬依赖牛人不好,如果你很幸运身边真就有牛人这种稀缺资源,自然是要好好利用,也可以少走很多弯路,节省很多摸索的时间。只是人生不如意十之八九,多数人还是没有这份幸运的,所以与其遍寻牛人讨教,不如多依赖依赖自己,多利用利用自己身边有的资源去寻找解决问题的途径。
对这三样看似普通的东西,关键在于很好地去利用,而不是拥有。就说 USB 把,USB 驱动和 USB 设备如何进行交流,交流的方式,交流的过程中出现了什么问题是什么引起的等都在 USB Spec 里有描述,而你的 USB 设备支持多少种配置包含多少端点只有设备的 datasheet 才知道。协议的 Spec 和设备的 datasheet 是最好的参考资料,驱动开发调试中出现的问题绝大部分都能在它们的某个角落里找到答案。而内核中类似设备的驱动源代码是最好的模版,对很多硬件设备,你都可以在内核找到同种设备的驱动代码进行参考实现,甚至于可以复制或共享大部分的代码,只进行局部的修改,比如说位于 drivers/input/touchscreen 目录下的各个触摸屏驱动,它们之间的代码很多都是类似的甚至是相同的。
如果你不仅仅只是打算写驱动,而是还想阅读内核中实现某种总线、设备的源代码,钻研它们的实现机制,那协议的 Spec 就尤为重要,它们在代码里的体现无处不在,你需要在阅读代码前就对协议规范有个整体的理解。形象点说,Spec 是理论基础,内核代码是具体实现,理论懂了,看代码就和看故事会差不多了。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!