前一章节对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,其定义如下:
structurb
{
/* private: usb core and host controller only fields in the urb */
structkref 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 */
structlist_head urb_list; /* list head for use by the urb's
* current owner */
structusb_device *dev; /* (in) pointer to associated device */
unsigned intpipe; /* (in) pipe information */
intstatus; /* (return) non-ISO status */
unsigned inttransfer_flags; /* (in) URB_SHORT_NOT_OK | ...*/
void*transfer_buffer; /* (in) associated data buffer */
dma_addr_t transfer_dma; /* (in) dma addr for transfer_buffer */
inttransfer_buffer_length; /* (in) data buffer length */
intactual_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 */
intstart_frame; /* (modify) start frame (ISO) */
intnumber_of_packets; /* (in) number of ISO packets */
intinterval; /* (modify) transfer interval
* (INT/ISO) */
interror_count; /* (return) number of ISO errors */
void*context; /* (in) context for completion */
usb_complete_t complete; /* (in) completion routine */
structusb_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
#include
#include
#include
#include
#include
staticstructinput_dev *uk_dev;
staticchar* usb_buf;
staticdma_addr_t usb_buf_phys;
staticintlen;
staticstructurb *uk_urb;
staticstructusb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
};
staticvoidusbmouse_as_key_irq(structurb *urb)
{
staticunsigned charpre_val ;
#if 0
inti;
staticintcnt = 0;
printk("data cnt %d:",++cnt);
for(i = 0; 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);
}
staticintusbmouse_as_key_probe(structusb_interface *intf, conststructusb_device_id *id)
{
structusb_device *dev = interface_to_usbdev(intf);
structusb_host_interface *interface;
structusb_endpoint_descriptor *endpoint;
intpipe;
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);
return0;
}
staticvoidusbmouse_as_key_disconnect(structusb_interface *intf)
{
structusb_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);
}
staticstructusb_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,
};
staticintusbmouse_as_key_init()
{
usb_register(&usbmouse_as_key_driver);
return0;
}
staticvoidusbmouse_as_key_exit()
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
测试截图如下: