LinuxGod

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

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

2023年2月26日 346点热度

后面学习了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大神网

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

最新 热点 随机
最新 热点 随机
如何安装便携式WiFi驱动程序?360wifi驱动的教程 CentOS云服务器搭建网站和CentOS搭建DNS解析服务 如何在linux上创建一个用户,减少不必要的沟通成本 如何在Linux系统中查看CPU信息使用lscpu命令行 linux服务器搭建ftp的6下安装vsftpd步骤及步骤 贵州工业职业技术学院求职意向期望工作地--诚聘英才 实验1Linux安装实验掌握虚拟机的使用 Linux系统tar命令的使用方法及使用命令教程 linux 开源nas系统 杰和科技NAS服务器媒体见面会在京召开 Android与Linux开发大不同 Linux系统软件安装包:自己动手,安装不用愁 车市新战局:汽车操作系统会复制智能手机的历史吗? Linux文件系统种类 如何卸载用源码包安装的软件?在线视频教程推荐 「职位」ASP.、PHP、Linux服务器集群开发 Torvalds:Linux内核开发的创新前景充满了热情 Linux文件系统的结构从终端窗口探索Linux目录树结构 卸载软件命令Linux.You linux软件开发如何入门?学习Linux步骤及学习方法介绍 14年Linux发行版的有趣历史观点
嵌入式Linux操作系统学习规划+LINUX路线,主攻江苏电信天翼校园客户端故障指引及解决办法(101)英特尔GMAGMA950显卡驱动程序/WIN8/8.1电信校园网宽带用USB数据线共享给电脑无线上网国防科大开源操作系统:它只是一个吉祥的象征10个常用Linux文本查看命令及其详细说明和使用示例Linux嵌入式系统内核裁剪与定制方法的介绍情况淘宝教育热卖C语言编程开发C++程序设计零基础入门课程从CPU、内存、硬盘、显卡等这些方面安装Linux系统的最低配置Linux通过chkconfig设置开机启动服务创建的几种常见方式(技术分析)Linux多线程的使用与操作系统的区别通常rar命令由一个主命令加若干选项(可选)构成RedHatLinux中自动运行程序中的应用linux 读写文件 关于Linux内核的神秘面纱,你知道几个?使用wget实用程序的有用命令行工具的使用怎么设置linux开机项自启动?方式是怎样的?嵌入式Linux应用层与驱动层要想学习关于Linux内核的交叉编译步骤和方法:步骤、方法STM32嵌入式linux开发流程及应用程序分析-STMlinux下有哪些文件在介绍lsof命令实用用法介绍?
Linux下查看版本号的命令转(组图) 解密Unix时间戳转换技巧,轻松读懂日期奥秘 人用extundelete软件突然恢复,官网据说还能恢复小文件 PCI总线带宽为132MB/s的PCI配置空间访问 安装Linux系统都是初学者的噩梦,安装失败也无所谓 为了配置Java开发环境,您可以按照以下步骤进行操作 如何查看Linux内核版本的命令(2种方法)? Linux上常见的目录软件常用命令汇总(二种) 微软考虑用Rust开发内核的Linux内核-Hartman STM32USB转串口指令的情况SSCom用起来不错了 Linux是个多用户多任务的分时操做系统,全部一个 Linux操作系统提权漏洞的解决办法有哪些?-八维教育 通过命令lsblk结果可判断sda3是个逻辑卷LV为根目录/ Linux系统用户系统上的三种类型的帐户的介绍 Unix或者类Unix文件系统中的每个文件(或者目录) IT之家UbuntuKylin14.1014.10壁纸预览(附下载地址) 关于Linux使用的一些常见问题解析:shell VPS服务器CPU配置解密,你知多少? VMwareUbuntuserver系统安装教程系统镜像下载地址—> Linux对用户和文件权限管理看了你就知道了
标签聚合
操作 linux服务器 文件 内核 软件 文件目录 应用 命令 虚拟机 linux系统
书籍
课程
技术群
技术干货大合集↓
  • 2023年9月 / 81篇
  • 2023年8月 / 93篇
  • 2023年7月 / 94篇
  • 2023年6月 / 90篇
  • 2023年5月 / 93篇
  • 2023年4月 / 90篇
  • 2023年3月 / 129篇
  • 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.