USB原理及驱动框架介绍编写

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了USB原理及驱动框架介绍编写相关的知识,希望对你有一定的参考价值。


  • 本文环境:
  • JZ2440V3开发板
  • Linux3.4.2内核
  • arm-linux-gcc4.3.2编译器
  • 参考资料:

1、几个常见疑惑?

  • 为什么一插上就会有提示信息?
    是因为windows自带了USB总线驱动程序;
  • 那USB总线驱动程序是干嘛用的?
  • 识别USB设备;
  • 给USB设备找到并安装对应的驱动程序;
  • 提供USB的读写函数。

首先,新接入的USB设备的默认地址(编号)为0,再未分配新编号前,PC主机使用0地址和它通信。然后USB总线驱动程序都会给它分配一个地址(编号)。PC机想访问USB总线上某个USB设备时,发出的命令都含有对应的地址(编号)。

  • USB的结构是怎样的?
  • USB是一种主从结构。主机叫做Host,从机叫做Device,所有的USB传输,都是从USB主机这方发起;USB设备没有“主动”通知USB主机的能力。
  • 例如:USB鼠标滑动一下立刻产生数据,但是它还没有能力通过OC机来读数据,只能被动地等待PC机来读。
  • USB可以热拔插的硬件原理
  • 在USB集线器(hub)的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。
  • 而在USB设备端,在D+或者D-上接了1.5k欧姆上拉电阻,对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。
  • 这样,当设备插入到集线器时,由1.5k的上拉电阻和15k的下拉电阻分呀,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。
  • USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到告诉模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。
  • USB的4大传输类型
  • 控制传输
  • 是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。
  • 中断传输
  • 支持中断传输的典型设备有USB鼠标、USB键盘等等。中断传输不是说我的设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。interval是间隔时间的意思,表示我这个设备希望主机多长时间来轮询自己,只要这个值确定了之后,我主机就会周期性来查看有没有数据需要处理。
  • 批量处理
  • 支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。
  • 实时传输
  • USB摄像头就是实时传输设备的典型代表,它同样进行大量数据的传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高
  • USB端点
  • 每个USB设备与主机会有若干个通信的“端点”。每个端点都有个端点号,除了端点0外,每一个端点只能工作在一种传输类型(控制传输、中断传输、批量传输、实时传输)下,一个传输方向。
  • 传输方向都是基于USB主机的立场说的,比如:鼠标的数据是从鼠标传到PC机,对应的端点称为“中断输入端点”。
  • 端点0是设备的默认控制端点,既能输出也能输入,主要用于USB设备的识别过程。
  • USB驱动整体框架
  • USB原理及驱动框架介绍、编写_描述符

  • USB主机控制器类型
    要想成为一个USB主机,硬件上就必须要有USB主机控制器,USB主机控制器又分为4种接口:
  • OHCI(Open Host Controller Inerface):微软主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的硬件简单,软件复杂。
  • UHCI(Universal Host Controller Interface):Intel主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的软件简单,硬件复杂。
  • EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)
  • xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9针脚设针,同时也支持USB2.0、1.1等。

2、USB总线驱动分析

2.1 USB描述符的层次及定义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvFI8va0-1627907220913)(C:\\Users\\liang\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210731131536533.png)]

  • USB设备描述符(usb_device_descriptor)
  • USB配置描述符(usb_config_descriptor)
  • USB接口描述符(usb_interface_descriptor)
  • USB端点描述符(usb_endpoint_descriptor)、

一个设备描述符可以有多个配置描述符;
一个配置描述符可以有多个接口描述符(比如声卡驱动就有两个接口:录音接口和播放接口)
一个接口描述符可以有多个端点描述符;

2.1.1 USB设备描述符(usb_device_descriptor)

USB设备描述符结构体如下所示:

struct usb_device_descriptor 
__u8 bLength; //本描述符的size
__u8 bDescriptorType; //描述符的类型,这里是设备描述符DEVICE
__u16 bcdUSB; //指明usb的版本,比如usb2.0
__u8 bDeviceClass; //类
__u8 bDeviceSubClass; //子类
__u8 bDeviceProtocol; //指定协议
__u8 bMaxPacketSize0; //端点0对应的最大包大小
__u16 idVendor; //厂家ID
__u16 idProduct; //产品ID
__u16 bcdDevice; //设备的发布号
__u8 iManufacturer; //字符串描述符中厂家ID的索引
__u8 iProduct; //字符串描述符中产品ID的索引
__u8 iSerialNumber; //字符串描述符中设备序列号的索引
__u8 bNumConfigurations; //配置描述符的个数,表示有多少个配置描述符
__attribute__ ((packed));
  USB设备描述符位于USB设备结构体usb_device中的成员descriptor中。同样地,配置、接口、端点描述符也是位于USB配置、接口、端点结构体中,不过这3个对于我们写驱动的不是很常用。
  • usb_device结构体如下所示:
struct usb_device 
   int devnum;            //设备号,是在USB总线的地址
   char devpath [16];        //用于消息的设备ID字符串
   enum usb_device_state state; //设备状态:已配置、未连接等等
   enum usb_device_speed speed; //设备速度:高速、全速、低速或错误
  
   struct usb_tt *tt;        //处理传输者信息;用于低速、全速设备和高速HUB
   int ttport;            //位于tt HUB的设备口
  
   unsigned int toggle[2];    //每个端点占一位,表明端点的方向([0] = IN, [1] = OUT)  
   struct usb_device *parent;   //上一级HUB指针
   struct usb_bus *bus;        //总线指针
   struct usb_host_endpoint ep0; //端点0数据
   struct device dev;          //一般的设备接口数据结构
 
   struct usb_device_descriptor descriptor; //USB设备描述符,
   struct usb_host_config *config;       //设备的所有配置结构体,配置结构体里包含了配置描述符
   struct usb_host_config *actconfig;     //被激活的设备配置
   struct usb_host_endpoint *ep_in[16];     //输入端点数组
   struct usb_host_endpoint *ep_out[16];     //输出端点数组
  
   char **rawdescriptors;             //每个配置的raw描述符
  
   unsigned short bus_mA;         //可使用的总线电流

   u8 portnum;               //父端口号
   u8 level;                //USB HUB的层数
  
   unsigned can_submit:1;         //URB可被提交标志
   unsigned discon_suspended:1;      //暂停时断开标志
   unsigned persist_enabled:1;       //USB_PERSIST使能标志
   unsigned have_langid:1;         //string_langid存在标志
   unsigned authorized:1;
   unsigned authenticated:1;
   unsigned wusb:1;             //无线USB标志
   int string_langid;             //字符串语言ID
  
   /* static strings from the device */ //设备的静态字符串
   char *product;               //产品名
   char *manufacturer;            //厂商名
   char *serial;                //产品串号
  
   struct list_head filelist;         //此设备打开的usbfs文件
#ifdef CONFIG_USB_DEVICE_CLASS
   struct device *usb_classdev;    //用户空间访问的为usbfs设备创建的USB类设备
#endif

#ifdef CONFIG_USB_DEVICEFS
   struct dentry *usbfs_dentry;        //设备的usbfs入口
#endif
  
   int maxchild;                      //(若为HUB)接口数
   struct usb_device *children[USB_MAXCHILDREN]; //连接在这个HUB上的子设备
   int pm_usage_cnt;                 //自动挂起的使用计数
   u32 quirks;
   atomic_t urbnum;                   //这个设备所提交的URB计数
  
   unsigned long active_duration;         //激活后使用计时

#ifdef CONFIG_PM                 //电源管理相关
   struct delayed_work autosuspend;       //自动挂起的延时
   struct work_struct autoresume;       //(中断的)自动唤醒需求
   struct mutex pm_mutex;           //PM的互斥锁 
 
   unsigned long last_busy;         //最后使用的时间
   int autosuspend_delay;
   unsigned long connect_time;       //第一次连接的时间
  
   unsigned auto_pm:1;           //自动挂起/唤醒
   unsigned do_remote_wakeup:1;     //远程唤醒
   unsigned reset_resume:1;       //使用复位替代唤醒
   unsigned autosuspend_disabled:1;   //挂起关闭
   unsigned autoresume_disabled:1;   //唤醒关闭
   unsigned skip_sys_resume:1;     //跳过下个系统唤醒
