一、引言

随着工作的时间增加,不知不觉就快要工作一年了。

从实习,跳槽,再到试用、转正,也做过一些项目,算是在Linux驱动开发这一行入了门。

一些简单的外设自己也有能力编写了,但一些复杂的驱动还是需要原厂支持。每到这个时候,就感觉自己特别的废物,就会想到,怪不得那些大厂喜欢深度学习的人才。想来,只有深度学习,了解驱动的架构及原理,才能不依赖他人。自己可以不造轮子,但要有造轮子的能力。

所以,我选了很常见的,但也同样复杂的Linux内核驱动——USB。立一个flag,接下来两年之内,我要对Linux的USB驱动进行深度学习,并输出相关技术博客。

二、USB驱动架构

1、主机侧和设备侧

俗话说,世间万物,皆逃不过一个套路。

没错,随着大家对Linux内核驱动架构学习的深入,也大概了解到了这个套路。

套路就是:只要是依赖总线的子系统,都有这样几个层次:host、core、device,拿USB子系统来说,就有USB主控制器、USB核心、USB子设备。

不过,USB与其他子系统有不同的地方在于,USB分为主机侧和设备侧,两者之间的架构也略有区别。

在介绍架构之间,先简单说一下什么是主机侧和设备侧。

简单理解,主机侧就相当于电脑,他可以读取任何usb设备,比如usb存储设备、usb音响、usb键盘、usb鼠标等等与usb相关的外设。

而作为设备侧,它没有主动读取别人的能力,只能被别人读取,这由USB协议所决定的——所有动作都只能由主机发起。

接下来来看一下结构图:

图 - Linux USB总体结构

下面两段话出自《Linux设备驱动开发详解(第2版)》:

“如图左侧所示,从主机侧的概念来看,在Linux驱动中,USB驱动处于最底层的是USB主机控制器硬件,在其之上运行的是USB主机控制器驱动,主机控制器驱动之上为USB核心层,再上层为USB设备驱动层(插入主机上的u盘、鼠标、usb转串口等设备驱动)。因此,在主机侧的层次结构中,要实现的usb驱动包括两类:usb主机控制器驱动和usb设备驱动,前者控制插入其中的usb设备,后者控制usb设备如何与主机通信。Linux内核usb核心负责usb驱动管理和协议处理的主要工作。主机控制器驱动和设备驱动之间的usb核心非常重要,其功能包括:通过定义一些数据结构、宏和功能函数,向上为设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口;通过全局变量维护整个系统的USB设备信息;完成热插拔、总线数据传输控制等。”

“如图右侧所示,Linux内核中USB设备侧驱动程序分为3个层次:UDC驱动程序,Gadget API和Gadget驱动程序。UDC驱动程序直接访问硬件,控制USB设备和主机间的底层通信,向上提供与硬件相关控制的回调函数。当前Gadget API是UDC驱动程序回调函数的简单包装。Gadget驱动程序具体控制USB设备功能的实现,使设备表现出“网络连接”、“打印机”或“USB Mass Storage”等特性,它使用Gadget API控制UDC实现上述功能。Gadget API把下层的UDC驱动程序和上层的Gadget驱动程序隔离开,使得在Linux系统中编写USB设备侧驱动程序时能够把功能的实现和底层通信分离。”

2、设备、配置、接口和端点

2.1、概念

在我的理解当中,USB设备以设备、配置、接口和端点来形成它的逻辑结构。

那么,到底什么是逻辑结构呢?看下图:

图 - USB 设备、配置、接口和端点

简单来说,一个usb设备在软件方面的表现形式就是一个设备描述符,而设备描述符又由图中的部分组成。

软件方面的表现形式即逻辑结构。

下面的文字出自《Linux设备驱动开发详解(第2版)》:

“每个USB设备都提供了不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需要选一个),配置由多个接口组成。”

“在USB协议中,接口由多个端点组成,代表一个基本的功能,是USB设备驱动程序控制的对象,一个功能复杂的USB设备可以具备多个接口。每个配置中可以有多个接口,而设备接口是端点的汇集。例如:USB扬声器可以包含一个音频接口和对旋钮和按钮的接口。一个配置中的所有接口可以同时有效,并可被不同的驱动程序连接。每个接口都有备用接口,以提供不同质量的服务参数。”

“端点是usb通信的最基本形式,每一个USB设备接口在主机看来就是端点的一个集合。主机只能通过端点与设备进行通信,以使用设备的功能。在USB系统中每个端点都有唯一的地址,这是由设备地址和端点号给出来的。每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。一个端点只能从一个方向承载数据,或者从主机侧到设备侧(称为输出端点),或者从设备侧到主机侧(称为输入端点),因此端点可看做一个单向的管道。端点0通常作为控制端点,用于设备初始化参数等。只要设备连接到USB上并且上电,端点0就可以被访问。端点1、2等一般用作数据端点,存放主机与设备之间往来的数据。”

总的来讲:

设备通常有一个或多个配置;

配置通常有一个或多个接口;

接口通常有一个或多个设置;

接口有零或多个端点。

USB设备使用各种描述符来说明其设备架构,包括设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符,他们通常被保存在USB设备的固件程序中。

2.2、设备描述符

《Linux设备驱动开发详解(第2版)》:“设备描述符:关于设备的通用信息,如供应商ID、产品ID和修正ID,支持的设备类、子类和适用的协议以及默认端点的最大包大小等。”

设备代表一个USB设备,它由一个或多个配置组成。设备描述符用于说明设备的总体信息,并指明其所含的配置的个数。一个USB设备只能有一个设备描述符。

struct usb_device_descriptor
{
	__u8 bLength; //描述符长度
	__u8 bDescriptorType; //描述符类型编号
 
	__le16 bcdUSB; //USB版本号
	__u8 bDeviceClass; //USB分配的设备类code
	__u8 bDeviceSubClass;// USB分配的子类code
	__u8 bDeviceProtocol; //USB分配的协议code
	__u8 bMaxPacketSize0; //endpoint0最大包大小
	__le16 idVendor; //厂商编号
	__le16 idProduct; //产品编号
	__le16 bcdDevice; //设备出厂编号
	__u8 iManufacturer; //描述厂商字符串的索引
	__u8 iProduct; //描述产品字符串的索引
	__u8 iSerialNumber; //描述设备序列号字符串的索引
	__u8 bNumConfigurations; //可能的配置数量
} __attribute__((packed));

2.3、配置描述符

一个USB设备可以包含一个或多个配置,如USB设备的低功耗模式和高功耗模式可分别对应一个配置。在使用USB设备前,必须为其选择一个合适的配置。配置描述符用于说明USB设备中各个配置的特性,如配置所含接口的个数等。USB设备的每一个配置都必须有一个配置描述符。

struct usb_config_descriptor
{
	__u8 bLength; //描述符长度
	__u8 bDescriptorType; //描述符类型编号
	
	__le16 wTotalLength; //配置所返回的所有数据的大小
	__u8 bNumInterfaces; // 配置所支持的接口数
	__u8 bConfigurationValue; //Set_Configuration命令需要的参数值
	__u8 iConfiguration; //描述该配置的字符串的索引值
	__u8 bmAttributes; //供电模式的选择
	__u8 bMaxPower; //设备从总线提取的最大电流
} __attribute__((packed));

2.4、接口描述符

一个配置可以包含一个或多个接口,例如对一个光驱来说,当用于文件传输时,使用其大容量存储接口;而当用于播放CD时,使用其音频接口。接口是端点的集合,可以包含一个或多个可替换设置,用户能够在USB处于配置状态时改变当前接口所含的个数和特性。接口描述符用于说明设备中各个接口的特性,如接口所属的设备类及其子类等。USB设备的每个接口都必须有一个接口描述符。

struct usb_interface_descriptor
{
	__u8 bLength;           //描述符长度
	__u8 bDescriptorType; //描述符类型
	
	__u8 bInterfaceNumber;   // 接口的编号
	__u8 bAlternateSetting; //备用的接口描述符编号
	__u8 bNumEndpoints;      //该接口使用的端点数,不包括端点0
	__u8 bInterfaceClass;    //接口类型
	__u8 bInterfaceSubClass; //接口子类型
	__u8 bInterfaceProtocol; //接口所遵循的协议
	__u8 iInterface; //描述该接口的字符串索引值
} __attribute__((packed));

2.5、端点描述符

端点是USB设备中的实际物理单元,USB数据传输就是在主机和USB设备各个端点之间进行的。端点一般由USB接口芯片提供,例如Freescale公司的MC68HC908JB8和MC9S12UF32。

struct usb_endpoint_descriptor
{
	__u8 bLength; //描述符长度
	__u8 bDescriptorType; //描述符类型
	__u8 bEndpointAddress; //端点地址:0~3位是端点号,第7位是方向(0-OUT,1-IN)
	__u8 bmAttributes; //端点属性:bit[0:1] 的值为00表示控制,为01表示同步,为02表示批量,为03表示中断
	__le16 wMaxPacketSize;  //本端点接收或发送的最大信息包的大小
	__u8 bInterval;//轮询数据传送端点的时间间隔
	                       //对于批量传送的端点以及控制传送的端点,此域忽略
	                    //对于同步传送的端点,此域必须为1
	__u8 bRefresh;
	__u8 bSynchAddress;
} __attribute__((packed));

2.6、字符串描述符

《Linux设备驱动开发详解(第2版)》:“在其他描述符中会为某些字段提供字符串索引,他们可以被用来检索描述性字符串,可以以多种语言形式提供。字符串描述符是可选的,有的设备有,有的设备无。”

struct usb_string_descriptor
{
	__u8 bLength; //描述符长度
	__u8 bDescriptorType; //描述符类型
	
	__le16 wData[1];
} __attribute__((packed));

3、usb传输模式

3.1、控制传输模式(Control)

控制传输模式支持双向传输,用来处理从usb主机端口到usb设备端口的数据传输,用于控制指令,设备状态查询以及确认命令。

3.2、等时传输方式(lsochronous)

等时传输是一种周期性的连续性的意向传输模式,通常用于对时间有着密切关系的信息的传输,对准确性要求不高,但对时间要求极为敏感的设备,如视频,音频的传输。

3.3、中断传输模式(Interrupt)

中断传输模式用于非周期性的,自然发生的,数据量小的传输,数据传输的方向是从设备到主机。如usb键盘和鼠标。

3.4、批量传输模式(bulk)

批量传输模式是一种单向的,用于大量数据传输的模式,该方式用来传输正确无误的数据。通常打印机,扫描仪,数码相机以这种方式与主机连接。

三、结语

Linux驱动开发之USB驱动深入学习(一)就大概记录到这里,本篇主要讲了一下USB在Linux中的软件架构。

下一篇具体写什么,目前还不清楚,有可能写主机控制器驱动,也有可能介绍USB协议。

Logo

更多推荐