微软交流社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 92|回复: 0

linux内核之USB驱动分析

[复制链接]

1

主题

2

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-9-21 02:34:41 | 显示全部楼层 |阅读模式
第一部分  USB驱动程序框架
  app:
  -------------------------------------------
  USB设备驱动程序    // 知道数据含义
  内核 --------------------------------------
  USB总线驱动程序   // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义)
  -------------------------------------------
  USB主机控制器
  UHCI OHCI EHCI
  硬件 -----------
  USB设备
  UHCI: intel, 低速(1.5Mbps)/全速(12Mbps)  
  OHCI: microsoft 低速/全速
  EHCI: 高速(480Mbps)
第二部分 USB设备基础概念
在终端用户看来,USB设备为主机提供了多种多样的附加功能,如文件传输,声音播放等,但对USB主机来说,它与所有USB设备的接口都是一致的。一个USB设备由3个功能模块组成:USB总线接口、USB逻辑设备和功能单元:
  a -- 这里的USB总线接口指的是USB设备中的串行接口引擎(SIE);
  b -- USB逻辑设备被USB系统软件看作是一个端点的集合;
  c -- 功能单元被客户软件看作是一个接口的集合。SIE、端点和接口都是USB设备的组成单元;
为了更好地描述USB设备的特征,USB提出了设备架构的概念。从这个角度来看,可以认为USB设备是由一些配置、接口和端点组成,即一个USB设备可以含有一个或多个配置(不同的配置使设备表现出不同的功能组合,在探测/连接期间需要从中选定一个),在每个配置中可含有一个或多个接口(一个配置中的所有接口可以同时有效,并可被不同的程序连接),在每个接口中可含有若干个端点(代表一个基本功能,每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等)。其中,配置和接口是对USB设备功能的抽象,实际的数据传输由端点来完成。在使用USB设备前,必须指明其采用的配置和接口。这个步骤一般是在设备接入主机时设备进行枚举时完成的。
usb设备非常复杂,有许多不同的逻辑单元组成,这些单元的关系如下:


【文章福利】小编推荐自己的Linux内核技术交流群:【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)


内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
设备描述符(usb_device_descriptor):关于设备的通用信息,如供货商ID及适用的协议等,在Linux内核中,USB设备用usb_device结构体来描述,位于include/uapi/linux/usb/ch9.h中,一个USB设备只能有一个设备描述符。
1 struct usb_device_descriptor {
2     __u8  bLength;                    //描述符长度
3     __u8  bDescriptorType;            //描述符类型编号
4
5     __le16 bcdUSB;                    //USB版本号
6     __u8  bDeviceClass;                //USB分配的设备类code
7     __u8  bDeviceSubClass;            //USB分配的子类code
8     __u8  bDeviceProtocol;            //USB分配的协议code
9     __u8  bMaxPacketSize0;            //endpoint0最大包大小
10     __le16 idVendor;                //厂商编号
11     __le16 idProduct;
12     __le16 bcdDevice;
13     __u8  iManufacturer;
14     __u8  iProduct;
15     __u8  iSerialNumber;
16     __u8  bNumConfigurations;        //可能的配置数量
17 } __attribute__ ((packed));

usb_device_descriptor配置描述符(usb_config_descriptor):一个USB设备可以包含一个或多个配置,如USB设备的低功耗模式和高功耗模式可分别对应一个配置。在使用USB设备前,必须为其选择一个合适的配置。配置描述符用于说明USB设备中各个配置的特性,如配置所含接口的个数等。USB设备的每一个配置都必须有一个配置描述符。
1 struct usb_config_descriptor {
2     __u8  bLength;
3     __u8  bDescriptorType;
4
5     __le16 wTotalLength;
6     __u8  bNumInterfaces;
7     __u8  bConfigurationValue;
8     __u8  iConfiguration;
9     __u8  bmAttributes;
10     __u8  bMaxPower;
11 } __attribute__ ((packed));

usb_config_descriptor接口描述符(usb_interface_descriptor):一个配置可以包含一个或多个接口,例如对一个光驱来说,当用于文件传输时,使用其大容量存储接口;而当用于播放CD时,使用其音频接口。接口是端点的集合,可以包含一个或多个可替换设置,用户能够在USB处于配置状态时改变当前接口所含的个数和特性。接口描述符用于说明设备中各个接口的特性,如接口所属的设备类及其子类等。USB设备的每个接口(usb_interface)都必须有一个接口描述符。
1 struct usb_interface_descriptor {
2     __u8  bLength;
3     __u8  bDescriptorType;
4
5     __u8  bInterfaceNumber;
6     __u8  bAlternateSetting;
7     __u8  bNumEndpoints;
8     __u8  bInterfaceClass;
9     __u8  bInterfaceSubClass;
10     __u8  bInterfaceProtocol;
11     __u8  iInterface;
12 } __attribute__ ((packed));

usb_interface_descriptor端点描述符(usb_endpoint_descriptor):端点地址、方向、类型以及支持的最大包大小等。端点是USB设备中的实际物理单元,USB数据传输就是在主机和USB设备各个端点之间进行的。端点一般由USB接口芯片提供,例如Freescale公司的MC68HC908JB8和MC9S12UF32。USB设备中的每一个端点都有唯一的端点号,每个端点所支持的数据传输方向一般而言也是确定的:或是输入(IN),或是输出(OUT)。也有些芯片提供的端点的数据方向是可以配置的,例如MC68HC908JB8包含有两个用于数据收发的端点:端点1和端点2。其中端点1只能用于数据发送,即支持输入(IN)操作;端点2既能用于数据发送,也可用于数据接收,即支持输入(IN)和输出(OUT)操作。而MC9S12UF32具有6个端点。利用设备地址、端点号和传输方向就可以指定一个端点,并与它进行通信。端点的传输特性还决定了其与主机通信是所采用的传输类型,例如控制端点只能使用控制传输。根据端点的不同用途,可将端点分为两类:0号端点和非0号端点。0号端点比较特殊,它有数据输入IN和数据输出OUT两个物理单元,且只能支持控制传输。所有的USB设备都必须含有一个0号端点,用作默认控制管道。USB系统软件就是使用该管道与USB逻辑设备进行配置通信的。0号端点在USB设备上的以后就可以使用,而非0号端点必须要在配置以后才可以使用。
1 struct usb_endpoint_descriptor {
2     __u8  bLength;
3     __u8  bDescriptorType;
4
5     __u8  bEndpointAddress;
6     __u8  bmAttributes;
7     __le16 wMaxPacketSize;
8     __u8  bInterval;
9
10     /* NOTE:  these two are _only_ in audio endpoints. */
11     /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
12     __u8  bRefresh;
13     __u8  bSynchAddress;
14 } __attribute__ ((packed));

usb_endpoint_descriptor第三部分 USB设备驱动
(1)在编写新的USB设备驱动时,主要应该完成的工作是probe()和disconnect()函数,即探测函数和断开函数。USB设备驱动的模块加载函数通用的方法是在USB设备驱动的模块加载函数中使用usb_register(struct *usb_driver)函数添加usb_driver的工作,而在模块卸载函数中利用usb_deregister(struct *usb_driver)做相反的工作。 对应I2C设备驱动中的 i2c_add_driver(&i2c_driver)与i2c_del_driver(&i2c_driver)。 
static inline int usb_register(struct usb_driver *driver)
void usb_deregister(struct usb_driver *driver)
1 static int __init usb_skel_init(void)
2 {
3     int result;
4
5     /* register this driver with the USB subsystem */
6     result = usb_register(&skel_driver);
7     if (result)
8         err("usb_register failed. Error number %d", result);
9
10     return result;
11 }
12
13 static void __exit usb_skel_exit(void)
14 {
15     /* deregister this driver with the USB subsystem */
16     usb_deregister(&skel_driver);
17 }