#endif
   struct wusb_dev *wusb_dev;     //(如果为无线USB)连接到WUSB特定的数据结构
;

2.2.2 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.2.3 接口描述符(逻辑设备)

USB接口只处理一种USB逻辑连接。一个USB接口代表一个逻辑上的设备,比如声卡驱动就有两个接口:录音接口和播放接口。这可以在windows系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。

struct usb_interface_descriptor   
__u8 bLength; //描述符的长度
__u8 bDescriptorType; //描述符类型的编号

__u8 bInterfaceNumber; //接口的编号
__u8 bAlternateSetting; //备用的接口描述符编号,提供不同质量的服务参数.
__u8 bNumEndpoints; //要使用的端点个数(不包括端点0), 表示有多少个端点描述符,比如鼠标就只有一个端点
__u8 bInterfaceClass; //接口类型,与驱动的id_table对应
__u8 bInterfaceSubClass; //接口子类型
__u8 bInterfaceProtocol; //接口所遵循的协议
__u8 iInterface; //描述该接口的字符串索引值
__attribute__ ((packed)

它位于usb_interface->cur_altsetting->desc 这个成员结构体里,

  • usb_interface结构体如下所示:
struct usb_interface  
/* 包含所有可用于该接口的可选设置的接口结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置。这些接口结构没有特别的顺序。*/
struct usb_host_interface *altsetting;

/* 指向altsetting内部的指针,表示当前激活的接口配置*/
struct usb_host_interface *cur_altsetting;

unsigned num_altsetting; /* 可选设置的数量*/

/* If there is an interface association descriptor then it will list the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;

/* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。*/
int minor;
... ...
  • cur_altsetting成员的结构体是usb_host_interface,如下:
struct usb_host_interface 
struct usb_interface_descriptor desc; //当前被激活的接口描述符
struct usb_host_endpoint *endpoint;   /* 这个接口的所有端点结构体的联合数组*/
char *string;                /* 接口描述字符串 */
unsigned char *extra;           /* 额外的描述符 */
int extralen;
;

2.2.4 端点描述符

struct usb_endpoint_descriptor 
__u8 bLength; //描述符的长度

__u8 bDescriptorType; //描述符类型的编号

__u8 bEndpointAddress; //端点编号,比如端点1,就是1

__u8 bmAttributes; //端点的属性, 比如中断传输类型,输入类型
__le16 wMaxPacketSize; //一个端点的最大包大小,

__u8 bInterval; //间隔时间,用在中断传输上,比如间隔时间查询鼠标的数据

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

__attribute__ ((packed));

比如端点0就位于usb_interface->cur_altsetting->desc->endpoint[0].desc

  • endpoint的结构体为usb_host_endpoint,如下所示:
struct usb_host_endpoint 
struct usb_endpoint_descriptor desc; //端点描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp; //超快速端点描述符
struct list_head urb_list; //本端口对应的urb链表
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */

unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled; //使能的话urb才能被提交到此端口
;

2.2 USB总线驱动如何识别设备

由于内核自带了USB驱动,所以我们先插入一个USB键盘到开发板上看打印信息发现以下字段:

USB原理及驱动框架介绍、编写_USB设备驱动_02

如下图,找到第一段话是位于drivers/usb/core/hub.c的第2186行:

USB原理及驱动框架介绍、编写_usb_03

这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口

2.2.1 drivers/usb/core/hub.c的第2186行位于hub_port_init()函数里

它又是被谁调用的,如下图所示,我们搜索到它是通过hub_thread()函数调用的

USB原理及驱动框架介绍、编写_usb_04

hub_thread()函数如下:

static int hub_thread(void *__unused)


do
hub_events(); //执行一次hub事件函数
wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());                            //(1).每次执行一次hub事件,都会进入一次等待事件中断函数
try_to_freeze();
while (!kthread_should_stop() || !list_empty(&hub_event_list));

pr_debug("%s: khubd exiting\\n", usbcore_name);
return 0;

从上面函数中得到, 要想执行hub_events(),都要等待khubd_wait这个中断唤醒才行。

2.2.2 搜索”khubd_wait”,看看是被谁唤醒

找到该中断在kick_khubd()函数中唤醒,代码如下:

static void kick_khubd(struct usb_hub *hub)

unsigned long flags;
to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;

spin_lock_irqsave(&hub_event_lock, flags);
if (list_empty(&hub->event_list))
list_add_tail(&hub->event_list, &hub_event_list);
wake_up(&khubd_wait); //唤醒khubd_wait这个中断


spin_unlock_irqrestore(&hub_event_lock, flags);

2.2.3 继续搜索kick_khubd,发现被hub_irq()函数中调用

显然,就是当USB设备插入后,D+或D-就会被拉高,然后USB主机控制器就会产生一个hub_irq中断.

2.2.4 分析hub_port_connect_change()函数如何连接端口

static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)

  ... ...
  udev = usb_alloc_dev(hdev, hdev->bus, port1);   //(1)注册一个usb_device,然后会放在usb总线上

  usb_set_device_state(udev, USB_STATE_POWERED); //设置注册的USB设备的状态标志
  ... ...

  choose_address(udev);  //(2)给新的设备分配一个地址编号
   status = hub_port_init(hub, udev, port1, i);     //(3)初始化端口,与USB设备建立连接
  ... ...

  status = usb_new_device(udev);        //(4)创建USB设备,与USB设备驱动连接
  ... ...

所以最终流程图如下:

USB原理及驱动框架介绍、编写_USB设备驱动_05

2.2.5 进入hub_port_connect_change()->usb_alloc_dev(),看它是怎么设置usb_device

usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)

struct usb_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配一个usb_device设备结构体

... ...
device_initialize(&dev->dev); //初始化usb_device
dev->dev.bus = &usb_bus_type; //(1)设置usb_device的成员device->bus等于usb_bus总线

dev->dev.type = &usb_device_type; //设置usb_device的成员device->type等于usb_device_type

... ...

return dev; //返回一个usb_device结构体
  • 在第17行上,设置device成员,主要是用来后面8.2小节,注册usb总线的device表上.
  • 其中usb_bus_type是一个全局变量, 它和我们之前学的platform平台总线相似,属于USB总线, 是Linux中bus的一种.
  • 如下图所示,每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB设备驱动联系起来.

USB原理及驱动框架介绍、编写_USB设备驱动_06

usb_bus_type结构体如下:

struct bus_type usb_bus_type = 
.name = "usb", //总线名称,存在/sys/bus下
.match = usb_device_match, //匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
.uevent = usb_uevent, //事件函数
.suspend = usb_suspend,  //休眠函数
.resume = usb_resume,  //唤醒函数
;

2.2.6 进入hub_port_connect_change()->choose_address(),看它是怎么分配地址编号的

static void choose_address(struct usb_device *udev)

int devnum;
struct usb_bus *bus = udev->bus;
devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
//在bus->devnum_next~128区间中,循环查找下一个非0(没有设备)的编号

if (devnum >= 128) //若编号大于等于128,说明没有找到空余的地址编号,从头开始找
devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); //设置下次寻址的区间+1
if (devnum < 128)
set_bit(devnum, bus->devmap.devicemap);     //设置位
udev->devnum = devnum;              

从上面代码中分析到每次的地址编号是连续加的,USB接口最大能接127个设备,我们连续插拔两次USB键盘,也可以看出,如下图所示:

USB原理及驱动框架介绍、编写_描述符_07

2.2.7 再看hub_port_connect_change()->hub_port_init()函数是如何来实现连接USB设备的

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)

... ...
for (j = 0; j < SET_ADDRESS_TRIES; ++j)
retval = hub_set_address(udev); //(1)设置地址,告诉USB设备新的地址编号
if (retval >= 0)
break;
msleep(200);

retval = usb_get_device_descriptor(udev, 8); //(2)获得USB设备描述符前8个字节
... ...
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //重新获取设备描述符信息
... ...
  • 上面第6行中,hub_set_address()函数主要是用来告诉USB设备新的地址编号,hub_set_address()函数如下:
static int hub_set_address(struct usb_device *udev)

int retval;
... ...
retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT);                                              //(1.1)等待传输完成
if (retval == 0) //设置新的地址,传输完成,返回0
usb_set_device_state(udev, USB_STATE_ADDRESS); //设置状态标志
ep0_reinit(udev);

return retval;
  • usb_control_msg()函数就是用来让USB主机控制器把一个控制报文发给USB设备,如果传输完成就返回0.其中参数udev表示目标设备;使用的管道为usb_sndaddr0pipe(),也就是默认的地址0加上控制端点号0; USB_REQ_SET_ADDRESS表示命令码,既设置地址; udev->devnum表示要设置目标设备的设备号;允许等待传输完成的时间为5秒,因为USB_CTRL_SET_TIMEOUT定义为5000。
  • 上面第12行中,usb_get_device_descriptor()函数主要是获取目标设备描述符前8个字节,为什么先只开始读取8个字节?是因为开始时还不知道对方所支持的信包容量,这8个字节是每个设备都有的,后面再根据设备的数据,通过usb_get_device_descriptor()重读一次目标设备的设备描述结构.

其中USB设备描述符结构体如下所示:

struct usb_device_descriptor 
__u8 bLength; //本描述符的size
__u8 bDescriptorType;   //描述符的类型,这里是设备描述符DEVICE
__u16 bcdUSB; //指明usb的版本,比如usb2.0
__u8 bDeviceClass;   //类
__u8 bDeviceSubClass;    //子类
__u8 bDeviceProtocol; //指定协议
__u8 bMaxPacketSize0;    //端点0对应的最大包大小
__u16 idVendor; //厂家ID
__u16 idProduct; //产品ID
__u16 bcdDevice; //设备的发布号
__u8 iManufacturer; //字符串描述符中厂家ID的索引
__u8 iProduct; //字符串描述符中产品ID的索引
__u8 iSerialNumber;   //字符串描述符中设备序列号的索引
__u8 bNumConfigurations; //可能的配置的数目
__attribute__ ((packed));

2.2.8 再看hub_port_connect_change()->usb_new_device()函数是如何来创建USB设备的

int usb_new_device(struct usb_device *udev)

... ...
err = usb_get_configuration(udev); //(1)获取配置描述块
  ... ...
  err = device_add(&udev->dev); // (2)把device放入bus的dev链表中,并寻找对应的设备驱动
  1. 其中usb_get_configuration()函数如下,就是获取各个配置
int   usb_get_configuration(struct usb_device *dev)

... ...
/* USB_MAXCONFIG 定义为8,表示设备描述块下有最多不能超过8个配置描述块 */
/*ncfg表示 设备描述块下 有多少个配置描述块 */
if (ncfg > USB_MAXCONFIG)
dev_warn(ddev, "too many configurations: %d, "
"using maximum allowed: %d\\n", ncfg, USB_MAXCONFIG);
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;

... ...

for (cfgno = 0; cfgno < ncfg; cfgno++) //for循环,从USB设备里依次读入所有配置描述块
//每次先读取USB_DT_CONFIG_SIZE个字节,也就是9个字节,暂放到buffer中
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE);

... ...

//通过wTotalLength,知道实际数据大小
length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE);

bigbuffer = kmalloc(length, GFP_KERNEL); //然后再来分配足够大的空间
... ...

//在调用一次usb_get_descriptor,把整个配置描述块读出来,放到bigbuffer中
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length);

... ...

//再将bigbuffer地址放在rawdescriptors所指的指针数组中
dev->rawdescriptors[cfgno] = bigbuffer;

result = us

以上是关于USB原理及驱动框架介绍编写的主要内容,如果未能解决你的问题,请参考以下文章

Linux驱动开发: 编写USB接口光谱仪驱动

Linux驱动经典面试题目

Linux Framebuffer 驱动框架之一概念介绍及LCD硬件原理

aosp 制作 rom 刷机 添加厂家二进制驱动 及 出厂镜像

Linux设备驱动之USB

第七章