基于s5pv-210开发板 usb设备侧驱动

本文深入解析USB设备的逻辑组织及USB设备驱动的工作原理,涵盖设备、配置、接口和端点等概念,介绍USB设备驱动的核心数据结构urb及其处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

USB 设备的逻辑组织中,包含设备、配置、接口和端点 4 个层次

每个USB 设备都提供了不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需从其中选定一个),配置由多个接口组成。
USB 协议中,接口由多个端点组成,代表一个基本的功能,是 USB 设备驱动程序控制的对象,一个功能复杂的USB 设备可以具有多个接口。每个配置中可以有多个接口,而设备接口是端点的汇集( collection)。例如, USB 扬声器可以包含一个音频接口以及对旋钮和按钮的接口。一个配置中的所有接口可以同时有效,并可被不同的驱动程序连接。每个接口可以有备用接口,以提供不同质量的服务参数。
端点是 USB 通信的最基本形式,每一个 USB 设备接口在主机看来就是一个端点的集合,机只能通过端点与设备进行通信,以使用设备的功能。在USB 系统中每一个端点都有惟一的地址,这是由设备地址和端点号给出的。每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。一个USB 端点只能在一个方向承载数据,或者从主机到设备(称为输出端点),或者从设备到主机(称为输入端点),因此端点可看作一个单向的管道。端0 通常为控制端点,用于设备初始化参数等。只要设备连接到 USB 上并且上电端点 0 就可以被访问。端点12 等一般用作数据端点,存放主机与设备间往来的数据。

usb 逻辑单元图:



设备通常有一个或多个配置;
配置通常有一个或多个接口;
接口通常有一个或多个设置;
接口有零或多个端点。


设备结构体:

代表了一个插入的USB设备,在内核使用数据结构 struct usb_device来描述整个USB设备。(include/linux/usb.h)

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特定的数据结构

  };
设备描述符:关于设备的通用信息,如供应商ID、产品ID 和修订 ID,支持的设备类、子类和适用的协议以及默认端点的最大包大小等。在Linux 内核中, USB 设备用usb_device 结构体来描述, USB 设备描述符定义为 usb_device_descriptor结构体:

struct usb_device_descriptor {  
    __u8  bLength;//设备描述符的字节数大小,为0x12   
    __u8  bDescriptorType;//描述符类型编号,为0x01   
  
    __le16 bcdUSB;//USB版本号   
    __u8  bDeviceClass;//USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型   
      
    //0x00不是在设备描述符中定义的,如HID   
    __u8  bDeviceSubClass;//usb分配的子类代码,同上,值由USB规定和分配的  
    __u8  bDeviceProtocol;//USB分配的设备协议代码,同上   
    __u8  bMaxPacketSize0;//端点0的最大包的大小   
    __le16 idVendor;//厂商编号   
    __le16 idProduct;//产品编号   
    __le16 bcdDevice;//设备出厂编号  
    __u8  iManufacturer;//描述厂商字符串的索引   
    __u8  iProduct;//描述产品字符串的索引  
    __u8  iSerialNumber;//描述设备序列号字符串的索引   
    __u8  bNumConfigurations;//可能的配置数量  
} __attribute__ ((packed));  


配置结构体:

一个USB设备可以有多个配置,并可在它们之间转换以改变设备的状态。比如一个设备可以通过下载固件(firmware)的方式改变设备的使用状态(我感觉类似FPGA或CPLD),那么USB设备就要切换配置,来完成这个工作。一个时刻只能有一个配置可以被激活。Linux使用结构 struct usb_host_config 来描述USB配置。我们编写的USB设备驱动通常不需要读写这些结构的任何值。可在内核源码的文件include/linux/usb.h中找到对它们的描述。

struct usb_host_config {
    struct usb_config_descriptor desc; //配置描述符

    char *string; /* 配置的字符串指针(如果存在) */
    struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS]; //配置的接口联合描述符链表
    struct usb_interface *interface[USB_MAXINTERFACES]; //接口描述符链表
    struct usb_interface_cache *intf_cache[USB_MAXINTERFACES]; 
    unsigned char *extra; /* 额外的描述符 */
    int extralen;
};
配置描述符:此配置中的接口数、支持的挂起和恢复能力以及功率要求。USB 配置在内核中使用 usb_host_config结构体描述, USB 配置描述符定义为结构体 usb_config_descriptor

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


接口结构体:

USB端点被绑为接口,USB接口只处理一种USB逻辑连接。一个USB接口代表一个基本功能,每个USB驱动控制一个接口。所以一个物理上的硬件设备可能需要一个以上的驱动程序。这可以在“晕到死 差屁”系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。
USB 接口可以有其他的设置,它是对接口参数的不同选择. 接口的初始化的状态是第一个设置,编号为0。 其他的设置可以以不同方式控制独立的端点。

USB接口在内核中使用 struct usb_interface 来描述。USB 核心将其传递给USB驱动,并由USB驱动负责后续的控制。

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

    struct usb_host_interface *cur_altsetting; /* 指向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;

    int minor; /* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。*/
    /*以下的数据在我们写的驱动中基本不用考虑,系统会自动设置*/
    enum usb_interface_condition condition; /* state of binding */
    unsigned is_active:1; /* the interface is not suspended */
    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 reset_running:1;

    struct device dev; /* 接口特定的设备信息 */
    struct device *usb_dev;
    int pm_usage_cnt; /* usage counter for autosuspend */
    struct work_struct reset_ws; /* for resets in atomic context */
};

struct usb_host_interface {
    struct usb_interface_descriptor desc; //接口描述符

    struct usb_host_endpoint *endpoint; /* 这个接口的所有端点结构体的联合数组*/
    char *string; /* 接口描述字符串 */
    unsigned char *extra; /* 额外的描述符 */
    int extralen;
};

接口描述符:接口类、子类和适用的协议,接口备用配置的数目和端点数目。USB 接口在 内 核 中 使 用 usb_interface结 构 体 描 述 , USB 接 口 描 述 符 定 义 为 结 构 体usb_interface_descriptor

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

端点
USB 通讯的最基本形式是通过一个称为端点的东西。一个USB端点只能向一个方向传输数据(从主机到设备(称为输出端点)或者从设备到主机(称为输入端点))。端点可被看作一个单向的管道。

一个 USB 端点有 4 种不同类型, 分别具有不同的数据传送方式:

控制CONTROL 
控制端点被用来控制对 USB 设备的不同部分访问. 通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告。这些端点通常较小。每个 USB 设备都有一个控制端点称为"端点 0", 被 USB 核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备.

中断INTERRUPT 
每当 USB 主机向设备请求数据时,中断端点以固定的速率传送小量的数据。此为USB 键盘和鼠标的主要的数据传送方法。它还用以传送数据到 USB 设备来控制设备。通常不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备.

批量BULK
批量端点用以传送大量数据。这些端点常比中断端点大得多. 它们普遍用于不能有任何数据丢失的数据。USB 协议不保证传输在特定时间范围内完成。如果总线上没有足够的空间来发送整个BULK包,它被分为多个包进行传输。这些端点普遍用于打印机、USB Mass Storage和USB网络设备上。

等时ISOCHRONOUS 
等时端点也批量传送大量数据, 但是这个数据不被保证能送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等等。

控制和批量端点用于异步数据传送,而中断和同步端点是周期性的。这意味着这些端点被设置来在固定的时间连续传送数据,USB 核心为它们保留了相应的带宽。

端点在内核中使用结构 struct usb_host_endpoint 来描述
struct usb_host_endpoint {
    struct usb_endpoint_descriptor desc; //端点描述符
    struct list_head urb_list; //此端点的URB对列,由USB核心维护
    void *hcpriv;
    struct ep_device *ep_dev; /* For sysfs info */
    unsigned char *extra; /* Extra descriptors */
    int extralen;
    int enabled;
};
struct usb_host_endpoint 所包含的真实端点信息在另一个结构中:struct usb_endpoint_descriptor(端点描述符,包含所有的USB特定数据)。
端点描述符:端点地址、方向和类型,支持的最大包大小,如果是中断类型的端点则还包括轮询频率。
struct usb_endpoint_descriptor {
    __u8 bLength;
    __u8 bDescriptorType;

    __u8 bEndpointAddress; /*这个特定端点的 USB 地址,这个8位数据包含端点的方向,结合位掩码 USB_DIR_OUT 和 USB_DIR_IN 使用, 确定这个端点的数据方向。*/

    __u8 bmAttributes; //这是端点的类型,位掩码如下

    __le16 wMaxPacketSize; /*端点可以一次处理的最大字节数。驱动可以发送比这个值大的数据量到端点, 但是当真正传送到设备时,数据会被分为 wMaxPakcetSize 大小的块。对于高速设备, 通过使用高位部分几个额外位,可用来支持端点的高带宽模式。*/
    __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));


字符串描述符:在其他描述符中会为某些字段提供字符串索引,它们可被用来检索描述性字符串,可以以多种语言形式提供。字符串描述符是可选的,有的设备有,有的设备没有,字符串描述符对应于usb_string_descriptor结构体
struct usb_string_descriptor {  
    __u8  bLength;//设备描述符的字节数大小,为0x12   
    __u8  bDescriptorType;//描述符类型编号,为0x01   
  
    __le16 wData[1];        /* UTF-16LE encoded */  
} __attribute__ ((packed));  

例如,笔者在 PC 上插入一个 SanDisk U 盘后,通过 lsusb 命令得到这个 U 盘相关的描述符,从中可以显示这个U 盘包含了一个设备描述符、一个配置描述符、一个接口描述符以及批量输入和批量输出两个端点描述符。呈现出来的信息内容直接对应于usb_device_descriptorusb_config_
descriptorusb_interface_descriptorusb_endpoint_descriptorusb_string_descriptor结构体


USB驱动程序框架:

app:   
-------------------------------------------
          USB设备驱动程序      // 知道数据含义
内核 --------------------------------------
          USB总线驱动程序      // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义)
-------------------------------------------
           USB主机控制器
           UHCI OHCI EHCI
硬件        -----------
              USB设备
UHCI: intel,     低速(1.5Mbps)/全速(12Mbps)
OHCI: microsoft  低速/全速
EHCI:            高速(480Mbps)

USB总线驱动程序的作用
1. 识别USB设备
1.1 分配地址
1.2 并告诉USB设备(set address)
1.3 发出命令获取描述符
描述符的信息可以在include\linux\usb\Ch9.h看到

2. 查找并安装对应的设备驱动程序

3. 提供USB读写函数


usb设备驱动 ---鼠标

/*
 * drivers\hid\usbhid\usbmouse.c
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;

static struct usb_device_id usb_mouse_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);

static void usbmouse_as_key_irq(struct urb *urb)
{
	int i;
	static int cnt = 0;
	printk("data cnt %d: ", ++cnt);
	for (i = 0; i < len; i++)
	{
		//printk("%02x ", usb_buf[i]);
		
		if(usb_buf[i] == 01)
			printk("left\n");
		else 
			printk("no left\n");
	}
	printk("\n");
//data cnt 30: 01 ff 02 00   
	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf); //根据usb_interface指针intf获取usb_device的地址
	struct usb_host_interface *interface; //这里可能要补充以下一些关于usb_interface_descriptor的知识,但因为内核源码对该结构体的注释不多,所以只能靠个人猜测。在一个usb_host_interface结构里面有一个usb_interface_descriptor叫做desc的成员,他应该是用于描述该interface的一些属性,其中bNumEndpoints是一个8位(b for byte)的数字,他代表了该接口的端点数。probe然后遍历所有的端点,检查他们的类型跟方向,注册到usb_skel中。 
	struct usb_endpoint_descriptor *endpoint;
	int pipe;
	
	interface = intf->cur_altsetting;	//当前活跃的设置
	endpoint = &interface->endpoint[0].desc;  //本接口对应的端点 

	/* a. 分配一个input_dev */
	uk_dev = input_allocate_device();
	
	/* b. 设置 */
	/* b.1 能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);
	set_bit(EV_REP, uk_dev->evbit);
	
	/* b.2 能产生哪些事件 */
	set_bit(KEY_L, uk_dev->keybit);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */

	//设置端点信息,其实pipe是一个int类型的数据。urb所发送的特定目标struct usb_device的端点信息
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);  //bEndpointAddress 端点地址

	/* 信息包长度: */
	len = endpoint->wMaxPacketSize;

	/* 目的: */
	usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);

	/* 使用"3要素" */
	/* 分配usb request block(urb) */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);
	/* 使用"3要素设置urb" */
	//对于中断urb,使用usb_fill_int_urb初始化urb。
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 使用URB */
	usb_submit_urb(uk_urb, GFP_KERNEL);
	
	return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	//printk("disconnect usbmouse!\n");
	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

	usb_free_coherent(dev, len, usb_buf, usb_buf_phys);
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "s5p210usbmouse",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usb_mouse_id_table,
};

static int usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");

使用 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);
 void (*pre_reset) (struct usb_interface*intf);
 void (*post_reset) (struct usb_interface*intf);
 const struct usb_device_id*id_table;/*usb_device_id表指针 */
 struct usb_dynids dynids;
 struct usbdrv_wrap drvwrap;

 unsigned int no_dynamic_id:1;
 unsigned int supports_autosuspend:1;
 unsigned int soft_unbind:1;
 };

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "s5p210usbmouse",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usb_mouse_id_table,
};

<span style="font-family: SimSun; font-size: 10pt;">对<span style="font-family: TimesNewRomanPSMT; font-size: 10pt;">usb<span style="font-family: SimSun; font-size: 10pt;">_<span style="font-family: TimesNewRomanPSMT; font-size: 10pt;">driver <span style="font-family: SimSun; font-size: 10pt;">的注册和注销</span><br style="orphans: 2; text-align: -webkit-auto; widows: 2;" /></span></span></span></span>
static int usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

usb_driver结构体中的 id_table成员描述了这个 USB 驱动所支持的 USB 设备列表,它指向一usb_device_id数组, usb_device_id结构体用于包含 USB 设备的制造商 ID、产品 ID、产品版本、设备类、接口类等信息及其要匹配标志成员match_flags(标明要与哪些成员匹配,包含DEV_LODEV_HIDEV_CLASSDEV_SUBCLASSDEV_PROTOCOLINT_CLASSINT_SUBCLASSINT_PROTOCOL)。可以借助下面一组宏来生成usb_device_id结构体的实例:

static struct usb_device_id usb_mouse_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
USB_INTERFACE_INFO(class, subclass, protocol)
该宏用于创建一个匹配接口指定类型的 usb_device_id结构体实例。

USB_DEVICE_INFO(class, subclass, protocol)
该宏用于创建一个匹配设备指定类型的 usb_device_id结构体实例。

USB_DEVICE(vendor, product)
该宏根据制造商 ID 和产品 ID 生成一个 usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支持匹配制造商ID、产品ID 的设备。

USB_DEVICE_VER(vendor, product, lo, hi)
该宏根据制造商 ID、产品 ID、产品版本的最小值和最大值生成一个 usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支持匹配制造商ID、产品ID lohi范围内版本的设备。


USB核心检测到某个设备的属性和某个驱动程序的usb_device_id结构体所携带的信息一致时,这个驱动程序的probe()函数就被执行。拔掉设备或者卸掉驱动模块后,USB核心就执行disconnect()函数来响应这个动作。

上述usb_driver结构体中的函数是 USB 设备驱动中 USB 相关的部分,而 USB 只是一个总线,真正USB 设备驱动的主体工作仍然是 USB 设备本身所属类型的驱动,如字符设备、 tty设备、块设备、输入设备等。因此USB 设备驱动包含其作为总线上挂在设备的驱动和本身所属设备类型的驱动两部分。
platform_driver类似, usb_driver起到了“牵线”的作用,即在 probe()里注册相应的字符、tty等设备,在 disconnect()注销相应的字符、 tty 等设备,而原先对设备的注册和注销一般直接发生在模块加载和卸载函数中.
尽管USB 本身所属设备驱动的结构与其不挂在 USB 总线上时完全相同,但是在访问方式上却发生了很大的变化,例如,对于 USB 接口的字符设备而言,尽管仍然是 write()read()ioctl()这些函数,但是在这些函数中,贯穿始终的是称为 URB USB 请求块。

我们把树根比作主机控制器,树叶比作具体的USB 设备,树干和树枝就是 USB 总线。树叶本身与树枝通过 usb_driver连接,而树叶本身的驱动(读写、控制)则需要通过其树叶设备本身所属类设备驱动来完成。树根和树叶之间的“通信”依靠在树干和树枝里“流淌”的URB 来完成。

urb结构体
USB 请求块( USB request blockurb)是USB 设备驱动中用来描述与 USB 设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的 sk_buff结构体。

struct urb

2  {

3    /* 私有的:只能由usb核心和主机控制器访问的字段 */

4    struct kref kref; /*urb引用计数 */

5    spinlock_t lock; /* urb锁 */

6    void *hcpriv; /* 主机控制器私有数据 */

7    int bandwidth; /* int/iso请求的带宽 */

8    atomic_t use_count; /* 并发传输计数 */

9    u8 reject; /* 传输将失败*/

10

11   /* 公共的: 可以被驱动使用的字段 */

12   struct list_head urb_list; /* 链表头*/

13   struct usb_device *dev; /* 关联的usb设备 */

14   unsigned int pipe; /* 管道信息 */

15   int status; /* urb的当前状态 */

16   unsigned int transfer_flags; /* urb_short_not_ok | ...*/

17   void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */

18   dma_addr_t transfer_dma; /*用来以dma方式向设备传输数据的缓冲区 */

19   int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向缓冲区的大小 */

20                      

21   int actual_length; /* urb结束后,发送或接收数据的实际长度 */

22   unsigned char *setup_packet; /* 指向控制urb的设置数据包的指针*/

23   dma_addr_t setup_dma; /*控制urb的设置数据包的dma缓冲区*/

24   int start_frame; /*等时传输中用于设置或返回初始帧*/

25   int number_of_packets; /*等时传输中等时缓冲区数据 */

26   int interval; /* urb被轮询到的时间间隔(对中断和等时urb有效) */

27   int error_count;  /* 等时传输错误数量 */

28   void *context; /* completion函数上下文 */

29   usb_complete_t complete; /* 当urb被完全传输或发生错误时,被调用 */

30   struct usb_iso_packet_descriptor iso_frame_desc[0];

31   /*单个urb一次可定义多个等时传输时,描述各个等时传输 */

32 };


urb 处理流程
USB 设备中的每个端点都处理一个 urb 队列,在队列被清空之前,一个 urb 的典型生命周期如下:

1)被一个USB 设备驱动创建。
创建 urb 结构体的函数为:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags);

iso_packets是这个 urb 应当包含的等时数据包的数目,若为 0 表示不创建等时数据包。
mem_flags参数是分配内存的标志,和 kmalloc()函数的分配标志参数含义相同。如果分配成功,该函数返回一个urb 结构体指针,否则返回 0
urb 结构体在驱动中不能静态创建,因为这可能破坏 USB 核心给 urb 使用的引用计数方法。

2)初始化,被安排给一个特定USB 设备的特定端点。

/* 使用"3要素" */
	/* 分配usb request block(urb) */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);
	/* 使用"3要素设置urb" */
	//对于中断urb,使用usb_fill_int_urb初始化urb。
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
	/* 使用URB */
	usb_submit_urb(uk_urb, GFP_KERNEL);


对于中断 urb,使用 usb_fill_int_urb()函数来初始化urb:
urb参数指向要被初始化的 urb 的指针; dev 指向这个 urb 要被发送到的 USB 设备; pipe 是这urb 要被发送到的 USB 设备的特定端点; transfer_buffer是指向发送数据或接收数据的缓冲区的指针,和urb 一样,它也不能是静态缓冲区,必须使用 kmalloc()来分配;buffer_length
transfer_buffer指针所指向缓冲区的大小; complete 指针指向当这个 urb 完成时被调用的完成处理函数;context 是完成处理函数的“上下文”; interval是这个 urb 应当被调度的间隔。

( 3)被 USB 设备驱动提交给 USB 核心。
在完成创建和初始化urb 后, urb 便可以提交给 USB 核心,通过 usb_submit_urb()函数来完成,如下所示:
int usb_submit_urb(struct urb *urb, int mem_flags);
urb 参数是指向 urb 的指针, mem_flags参数与传递给 kmalloc()函数参数的意义相同,它用于告知USB 核心如何在此时分配内存缓冲区。
在提交 urb USB 核心后,直到完成函数被调用之前,不要访问 urb 中的任何成员。
usb_submit_urb()在原子上下文和进程上下文中都可以被调用,mem_flags变量需根据调用环境进行相应的设置,如下所示。

GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及urb 完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将 currentstate修改为非 TASK_RUNNING时,应使用此标志。
GFP_NOIO:在存储设备的块I/O 和错误处理路径中,应使用此标志;
GFP_KERNEL:如果没有任何理由使用GFP_ATOMICGFP_NOIO,就使用GFP_KERNEL
如果 usb_submit_urb()调用成功,即urb 的控制权被移交给 USB 核心,该函数返回 0;否则,返回错误号。

4)提交由USB 核心指定的 USB主机控制器驱动。
5)被USB 主机控制器处理,进行一次到 USB设备的传送。
第( 4) ~(5)步由USB 核心和主机控制器完成,不受 USB设备驱动的控制。
6)当urb 完成, USB主机控制器驱动通知 USB 设备驱动。
在如下3 种情况下, urb 将结束, urb 完成函数将被调用。
urb被成功发送给设备,并且设备返回正确的确认。如果 urbstatus0,意味着对于一个输出urb,数据被成功发送;对于一个输入urb,请求的数据被成功收到。
如果发送数据到设备或从设备接收数据时发生了错误,urbstatus将记录错误值。
urb被从 USB 核心“去除连接”,这发生在驱动通过 usb_unlink_urb()usb_kill_urb()函数取消urb,或urb 虽已提交,而 USB 设备被拔出的情况下




































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值