首页 » 技术分享 » (一)USB驱动程序_USB基础知识

(一)USB驱动程序_USB基础知识

 

深入,并且广泛
				-沉默犀牛

USB设备驱动分类

USB驱动程序可以粗分为两类:
一、主机(Host)系统上的驱动程序 ,这个驱动程序控制插入其中的USB设备
二、设备(Device)上的驱动程序,这个驱动程序控制USB设备如何与主机通信

为了举一个形象的例子,我得先展示一张图片,更细致的介绍一下以上的两种分类:

Host Device
USB设备驱动(Mass storage/CDC/HID) Gadget Function 驱动(serial…)
USB核心 Gadet Funtion API
USB Host Controller 驱动(OHCI/EHCI/UHCI) UDC驱动(omap/pxa2xx…)
USB Host Controller UDC

在这个图中,主机侧与设备侧又分别分为了两个驱动:
Host:USB设备驱动、USB Host Controller 驱动
Device:Gadget Function 驱动、UDC驱动

USB设备驱动和USB Host Controller驱动之间的USB core负责了USB驱动管理和协议处理的主要工作。
1.它通过定义一些数据结构体、宏和功能函数,向上为设备驱动提供编程接口,向下为USB Host Controller驱 动提供编程接口;
2.维护整个系统的USB设备信息;
3.完成设备热拔插控制、总线数据传输控制等

Gadet Function API现在是UDC驱动程序回调函数的包装

举一个生活中的例子:当用Android系统的手机 通过USB线 连接Linux系统的PC

Host(PC)的动作:

1.如果你插入了USB2.0的设备,则Host的USB Host Controller 驱动要使能EHCI,如果是USB1.1的设备,就要使能OHCI
/UHCI,不过一般做法是全都使能。在这里我们手机插入后,Host Controller 驱动会启用EHCI

2.这个USB设备驱动,会根据插入的设备不同,而选择不同的驱动。默认已经有的驱动有存储设备、键盘、鼠标、游戏
杆、网络设备和调制解调器。如果是其他的设备,则需要自己写驱动了。在我们的例子里,手机通过USB连接到PC上
后,可以选择几个不同的模式,当选择仅限充电时,Host的USB设备驱动就走充电的驱动,当选择传输文件(MTP)
时,Host的USB设备驱动就走传输文件的驱动,当选择当做摄像头时(现在大部分手机不会设置这个功能了),Host
的USB设备驱动就走摄像头的驱动。

Device(手机)的动作:

1.当我的手机通过USB连接电脑以后,手机的底层USB控制器行使UDC功能,这时运行在底层的是UDC驱动.

2.然后我再选择手机的模式,当选择仅充电时,Device的Gadget Function驱动就走充电的驱动,当选择文件传输时,
Device的Gadget Function驱动就走文件传输的驱动(Mass storage)

现在我们已经理清了这四种驱动都是在哪用的(Host/Device)、都是何时用的、都是干什么的。

USB设备基础

USB设备是非常复杂的,它由许多不同的逻辑单元组成,这些逻辑单元之间的关系可以简单地描述如下:

  1. 设备通常具有一个或者多个配置
  2. 配置经常具有一个或者多个接口
  3. 接口通常具有一个或者多个设置
  4. 接口没有或者具有一个以上端点

接下来就以上提到的概念详细分析:

端点:

USB通信的最基本形式是通过一个名为端点(endpoint)的东西,USB端点只能往一个方向传送数据,从Host到Device称为OUT端点,从Device到Host称为IN端点,端点可以看做是单项的管道它有四种不同的类型,分别具有不同的传送数据的方式:

控制:

控制端点用来控制对USB设备的不同部分的访问。它们通常用于配置设备、获取设备信息、发送命令到设备,或者获取设备的状态报告。这些端点一般体积较小,每个USB设备都有一个名为“端点0”的控制端点,USB核心使用该端点在插入时进行设备的配置。USB协议保证这些传输始终有足够的传输带宽以传送数据到设备,这里关于端点0的功能举一个例子:在手机插入PC的例子中,我选择charge模式后,就是通过端点0来告诉PC的,切换成MTP模式后,也是通过端点0来告诉PC的,PC知道后就会进行相应的配置,这个配置就是调用上述相应的driver

中断

每当USB Host要求设备传输数据时,中断端点就以一个固定的速率来传送少量的数据。这些端点是USB键盘和鼠标所使用的主要传输方式。它们通常还用于发送数据到USB设备以控制设备,不过一般不用来传输大量的数据。USB协议保证这些传输始终有足够的保留带宽以传送数据

批量

批量(bulk)端点传送大批量的数据。这些端点通常比中断端点大得多(它们可以一次持有更多的字符)。它们常见于需要确保没有数据丢失的传输的设备。USB协议不保证这些传输始终可以在特定的时间内完成。如果总线上的空间不足以发送整个批量包,它将被分割为多个包进行传输。这些端点通常出现在打印机、存储设备和网络设备上

等时

等时(isochronous)端点同样可以传送大批量的数据,但数据是否达到是没有保证的。这些端点用于可以应付数据丢失情况的设备,这类设备更注重于保持一个恒定的数据流。实时的数据手记(例如音频和视频设备)几乎毫无例外都使用这类端点。

