官方QQ群收藏本站

百问linux嵌入式论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2911|回复: 2

tiny6410驱动移植之USB鼠标驱动

[复制链接]

23

主题

177

帖子

1592

积分

版主

Rank: 7Rank: 7Rank: 7

积分
1592
发表于 2015-1-27 13:55:45 | 显示全部楼层 |阅读模式
本帖最后由 望穿墙 于 2015-1-27 14:00 编辑

代码如下:
#include <asm/types.h>
#include <asm/io.h>
#include <linux/usb.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/hid.h>

static struct input_dev * input_usb_dev;
static struct urb *usb_urb;

static dma_addr_t usb_data_dma;
/* 必须是定义为 signed char* 才能在 handle里判断出数据小于零 */
static signed char *usb_data;
static int len;
static char pre_data;

static volatile unsigned long *gpkcon;
static volatile unsigned long *gpkdat;
static int x_pos = 0;
static int y_pos = 0;

static void usb_mouse_handle(struct urb*urb)
{
         /*当前这个鼠标数据含义
           * usb_data[0] bit 0:1为左键按下0为左键松开
           * usb_data[0] bit 1:1为右键按下0为右键松开
           * usb_data[0] bit 2:1为中键按下0为中键松开
           * usb_data[1] 左移为负 右移为正
           * usb_data[2] 上移为负 下移为正
           * usb_data[3] 滚轮下滚为负上滚为正
           */
#if 0
         inti;
         for(i = 0;i < len; ++i)
         {
                   printk("%02x",usb_data);
         }
         printk("\n");
#endif

         /*判断键值并上报事件 */
         if((pre_data & (1<<0)) != (usb_data[0] & (1<<0)))
         {
                   /*左键有变化 */
                   input_event(input_usb_dev,EV_KEY, KEY_L, (usb_data[0] & (1<<0))? 1:0);
                   input_sync(input_usb_dev);
         }

         if((pre_data & (1<<1)) != (usb_data[0] & (1<<1)))
         {
                   /*右键有变化 */
                   input_event(input_usb_dev,EV_KEY, KEY_S, (usb_data[0] & (1<<1))? 1:0);
                   input_sync(input_usb_dev);
         }
         if((pre_data & (1<<2)) != (usb_data[0] & (1<<2)))
         {
                   /*左键有变化 */
                   input_event(input_usb_dev,EV_KEY, KEY_ENTER, (usb_data[0] & (1<<2))? 1:0);
                   input_sync(input_usb_dev);
         }
         pre_data= usb_data[0];

         /*判断鼠标移动方向并记录坐标 */
         if(usb_data[1] < 0)
         {
                   /*左移 */
                   x_pos-= 10;
                   if(x_pos < 0)
                            x_pos= 0;
         }
         elseif (usb_data[1] > 0)
         {
                   x_pos+= 10;
                   if(x_pos > 800)
                            x_pos= 800;
         }

         if(usb_data[2] < 0)
         {
                   /*上移 */
                   y_pos-= 10;
                   if(y_pos < 0)
                            y_pos= 0;
         }
         elseif (usb_data[2] > 0)
         {
                   y_pos+= 10;
                   if(y_pos > 480)
                            y_pos= 480;
         }

         if(x_pos < 400 && y_pos < 240)
         {
                   /*点亮LED0 */
                   *gpkdat|= (0xf<<4);
                   *gpkdat&= ~(1<<4);
         }
         if(x_pos < 400 && y_pos > 240)
         {
                   /*点亮LED1 */
                   *gpkdat|= (0xf<<4);
                   *gpkdat&= ~(1<<5);
         }
         if(x_pos > 400 && y_pos < 240)
         {
                   /*点亮LED2 */
                   *gpkdat|= (0xf<<4);
                   *gpkdat&= ~(1<<6);
         }
         if(x_pos > 400 && y_pos > 240)
         {
                   /*点亮LED3 */
                   *gpkdat|= (0xf<<4);
                   *gpkdat&= ~(1<<7);
         }

         //printk("x_pos= %d, y_pos = %d\n",x_pos,y_pos);

         /*重新提交urb 必须重新提交  否则不会再进入这个处理函数*/
         usb_submit_urb(usb_urb,GFP_ATOMIC);
}


