S3C2440 USB鼠标驱动(十九)

2019-07-13 00:12发布

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进程测试,