http://www.cnblogs.com/lifexy/p/7641602.html
在
上一章分析完USB总线驱动程序后,接下来开始写一个USB驱动:
本节目的:将USB鼠标的左键当作L按键,将USB鼠标的右键当作S按键,中键当作回车按键
参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动)
1、本节需要用到的宏如下:
struct usb_device_id usbmouse_id_table []=USB_INTERFACE_INFO(cl,sc,pr);
USB_INTERFACE_INFO()设置usb_driver驱动的id_table成员
cl:接口类,我们USB鼠标为HID类,所以填入0X03,也就是USB_INTERFACE_CLASS_HID
sc:接口子类为启动设备,填入USB_INTERFACE_SUBCLASS_BOOT
pr:接口协议为鼠标协议,填入USB_INTERFACE_PROTOCOL_MOUSE
struct usb_device *dev=interface_to_usbdev(intf);
通过usb_interface接口获取usb_device设备,为后面设置USB数据传输用
pipe=usb_rcvintpipe(dev,endpoint);
创建一个接受(rcv)中断(int)类型的端点管道(pipe),用来端点和数据缓冲区之间的连接,鼠标为接受中断类型
dev:usb_device设备结构体
endpoint:为端点描述符的成员endpoint->bEndpointAddress //端点地址
- 对于控制类型的端点管道使用: usb_sndctrlpipe()/usb_rcvctrlpipe()
- 对于实时类型的端点管道使用:usb_sndisocpipe()/usb_sndisocpipe()
- 对于批量类型的端点管道使用:usb_sndbulkpipe()/usb_rcvbulkpipe()
2、本节需要用到的函数如下:
usb_register(struct usb_driver *driver);
注册一个usb_driver驱动,然后内核会通过usb_driver的成员id.table函数匹配一次USB设备,匹配成功会调用USB_driver的成员.probe函数
usb_deregister(struct usb_driver *driver);
注销一个usb_driver驱动,在出口函数中写
*usb_buffer_alloc(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma);
分配一个usb缓冲区,该缓冲区的物理地址和虚拟地址的数据一致,分配成功返回一个char型缓冲区虚拟地址
*dev:usb_device设备结构体
size:分配的缓冲区大小,这里填端点描述符的成员endpoint->wMaxPacketSize //端点最大包长
mem_flags:分配内存的参数,这里填GFP_ATOMIC,表示从不睡眠
dma:分配成功则会返回一个DMA缓冲区物理地址
void usb_buffer_free(struct usb_device *dev,size_t size,void *addr,dma_addr_t dma);
注销分配的usb缓冲区,在usb_driver的disconnect成员函数中使用
addr:要注销的缓冲区虚拟地址
dma:要注销的DMA缓冲区虚拟地址
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
分配一个urb数据结构体,分配成功返回一个urb结构体
urb全称为usb request block,USB传输数据时,就是打包成urb结构体来传输
iso_packet:表示iso类型的包个数,这里我们不是iso类型包,直接填0
mem_flags:分配内存的参数,这里填入GFP_KERNEL,正常分配
其中urb结构体如下所示:
struct urb
{
... ...
struct usb_device *dev; //指向usb设备
struct usb_host_endpoint *ep; //指向端点的数据结构
unsigned int pipe; //指向端点管道(pipe), 本节的pipe通过usb_rcvintpipe()宏获取
int status; //状态,当status==0,表示数据被成功地收到/发送
unsigned int transfer_flags; //传输状态
... ...
/*以下两个缓冲区通过usb_buffer_alloc ()函数获取 */
//urb结构体默认的transfer_flags是URB_NO_SETUP_DMA_MAP ,也就是说没有提供DMA的缓冲区
//就会使用transfer_buffer虚拟地址缓冲区来当缓冲区
//当支持DMA缓冲区时,就需要手动设置transfer_flags =URB_NO_TRANSFER_DMA_MAP,并手动设置transfer_dma等于获取到的DMA物理地址
void *transfer_buffer; //虚拟缓冲区
dma_addr_t transfer_dma; //DMA物理缓冲区
... ...
};
void usb_free_urb(struct urb *urb);
释放申请的urb,在usb_driver的disconnect成员函数中使用
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);
初始化中断型端点的urb数据结构体
针对批量型端点的urb使用usb_fill_bulk_urb()
针对控制型端点的urb使用usb_fill_control_urb()
针对等时型端点的urb需要手动初始化
urb:指向要初始化的urb
dev:指向要传输的usb设备
pipe:要传输的端点管道,本节的pipe通过usb_rcvintpipe()宏获取
transfer_buffer:指向要传输数据的虚拟地址缓冲区
buffer_length:数据大小,这里填端点描述符的成员endpoint->wMaxPacketS //端点最大包长
complete_fn:数据传输完成后产生的中断函数
context:会放在urb->context结构成员中,用来给中断函数用,本节不需要,填NULL即可
interval:间隔时间,表示间隔多少时间读一次数据,填入endpoint->bInterval即可
int usb_submit_urb(struct urb *urb,gfp_t mem_flags);
提交urb到内核,初始化urb和中断函数退出时,都要重新提交一次,搞死内核初始化内存缓存等
void usb_kill_urb(struct urb *urb);
杀掉urb,在usb_driver的disconnect成员函数中使用
3、步骤如下:
首先先定义全局变量:usb_driver结构体,input_dev指针结构体,虚拟地址缓存区,DMA地址缓存区
3.1 在入口函数中
1) 通过usb_register()函数注册usb_driver结构体
3.2 在usb_driver的probe函数中
1)分配一个input_dev结构体
2)设置input_dev支持L、S、回车、3个按键事件
3)注册input_dev结构体
4)设置USB数据传输:
->4.1)通过usb_rcvintpipe创建一个接受中断类型的端点管道,用来端点和数据缓冲区之间的连接
->4.2)通过usb_buffer_alloc()申请USB缓冲区
->4.3)申请并初始化urb结构体,urb:用来传输数据
->4.4)因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址
->4.5)使用usb_submit_urb()提交urb
3.3 在鼠标中断函数中
1)判断缓存区数据是否改变,若改变则上传鼠标事件
2)使用usb_submit_urb()提交urb
3.4 在usb_driver的disconnect函数中
1)通过usb_kill_urb()杀掉提交到内核中的urb
2)释放urb
3)释放USB缓存区
4)注销input_device,释放input_device
3.5 在出口函数中
通过usb_deregister()函数注销usb_driver结构体
4、代码如下:
/*
* drivershidusbhidusbmouse.c
*/
#include
#include
#include
#include
#include
#include
static struct input_dev *uk_dev;
static char *usb_buf; //虚拟地址缓存区
static dma_addr_t usb_buf_phys;//DMA缓存区(物理地址)
static int len; //数据包长度
static struct urb *uk_urb; //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) },//只要接口类是HID,子类是BOOT启动设备类,协议是MOUSE鼠标协议,就能支持你
//{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
//数据存在buff完成后产生中断(complete_fn)
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("
");
#endif
/* USB鼠标数据含义
* data[1]:bit0-左键,1-按下,0-松开,L
* bit1-右键,1-按下,0-松开,S
* bit2-中键,1-按下,0-松开,ENTER
*/
if((pre_val & (1<<0)) != (usb_buf[1] & (1<<0)))//如果上一次的数据的bit0 不等于 这一次数据的bit0
{
/* 左键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[1] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if((pre_val & (1<<1)) != (usb_buf[1] & (1<<1)))//如果上一次的数据的bit0 不等于 这一次数据的bit0
{
/* 右键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[1] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if((pre_val & (1<<2)) != (usb_buf[1] & (1<<2)))//如果上一次的数据的bit0 不等于 这一次数据的bit0
{
/* 中键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[1] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[1]; //更新数据,储存这一次的数据,将与下一次的数据进行比较
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);//重新提交urb
}
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; //端点管道
printk("bcdUSB = %x
", dev->descriptor.bcdUSB);
printk("VID = 0x%x
", dev->descriptor.idVendor);
printk("PID = 0x%x
", dev->descriptor.idProduct);
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);//能产生重复类事件LLLLLLL
/* b.2 能产生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);//能产生按键类事件的L键
set_bit(KEY_S, uk_dev->keybit);//能产生按键类事件的S键
set_bit(KEY_ENTER, uk_dev->keybit);//能产生按键类事件的ENTER键
/* c. 注册 */
input_register_device(uk_dev);
/* d. 硬件相关的操作 */
/* 数据传输3要素:源,目的,长度 */
/* 源:USB设备的某个端点 */
//设置USB数据传输
//d.1 通过usb_rcvintpipe创建一个端点管道
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度: */
len = endpoint->wMaxPacketSize;//端点描述符的最大包大小
/* 目的: */
//d.2 通过usb_buffer_alloc申请USB缓冲区
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);//读数据到缓冲区,虚拟地址void *
/* 使用"3要素" */
//d.3 通过usb_alloc_urb和usb_fill_int_urb申请初始化urb结构体
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素"设置urb */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);//interval:间隔(查询的频率)
//因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址
uk_urb->transfer_dma = usb_buf_phys;//物理地址
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 提交URB */
usb_submit_urb(uk_urb, GFP_KERNEL);//提交urb
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
//printk("disconnect usbmouse
");//拔出鼠标
usb_kill_urb(uk_urb);//杀掉urb
usb_free_urb(uk_urb);//释放urb
//虚拟地址usb_buf,物理地址usb_buf_phys
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);//释放buffer
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
/* 1.分配/设置usb_driver */
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(void)
{
/* 2.注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void 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");
5、测试运行
5.1 重新设置编译内核(去掉默认的hid_usb驱动)
make menuconfig,进入menu菜单重新设置内核参数:
进入->Device Drivers->HID Devices
<>USB Human Interface Device(full HID)support //hid:人机交互的USB驱动,比如:鼠标,键盘等。
然后make uImage 编译内核
将新的触摸屏驱动模块放入nfs文件系统目录中
5.2 然后烧写内核,装载触摸屏驱动模块
如下图,当我们插上USB鼠标时,可以看到该VID和PID,和电脑上的鼠标的参数一样

vendor厂家的ID为0x62a,product设备的ID为0x4101。
5.3 使用hexdump命令来测试(hexdump /dev/event0)
第一列为 hexdump序列号,
第二和第三列为 秒,
第四和第五列为 微妙,
第六列为 键盘事件(0001)或者同步事件(0000),
第七列为 code(0026为左键,001f为右键,001c为中键),
第八列和第九列为 value(按下为0001 0000,松开0000 0000)
5.4 使用tty1进程测试,