static int usb_probe(struct usb_interface*intf,const struct usb_device_id *id)
{
         intret;
         /*usb鼠标接入板子时usb总线驱动程序已经将该设备
           * 的信息读出来并保存起来了通过正面这个函数可以
           * 获得指向usb_device 结构体的指针
           */
         structusb_device *dev = interface_to_usbdev(intf);
         structusb_host_interface *interface;
         structusb_endpoint_descriptor *endpoint;
         intpipe;
         interface= intf->cur_altsetting;
         /*获得端点 */
         endpoint= &interface->endpoint[0].desc;


         /*分配一个input_dev结构体 */
         input_usb_dev= input_allocate_device();
         /*设置支持哪一类事件 */
         __set_bit(EV_KEY,input_usb_dev->evbit);

         __set_bit(EV_REL,input_usb_dev->evbit);
         /*设置支持哪些按键 */
         __set_bit(KEY_L,input_usb_dev->keybit);
         __set_bit(KEY_S,input_usb_dev->keybit);
         __set_bit(KEY_ENTER,input_usb_dev->keybit);

         /*注册 */
         ret= input_register_device(input_usb_dev);
         if(ret)
         {
                   printk("input_register_devicefail\n");
         }

         /*硬件相关操作 */
         /*初始化IO 控制LED */
         gpkcon= ioremap(0x7F008800,16);
         gpkdat= gpkcon + 2;
         *gpkcon&= ~(0xffff<<(4*4));
         *gpkcon|= (0x1111<<(4*4));
         /* */
         pipe= usb_rcvintpipe(dev, endpoint->bEndpointAddress);
         /*目的  usb_data虚拟地址  usb_data_dma物理地址*/
         usb_data= usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_data_dma);
         /*长度 */
         len= endpoint->wMaxPacketSize;

         /*使用三要素 */
         usb_urb= usb_alloc_urb(0, GFP_KERNEL);

         usb_fill_int_urb(usb_urb,dev, pipe, usb_data,len,usb_mouse_handle, NULL, endpoint->bInterval);
         usb_urb->transfer_dma= usb_data_dma;
         usb_urb->transfer_flags|= URB_NO_TRANSFER_DMA_MAP;

         /*提交urb */
         if(usb_submit_urb(usb_urb, GFP_ATOMIC))
                   return-EIO;

         printk("bcdUSB    =%d\n",dev->descriptor.bcdUSB);
         printk("idProduct = 0x%x\n",dev->descriptor.idProduct);
         printk("idVendor  =0x%x\n",dev->descriptor.idVendor);
         return0;
}

static void usb_disconnect(structusb_interface *intf)
{
         structusb_device *dev = interface_to_usbdev(intf);
         /*对应usb_submit_urb */
         usb_kill_urb(usb_urb);
         usb_free_urb(usb_urb);
         usb_free_coherent(dev,len,usb_data,usb_data_dma);
         input_unregister_device(input_usb_dev);
         input_free_device(input_usb_dev);
         iounmap(gpkcon);
}

static struct usb_device_id usb_idtable[] ={
         /*类是HID  子类是BOOT  协议是MOUSE就可以支持 */
         //{USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
         //      USB_INTERFACE_PROTOCOL_MOUSE) },

         /*因为本人的鼠标比较垃圾只有四个字节的
           * 有效数据只支持本人的鼠标。。。 */
         {USB_DEVICE(0x93a,0x2510) },



         {}      /* Terminating entry */   

};


struct usb_driver myusb_drv = {
         .name= "myusb",
         .probe= usb_probe,
         .disconnect= usb_disconnect,
         .id_table= &(usb_idtable[0]),

};




int __init myusb_init(void)
{
         usb_register(&myusb_drv);
         return0;
}

void __exit myusb_exit(void)
{
         usb_deregister(&myusb_drv);
}