我们知道“端点”是逻辑上的意义,在代码中是如何实现的呢?或者说,用什么东西来代表端点呢?我之后还会再多啰嗦一下其他的类似“逻辑上的意义”这种问题。现在先来看描述端点的东西吧!其实就是结构体啦,我会结合《LDD3》和《Linux设备驱动开发详解》中的描述进行注释

/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
	__u8  bLength;			//描述符长度
	__u8  bDescriptorType;		//描述符类型
	
	__u8  bEndpointAddress;  	//端点地址:0-3位是端点号,第7位是方向(0为out,1为in)
	__u8  bmAttributes;		//端点属性(类型):bit[0:1] 00表示控制 01表示等时 10表示批量 11表示中断
	__le16 wMaxPacketSize;		//该端点一次可以处理的最大字节数。
					  注意:driver可以发送数量大于此值的数据到端点,
					  但是在实际传输到设备的时候,数据将被分割为wMaxPacketSize大小的块。
	__u8  bInterval;		//轮询数据传送端点的时间间隔
					  对于批量和控制的端点,忽略此域
					  对于等时的端点,必须为1
					  对于中断的端点,必须为1-255

	/* NOTE:  these two are _only_ in audio endpoints. */
	/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
	__u8  bRefresh;
	__u8  bSynchAddress;
} __attribute__ ((packed));

接口:

USB端点被捆绑为接口。USB接口只处理一种USB逻辑连接,例如鼠标、键盘或者音频流。一些USB设备具有多个接口,例如USB扬声器可以包括两个接口:一个USB键盘用于按键和一个USB音频流。因为一个USB接口代表了一个基本功能,而每个USB驱动程序控制一个接口,因此,以扬声器为例,Linux需要两个不同的驱动程序来处理一个硬件

接下来看看接口的结构体:

/* USB_DT_INTERFACE: Interface descriptor */
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));

配置

USB接口本身被捆绑为配置。一个USB设备可以有多个配置,而且可以在配置之间切换以改变设备的状态。比如手机可以选择charge only 、MTP、PTP、Camera这几个配置。用usb_config_descriptor结构体来描述配置。USB设备驱动程序通常不需要改变这个结构体中的任何值,因此在这里不详述了。

这里顺便提一嘴怎么看你设备中的usb设备的这些结构体信息:
1.lsusb 查看每一个设备的 bus号 和 device号
在这里插入图片描述
2.lsusb -s -v [Bus:Device] 就可以显示出你填入的Bus和Device指定的设备的信息
在这里插入图片描述

以上是先要准备的有关USB的基础知识,下两篇文章我们分析一下驱动的源码,这四个驱动中有两个是我们不会去改动的:Host Controller driver 和 UDC 因为这都是默认的,只需要config一下就可以了。所以我们的目标放在了另外的两个驱动上: USB设备驱动 和 Function 驱动

这里多说一下上文提到的“逻辑上的意义”,在我学习Linux驱动这三个月以来,我感觉到Device、Driver这样的东西对于Kernel来说,都是逻辑上的,只要我把Device的信息写到DTS中,那Linux的Kernel启动时,就会展开DTS,并且把这些node最终解析为device结构体,然后注册(这时是注册platform_device,其他总线上的Device是(在对应总线注册时)注册的 ),你看,kernel并不知道这些我们硬件上到底有没有连接这些Device,我想表达的就是,在Kernel看来,这些最终解析成的device结构体,就是有意义的。但是这些node在我们看来,只有逻辑上的意义。有了它们,Kernel才能认识Device。同理的去理解本文中的结构体,和Kernel中的其他结构体,它们都是只有逻辑上的意义

那么既然Kernel并不知道Device到底存不存在,那也不能让Kernel去控制一个不存在的Device吧,这个问题怎么解决的呢?这就是Driver的用处之一了。Driver也都是体现成了结构体。比如usb_driver、i2c_driver等等,所以Driver也是在我们的眼中看来,它们也是逻辑上的并不像是我们用来控制游戏人物的键盘、鼠标那么具体。Driver写好后,Kernel就认为这是一个Device的“控制器”了,但是它不会对应现实世界的任何实物。那么Driver是怎么解决“到底有没有真实的Device连接在硬件上?”这个问题的呢?Driver通过向真实器件读取信息来判断到底有没有真实的Device,如果没有真实的Device,那Driver就读不出来正确的数据,那我Kernel就认为没有这个真实器件,也正是这个原因,我们调试某器件的时候发现driver没有加载上,就是因为Device的配置有问题,导致Kernel误认为没有这个真实器件

为了更好的解释这个问题,我用我所接触过的两个例子,来再次阐述:
1.TouchPanel: 大部分TouchPanel是挂载在i2c上的,所以TP的Driver->probe函数中,会通过i2c总线向TP硬件的IC读取一些信息,这就起到了验证硬件在不在的作用,如果读出的数据不对,直接return
2.LCD:LCD会面对一个问题,就是一套代码,要适应多个屏幕,(因为咱们手机或者其他嵌入式设备的配置不同,高配的用好一点的,低配的用差一点的)那一套代码怎么适应不同的屏幕呢?就是从LCD的硬件IC读取一些信息,比如硬件的编号,GPIO的高低等等,然后用这个编号来找对应的配置,读取编号的时候,不就可以验证硬件在不在了吗。

这篇博文参考了《Linux设备驱动开发详解》《Linux Device Driver 3》和《Linux那些事儿-我是USB》

转载自原文链接, 如需删除请联系管理员。

原文链接:(一)USB驱动程序_USB基础知识,转载请注明来源!

0