1 概述 ######
1.1 USB总线拓扑结构
USB设备的连接如图19.1所示,对于每个PC来说,都有一个或者多个称为主机(Host)控制器的设备,该主机控制器和一个根集线器(Hub)作为一个整体。这个根Hub下可以接多级的Hub,每个子Hub又可以接子Hub,每个USB设备作为一个结点接在不同级别的Hub上
1 USB主机控制器(Host Control)
USB Host控制器:每个PC的主板上都会有多个Host控制器,每个Host控制器其实就是一个PCI设备,挂载在PCI总线上,嵌入式设备也如此。在Linux系统中,驱动开发人员应该给Host控制器提供驱动程序,用usbhcd结构来表示。值得注意的是,目前Host控制器驱动主要有两种,一种是1.0,另一种是2.0,分别对应着USB协议1.0和USB协议2.0。
2 USB集线器(USB Hub)
USB Hub:每个USB Host控制器都会自带一个USB Hub,被称为根(Root) Hub。这个根Hub可以接子(Sub) Hub,每个Hub上挂载USB设备。一般PC有8个USB口,通过外接USB Hub,可以插更多的USB设备。当USB设备插入到USB Hub或从上面拔出时,都会发出电信号通知系统。这样可以枚举USB设备。
3 USB设备USB设备:
USB设备就是插在USB总线上工作的设备,广义地讲USB Hub也算是 USB设备。每个根USB Hub下可以直接或间接地连接127个设备,并且彼此不会干扰。对于用户来说,可以看成是USB设备和USB控制器直接相连,之间通信需要满足USB的通信协议。
https://www.cnblogs.com/mahj/p/8489186.html
1.2 USB驱动总体架构
- USB驱动由USB主机控制器驱动和USB设备驱动组成。
- USB主机控制器驱动,主要用来驱动芯片上的主机控制器硬件。
- USB设备驱动是指具体的例如USB摄像头等设备驱动。
1 USB主机控制器
如图19.2所示,在Linux驱动中, USB驱动处于最底层是USB主机控制器硬件。主机控制器硬件用来实现USB协议规定的相关操作,完成与USB设备之间的通信。在嵌入式系统中, USB主机控制器硬件一般集成在CPU芯片中。事实上,在USB的世界里,要使USB设备正常工作,除了有USB设备本身外,在计算机系统中,还需要USB主机控制器才能使USB设备工作。
顾名思义,主机控制器就是用来控制USB设备与CPU之间通信的。通常计算机的CPU并不是直接和USB设备通信,而是和主机控制器通信。CPU要对设备做什么操作,会先通知主机控制器,而不是直接发送指令给USB设备。主机控制器接收到CPU的命令后,会去指挥USB设备完成相应的任务。这样, CPU把命令传给主机控制器后,就不用管余下的工作了, CPU转向处理其他事情。
2. USB主机控制器驱动
USB主机控制器硬件必须由USB主机控制器驱动程序驱动才能运行。USB主机控制器驱动用he driver表示,在计算机系统中的每一个主机控制器都有一个对应的hc_driver结构体,该结构体在/drivers/usb/core/hcd.h文件中定义,代码如下:
struct hc_driver {
const char *description; /* "ehci-hcd" etc */
const char *product_desc; /* product/vendor string */
size_t hcd_priv_size; /* size of private data */
/* irq handler */
irqreturn_t (*irq) (struct usb_hcd *hcd);
int flags;
#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */
#define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */
#define HCD_USB11 0x0010 /* USB 1.1 */
#define HCD_USB2 0x0020 /* USB 2.0 */
#define HCD_USB3 0x0040 /* USB 3.0 */
#define HCD_MASK 0x0070
/* called to init HCD and root hub */
int (*reset) (struct usb_hcd *hcd);
int (*start) (struct usb_hcd *hcd);
/* NOTE: these suspend/resume calls relate to the HC as
* a whole, not just the root hub; they're for PCI bus glue.
*/
/* called after suspending the hub, before entering D3 etc */
int (*pci_suspend)(struct usb_hcd *hcd);
/* called after entering D0 (etc), before resuming the hub */
int (*pci_resume)(struct usb_hcd *hcd, bool hibernated);
/* cleanly make HCD stop writing memory and doing I/O */
void (*stop) (struct usb_hcd *hcd);
/* shutdown HCD */
void (*shutdown) (struct usb_hcd *hcd);
/* return current frame number */
int (*get_frame_number) (struct usb_hcd *hcd);
/* manage i/o requests, device state */
int (*urb_enqueue)(struct usb_hcd *hcd,
struct urb *urb, gfp_t mem_flags);
int (*urb_dequeue)(struct usb_hcd *hcd,
struct urb *urb, int status);
//.........
}
3 USB核心
再往上一层是USB核心, USB核心负责对USB设备的整体控制,包括实现USB主机控制器到USB设备之间的数据通信。本质上, USB核心是为设备驱动程序提供服务的程序,包含内存分配和一些设备驱动公用的函数,例如初始化Hub、初始化主机控制器等。USB核心的代码存放在drivers/usb/core目录下。
4 USB设备驱动程序
最上一层是USB设备驱动程序,用来驱动相应的USB设备。USB设备驱动用usb-driver表示,它主要用来将USB设备挂接到USB核心中,并启动USB设备,让其正常工作。对 USB设备的具体读写操作由放在usb driver设备中的usb class drivers成员来实现,该成员中定义了一个file-operations结构体,用来对设备进行读写操作。关于usbdriver结构体的详细说明将在下面介绍。
5 USB设备与USB驱动之间的通信
要理解USB设备和USB驱动之间是怎样进行通信的,需要知道两个概念。一是USB设备固件,二是USB协议。这里的USB驱动包括USB主机控制器驱动和USB设备驱动。
固件(Firmware)就是写入EROM或EPROM (可编程只读存储器)中的程序,通俗地理解就是“固化的软件”。更简单地说,固件就是BIOS (基本输入输出软件)的软件,但又与普通软件完全不同,它是固化在集成电路内部的程序代码,负责控制和协调集成电路的功能。USB固件中包含了USB设备的出厂信息,标识该设备的厂商ID、产品ID、主版本号和次版本号等。
另外固件中还包含一组程序,这组程序主要完成两个任务: USB协议的处理和设备的!读写操作。例如将数据从设备发送到总线上,或从总线中将数据读取到设备存储器中。对设备的读写需要固件程序来完成,所以固件程序应该了解对设备读写的方法。驱动程序只是将USB规范定义的请求发送给固件程序,固件程序负责将数据写入设备的存储器中。现在的一些U盘病毒,例如exe文件夹图标病毒,可以破坏USB固件中的程序,导致U盘损害,在使用U盘时,需要引起读者的注意。
USB设备固件和USB驱动之间通信的规范是通过USB协议来完成的。通俗地讲,USB协议规定了USB设备之间是如何通信的。如图19.3是USB设备固件和USB驱动通信的简易关系图。
2 USB设备驱动模型 ########
1 USB驱动初探 @@@@@@
Linux操作系统提供了大量的默认驱动程序。一般来说,这些驱动程序适用于大多数硬件,但也有许多特殊功能的硬件不能在操作系统中找到相应的驱动程序。这时,驱动开发人员一般在内核中找到一份相似的驱动代码,再根据实际的硬件情况进行修改。所以通过什么样的方法找到相似的驱动程序非常重要。
幸好Linux内核源码具有好的分类目录, drivers/usb/storage/目录便是常见的USB设备驱动程序目录。该目录中实现了一个重要的usb-storage模块,该模块支持常用的USB存储设备。本文将对这种设备驱动进行分析。找到了USB驱动目录后,哪些才是USB驱动相关的重要文件呢,请看下面的分析。
1.1 如何找到我们想要的驱动文件?
在USB目录下,有一个重要的storage目录,这里面的代码就是实际需要讲解的USB "设备驱动"的代码。我们日常生活中频繁使用的U盘的驱动,就被放在这个目录中。由于USB设备非常复杂, storage目录中的代码也与其他目录中的代码有千丝万缕的联系,在以后的学习中将逐步讲解,希望引起读者的注意。storgae目录中的主要文件可以用ls命令查看,如下所示。
但是由于storage.o文件的生成很复杂,所有直接给出以后参考的文件是
文件为:\drivers\usb\storage\usb.c
2 USB设备驱动模型 @@@@@
2.1 总线,设备和驱动
Linux设备驱动模型中有3个重要的概念,分别是总线(bus)、设备(device)和驱动(driver) 。
这3个数据结构在Linux内核源码中分别对应struct_bus_type, struct_device和struct_device_driver.
Linux系统中总线的概念与实际的物理主机中总线的概念是不同的。物理主机中的总线是实际的物理线路,例如数据总线、地址总线。而在Linux系统中,总线是一种用来管理设备和驱动程序的数据结构,它与实际的物理总线相对应。在计算机系统中,总线有很多种。例如USB总线、SCSI总线、PCI总线等,在内核代码中,分别对应usb-bus_type,scsibustype和pcibus type变量,这些变量的类型是bus_type.在此处需要关注bus type结构体中的bus type private成员, bus type结构体和 bus type private结构体的省略定义如下:
struct bus_type {
//......
struct bus_type_private *p;
};
struct bus_type_private {
struct kset subsys;
struct kset *drivers_kset;
struct kset *devices_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
};
bus_type_private结构体表示总线拥有的私有数据,其中drivers kset和devices kset这两个数据非常重要,其他成员在此处可以忽略。内核设计者将总线与两个链表联系起来,一个是drivers_kset,另一个是devices_kset, drivers_set链表表示连接到该总线上的所有驱动程序, devices_kset链表表示连接到该总线上的所有设备。这种关系如图19.4所示。
在内核中总线、驱动、设备三者之间是通过指针互相联系的。知道其中任何一个结构都可以通过指针获得其他结构。
设备与驱动的绑定
在内核中总线、驱动、设备三者之间是通过指针互相联系的。知道其中任何一个结构都可以通过指针获得其他结构绑定。设备与驱动的绑定,只能在同一总线上的设备与驱动之间进行。
在设备模型中,当知道一条总线的数据结构时,就可以找到这条总线所连接的设备和驱动程序。要实现这个关系,就要求当每次总线上有新设备出现时,系统就要向总线汇报,告知有新设备添加到系统中。系统为设备分配一个struct device数据结构,并将其挂接到 devices kset链表中。特别是在开机时,系统会扫描连接了哪些设备,并为每一个设备分配个 struct device数据结构,同样将其挂接在总线的devices kset链表中。
当驱动开发者申请了一条总线,用bus type来表示,这时总线并不知道连在总线上的设备有哪些,驱动程序有哪些。总线与设备和驱动的连接,需要相应总线的核心代码来实现。对USB总线,实现总线与驱动和设备的连接,是通过USB核心(USB core)来完成的。USB core会完成总线的初始化工作,然后再扫描USB总线,看USB总线上连接了哪些设备。
当USB core发现设备时,会为其分配一个struct device结构体,并将其连到总线上。当发现所有设备后, USB总线上的设备链表就建立好了。
相比设备的连接,将驱动连接到总线上就容易多了。每当驱动注册时,会将自己在总线上注册,并连接到总线的驱动链表中。这时,驱动会遍历总线的设备链表,寻找自己适的设备,并将其通过内部指针连接起来。
3 USB设备驱动结构体 usb_driver @@@@@
在USB设备驱动模型中, USB设备驱动使用usb driver结构体来表示。该结构体中包含了与具体设备相关的核心函数,对于不同的USB设备,驱动开发人员需要实现不同功能的函数, USB核心通过在框架中调用这些自定义的函数完成相应的功能。下面对usb driver结构体进行简要的介绍。
挂接在usb总线上的驱动程序,使用usb_driver结构体来表示。这个结构在系统驱动注册时,将加载到USB设备驱动子系统中。usb_driver结构的具体定义代码如下:
struct usb_driver {
const char *name;
int (*probe) (struct usb_interface *intf,//探测函数
const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);//断开函数
int (*ioctl) (struct usb_interface *intf, unsigned int code,
void *buf);//I/O控制函数
int (*suspend) (struct usb_interface *intf, pm_message_t message);//挂起
int (*resume) (struct usb_interface *intf);//恢复
int (*reset_resume)(struct usb_interface *intf);//重置
int (*pre_reset)(struct usb_interface *intf);//完成恢复前的一些工作
int (*post_reset)(struct usb_interface *intf);//恢复后的一些工作
const struct usb_device_id *id_table;//USB驱动所支持的设备列表
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
unsigned int no_dynamic_id:1;//是否允许动态加载驱动
unsigned int supports_autosuspend:1;//是否支持自动挂起驱动
unsigned int soft_unbind:1;
};
13~17:驱动开发用不着关心
前面已经说过,一个设备只能绑定到一个驱动程序,但是一个驱动程序却可以支持很多设备。例如,用户插入两块不同厂商的U盘,但是他们都符合USB 2.0协议,那么只需要一个支持USB 2.0协议的驱动程序即可。也就是说,不论插入多少个同类型U盘,系统都只使用一个驱动程序。这样就有效地减少了模块的引用,节省了系统的内存开销。
既然一个驱动可以支持多个设备,那么怎样知道驱动支持哪些设备呢?通过usb driver结构中的id table成员就可以完成这个功能。id table成员描述了一个USB设备所支持的所有USB设备列表,它指向一个usb_deviceid数组。usb-device id结构体包含了USB设备的制造商ID、产品ID、产品版本、结构类等信息。
usb-deviceid结构体就像一张实名制火车票,票上有姓名、车次、车厢号、座位。旅客上车时,乘务员将检查这些信息,只有当这些信息都相同时,乘务员才允许旅客上车。USB设备驱动也一样,在USB设备中有一个固件程序,固件程序中包含了这些信息。当 USB设备中的信息和总线上驱动的id table信息中的一项相同时,就将USB设备与驱动绑定。由于一个驱动可以适用与多个设备,所以id table表项中可能有很多项。usb device id结构体定义如下:
struct usb_device_id {
/* which fields to match against? */
__u16 match_flags;//匹配标志,定义下面哪些项应该被匹配
/* Used for product specific matches; range is inclusive */
__u16 idVendor;//制造商ID
__u16 idProduct;//产品ID
__u16 bcdDevice_lo;
__u16 bcdDevice_hi;
/* Used for device class matches */
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
/* Used for interface class matches */
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
/* not matched against */
kernel_ulong_t driver_info;
};
第02行的match-flags字段表示设备的固件信息与usb deviceid的哪些字段相匹配,才能认为驱动适合于该设备。这个标志可以取下列标志的组合。
/* Some useful macros to use to create struct usb_device_id */
#define USB_DEVICE_ID_MATCH_VENDOR 0x0001
#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002
#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004
#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008
#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010
#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020
#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040
#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080
#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100
#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200
例如一个驱动只需要比较厂商ID和产品ID,那么如下
.mach_flages = (USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
USB注册驱动函数usb_register()
static inline int usb_register(struct usb_driver *driver)
{
return usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME);
}
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
int retval = 0;
if (usb_disabled())//禁用USB设备
return -ENODEV;
new_driver->drvwrap.for_devices = 0;
new_driver->drvwrap.driver.name = (char *) new_driver->name;
new_driver->drvwrap.driver.bus = &usb_bus_type;
new_driver->drvwrap.driver.probe = usb_probe_interface;
new_driver->drvwrap.driver.remove = usb_unbind_interface;
new_driver->drvwrap.driver.owner = owner;
new_driver->drvwrap.driver.mod_name = mod_name;
spin_lock_init(&new_driver->dynids.lock);
INIT_LIST_HEAD(&new_driver->dynids.list);
retval = driver_register(&new_driver->drvwrap.driver);
if (retval)
goto out;
usbfs_update_special();
retval = usb_create_newid_file(new_driver);
if (retval)
goto out_newid;
retval = usb_create_removeid_file(new_driver);
if (retval)
goto out_removeid;
pr_info("%s: registered new interface driver %s\n",
usbcore_name, new_driver->name);
}
3 USB设备驱动程序 ########
文件为:\drivers\usb\storage\usb.c
3.1 USB设备驱动加载和卸载函数 @@@@@
USB设备驱动程序对应一个usb_driver结构体,这个结构体相当于Linux设备驱动模型中的driver结构体。下面对usb_driver结构进行详细的介绍。
1, usb_driver结构
作为USB设备驱动的实现,首先需要定义一个usb_driver结构变量作为要注册到USB核心的设备驱动。这里定义了一个变量usb storage_driver进行注册,变量usb_storage_driver是由USB设备模块的加载模块中的usb register)函数加入系统的,这个函数已经详细讲述。usb_storage_driver变量的定义如下:
static struct usb_driver usb_storage_driver = {
.name = "usb-storage",
.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,
.soft_unbind = 1,
};
安装和卸载驱动
static int __init usb_stor_init(void)
{
int retval;
pr_info("Initializing USB Mass Storage driver...\n");
/* register the driver, return usb_register return code if error */
retval = usb_register(&usb_storage_driver);//注册USB驱动
if (retval == 0) {
pr_info("USB Mass Storage support registered.\n");
usb_usual_set_present(USB_US_TYPE_STOR);//设置模块的状态
}
return retval;
}
static void __exit usb_stor_exit(void)
{
US_DEBUGP("usb_stor_exit() called\n");
/* Deregister the driver
* This will cause disconnect() to be called for each
* attached unit
*/
US_DEBUGP("-- calling usb_deregister()\n");
usb_deregister(&usb_storage_driver) ;//卸载驱动程序
usb_usual_clear_present(USB_US_TYPE_STOR);
}
3.2 探测函数probe()的参数usb_interface
前面已经讲了USB设备驱动加载函数usb stor init。从代码中可以看出,该函数的执行流已经结束,此时,我们几乎不知道程序会从哪里开始执行。事实上,当usb_stor_init()函数执行完后,就没有代码会执行了,除非有事件触发使USB驱动开始工作。
触发事件是什么呢?当USB设备插入USB插槽时,会引起一个电信号的变化,主机控制器捕获这个电信号,并命令USB核心处理对设备的加载工作。USB核心读取USB设备固件中关于设备的信息,并与挂接在USB总线上的驱动程序相比较,如果找到合适的驱动程序usb_driver,就会调用驱动程序的probe)函数。本节讲解的代码中的probe)函数就 storage probe), probe)函数的原型是:
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
该函数的第一个参数usb_interface是USB驱动中最重要的一个结构体了。它代表着设备中的一种功能,与一个usb driver相对应。usb interface在USB驱动中只有一个,有USB核心负责维护,所以需要注意的是,以后提到的usb_interface指的都是同一个.usb_interface。要了解usb interface就需要先了解一些USB协议中的内容了。这里,先从 USB协议中的设备开始。
3.3 USB协议中的设备 @@@@@@
设备:一个设备就表示一个真实的物理设备,(如U盘,USB鼠标)
配置:多个功能的组合(如打印机的第一个配置:扫描功能,第二个配置:打印和复印功能)
接口:一个接口对应一个单一的功能,如(打印机的打印功能)
端点:端点是USB的最基本的通信方式,只能通过端点传输数据(一般一个接口有输入端点,输出端点,控制端点)
端点描述符:是端点的一些配置
综合上面的内容,设备、配置、接口、端点之间的关系是:
设备通常有一个或者多个配置。
配置通常有一个或者多个接口。
接口通常有一个或者多个端点。
4种描述符的关系
一个接口必须有一个控制端点的描述符
端点就是用来传输数据的
端点传输方式:
1 控制传输:
控制传输可以访问一个设备的不同部分。其主要用于向设备发送配置信息、获取设备 ,信息、发送命令到设备,或者获取设备的状态报告。控制传输是任何USB设备都应该支持的一种传输方式。
2 中断传输
以一个固定的时间周期传输一些数据,不能传输大量数据,因为这是周期传输
3 批量传输
无周期,能传输大量的数据,如U盘
4 等时传输
用于传输达量数据,但是可以接收数据的一些丢失或者错误,但是不能接受数据传输的延时,如视频传输,音频传输