S3C2440 USB鼠标驱动(十九)

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、代码如下:


/*
 * drivers\hid\usbhid\usbmouse.c
 */
#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;//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("\n");
#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\n", dev->descriptor.bcdUSB);
	printk("VID    = 0x%x\n", dev->descriptor.idVendor);
	printk("PID    = 0x%x\n", 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\n");//拔出鼠标
	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进程测试,

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值