2019独角兽企业重金招聘Python工程师标准>>>

编写与一个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[i].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.      }

转载于:https://my.oschina.net/u/3629177/blog/1560284

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

  1. 嵌入式 Linux 内核驱动开发【The first day: 36093万字】

    嵌入式 Linux 内核驱动开发[1] 嵌入式 Linux 内核驱动开发前言 第1章 Linux 内核裁剪和定制 [1]Linux 内核开发简介 [2] Linux 源码阅读工具 [1.2.1]Sou ...

  2. linux cached释放_正点原子Linux第四十一章嵌入式Linux LED驱动开发实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第四十一章嵌入式Linux LED驱动开发实验 上一章我 ...

  3. 【正点原子Linux连载】第四十一章 嵌入式Linux LED驱动开发实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  4. 嵌入式linux设备驱动开发,嵌入式Linux设备驱动开发简介.pdf

    清远见--嵌入式培训专家 http :// "黑色经典"系列之<嵌入式Linux 应用程序开发详解> 11 章 嵌入式Linux 设备驱动开发 本章目标 本书从 6 章 ...

  5. 【正点原子MP157连载】第二十一章 嵌入式Linux LED驱动开发实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  6. 嵌入式 Linux LED 驱动开发实验

    41.1 Linux 下 LED 灯驱动原理 Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器.所以本章的 LED 灯驱动最 终也是对 I.MX6ULL 的 IO 口进行配置,与裸机实验不 ...

  7. linux课程_【课程完结】嵌入式Linux应用/驱动开发基础知识两大篇章已全部录制完毕 共72集...

    完结撒花 <第四篇嵌入式Linux应用开发基础知识> <第五篇嵌入式Linux驱动开发基础知识> 两大篇章已全部录制完毕 共计 72 集 01 嵌入式Linux应用开发基础知识 ...

  8. 嵌入式Linux ———触摸屏驱动开发

    声明:本文章是看完韦东山老师的触摸屏驱动视频所写的关于触摸屏的驱动,因此如果有相关内容与其他网友相同,敬请原谅.同时我还是想说本文只是总结自己的学习所得,同时也将自己所学到的知识写下来,所以如果这篇文 ...

  9. 嵌入式linux应用程序开发详解_【精品套餐】嵌入式linux应用驱动开发完全学习路线...

    学习本课程,你将收获 本课程由<朱老师物联网大讲堂>推出,朱有鹏老师精心录制,提供从零开始.全面系统的学习体系,目的是让大家真正彻底的掌握嵌入式Linux应用和驱动程序开发知识和技能,真正 ...

最新文章

  1. Jenkins 持续集成 概念(学习笔记二十六)
  2. HDU -- 2084 数塔(简单DP)
  3. 统计的一个小题目python实现
  4. AutoLayout ScrollView在ios7下无法滑动
  5. php多线程模拟请求,浅谈php使用curl模拟多线程发送请求
  6. validate针对checkbox、radio、select标签的验证
  7. TOYOTA SYSTEMS Programming Contest 2021(AtCoder Beginner Contest 228) ABCD
  8. Java对字符串进行的操作
  9. excel数据透视表应用大全_从Excel进阶到Python:更强大的数据透视表
  10. 表达式类型错误oracle,这个语句报pls_00382 表达式类型错误 求问为什么。
  11. 将xlsx转换为xls格式
  12. 7-10 哈利·波特的考试
  13. [学习Cython编程]Cython编程入门
  14. Pyspider启动过程中的问题ssl/nss错配问题
  15. 15分钟带你了解lower_bound和upper_bound
  16. 推荐一个C++枚举转字符串的开源项目magic_enum
  17. 谱聚类Python代码详解
  18. LTE下行物理层传输机制(1)-天线端口Antenna Port和小区特定参考信号CRS
  19. 工业相机 linux驱动软件,菲力尔FLIR-灰点Point Grey工业相机Linux Ubuntu18.04系统驱动Spinnaker-2.0.0.147-amd64/arm64...
  20. 解析 Linux 中的 VFS 文件系统机制(1)

热门文章

  1. 【博弈论】【SG函数】bzoj1457 棋盘游戏
  2. perl use 命令中指定路径
  3. java禁止放大_java 所有组件缩放、放大
  4. 微信多开txt_微信仅需3步操作,就能多开登录?手把手包教包会
  5. Xamarin XAML语言教程模板页面TemplatedPage
  6. Xamarin.Forms的基本页面和基本视图
  7. filco蓝牙不好用_2020双十二机械键盘选购 牌子推荐 附雷柏/ikbc/akko/杜伽/FILCO热销机械键盘品牌...
  8. java 创建多线程_Java创建多线程
  9. arch linux 下 安装搭建python机器学习环境
  10. 使用大脑活动反馈的刺激技术自动化治疗脑部疾病