1. 如何编写USB鼠标驱动
结合十四、Linux驱动之USB驱动分析中的分析,我们开始写一个USB鼠标驱动。
USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动,通常,对于USB这种标准化的设备,内核已经将主机控制器的驱动编写好了,设备上的Gadget驱动通常只运行固件程序而不是基于Linux, 所以驱动工程师的主要工作就是编写主机端的USB设备驱动。也就是说在开发板上接上一个USB鼠标后,我们不必关系USB鼠标是如何检测事件如何发送数据,只需要懂得在主机端(即对应开发板)如何接收USB鼠标发来的数据与知道接收到的数据的含义即可。
1.1 如何接收USB鼠标发来的数据
linux 内核中的USB代码和所有的USB设备通讯使用称为USB请求块(USB request block,urb)。URB是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的sk_buff结构体。
URB数据结构如下:
struct urb {
/* 私有的:只能由USB 核心和主机控制器访问的字段 */
struct kref kref; /*urb 引用计数 */
void *hcpriv; /* 主机控制器私有数据 */
atomic_t use_count; /* 并发传输计数 */
u8 reject; /* 传输将失败*/
int unlink; /* unlink 错误码 */
/* 公共的: 可以被驱动使用的字段 */
struct list_head urb_list; /* 链表头*/
struct usb_anchor *anchor;
struct usb_device *dev; /* 关联的USB 设备 */
struct usb_host_endpoint *ep;
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 被完全传输或发生错误时,被调用 */
/*单个URB 一次可定义多个等时传输时,描述各个等时传输 */
struct usb_iso_packet_descriptor iso_frame_desc[0];
};
1.1.1 创建URB
创建URB结构体的函数为:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
参数:
iso_packets:是这个urb 应当包含的等时数据包的数目,若为0 表示不创建等时数据包。
mem_flags:参数是分配内存的标志,和kmalloc()函数的分配标志参数含义相同。如果分配成功,该函数返回一个urb 结构体指针,否则返回0。
usb_alloc_urb()的“反函数”为:
void usb_free_urb(struct urb *urb); 注销urb
1.1.2 填充URB
在十四、Linux驱动之USB驱动分析中介绍了USB数据有四种传输类型,填充URB函数(初始化特定USB设备的特定端点)也分为4种:
(1) 中断类型(USB鼠标使用的就是该类型)
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,
void *context,
int interval);
参数:
urb:指向要被初始化的urb的指针;
dev:指向这个urb要被发送到的USB设备;
pipe:是这个urb要被发送到的USB设备的特定端点;
transfer_buffer:是指向发送数据或接收数据的缓冲区的指针,和urb一样,它也不能是静态缓冲区,必须使用kmalloc()来分配;
buffer_length:是transfer_buffer指针所指向缓冲区的大小;
complete:指针指向当这个urb完成时被调用的完成处理函数;
context:是完成处理函数的“上下文”;
interval:是这个urb应当被调度的间隔。
(2) 批量类型
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,
void *context);
除了没有对应于调度间隔的interval参数以外,该函数的参数和usb_fill_int_urb()函数的参数含义相同。上述函数参数中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函数来创建。
(3) 控制类型
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,
void *context);
除了增加了新的setup_packet参数以外,该函数的参数和usb_fill_bulk_urb()函数的参数含义相同。setup_packet参数指向即将被发送到端点的设置数据包。
(4) 等时类型
针对等时型端点的urb,需要手动初始化。
1.1.3 提交URB
在完成第上面两步的创建和初始化urb后,urb便可以提交给USB核心,通过usb_submit_urb()函数来完成,函数原型如下:
int usb_submit_urb(struct urb *urb, int mem_flags);
参数:
urb:是指向urb 的指针;
mem_flags:与传递给kmalloc()函数参数的意义相同,它用于告知USB 核心如何在此时分配内存缓冲区。
usb_submit_urb()在原子上下文和进程上下文中都可以被调用,mem_flags变量需根据调用环境进行相应的设置,如下所示:
GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及urb完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将current→state 修改为非 TASK_RUNNING 时,应使用此标志;
GFP_NOIO:在存储设备的块I/O和错误处理路径中,应使用此标志;
GFP_KERNEL:如果没有任何理由使用GFP_ATOMIC 和GFP_NOIO,就使用GFP_KERNEL。
在提交urb到USB核心后,直到完成函数被调用之前,不要访问urb中的任何成员。如果usb_submit_urb()调用成功,即urb的控制权被移交给USB核心,该函数返回0,否则返回错误号。
1.1.4 URB处理完成
完成上面的步骤,当USB鼠标被按下,将会触发usb_fill_int_urb()填充URB函数中第6个参数传入的urb处理完成函数,在该函数里面对数据处理,就能得到USB鼠标的操作数据了。
1.2 接收到数据的含义
接收到的数据存在usb_fill_int_urb()填充URB函数中的第4个参数传入的缓冲区(使用kmalloc()来分配)中,鼠标操作数据都存在该缓冲区内,缓冲区内数据含义如下:
bit0表示鼠标左键, 该位为1表示按下, 为0表示松开
bit1表示鼠标右键, 该位为1表示按下, 为0表示松开
bit2表示鼠标中键, 该位为1表示按下, 为0表示松开
1.3 使用输入子系统进行上报事件
在urb处理完成函数中使用输入子系统将事件上报。
2. 编写代码
2.1 代码框架
代码的流程框架如下:
2.1.1 在入口函数中
通过usb_register()函数注册usb_driver结构体。
2.1.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
2.1.3 在鼠标中断函数中
1. 判断缓存区数据是否改变,若改变则上传鼠标事件
2. 使用usb_submit_urb()提交urb
2.1.4 在usb_driver的disconnect函数中
1. 通过usb_kill_urb()杀掉提交到内核中的urb
2. 释放urb
3. 释放USB缓存区
4. 注销input_device,释放input_device
2.1.5 在出口函数中
通过usb_deregister()函数注销usb_driver结构体。
2.2 编写代码
USB鼠标驱动程序(鼠标按键模拟键盘按键l、s、回车键)如下:
/*
* 参考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;
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) },
{ } /* Terminating entry */
};
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val;
/* USB鼠标数据含义
* data[1]: bit0-左键, 1-按下, 0-松开
* bit1-右键, 1-按下, 0-松开
* bit2-中键, 1-按下, 0-松开
*
*/
if ((pre_val & (1<<0)) != (usb_buf[1] & (1<<0)))
{
/* 左键发生了变化,上报键值KEY_L */
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)))
{
/* 右键发生了变化,上报键值KEY_S*/
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)))
{
/* 中键发生了变化,上报键值KEY_ENTER */
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);
}
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. 硬件相关操作 */
/* 数据传输3要素: 源,目的,长度 */
/* 源: USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度: */
len = endpoint->wMaxPacketSize;
/* 目的: */
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 分配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);
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);
//printk("disconnect usbmouse!\n");
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);
}
/* 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");
Makefile代码如下:
KERN_DIR = /work/system/linux-2.6.22.6 //内核目录
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += usbmouse_as_key.o
3. 测试
内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10
3.1 配置内核
1. 重新配置内核,去掉内核自带的usbmouse鼠标驱动。在linux-2.6.22.6内核目录下执行:
make menuconfig
2. 配置步骤如下:
Device Drivers --->
HID Devices --->
< > USB Human Interface Device (full HID) support
3.2 重烧内核
1. 编译内核与模块
make uImage
2. 将linux-2.6.22.6/arch/arm/boot下的uImage烧写到开发板,网络文件系统启动。
3.3 测试
首先编译自己写的USB鼠标驱动。在驱动文件目录下执行:
make
在开发板上执行:
insmod usbmouse_as_key.ko:
方法1:
cat /dev/tty1 (此时按下鼠标左键、右键、中键,串口就会有显示“ls”了)
方法2:
hexdump /dev/event0 (/dev/event0为对应的usb驱动)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)