前一章节对linux内核中USB驱动程序的框架进行了分析,这一节以USB鼠标为对象,编写USB鼠标驱动程序。
实验内容:编写USB鼠标设备驱动程序。并将USB鼠标左键定义为"L"功能,右键定义为"S"功能,中间滚轮键定义为"ENTER"功能,方便测试。
参考内核中/driver/hid/usbhid/usbmouse.c文件。
从入口函数usbmouse_as_key_init开始。按照之前编写字符驱动程序的惯例,入口函数中需要实现usb_driver结构体的分配,配置、注册以及和硬件相关的操作。
那么,首先需要定义一个usb_driver结构体。
其中probe函数是整个驱动程序的重点,后面再讲。disconnect函数是当设备断开连接时调用,后面再讲。
id_table用于保存usb设备的id信息,其结构体定义如下:
这里usbmouse_as_key_id_table的定义如下:
定义中使用宏USB_INTERFACE_INFO,具体定义如下:
对宏USB_INTERFACE_INFO进行展开,usbmouse_as_key_id_table [] ={
{
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO,
.bInterfaceClass = USB_INTERFACE_CLASS_HID,
.bInterfaceSubClass = USB_INTERFACE_SUBCLASS_BOOT,
.bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE },
},即我们编写的USB鼠标驱动的支持接口类是USB_INTERFACE_CLASS_HID:0x03,支持的接口子类是USB_INTERFACE_SUBCLASS_BOOT:0x01,接口协议为USB_INTERFACE_PROTOCOL_MOUSE:0x03。
probe函数是整个驱动的核心,实现设备的分配、设置、注册以及硬件相关的操作。
(1)分配输入设备
这里对USB鼠标是按照按键类输入事件进行处理的,因此需要分配一个输入设备uk_dev。
static struct input_dev *uk_dev;
uk_dev = input_allocate_device();
(2)设置事件
USB鼠标产生按键类事件,并且支持长按重复操作。左键按下对应"L"功能,邮件按下对应"S"功能,中键对应"ENTER"功能。
(3)注册
调用input_register_device函数注册输入设备uk_dev。
(4)与硬件相关的操作
USB设备驱动的硬件操作与之前的LCD、按键等有所区别,不是直接操作寄存器的,这里是调用USB总线驱动程序的接口来实现数据的访问。
urb是linux内核中USB驱动程序中实现数据传输的一种数据结构,全称USB request block,其定义如下:
-
struct urb
-
{
-
/* private: usb core and host controller only fields in the urb */
-
struct kref kref; /* reference count of the URB */
-
spinlock_t lock; /* lock for the URB */
-
void *hcpriv; /* private data for host controller */
-
atomic_t use_count; /* concurrent submissions counter */
-
u8 reject; /* submissions will fail */
-
-
/* public: documented fields in the urb that can be used by drivers */
-
struct list_head urb_list; /* list head for use by the urb's
-
* current owner */
-
struct usb_device *dev; /* (in) pointer to associated device */
-
unsigned int pipe; /* (in) pipe information */
-
int status; /* (return) non-ISO status */
-
unsigned int transfer_flags; /* (in) URB_SHORT_NOT_OK | ...*/
-
void *transfer_buffer; /* (in) associated data buffer */
-
dma_addr_t transfer_dma; /* (in) dma addr for transfer_buffer */
-
int transfer_buffer_length; /* (in) data buffer length */
-
int actual_length; /* (return) actual transfer length */
-
unsigned char *setup_packet; /* (in) setup packet (control only) */
-
dma_addr_t setup_dma; /* (in) dma addr for setup_packet */
-
int start_frame; /* (modify) start frame (ISO) */
-
int number_of_packets; /* (in) number of ISO packets */
-
int interval; /* (modify) transfer interval
-
* (INT/ISO) */
-
int error_count; /* (return) number of ISO errors */
-
void *context; /* (in) context for completion */
-
usb_complete_t complete; /* (in) completion routine */
-
struct usb_iso_packet_descriptor iso_frame_desc[0];
-
/* (in) ISO ONLY */
-
};
使用urb实现数据传输的过程如下:
-
分配一个urb结构体
调用usb_alloc_urb函数,分配一个uk_urb结构体空间,并初始化结构体。
-
设置urb结构体
USB2.0中定义了控制、中断、批量、同步四种传输方式。Linux内核中对应这四种传输方式定义了对应的urb的接口函数。
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)
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_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)
usb鼠标因其数据量少,对传输的实时性要求较高,因此选用中断传输的方式。这里重点讲解usb_fill_int_urb函数。
urb:需要设置的urb结构体;
dev:urb要发送到的usb设备;
pipe:urb要被发送到的USB设备的特定端点,使用usb_rcvintpipe函数或者usb_sndintpipe函数创建端点;
transfer_buffer:指向发送数据或者接收数据的虚拟缓冲区;
buffer_length:transfer_buffer数据缓冲区的长度;
complete_fn:指向urb完成时被调用的完成处理函数;
context:完成处理函数的上下文;
interval:urb调度的间隔时间。
-
提交urb
调用usb_submit_urb函数,将urb提交到usb主机控制器驱动程序。
在urb完成处理函数usbmouse_as_key_irq中执行如下操作:
(1)判断按键是否是否发生变化,若变化,则上传对应的按键事件。
(2)重新提交urb。
usbmouse_as_key_disconnect函数中完成如下操作:
-
调用usb_kill_urb函数杀死urb;
-
调用usb_free_urb函数释放urb空间;
-
调用usb_buffer_free释放缓冲区;
-
调用input_unregister_device,卸载设备;
-
调用input_free_device,释放dev设备。
代码如下:
-
#include <linux/kernel.h>
-
#include <linux/slab.h>
-
#include <linux/module.h>
-
#include <linux/init.h>
-
#include <linux/usb/input.h>
-
#include <linux/hid.h>
-
-
static struct input_dev *uk_dev;
-
static char * usb_buf;
-
static dma_addr_t usb_buf_phys;
-
static int len;
-
static struct urb *uk_urb;
-
-
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) },
-
};
-
-
static void usbmouse_as_key_irq(struct urb *urb)
-
{
-
static unsigned char pre_val ;
-
-
#if 0
-
int i;
-
static int cnt = 0;
-
printk("data cnt %d:",++cnt);
-
-
for(i = 0; i < len; i++)
-
{
-
printk("%02x", usb_buf[i]);
-
}
-
printk("\n");
-
#endif
-
-
/*USB鼠标数据含义
-
* data[0]: bit0-左键 ,1-按下,0-松开
-
* bit1-右键 ,1-按下,0-松开
-
* bit2-中键 ,1-按下,0-松开
-
*/
-
if((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
-
{
-
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0))?1:0);
-
input_sync(uk_dev);
-
}
-
if((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
-
{
-
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1))?1:0);
-
input_sync(uk_dev);
-
}
-
if((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
-
{
-
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2))?1:0);
-
input_sync(uk_dev);
-
}
-
-
pre_val = usb_buf[0];
-
-
/* 重新提交urb */
-
usb_submit_urb(uk_urb, GFP_KERNEL);
-
}
-
-
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
-
{
-
struct usb_device *dev = interface_to_usbdev(intf);
-
struct usb_host_interface *interface;
-
struct usb_endpoint_descriptor *endpoint;
-
int pipe;
-
-
interface = intf->cur_altsetting;
-
endpoint = &interface->endpoint[0].desc;
-
-
/* a、分配一个input_dev */
-
uk_dev = input_allocate_device();
-
-
/* b、设置 */
-
/* b.1 能产生哪类事件 */
-
set_bit(EV_KEY, uk_dev->evbit);
-
set_bit(EV_REP, uk_dev->evbit);
-
/* b.2 能产生哪些事件 */
-
set_bit(KEY_L, uk_dev->keybit);
-
set_bit(KEY_S, uk_dev->keybit);
-
set_bit(KEY_ENTER, uk_dev->keybit);
-
-
/* c、注册 */
-
input_register_device(uk_dev);
-
-
/* d、硬件相关的操作 */
-
/* 数据传输三要素: 源、目的、长度 */
-
/* 源: */
-
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
-
/* 长度 */
-
len = endpoint->wMaxPacketSize;
-
/* 目的 */
-
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
-
-
/* 分配urb: usb request block */
-
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
-
/* 使用"数据传输三要素"设置urb */
-
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;
-
}
-
-
-
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
-
{
-
struct usb_device *dev = interface_to_usbdev(intf);
-
-
usb_kill_urb(uk_urb);
-
usb_free_urb(uk_urb);
-
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
-
input_unregister_device(uk_dev);
-
input_free_device(uk_dev);
-
}
-
-
-
static struct usb_driver usbmouse_as_key_driver = {
-
.name = "usbmouse_as_key",
-
.probe = usbmouse_as_key_probe,
-
.disconnect = usbmouse_as_key_disconnect,
-
.id_table = usbmouse_as_key_id_table,
-
};
-
-
static int usbmouse_as_key_init()
-
{
-
usb_register(&usbmouse_as_key_driver);
-
return 0;
-
}
-
-
static void usbmouse_as_key_exit()
-
{
-
usb_deregister(&usbmouse_as_key_driver);
-
}
-
-
module_init(usbmouse_as_key_init);
-
module_exit(usbmouse_as_key_exit);
-
-
MODULE_LICENSE("GPL");
测试截图如下: