微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > ARM技术讨论 > 嵌入式Linux USB驱动开发之教你一步步编写USB驱动程序

嵌入式Linux USB驱动开发之教你一步步编写USB驱动程序

时间:10-02 整理:3721RD 点击:

编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。当然,这些制造商和设备标识需要我们编写进USB 驱动程序中。

USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C 总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的USB 设备是什么样的,如何实现的,比如一个字符设备,我们还需填写相应的文件操作接口 ,下面我们从外到里进行剖析,学习如何搭建这样的一个USB驱动外壳框架:

一、注册USB驱动程序

Linux的设备驱动,特别是这种hotplug的USB设备驱动,会被编译成模块,然后在需要时挂在到内核。所以USB驱动和注册与正常的模块注册、卸载是一样的,下面是USB驱动的注册与卸载:

[cpp] view plain copy

1. static int __init usb_skel_init(void)     

2. {     

3.      int result;     

4.      /* register this driver with the USB subsystem */     

5.      result = usb_register(&skel_driver);     

6.      if (result)     

7.          err("usb_register failed. Error number %d", result);     

8.     

9.      return result;     

10. }     

11.     

12. static void __exit usb_skel_exit(void)     

13. {     

14.      /* deregister this driver with the USB subsystem */     

15.      usb_deregister(&skel_driver);     

16. }     

17.     

18. module_init (usb_skel_init);     

19. module_exit (usb_skel_exit);     

20. MODULE_LICENSE("GPL");  

USB设备驱动的模块加载函数通用的方法是在I2C设备驱动的模块加载函数中使用usb_register(struct *usb_driver)函数添加usb_driver的工作,而在模块卸载函数中利用usb_deregister(struct *usb_driver)做相反的工作。 对比I2C设备驱动中的 i2c_add_driver(&i2c_driver)与i2c_del_driver(&i2c_driver)。

struct usb_driver是USB设备驱动,我们需要实现其成员函数:

[cpp] view plain copy

1. static struct usb_driver skel_driver = {     

2.      .owner = THIS_MODULE,      

3.      .name = "skeleton",   

4.      .id_table = skel_table,         

5.      .probe = skel_probe,      

6.      .disconnect = skel_disconnect,      

7. };      

从代码看来,usb_driver需要初始化五个字段:

模块的所有者 THIS_MODULE
模块的名字  skeleton
probe函数   skel_probe
disconnect函数skel_disconnect
id_table

最重要的当然是probe函数与disconnect函数,这个在后面详细介绍,先谈一下id_table:

id_table 是struct usb_device_id 类型,包含了一列该驱动程序可以支持的所有不同类型的USB设备。如果没有设置该变量,USB驱动程序中的探测回调该函数将不会被调用。对比I2C中struct i2c_device_id *id_table,一个驱动程序可以对应多个设备,i2c 示例:

[cpp] view plain copy

1. static const struct i2c_device_id mpu6050_id[] = {      

2.     { "mpu6050", 0},      

3.     {}      

4. };   

usb子系统通过设备的production ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程序作处理。我们可以看看这个id_table到底是什么东西:

[cpp] view plain copy

1. static struct usb_device_id skel_table [] = {      

2.      { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },      

3.      { }                    /* Terminating entry */      

4. };      

5.      

6. MODULE_DEVICE_TABLE (usb, skel_table);     

MODULE_DEVICE_TABLE的第一个参数是设备的类型,如果是USB设备,那自然是usb。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor ID和product ID,如果它们的值是0xfff0时,那么子系统就会调用这个skeleton模块作为设备的驱动。

当USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID,或者是设备所属的class、subclass跟protocol,以便确定应该调用哪一个驱动处理该设备。  

我们下面所要做的就是对probe函数与disconnect函数的填充了,但是在对probe函数与disconnect函数填充之前,有必要先学习三个重要的数据结构,这在我们后面probe函数与disconnect函数中有很大的作用:

