LinuxGod.net
Linux大神网——精选每一篇高品质的技术干货
  1. 首页
  2. 开源快讯
  3. 正文

如何编写一个USB驱动外壳框架:如何搭建这样的一个

2023年2月26日 378点热度

后面学习了USB驱动的一些基础概念与重要的数据结构,这么到底怎么编撰一个USB驱动程序呢?编撰与一个USB设备驱动程序的方式和其他总线驱动方法类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标示来判定是否安装了硬件。其实,这种制造商和设备标示须要我们编撰进USB驱动程序中。

USB驱动程序仍然遵守设备模型——总线、设备、驱动。和I2C总线设备驱动编撰一样,所有的USB驱动程序都必须创建的主要结构体是structusb_driver,它们向USB核心代码描述了USB驱动程序。但这是个壳体,只是实现设备和总线的挂接,具体的USB设备是哪些样的,怎么实现的,例如一个字符设备,我们还需填写相应的文件操作插口,下边我们从外到里进行分析linux usb 驱动程序开发,学习怎么搭建这样的一个USB驱动壳体框架:

一、注册USB驱动程序

Linux的设备驱动,非常是这些hotplug的USB设备驱动,会被编译成模块,之后在须要时挂在到内核。所以USB驱动和注册与正常的模块注册、卸载是一样的,下边是USB驱动的注册与卸载:

static int __init usbmouse_as_key_init(void)
{ 
    int ret;
	//注册
	ret = usb_register(&usbmouse_as_key_driver);
	if (ret)     
         err("usb_register failed. Error number %d", ret); 
	return 0;
}
static void __exit 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设备驱动的模块加载函数通用的方式是在I2C设备驱动的模块加载函数中使用usb_register(struct*usb_driver)函数添加usb_driver的工作,而在模块卸载函数中借助usb_deregister(struct*usb_driver)做相反的工作。对比I2C设备驱动中的i2c_add_driver(&i2c_driver)与i2c_del_driver(&i2c_driver)。

structusb_driver是USB设备驱动,我们须要实现其成员函数:

//1.分配/设置usb_driver结构体
static struct usb_driver usbmouse_as_key_driver = {
	.owner 		= THIS_MODULE,
	.name		= "usbmouse_as_key",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table		= usbmouse_as_key_id_table,
};

从代码看来,usb_driver须要初始化五个数组:

模块的所有者THIS_MODULE

模块的名子usbmouse_as_key

probe函数usbmouse_as_key_probe

disconnect函数usbmouse_as_key_disconnect

id_tableusbmouse_as_key_id_table

最重要的其实是probe函数与disconnect函数,这个在前面详尽介绍linux usb 驱动程序开发,先谈一下id_table:

id_table是structusb_device_id类型,包含了一列该驱动程序可以支持的所有不同类型的USB设备。假如没有设置该变量,USB驱动程序中的侦测反弹该函数将不会被调用。对比I2C中structi2c_device_id*id_table,一个驱动程序可以对应多个设备linux重启命令,i2c示例:

static const struct i2c_device_id mpu6050_id[] = {      
    { "mpu6050", 0},      
    {}      

linux 串口驱动 开发_linux usb驱动程序开发_linux usb 驱动程序开发

};

usb子系统通过设备的productionID和vendorID的组合或则设备的class、subclass跟protocol的组合来辨识设备,并调用相关的驱动程序作处理。我们可以瞧瞧这个id_table究竟是哪些东西:

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

USB_INTERFACE_INFO是一个宏定义:

#define USB_INTERFACE_INFO(cl,sc,pr) 
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), 
	.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)

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

侦测和断掉函数剖析:

侦测函数:

