后面学习了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},
{}

};
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);

/* 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,

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()函数).
本文内容部份节选自: