Linux USB 驱动开发(三)—— 编写USB 驱动程序

前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。当然,这些制造商和设备标识需要我们编写进USB 驱动程序中。

USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的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)
struct usb_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_table                 usbmouse_as_key_id_table

最重要的当然是probe函数与disconnect函数,这个在后面详细介绍,先谈一下id_table:

 id_table 是struct usb_device_id 类型,包含了一列该驱动程序可以支持的所有不同类型的USB设备。如果没有设置该变量,USB驱动程序中的探测回调该函数将不会被调用。对比I2C中struct i2c_device_id *id_table,一个驱动程序可以对应多个设备,i2c 示例:

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

usb子系统通过设备的production ID和vendor ID的组合或者设备的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核心认为该驱动程序应该处理时,该驱动程序探测函数被调用;

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

系统会传递给探测函数的信息是什么呢?一个usb_interface * 跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认设置也是第0号设置)跟它的设备ID描述(包括Vendor ID、Production ID等)。

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用 struct urb 结构描述(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

struct urb 结构不能静态创建,必须使用 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:

void usb_kill_urb(struct urb *urb)
int usb_unlink_urb(struct urb *urb);

如果调用usb_kill_urb函数,则urb 的生命周期将被终止,这通常在设备从系统移除时,在断开回调函数(disconnect callback)中调用。

对一些驱动,应当调用usb_unlink_urb函数来使USB核心停止urb,这个函数不会等待urb 完全停止才返回。这对于在中断处理例程中或者持有一个自旋锁时去停止urb是很有用的,因为等待一个urb完全停止需要 USB核心有使调用进程休眠的能力(wait_event()函数).

本文内容部分节选自:http://blog.csdn.net/zqixiao_09/article/details/50986965

转载于:https://my.oschina.net/cht2000/blog/1073891

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值