static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	printk("found usbmouse!n");
	
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	interface = intf->cur_altsetting;
	
	endpoint = &interface->endpoint[0].desc;
	int pipe; //数据源
	/* 1.分配一个input_dev结构体 */
	uk_dev = input_allocate_device();
	if (!uk_dev)
		return -ENOMEM;
	/* 2.设置 */
	/* 2.1.能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);  //产生按键类事件
	set_bit(EV_REP, uk_dev->evbit); //产生重复类事件
	/* 2.2.能产生那些事件 */
	set_bit(KEY_S, uk_dev->keybit);  		//对应键盘L键
	set_bit(KEY_L, uk_dev->keybit);			//对应键盘S键
	set_bit(KEY_ENTER, uk_dev->keybit);	//对应键盘ENTER键
	
	/* 3.注册 */
	input_register_device(uk_dev);

linux usb驱动程序开发_linux 串口驱动 开发_linux usb 驱动程序开发

/* 4.硬件相关的操作 */ /* USB数据传输的三要素:源、目的、长度*/ /* 源:USB设备的某个端点 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 长度 */ len = endpoint -> wMaxPacketSize; /* 目的*/ usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys); /* 使用"三要素" */ /* 分配usb request block */ uk_urb = usb_alloc_urb(0, GFP_KERNEL); /* 使用三要素设置urb */ //endpoint->bInterval 表示查询间隔 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; }

当一个设备被安装而USB核心觉得该驱动程序应当处理时linux操作系统介绍,该驱动程序侦测函数被调用;

侦测函数应当检测传递给他的设备信息,确定驱动程序是否适宜该设备。当驱动程序由于某种诱因不应控制设备时,断掉函数被调用,它可以做一些清洁的工作。

系统会传递给侦测函数的信息是哪些呢?一个usb_interface*跟一个structusb_device_id*作为参数。她们分别是该USB设备的插口描述(通常会是该设备的第0号插口,该插口的默认设置也是第0号设置)跟它的设备ID描述(包括VendorID、ProductionID等)。

USB驱动程序应当初始化任何可能用于控制USB设备的局部结构体,它还应当把所需的任何设备相关信息保存到局部结构体中。诸如,USB驱动程序一般须要侦测设备对的端点地址和缓冲区大小,由于须要她们能够和端点通讯。

断掉函数:

当设备被拔出网桥时,usb子系统会手动地调用disconnect,它做的事情主要是和侦测函数的工作相反。

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	printk("disconnect usbmouse!n");
	
	struct usb_device *dev = interface_to_usbdev(intf);
	//杀死URB
	usb_kill_urb(uk_urb); 
	//释放URB
	usb_free_urb(uk_urb);

//释放usb buffer usb_buffer_free(dev, len, usb_buf, usb_buf_phys); //usb_buffer_free(struct usb_device * dev, size_t size, void * addr, dma_addr_t dma) //注销input_dev结构体 input_unregister_device(uk_dev); //释放input_dev结构体 input_free_device(uk_dev); }

USB恳求块:

USB设备驱动代码通过urb和所有的USB设备通信。urb用structurb结构描述(include/linux/usb.h)。

urb以一种异步的形式同一个特定USB设备的特定端点发送或接受数据。一个USB设备驱动可依照驱动的须要,分配多个urb给一个端点或重用单个urb给多个不同的端点。设备中的每位端点都处理一个urb队列,所以多个urb可在队列清空之前被发送到相同的端点。

一个urb的典型生命循环如下:

(1)被创建;

(2)被分配给一个特定USB设备的特定端点;

(3)被递交给USB核心;

(4)被USB核心递交给特定设备的特定USB主机控制器驱动;

(5)被USB主机控制器驱动处理,并传送到设备;

(6)以上操作完成后,USB主机控制器驱动通知USB设备驱动。

urb也可被递交它的驱动在任何时间取消;假如设备被移除,urb可以被USB核心取消。urb被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被手动释放。

struct urb  
{  
    /* 私有的:只能由usb核心和主机控制器访问的字段 */  
    struct kref kref; /*urb引用计数 */  
    spinlock_t lock; /* urb锁 */  
    void *hcpriv; /* 主机控制器私有数据 */  
    int bandwidth; /* int/iso请求的带宽 */  
    atomic_t use_count; /* 并发传输计数 */  
    u8 reject; /* 传输将失败*/  
      
    /* 公共的: 可以被驱动使用的字段 */  
    struct list_head urb_list; /* 链表头*/  
    struct usb_device *dev; /* 关联的usb设备 */  
    unsigned int pipe; /* 管道信息 */  
    int status; /* urb的当前状态 */  
    unsigned int transfer_flags; /* urb_short_not_ok | ...*/  
    void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */  
    dma_addr_t transfer_dma; /*用来以dma方式向设备传输数据的缓冲区 */  
    int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向缓冲区的大小 */  
                           
    int actual_length; /* urb结束后,发送或接收数据的实际长度 */  

    unsigned char *setup_packet; /* 指向控制urb的设置数据包的指针*/  
    dma_addr_t setup_dma; /*控制urb的设置数据包的dma缓冲区*/  
    int start_frame; /*等时传输中用于设置或返回初始帧*/  
    int number_of_packets; /*等时传输中等时缓冲区数据 */  
    int interval; /* urb被轮询到的时间间隔(对中断和等时urb有效) */  
    int error_count;  /* 等时传输错误数量 */  
    void *context; /* completion函数上下文 */  
    usb_complete_t complete; /* 当urb被完全传输或发生错误时,被调用 */  
    struct usb_iso_packet_descriptor iso_frame_desc[0];  
    /*单个urb一次可定义多个等时传输时,描述各个等时传输 */  
};  

1、创建和注销urb

structurb结构不能静态创建,必须使用usb_alloc_urb函数创建.函数原型:

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
//int iso_packets : urb 包含等时数据包的数目。如果不使用等时urb,则为0
//gfp_t mem_flags : 与传递给 kmalloc 函数调用来从内核分配内存的标志类型相同
//返回值          : 如果成功分配足够内存给 urb , 返回值为指向 urb 的指针. 如果返回值是 NULL, 则在 USB 核心中发生了错误, 且驱动需要进行适当清理

假如驱动早已对urb使用完毕,必须调用usb_free_urb函数,释放urb。函数原型:

void usb_free_urb(struct urb *urb);
//struct urb *urb : 要释放的 struct urb 指针

2、初始化urb

static inline void usb_fill_int_urb(struct urb *urb,                                                                                                         
                 struct usb_device *dev,  
                 unsigned int pipe,  
                 void *transfer_buffer,  
                 int buffer_length,  
                 usb_complete_t complete_fn,  
                 void *context,  
                 int interval);  
  
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);  
  
static inline void usb_fill_control_urb(struct urb *urb,  
                    struct usb_device *dev,  
                    unsigned int pipe,  
                    unsigned char *setup_packet,  
                    void *transfer_buffer,  
                    int buffer_length,  
                    usb_complete_t complete_fn,  

linux usb驱动程序开发_linux usb 驱动程序开发_linux 串口驱动 开发

void *context); //struct urb *urb :指向要被初始化的 urb 的指针 //struct usb_device *dev :指向 urb 要发送到的 USB 设备. //unsigned int pipe : urb 要被发送到的 USB 设备的特定端点. 必须使用前面提过的 usb_******pipe 函数创建 //void *transfer_buffer :指向外发数据或接收数据的缓冲区的指针.注意:不能是静态缓冲,必须使用 kmalloc 来创建. //int buffer_length :transfer_buffer 指针指向的缓冲区的大小 //usb_complete_t complete :指向 urb 结束处理例程函数指针 //void *context :指向一个小数据块的指针, 被添加到 urb 结构中,以便被结束处理例程函数获取使用. //int interval :中断 urb 被调度的间隔. //函数不设置 urb 中的 transfer_flags 变量, 因此对这个成员的修改必须由驱动手动完成

3、提交urb

一旦urb被正确地创建并初始化,它就可以递交给USB核心以发送出到USB设备.这通过调用函数usb_submit_urb实现:

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
//struct urb *urb :指向被提交的 urb 的指针 
//gfp_t mem_flags :使用传递给 kmalloc 调用同样的参数, 用来告诉 USB 核心如何及时分配内存缓冲
/*因为函数 usb_submit_urb 可被在任何时候被调用(包括从一个中断上下文), mem_flags 变量必须正确设置. 根据 usb_submit_urb 被调用的时间,只有 3 个有效值可用:
GFP_ATOMIC 
只要满足以下条件,就应当使用此值:
1.调用者处于一个 urb 结束处理例程,中断处理例程,底半部,tasklet或者一个定时器回调函数.
2.调用者持有自旋锁或者读写锁. 注意如果正持有一个信号量, 这个值不必要.
3.current->state 不是 TASK_RUNNING. 除非驱动已自己改变 current 状态,否则状态应该一直是 TASK_RUNNING .
GFP_NOIO 
驱动处于块 I/O 处理过程中. 它还应当用在所有的存储类型的错误处理过程中.
GFP_KERNEL 
所有不属于之前提到的其他情况
*/

在urb被成功递交给USB核心以后,直至结束处理类库函数被调用前,都不能访问urb结构的任何成员。

4、urb结束处理类库

假如usb_submit_urb被成功调用,并把对urb的控制权传递给USB核心,函数返回0;否则返回一个负的错误代码.假如函数调用成功,当urb被结束的时侯结束处理解释器会被调用一次.当这个函数被调用时,USB核心就完成了这个urb,并将它的控制权返回给设备驱动。

只有3种结束urb并调用结束处理解释器的情况:

(1)、urb被成功发送给设备,且设备返回正确的确认.假如这样,urb中的status变量被设置为0。

(2)、发生错误,错误值记录在urb结构中的status变量。

(3)、urb从USB核心unlink,这发生在要么当驱动通过调用usb_unlink_urb或则usb_kill_urb告知USB核心取消一个已递交的urb,或则在一个urb早已被递交给它时设备从系统中清除。

5、取消urb

使用以下函数停止一个早已递交给USB核心的urb:

voidusb_kill_urb(structurb*urb)

intusb_unlink_urb(structurb*urb);

假如调用usb_kill_urb函数,则urb的生命周期将被中止,这一般在设备从系统移除时,在断掉反弹函数(disconnectcallback)中调用。

对一些驱动,应该调用usb_unlink_urb函数来使USB核心停止urb,这个函数不会等待urb完全停止才返回。这对于在中断处理类库中或则持有一个载流子锁时去停止urb是很有用的,由于等待一个urb完全停止须要USB核心有使调用进程休眠的能力(wait_event()函数).

本文内容部份节选自:

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: usb接口 函数调用
最后更新:2023年2月26日

Linux大神网

每日更新,欢迎收藏♥ 不积跬步无以至千里,加油,共勉。

点赞
< 上一篇
下一篇 >

Linux大神网

每日更新,欢迎收藏♥
不积跬步无以至千里,加油,共勉。

最新 热点 随机
最新 热点 随机
Linux操作系统不仅仅端口-u:仅显示监听套接字(protocol) (知识点)Linux文件权限详解:执行权限 VMware过期Linux系统CentOS7下载安装1.77下载地址 红旗linux操作系统v11.0-红旗操作系统操作系统吗? 5yw红软基地驱动完美支持windows、mac、linux系统 轻松安装ZeroMQ:Ubuntu系统必备的解决方案 怎么在Win7系统虚拟机上安装LinuxUbuntu的技巧?在这里 Linux必备!安装RAR解压器,轻松解压文件! 虚拟机linux系统下载 什么是linuxLinux(/托瓦兹)的Linux内核 文件永久删除还能找回来吗?关于Win和Mac系统的恢复方法 Ubuntu16.04怎样安装系统更新和应用更新16.04 网卡 centos 启动 Linux选择选择时各企业所参考的依据:以下内容和Centos Linux内核内存检测工具系列中的一篇,配置以及典型应用 虚拟机安装Ubuntu操作系统-Ubuntu空间20G镜像下载 0渗透操作0x02绕过disable_functions插件(组图) 【好玩的网络-第2.5期】分配IP有哪些骚操作?小白保姆级教程 Windows远程连接工具、Ubuntu系统的安装软件及系统 Linux一模一样远程连接编辑的操作图是什么? DRAM中的虚拟地址空间的缓存简化内存管理(组图) 2.5创建和配置虚拟机2.5.1创建虚拟机双击桌面的VMwarePlayer
Linux文件路径查询方法,轻松获取文件位置Linux移植6410:从零开始的挑战!linux php安装 快速掌握Linux下PHP安装,轻松入门流行技术linux patch 文件 解决LinuxPatch文件10大疑难杂症,轻松应对!Ubuntu系统更改IP地址的简易教程,跟随以下步骤即可搞定!高效搭建Linux Android开发环境,轻松提升开发体验轻松掌握:如何查询Linux内核版本?Linux安装Matlab指南,快速掌握安装步骤Ubuntu 16安装网卡驱动教程,轻松解决网络连接问题Linux启动时,不使用图形界面可能更有效率!移植Linux,手机变身“大杀器”!详解8大关键操作Linux更高效,掌握启动终端快捷键!纯Python库实现上面介绍的HTML转换为PDF的软件Fedora 13服务器配置指南:9个实用技巧全解析9种方法轻松获取Linux版本,快速掌握系统信息!博客韦东山freeRTOS系列教程:入门文档教程+进阶视频教程轻松掌握linux ldd命令的技巧,成为高效开发者与管理员Linux VPS安全检测,保障服务器稳定运行!Linux下高效开发必备:配置Tomcat和JDK分析Linux中的I2C驱动程序框架核心结构(i2c-bus结构)
Java对象持久化技术详解--孙卫琴Tomcat与JavaWeb开发技术详解 新手重装win7系统步骤和教程,你知道怎么操作吗 (编程)编程的更进一步更进一步.1--2 你没有装系统,你装了系统就可以用了 Linux上的防火墙和地址命令使用WinSCP 自定义登录session时要注意的几个小细节,你知道吗? 如何增加一块硬盘虚拟机插硬盘分区格式化一、命令查看设备情况 如何实现局域网内的文件夹共享?全面详细详细地向 Ubuntu18验证dhcp服务器配置是否成功?.04的配置 UNIX网络编程卷1:套接字联网API第3版pdf百度网盘下载地址 服务器上安装linux系统的具体操作过程-如何选择系统? 如需最新mainline内核版本下载并安装新版本删除旧内核 问在线客服扫码选项配置选项选项 本发明可动态检测C/C++内存泄露的方法,所述方法 Linux下的安装zip解压功能及安装unzip教程 Linux进程中的基本概念程序是什么?进程的定义? 如何远程连接服务器桌面?如何保证在ssh故障情况下还能够访问到服务器? Linux内核6.0正式版发布:引入了新的伪伪设备 Ubuntu系统更改IP地址的简易教程,跟随以下步骤即可搞定! TCL电视看电视直播方法购买智能电视的热卖,不少
标签聚合
命令模式 linux系统 软件 文件目录 sudo 电脑 命令 虚拟机 linux服务器 linux脚本
书籍
课程
技术群
技术干货大合集↓
  • 2023年3月 / 114篇
  • 2023年2月 / 84篇
  • 2023年1月 / 161篇
  • 2022年12月 / 187篇
  • 2022年11月 / 76篇
友情链接:

Linux书籍 | Linux命令 | Linux系统 | RHCE红帽认证 | Linux软件 | Linux教程 | CentOS系统 | Linux内核 | Linux服务器 | Linux大神 | IT资源

COPYRIGHT © 2023 linuxgod.net ALL RIGHTS RESERVED.