usb_skel_initusb_driver结构体中的id_table成员描述了这个usb驱动所支持的USB设备列表,它指向一个usb_device_id数组,usb_device_id结构体包含有USB设备的制造商ID、产品ID、产品版本等信息。
1 struct usb_driver {
2     const char *name;
3     int (*probe) (struct usb_interface *intf,
4               const struct usb_device_id *id);
5     void (*disconnect) (struct usb_interface *intf);
6     int (*ioctl) (struct usb_interface *intf, unsigned int code,
7             void *buf);
8     int (*suspend) (struct usb_interface *intf, pm_message_t message);
9     int (*resume) (struct usb_interface *intf);
10     int (*reset_resume)(struct usb_interface *intf);
11     int (*pre_reset)(struct usb_interface *intf);
12     int (*post_reset)(struct usb_interface *intf);
13     const struct usb_device_id *id_table;
14     struct usb_dynids dynids;
15     struct usbdrv_wrap drvwrap;
16     unsigned int no_dynamic_id:1;
17     unsigned int supports_autosuspend:1;
18     unsigned int soft_unbind:1;
19 };

usb_driver设备驱动工作过程:当USB设备核心检测到某个设备的属性和某个驱动程序的usb_device_id结构体所携带的信息一致时,这个程序的probe()函数就会执行,拔掉设备或者卸载驱动模块之后USB核心就会执行disconnect()函数。
usb_driver本身只是找到USB设备、管理USB设备连接和断开作用,也就是说它是公司入口处的“打卡机”,可以获取员工(USB设备)的上下班情况,树叶和员工一样,可以是研发工程师,也可以是销售工程师,而作为USB设备的树叶可以是字符树叶、网络树叶或块树叶,因此必须实现相应设备类的驱动
1 retval = usb_register_dev(interface, &skel_class);
2     static struct usb_class_driver skel_class = {
3         .name =        "skel%d",
4         .fops =        &skel_fops,
5         .minor_base =    USB_SKEL_MINOR_BASE,
6     };
7         //实现设备类的驱动
8         static const struct file_operations skel_fops = {   
9             .owner =    THIS_MODULE,
10             .read =        skel_read,
11             .write =    skel_write,
12             .open =        skel_open,
13             .release =    skel_release,
14             .flush =    skel_flush,
15             };

skel_class(2)USB请求块(urb结构体)
USB请求块是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数组结构,非常类似于网络设备驱动中的sk_buff结构体。
a、URB处理流程
USB设备中每个端点都处理一个urb队列,在队列被清空之前,一个urb的典型生命周期如下:
1)创建urb:struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
    参数:iso_packets是这个URB应当包含的等时数据包的数目,mem_flags参数是分配内存的标志,如果分配成功则返回一个urb结构体指针。
2)初始化urb(被安排给一个特定的USB设备的特定端点)static inline void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,void *transfer_buffer,int buffer_length,usb_complete_t complete_fn,void *context)
参数:urb指向要被初始化的urb的指针,dev指向这个urb要被发送到的USB设备,pipe是这个URB要被发送到的USB设备的特定端点,transfer_buffer是指向发送数据或者接收数据的缓冲区的指针,是动态分配的;complete_fn函数指向当这个URB完成时被调用的完成处理函数;context是完成处理函数的“上下文”。 
3)提交urb:int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
参数:mem_flag,与传递给kmalloc()函数参数的意义相同,用于告知USB核心如何在此时分配内存缓冲区。
如果usb_submit_urb()函数调用成功,即URB的控制权被移交给USB核心,该函数返回0,否则返回错误号。
4)提交由USB核心指定的USB主机控制器驱动。
5)被USB主机控制器处理,进行一次到USB设备的传送。
第4)~5)步由USB核心和主机控制器完成,不受USB设备驱动的控制。
6)当URB完成,URB将结束,USB主机控制器驱动通知USB设备驱动。
原文链接:https://www.cnblogs.com/gzqblogs/p/10159417.html(版权归原作者所有,侵删)


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|微软交流社区

GMT+8, 2025-1-7 04:42 , Processed in 0.073529 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表