module_init(myusb_init);
module_exit(myusb_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("WCQ");
MODULE_DESCRIPTION("USB DRV");

测试程序:

实验现象:
先去掉内核中的USB鼠标驱动
make menuconfig
Device Drivers  --->
        HID Devices  --->
                   <>   USB Human Interface Device (fullHID) support   去掉这个选项
make
用新内核启动系统

接上USB鼠标出现打印如下:
usb 1-1.3: new low speed USB device usings3c2410-ohci and address 4
usb 1-1.3: New USB device found,idVendor=093a, idProduct=2510
usb 1-1.3: New USB device strings: Mfr=1,Product=2, SerialNumber=0
usb 1-1.3: Product: USB OPTICAL MOUSE
usb 1-1.3: Manufacturer: PIXART
从这里我们可以看到 idVendor=093a,idProduct=2510  驱动程序里就可以写成只支持这个厂家ID 产品ID的驱动

[root@FriendlyARM /]# insmod usb_drv.ko
input: Unspecified device as/devices/virtual/input/input1
bcdUSB   = 272
idProduct = 0x2510
idVendor = 0x93a
usbcore: registered new interface drivermyusb
其中 myusb 就是myusb_drv 结构体里面的 name 这个名字不重要可以随便写

[root@FriendlyARM /]# hexdump/dev/input/event1
               微秒   类型键值 按下/松开   
0000000 056d 386d 359c 000b 0001 0026   0001    0000
0000010 056d 386d 35b8 000b 0000 0000   0000    0000

0000020 056d 386d 873e 000d 0001 0026   0000    0000
0000030 056d 386d 8754 000d 0000 0000   0000    0000

0000040 0571 386d bc1a 0004 0001 001f    0001     0000
0000050 0571 386d bc36 0004 0000 0000    0000    0000

0000060 0571 386d aa03 0007 0001 001f   0000    0000
0000070 0571 386d aa1a 0007 0000 0000   0000    0000

0000080 0573 386d a47b 000d 0001 001c   0001    0000
0000090 0573 386d a498 000d 0000 0000   0000    0000

00000a0 0574 386d e651 0002 0001 001c   0000    0000
00000b0 0574 386d e668 0002 0000 0000   0000    0000

注意: 对于 tiny6410 来说 产生的设备节点是在 /dev/input目录下 而不是像2440那样在/dev/目录下

对于hexdump 分别是秒微秒 类型 键值  状态  两行为一组数据

由于我增加了用鼠标控制led灯的功能当鼠标移动到 下图所示位置的
鼠标控制LED显示.png

0 、1、 2、 3 区域时 分别点亮led0、led1、led2、led3  (前提是先把友善自带的 led测试程序去掉 本人使用的是 S70屏 分辨率800*480)
程序中我的 x_pos y_pos是加10操作这样纯粹是为了快些见到效果 正确的办法应该是++操作这样每一个点都可以移过去只是这样很慢 我们可以通过修改 usb_fill_int_urb()里的endpoint->bInterval来加快扫描速度(个人猜测没有去试 有兴趣的可以去试一下)
由于我没有注释掉qt 所以打开终端 点击鼠标 左键右键 中键可以看到 ls命令及其结果如下:
1.png

当然你也可以 关掉qt
用 cat /dev/tty1 或 cat /dev/tty0 来做实验
甚至用 exec 0</dev/tty1 来做实验

关于USB驱动:
USB总线的驱动可以参考:韦老师的笔记USB设备驱动程序.TXT
这里就不多说了 总线那一块内核已经写好了我们要做的就是设备这一块
简要步骤:
构造 usb_driver 结构体 填充里面的 .probe .disconnect .name . id_table
其中 id_table非常关键 因为 当你注册这个USB设备驱动的时候 内核USB总线驱动会从usb_bus总线里查找已经发现的设备和你的USB设备驱动里的id_table一一match 如果匹配就调用你注册的USB设备驱动里的 probe函数
Probe函数最基本要做如下事情:
通过 interface_to_usbdev();获得指向usb_device 结构体的指针这个结构体里存有你接入的USB设备的信息

   interface = intf->cur_altsetting; 获得interface 并通过这个接口获得端点
         endpoint= &interface->endpoint[0].desc;

设置数据传输三要素 (源、目的地址、长度)
源:         pipe = usb_rcvintpipe(dev,endpoint->bEndpointAddress);   pipe是个整形变量相当于一个寄存器吧 不同的bit有不同的含义
目的:   usb_data = usb_alloc_coherent(dev, len,GFP_ATOMIC, &usb_data_dma);
        usb_data 是虚拟地址 提供给内核用  usb_data_dma 是物理地址给硬件用
长度:   len = endpoint->wMaxPacketSize;

使用三要素
  分配urb(usb request block)      usb_urb = usb_alloc_urb(0, GFP_KERNEL);
  填充 urb    usb_fill_int_urb(usb_urb,dev, pipe, usb_data,len,usb_mouse_handle, NULL, endpoint->bInterval);
usb_mouse_handle是所谓的中断处理函数 即usb控制器查询到usb设备有数据时会向cpu产生中断最终会调用usb_mouse_handle
设置物理地址给usb控制器用    usb_urb->transfer_dma = usb_data_dma;
设置标志usb_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
  提交urb  usb_submit_urb(usb_urb,GFP_ATOMIC);

Probe函数最起码要做上面这些至于插入input子系统的东西只是为了做用鼠标当按键的实验而已
usb_mouse_handle 的最后必须要重新提交urb 否则就算有数据产生也不会再进入usb_mouse_handle







值得注意的是:

相对于韦老师的代码
2.6.38有了一些改动

usb_buffer_alloc 改名为 usb_alloc_coherent
usb_buffer_free  改名为 usb_free_coherent

在所谓的中断处理函数中
原:         usb_submit_urb(usb_urb, GFP_KERNEL); 用这个来提交urb会出现正面的情况  也不是说不能用
正常的数据还是能得到的 只是多出一些东西来很不爽


BUG: sleeping function called from invalidcontext at mm/slub.c:795
in_atomic(): 1, irqs_disabled(): 128, pid:0, name: swapper
[<c0038c44>] (unwind_backtrace+0x0/0xe4)from [<c00a00c4>] (__kmalloc+0x6c/0xf4)
[<c00a00c4>] (__kmalloc+0x6c/0xf4)from [<c02227b4>] (ohci_urb_enqueue+0x218/0x68c)
[<c02227b4>](ohci_urb_enqueue+0x218/0x68c) from [<c02168b4>](usb_hcd_submit_urb+0x7ac/0x8c4)
[<c02168b4>] (usb_hcd_submit_urb+0x7ac/0x8c4)from [<c02157e4>] (usb_hcd_giveback_urb+0x70/0xb8)
[<c02157e4>](usb_hcd_giveback_urb+0x70/0xb8) from [<c02222e0>] (finish_urb+0x7c/0xb8)
[<c02222e0>] (finish_urb+0x7c/0xb8)from [<c0225d18>] (ohci_irq+0x314/0x4e4)
[<c0225d18>] (ohci_irq+0x314/0x4e4)from [<c02152e0>] (usb_hcd_irq+0x38/0x88)
[<c02152e0>] (usb_hcd_irq+0x38/0x88)from [<c0076078>] (handle_IRQ_event+0x24/0xe0)
[<c0076078>](handle_IRQ_event+0x24/0xe0) from [<c00779b0>](handle_level_irq+0xbc/0x13c)
[<c00779b0>] (handle_level_irq+0xbc/0x13c)from [<c0029070>] (asm_do_IRQ+0x70/0x94)
[<c0029070>] (asm_do_IRQ+0x70/0x94)from [<c0033228>] (__irq_svc+0x48/0xa0)
Exception stack(0xc05d3f78 to 0xc05d3fc0)
3f60:                                                      00000000 00000000
3f80: 00000021 f6100000 c05d2000 c05d726cc0616c24 c0859920 500217e8 410fb766
3fa0: 50021780 00000000 cd4be044 c05d3fc0c00348a0 bf00003c 60000013 ffffffff
[<c0033228>] (__irq_svc+0x48/0xa0)from [<bf00003c>] (__cpu_pfn_fa+0x3c/0x44 [fa_cpu_pfn])
[<bf00003c>] (__cpu_pfn_fa+0x3c/0x44[fa_cpu_pfn]) from [<c00348a0>] (cpu_idle+0x40/0x8c)
[<c00348a0>] (cpu_idle+0x40/0x8c)from [<c00089a8>] (start_kernel+0x22c/0x274)
[<c00089a8>](start_kernel+0x22c/0x274) from [<50008034>] (0x50008034)


根据内核的调试方法再结合实验现象我们容易知道问题就出在中断处理函数那里 而最有可能的就是提交urb的函数 usb_submit_urb(usb_urb, GFP_ATOMIC); 不要问我是怎么知道的。。。请看2期视频的调试方法相关的视频
根据回塑信息我们一路一路跟进去内核源码看就知道  最终是在
__kmalloc –>slab_alloc->slab_pre_alloc_hook->might_sleep_if(flags& __GFP_WAIT);
就是这里休眠了 导致的bug
这个标志就有很大嫌疑了 在友善的 usb鼠标驱动中和韦老师的代码中我们发现 他们用的标志都是GFP_KERNEL  网上搜索一下GFP_KERNEL GFP_ATOMIC的区别我们就知道大概要怎么做了

据说 GFP_KERNEL 是交给内核来控制有可能使进程休眠
GFP_ATOMIC 从不休眠  多用于中断处理函数
为了保证数据的实时性显然不应该休眠 至于为什么友善的源码没有问题可能是在某些地方做了优化吧
如此:把所有的 usb_submit_urb(usb_urb,GFP_KERNEL)flag 改成GFP_ATOMIC即可


回复

使用道具 举报

189

主题

1760

帖子

6145

积分

超级版主

答疑助手

Rank: 8Rank: 8

积分
6145
QQ
发表于 2015-1-28 09:11:02 | 显示全部楼层
可以以代码形式贴的
回复 支持 反对

使用道具 举报

23

主题

177

帖子

1592

积分

版主

Rank: 7Rank: 7Rank: 7

积分
1592
 楼主| 发表于 2015-1-28 22:01:08 | 显示全部楼层
st_100ask 发表于 2015-1-28 09:11
可以以代码形式贴的

这次的字体不知道怎么有些是斜的。。。还真不知道可以以代码的形式贴 下次试试
回复 支持 反对

使用道具 举报

技术支持
在线咨询
咨询热线
0755-86200561
微信扫一扫
获取更多资讯!

Archiver|小黑屋|百问linux嵌入式论坛     

GMT+8, 2020-2-29 20:53 , Processed in 0.195357 second(s), 14 queries , File On.

Powered by Discuz! X3.3 Licensed

© 2001-2017 Comsenz Inc. Template By 【未来科技】【 www.wekei.cn 】

快速回复 返回顶部 返回列表