学习目的:
- 编写usb鼠标驱动程序,模拟l、s、enter按键值按下
前面对Linux中USB层次进行了简单分析,了解到内核中USB驱动分为两类:USB主机控制器驱动程序(Host Controller Driver)、USB设备驱动程序(USB device drivers)。USB主机控制器驱动程序由内核实现,提供访问USB设备的接口,它是一个“数据通道”,至于这些数据有什么作用,这要靠上层的USB设备驱动程序来解释。USB设备驱动程序使用USB核心层提供的接口来访问USB设备,不需要关心主机控制器和设备如何进行通信,只需实现usb设备功能使用程序,这部分就是驱动编写者根据功能要求来完成的工作。
编写USB设备驱动程序主要可以概括为以下几个步骤:
① 填充ubs_driver结构体
② 完成usb_driver结构体成员函数
③ 向内核注册usb_driver
下面根据上述的几个步骤来完成我们今天的usb鼠标驱动程序的编写,实现当鼠标左键按下模拟键盘上L按键值,鼠标右键按下模拟键盘S键值,滚轮键按下模拟键盘enter键值。这个驱动程序中不仅用到了usb设备驱动编写相关方面知识,还需要对驱动中输入子系统模型有所了解
1、填充usb_driver结构体
static struct usb_driver usb_drv = { .name = "usbmouse", .probe = usb_drv_probe, .disconnect = usb_drv_disconnect, .id_table = usb_drv_id_table, };
.probe函数:probe函数在插入的usb设备匹配到支持的驱动程序时,被自动调用,一般用来分配所需要的资源
.disconnect函数:disconnect函数在插入usb设备拔出时,被自动调用,一般用来释放分配的资源
.id_table:usb设备和设备驱动通过id_table进行匹配,id_table中包含当前驱动所支持的设备信息
2、usb_driver结构体成员实现
2.1 usb_drv_probe函数
static int usb_drv_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; /* 分配一个input_dev */ usb_input_dev = input_allocate_device();--------------->① /* 能产生哪一类事件 */ set_bit(EV_KEY, usb_input_dev->evbit);----------------->② set_bit(EV_REP, usb_input_dev->evbit); /* 能产生该类事件的那些事件 */ set_bit(KEY_L, usb_input_dev->keybit); set_bit(KEY_S, usb_input_dev->keybit); set_bit(KEY_ENTER, usb_input_dev->keybit); /* 注册一个input_dev */ input_register_device(usb_input_dev); /* 数据传输3要素:源-目的-长度 */ /* 源:USB设备的某个端点 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);----->③ /* 长度 */ len = endpoint->wMaxPacketSize;----------------------------->④ /* 目的 */ usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);------------>⑤ uk_urb = usb_alloc_urb(0, GFP_KERNEL); usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usb_drv_irq, NULL, endpoint->bInterval);------------->⑥ uk_urb->transfer_dma = usb_buf_phys; uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; usb_submit_urb(uk_urb, GFP_KERNEL);------------------------>⑦ return 0; }
usb_drv_probe函数是这个驱动程序的核心,当usb设备的接口和这个驱动程序的id_table匹配成功时,自动被调用。
函数内分配了input_dev结构体,用来支持产生输入事件;通过usb核心层提供函数,获取与该驱动匹配成功的设备接口的端点信息,并根据这些信息设置了urb,并向主机控制器请求数据传输
① 分配了input_dev结构体
② 设置输入设备可以产生按键类和重复类事件,并支持上报按键类事件中的l、s、enter键状态信息
③ 获取usb通信所需要的地址信息,设备地址信息+端点号,从端点读取数据
⑤ 获取端点一次数据通信可以传输的最大数据长度
⑥ 填充urb,鼠标和usb主机通信使用的是中断方式的数据传输。urb(usb request block)可以看成是设备驱动对主机控制器如何进行数据传输的描述,包括数据传输的三要素,源、目的、输出长度。这里我们使用到的传输方式是周期性的检测驱动匹配的设备的接口的某个端点中是否有数据,如果有数据,就从中读取一定长度的数据,将数据存放到我们指定的内存地址中
⑦ urb数据填充完成后,还需要调用usb_submit_urb函数告诉主机控制器自己的请求,此时控制权将交给主机控制器驱动程序,主机控制器根据提交的urb信息进行数据传输。
注:usb中断方式数据传输不是真正意义上的硬件中断请求传输,而是通过endpoint->bInterval指定时间,在指定时间内主机控制器去查询端点内是否有数据可供读取
2.1.1 usb_drv_irq函数
static void usb_drv_irq(struct urb *urb) { /* *bit[1] 鼠标 按键值 * |--->1 左键----->s * |--->2 右键----->l *bit[6] * |--->1 中间键-->enter */ static char old_ls = 0, old_ent = 0; if(usb_buf[1] != old_ls) { if(usb_buf[1]&0x01 || old_ls&0x01) { input_report_key(usb_input_dev, KEY_L, usb_buf[1] & 0x01); } else if(usb_buf[1]&0x02 || old_ls&0x02) { input_report_key(usb_input_dev, KEY_S, usb_buf[1] & 0x02); } input_sync(usb_input_dev); old_ls = usb_buf[1]; } else if(usb_buf[6] != old_ent) { input_report_key(usb_input_dev, KEY_ENTER, usb_buf[6] & 0x01); input_sync(usb_input_dev); old_ent = usb_buf[6]; } usb_submit_urb(uk_urb, GFP_KERNEL); }
usb_drv_irq函数是urb结构中设置的数据传输完成时调用的处理函数,这个函数是当数据从源到目的地址传输完成时,被主机控制器调用的
usb_drv_irq函数根据解析usb主机控制器获取的数据,判断鼠标左键、右键、滚轮键是否被按下,如果其中的一个键被按下,将通过输入子系统上报数据状态信息
注意:usb鼠标传输的数据的信息中,哪一字节,哪一位代表鼠标的那些动作状态,需要根据自己鼠标特性去设置。最简单的是打印出从端点中一次读取的数据信息,多次尝试,将动作状态和数据信息位进行匹配
2.2 usb_drv_id_table
static struct usb_device_id usb_drv_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* Terminating entry */ };
usb_device_id结构体类型数组,用来存放匹配可支持设备的相关信息,此处使用USB_INTERFACE_INFO宏对成员进行的初始化
USB_INTERFACE_CLASS_HID:匹配USB设备接口类型须为人机交互类设备
USB_INTERFACE_SUBCLASS_BOOT:匹配USB设备接口子类须为boot
USB_INTERFACE_PROTOCOL_MOUSE:匹配USB设备接口协议须为鼠标
2.3 usb_drv_disconnect函数
static void usb_drv_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); usb_kill_urb(uk_urb);------------------------->① input_unregister_device(usb_input_dev);------->② usb_free_urb(uk_urb);------------------------->③ usb_free_coherent(dev, len, NULL, usb_buf_phys);---->④ }
disconnect函数在拔出设备时被自动调用,用来释放驱动中分配的数据信息
① 取消提交的urb请求
② 注销注册的输入子系统设备
③ 释放动态分配urb结构内存
④ 释放动态分配用于存放usb传输数据的内存
3、注册usb_driver
static int usb_drv_init(void) { usb_register(&usb_drv); return 0; }
usb_drv_init驱动入口函数,usb_register向内核注册usb驱动
4、测试
1)使用insmod加载驱动程序
2)插入鼠标设备,执行exec 0</dev/tty1让系统标准输入来tty1设备,这样鼠标按下后上报按键值可以显示在设备终端上
3) 依次按下鼠标左键-》右键-》滚轮键,看终端上是否显示ls,以及是否执行了ls命令
完整驱动程序
#include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/init.h> #include <linux/usb/input.h> #include <linux/usb/hcd.h> #include <linux/hid.h> static struct input_dev *usb_input_dev; static char *usb_buf; static dma_addr_t usb_buf_phys; static struct urb *uk_urb; static int len; static void usb_drv_irq(struct urb *urb) { /* *bit[1] 鼠标 按键值 * |--->1 左键----->s * |--->2 右键----->l *bit[6] * |--->1 中间键-->enter */ #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(" "); usb_submit_urb(uk_urb, GFP_KERNEL); #else static char old_ls = 0, old_ent = 0; if(usb_buf[1] != old_ls) { if(usb_buf[1]&0x01 || old_ls&0x01) { input_report_key(usb_input_dev, KEY_L, usb_buf[1] & 0x01); } else if(usb_buf[1]&0x02 || old_ls&0x02) { input_report_key(usb_input_dev, KEY_S, usb_buf[1] & 0x02); } input_sync(usb_input_dev); old_ls = usb_buf[1]; } else if(usb_buf[6] != old_ent) { input_report_key(usb_input_dev, KEY_ENTER, usb_buf[6] & 0x01); input_sync(usb_input_dev); old_ent = usb_buf[6]; } usb_submit_urb(uk_urb, GFP_KERNEL); #endif } static int usb_drv_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; /* 分配一个input_dev */ usb_input_dev = input_allocate_device(); /* 能产生哪一类事件 */ set_bit(EV_KEY, usb_input_dev->evbit); set_bit(EV_REP, usb_input_dev->evbit); /* 能产生该类事件的那些事件 */ set_bit(KEY_L, usb_input_dev->keybit); set_bit(KEY_S, usb_input_dev->keybit); set_bit(KEY_ENTER, usb_input_dev->keybit); /* 注册一个input_dev */ input_register_device(usb_input_dev); /* 数据传输3要素:源-目的-长度 */ /* 源:USB设备的某个端点 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 长度 */ len = endpoint->wMaxPacketSize; /* 目的 */ usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys); uk_urb = usb_alloc_urb(0, GFP_KERNEL); usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usb_drv_irq, NULL, endpoint->bInterval); uk_urb->transfer_dma = usb_buf_phys; uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; usb_submit_urb(uk_urb, GFP_KERNEL); return 0; } static void usb_drv_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); usb_kill_urb(uk_urb); input_unregister_device(usb_input_dev); usb_free_urb(uk_urb); usb_free_coherent(dev, len, NULL, usb_buf_phys); } static struct usb_device_id usb_drv_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* Terminating entry */ }; static struct usb_driver usb_drv = { .name = "usbmouse", .probe = usb_drv_probe, .disconnect = usb_drv_disconnect, .id_table = usb_drv_id_table, }; static int usb_drv_init(void) { usb_register(&usb_drv); return 0; } static void usb_drv_exit(void) { usb_deregister(&usb_drv); } module_init(usb_drv_init); module_exit(usb_drv_exit); MODULE_LICENSE("GPL");