二、USB驱动程序中重要数据结构

1、usb-skeleton

usb-skeleton 是一个局部结构体,用于与端点进行通信。下面先看一下Linux内核源码中的一个usb-skeleton(就是usb驱动的骨架咯),其定义的设备结构体就叫做usb-skel:

[cpp] view plain copy

1. struct usb_skel {     

2.      struct usb_device *udev;                 /* the usb device for this device */     

3.      struct usb_interface  *interface;            /* the interface for this device */     

4.      struct semaphore limit_sem;         /* limiting the number of writes in progress */     

5.      unsigned char *bulk_in_buffer;     /* the buffer to receive data */     

6.      size_t         bulk_in_size;                  /* the size of the receive buffer */     

7.      __u8          bulk_in_endpointAddr;        /* the address of the bulk in endpoint */     

8.      __u8          bulk_out_endpointAddr;      /* the address of the bulk out endpoint */     

9.      struct kref   kref;     

10. };   

他拥有:

描述usb设备的结构体udev
一个接口interface
用于并发访问控制的semaphore(信号量) limit_sem
用于接收数据的缓冲bulk_in_buffer
用于接收数据的缓冲尺寸bulk_in_size
批量输入端口地址bulk_in_endpointAddr
批量输出端口地址bulk_out_endpointAddr
内核使用的引用计数器

从开发人员的角度看,每一个usb设备有若干个配置(configuration)组成,每个配置又可以有多个接口(interface)(我理解就是USB设备的一项功能),每个接口又有多个设置,而接口本身可能没有端点或者多个端点(end point)

2、USB 接口数据结构 struct usb_interface

[cpp] view plain copy

1. struct usb_interface   

2. {            

3.          struct usb_host_interface *altsetting;     

4.          struct usb_host_interface *cur_altsetting;         

5.          unsigned num_altsetting;            

6.          int minor;                          

7.          enum usb_interface_condition condition;            

8.          unsigned is_active:1;               

9.          unsigned needs_remote_wakeup:1;      

10.          struct device dev;                  

11.          struct device *usb_dev;            

12.          int pm_usage_cnt;                  

13. };   

在逻辑上,一个USB设备的功能划分是通过接口来完成的。比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另外一个用于音频流传输。而事实上,这种设备需要用到不同的两个驱动程序来操作,一个控制键盘,一个控制音频流。但也有例外,比如蓝牙设备,要求有两个接口,第一用于ACL跟EVENT的传输,另外一个用于SCO链路,但两者通过一个驱动控制。在Linux上,接口使用struct usb_interface来描述,以下是该结构体中比较重要的字段:

a -- struct usb_host_interface *altsetting(注意不是usb_interface)

其实据我理解,他应该是每个接口的设置,虽然名字上有点奇怪。该字段是一个设置的数组(一个接口可以有多个设置),每个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置。但这些配置次序是不定的。


b -- struct usb_host_interface *cur_altsetting

       当前活动的设置,指向altsetting数组中的一个

struct usb_host_interface数据结构:

[cpp] view plain copy

1. struct usb_host_interface     

2. {   

3.          struct usb_interface_descriptor desc;//usb描述符,主要有四种usb描述符,设备描述符,配置描述符,接口描述符和端点描述符,协议里规定一个usb设备是必须支持这四大描述符的信盈达嵌入式要领吧五六零五四五吧。   

4.                                  //usb描述符放在usb设备的eeprom里边   

5.          /* array of desc.bNumEndpoint endpoints associated with this  

6.           * interface setting. these will be in no particular order.  

7.           */   

8.          struct usb_host_endpoint *endpoint;//这个设置所使用的端点   

9.     

10.          char *string;           /* iInterface string, if present */   

11.          unsigned char *extra;   /* Extra descriptors */关于额外描述符   

12.          int extralen;   

13. };   

c -- unsigned num_altstting

可选设置的数量,即altsetting所指数组的元素个数

d -- int minor

当捆绑到该接口的USB驱动程序使用USB主设备号时,USB core分配的次设备号。仅在成功调用usb_register_dev之后才有效。

3、USB 端点 struct usb_host_endpoint

Linux中用struct usb_host_endpoint 来描述USB端点

[cpp] view plain copy

1. struct usb_host_endpoint     

2. {   

3.          struct usb_endpoint_descriptor desc;   

4.          struct list_head                urb_list;//端点要处理的urb队列.urb是usb通信的主角,设备中的每个端点都可以处理一个urb队列.要想和你的usb通信,就得创建一个urb,并且为它赋好值,   

5.                                    //交给咱们的usb core,它会找到合适的host controller,从而进行具体的数据传输   

6.          void                            *hcpriv;//这是提供给HCD(host controller driver)用的   

7.          struct ep_device                *ep_dev;        /* For sysfs info */   

8.     

9.          unsigned char *extra;   /* Extra descriptors */   

10.          int extralen;   

11. };   

每个usb_host_endpoint中包含一个struct usb_endpoint_descriptor结构体,当中包含该端点的信息以及设备自定义的各种信息,这些信息包括:

a -- bEndpointAddress(b for byte)

8位端点地址,其地址还隐藏了端点方向的信息(之前说过,端点是单向的),可以用掩码USB_DIR_OUT和USB_DIR_IN来确定。

b -- bmAttributes

端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK可以确定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)还是USB_ENDPOINT_XFER_INT(中断)。

c -- wMaxPacketSize

端点一次处理的最大字节数。发送的BULK包可以大于这个数值,但会被分割传送。

d -- bInterval

如果端点是中断类型,该值是端点的间隔设置,以毫秒为单位

三、探测和断开函数分析

USB驱动程序指定了两个USB核心在适当时间调用的函数。

1、探测函数

当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用;

探测函数应该检查传递给他的设备信息,确定驱动程序是否真的适合该设备。当驱动程序因为某种原因不应控制设备时,断开函数被调用,它可以做一些清洁的工作。

系统会传递给探测函数的信息是什么呢?一个usb_interface * 跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认设置也是第0号设置)跟它的设备ID描述(包括Vendor ID、Production ID等)。

USB驱动程序应该初始化任何可能用于控制USB设备的局部结构体,它还应该把所需的任何设备相关信息保存到局部结构体中。例如,USB驱动程序通常需要探测设备对的端点地址和缓冲区大小,因为需要他们才能和端点通信。

下面具体分析探测函数做了哪些事情:

a -- 探测设备的端点地址、缓冲区大小,初始化任何可能用于控制USB设备的数据结构

下面是一个实例代码,他们探测批量类型的IN和OUT端点,把相关信息保存到一个局部设备结构体中:

[cpp] view plain copy

1. /* set up the endpoint information */     

2.      /* use only the first bulk-in and bulk-out endpoints */     

3.      iface_desc = interface->cur_altsetting;     

4.      for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {     

5.          endpoint = &iface_desc->endpoint.desc;   

6.     

7.          if ( !dev->bulk_in_endpointAddr &&     

8.                 ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&     

9.              ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {     

10.              /* we found a bulk in endpoint */     

11.               buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);     

12.               dev->bulk_in_size = buffer_size;     

13.               dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;     

14.               dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);     

15.               if (!dev->bulk_in_buffer) {     

16.                   err("Could not allocate bulk_in_buffer");     

17.                    goto error;                  

18.               }  

19.          }     

20.     

21.          if (!dev->bulk_out_endpointAddr &&     

22.             ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&     

23.                ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {     

24.               /* we found a bulk out endpoint */     

25.               dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;     

26.          }     

27.      }     

28.     

29.      if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {     

30.          err("Could not find both bulk-in and bulk-out endpoints");     

31.          goto error;     

32.      }   


Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top