首页 > 系统相关 >linux驱动移植-usb键盘接口驱动

linux驱动移植-usb键盘接口驱动

时间:2022-09-04 22:11:07浏览次数:111  
标签:usb urb linux dev kbd input 驱动 struct

在前面的章节我们已经介绍了usb鼠标驱动的编写,并对usb摄像头驱动源码进行了分析。由于usb键盘驱动和usb鼠标驱动代码非常相似,所以这一节就粗略介绍一下usb键盘驱动的编写。

一、接收USB键盘数据准备工作

1.1 键盘数据格式

键盘发送给usb主机控制器的数据格式包含8个字节,BYTE0、BYTE1、BYTE2、BYTE3,BYTE4、BYTE5、BYTE6、BYTE7,定义分别是:

  BYTE0

BYTE1

BYTE2~BYTE7

[7] Right Ctrl1:按下  0:松开 

保留

普通按键

比如A-Z、a-z、0-9等

 

[6] Right Alt   1:按下  0:松开
[5] Right Shift  1:按下  0:松开
[4] Right Ctrl   1:按下  0:松开
[3] Left GUI   1:按下  0:松开
[2] Left Alt   1:按下  0:松开
[1] Left Shift  1:按下  0:松开
[0] Left Ctrl   1:按下  0:松开

比如我们按下A,接收到的8个字节数据为:

00 00 04 00 00 00 00 00

我们同时按下A和S,接收到的8个字节数据为:

00 00 04 16 00 00 00 00

我们可以看到按下A,usb键盘传输的数据为0x04、打下S时,usb键盘传输的数据是0x16。

我们找到输入子系统include/uapi/linux/input-event-codes.h中按键定义的值,可以看到按键A定义的值为30,按键S对应的值是31。

#define KEY_RESERVED            0
#define KEY_ESC                 1
#define KEY_1                   2
#define KEY_2                   3
#define KEY_3                   4
#define KEY_4                   5
#define KEY_5                   6
#define KEY_6                   7
#define KEY_7                   8
#define KEY_8                   9
#define KEY_9                   10
#define KEY_0                   11
#define KEY_MINUS               12
#define KEY_EQUAL               13
#define KEY_BACKSPACE           14
#define KEY_TAB                 15
#define KEY_Q                   16
#define KEY_W                   17
#define KEY_E                   18
#define KEY_R                   19
#define KEY_T                   20
#define KEY_Y                   21
#define KEY_U                   22
#define KEY_I                   23
#define KEY_O                   24
#define KEY_P                   25
#define KEY_LEFTBRACE           26
#define KEY_RIGHTBRACE          27
#define KEY_ENTER               28
#define KEY_LEFTCTRL            29
#define KEY_A                   30
#define KEY_S                   31
......

在linux内核键盘驱动程序中drivers/hid/usbhid/usbkbd.c,定义了键盘描述码表:

/* usb键盘码 */
static const unsigned char usb_kbd_keycode[256] = {
          0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
         50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
          4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
         27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
         65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
        105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
         72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
        191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
        115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
        122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
         29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
        150,158,159,128,136,177,178,176,142,152,173,140
};

发现该数据usb_kbd_keycode[0x04]=30,usb_kbd_keycode[0x16]=31。

1.2 usb键盘和接口驱动匹配

usb键盘接口驱动usb_kbd_drv的成员id_table应该怎么设置呢。我们参考drivers/hid/usbhid/usbkbd.c(内核自带的usb鼠标驱动)是如何使用的,如下:

static const struct usb_device_id usb_kbd_id_table[] = {
        { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
                USB_INTERFACE_PROTOCOL_KEYBOARD) },
        { }                                             /* Terminating entry */
};

usb鼠标和usb键盘的usb_mouse_id_table数组基本是一样的,都是HID类型设备,并且接口子类都是启动设备,只是接口协议略有不同,usb鼠标接口协议为USB_INTERFACE_PROTOCOL_MOUSE、而usb键盘接口协议为USB_INTERFACE_PROTOCOL_KEYBOARD。

二、usb鼠标接口驱动编写

新建项目15.usb_keyboard,编写usb键盘接口驱动程序,代码放在keyboard_dev.c文件。代码参考drivers/hid/usbhid/usbkbd.c。

2.1 初始化usb接口驱动

首先定义struct usb_kbd_driver 类型的全局变量,并初始化成员name、probe、disconnect、id_table:

static struct usb_driver usb_kbd_driver = {
        .name =         "usbkbd",
        .probe =        usb_kbd_probe,
        .disconnect =   usb_kbd_disconnect,
        .id_table =     usb_kbd_id_table,
};

2.2 编写驱动模块入口函数

在模块入口函数,调用usb_register注册usb_struct结构体:

2.3 编写usb_kbd_probe

当usb键盘设备和usb接口驱动匹配时,usb_kbd_probe将会被调用:

2.3.1 动态分配input_device设备
  • 我们首先通过input_allocate_device动态创建struct input_dev结构对象dev;
  • 通过input_set_capability设置input设备可以上报哪些输入事件;
  • 然后调用input_register_device注册这个设备;
2.3.2 设置usb数据传输
  • 通过usb_rcvintpipe创建一个接收中断类型的端点管道,用来端点和数据缓冲区之间的连接
  • 通过usb_buffer_alloc申请usb缓冲区
  • 通过usb_alloc_coherent申请urb结构体;
  • 通过usb_fill_int_urb填充urb结构体;当鼠标点击时触发urb处理完成函数,在urb处理完成函数进行输入事件上报;
  • 因为我们S3C2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址;
  • 使用usb_submit_urb提交urb

2.4 编写usb_mouse_disconnect

  • 使用usb_kill_urb杀掉提交到内核中的urb;
  • 使用usb_free_urb释放urb;
  • 使用usb_free_coherent释放usb缓存区;
  • 使用input_register_device函数注销input_dev;
  • 使用input_free_device函数释放input_de;

2.5 编写驱动模块出口函数

在模块出口函数,调用usb_deregister注销usb_struct结构体:

2.6 完整代码keyboard_dev.c

 

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/usb/input.h>
#include <linux/hid.h>


/* usb键盘码 */
static const unsigned char usb_kbd_keycode[256] = {
          0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
         50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
          4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
         27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
         65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
        105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
         72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
        191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
        115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
        122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
         29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
        150,158,159,128,136,177,178,176,142,152,173,140
};


/**
 * struct usb_kbd - state of each attached keyboard
 * @dev:        input device associated with this keyboard
 * @usbdev:     usb device associated with this keyboard
 * @old:        data received in the past from the @irq URB representing which
 *              keys were pressed. By comparing with the current list of keys
 *              that are pressed, we are able to see key releases.
 * @irq:        URB for receiving a list of keys that are pressed when a
 *              new key is pressed or a key that was pressed is released.
 * @led:        URB for sending LEDs (e.g. numlock, ...)
 * @newleds:    data that will be sent with the @led URB representing which LEDs
                should be on
 * @name:       Name of the keyboard. @dev's name field points to this buffer
 * @phys:       Physical path of the keyboard. @dev's phys field points to this
 *              buffer
 * @new:        Buffer for the @irq URB
 * @cr:         Control request for @led URB
 * @leds:       Buffer for the @led URB
 * @new_dma:    DMA address for @irq URB
 * @leds_dma:   DMA address for @led URB
 * @leds_lock:  spinlock that protects @leds, @newleds, and @led_urb_submitted
 * @led_urb_submitted: indicates whether @led is in progress, i.e. it has been
 *              submitted and its completion handler has not returned yet
 *              without resubmitting @led
 */
struct usb_kbd{
    char name[128];      /* 键盘设备的名称,包括生产厂商、产品类别、产品等信息 */
    char phys[64];       /* 设备节点名称 */
    /* usb 键盘是一种 usb设备,需要内嵌一个usb设备结构体来描述其usb属性 */
    struct usb_device *usbdev;
    /* usb键盘同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */
    struct input_dev *dev;


    /* urb请求包结构体,用于接收键盘按下/松开时发送的数据  采用中断传输 */
    struct urb *irq;
    /* 上一次键盘按下的值 */
    unsigned char old[8];
    /* Buffer for the @irq URB, 8个字节 */
    unsigned char *new;
     /* 内存空间new的dma映射,即这块内存空间对应的 dma 地址 */
    dma_addr_t new_dma;


    /* urb请求包结构体,用于主机控制器发送数据给键盘从而实现控制led 采用控制传输 */
    struct urb *led;
    /* led待发送的数据,用来控制键盘灯 */
    unsigned char newleds;
    /* 控制传输的数据指针 */
    struct usb_ctrlrequest *cr;
    /* Buffer for the @led UR,1个字节 */
    unsigned char *leds;
    /* 内存空间leds的dma映射,即这块内存空间对应的 dma 地址 */
    dma_addr_t leds_dma;

    /* 自旋锁 */
    spinlock_t leds_lock;
    /* led控制请求已经提交,urb处理尚未完成标志位 */
    bool led_urb_submitted;
};

/**
 * urb完成时被调用的完成处理函数(中断传输)
 */
static void usb_kbd_irq(struct urb *urb)
{
     /* 私有数据  */
     struct usb_kbd *kbd = urb->context;
     /* 接收到的数据 */
     char *new = kbd->new;
     /* 上一次接收到的数据 */
     char *old = kbd->old;

     /* input_dev */
     struct input_dev *dev = kbd->dev;
     /* usb_device */
     struct usb_device *usbdev = kbd->usbdev;
    
     int status,i;

     /* urb的当前状态 */
     switch (urb->status) {
        case 0:                 /* success */
            break;
        case -ECONNRESET:       /* unlink */
        case -ENOENT:
        case -ESHUTDOWN:
            return;
        /* -EPIPE:  should clear the halt */
        default:                /* error */
            goto resubmit;
      }


    /*
     * usb键盘数据含义
     * 向输入子系统汇报键盘事件情况,以便作出反应。
     * data[0] 每一位都有特特殊含义  Left Ctrl、Left Shift、Left Alt、...
     * data[1] 保留
     * data[2]~date[7] 每一位都对应一个普通按键
     */
    for( i=0; i<8; i++)   // 处理特定功能键
    {
        input_report_key(dev, usb_kbd_keycode[i+224], (new[0] >> i) & 1);   
    }
    /* 上报普通按键 */
    for (i = 2; i < 8; i++) 
    {
         /* 通过上个状态的按键数据old[i]的非0值,来查找当前状态的按键数据,若没有找到,说明已经松开了该按键 */
        if (old[i] > 3 && memscan(new + 2, old[i], 6) == new + 8) 
        {
            /* 再次判断键盘描述码表的值是否不是0 */
            if (usb_kbd_keycode[old[i]])
                input_report_key(dev, usb_kbd_keycode[old[i]], 0);  // 按键已经松开
            else
                hid_info(usbdev,"Unknown key (scancode %#x) released.\n",old[i]);
        }
        
        /* 通过当前状态的按键数据new[i]的非0值,来查找上个状态的按键数据,若没有找到,说明已经按下了该按键 */
        if (new[i] > 3 && memscan(old + 2, new[i], 6) == old + 8)
        {
            /* 再次判断键盘描述码表的值是否不是0 */
            if (usb_kbd_keycode[new[i]])
                input_report_key(dev, usb_kbd_keycode[new[i]], 1);  // 按键已经按下
            else
                hid_info(usbdev,"Unknown key (scancode %#x) pressed.\n",new[i]);
        }
    }


     /*上报同步事件,通知系统有事件上报 */
    input_sync(dev);

    /* 更新上个状态值  */
    memcpy(old, new, 8);

     /*
      * 系统需要周期性不断地获取键盘的事件信息,因此在 urb 回调函数的末尾再次提交urb请求块,这样又会调用新的回调函数,周而复始。
      * 在回调函数中提交urb一定只能是 GFP_ATOMIC 优先级的,因为 urb 回调函数运行于中断上下文中,在提
      * 交urb过程中可能会需要申请内存、保持信号量,这些操作或许会导致USB core睡眠,一切导致睡眠的行
      * 为都是不允许的。
      */
resubmit:
        status = usb_submit_urb (urb, GFP_ATOMIC);
        if (status)
                dev_err(usbdev,"can't resubmit intr, %s-%s/input0, status %d\n",
                        usbdev->bus->bus_name,
                        usbdev->devpath, status);
}

/* 
 * 事件处理函数,当读取input_dev设备时,并处于堵塞模式时,
 */
static int usb_kbd_event(struct input_dev *dev, unsigned int type,
                         unsigned int code, int value)                        
{
        unsigned long flags;
        struct usb_kbd *kbd = input_get_drvdata(dev);

        printk("usb kbd event");

        if (type != EV_LED)
                return -1;

        /* 获取自旋锁、关中断 */
        spin_lock_irqsave(&kbd->leds_lock, flags);
        kbd->newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
                       (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL,   dev->led) << 1) |
                       (!!test_bit(LED_NUML,    dev->led));

        if (kbd->led_urb_submitted){
                spin_unlock_irqrestore(&kbd->leds_lock, flags);
                return 0;
        }

        if (*(kbd->leds) == kbd->newleds){
                spin_unlock_irqrestore(&kbd->leds_lock, flags);
                return 0;
        }

        *(kbd->leds) = kbd->newleds;

        kbd->led->dev = kbd->usbdev;
        if (usb_submit_urb(kbd->led, GFP_ATOMIC))
                pr_err("usb_submit_urb(leds) failed\n");
        else
                kbd->led_urb_submitted = true;

        spin_unlock_irqrestore(&kbd->leds_lock, flags);

        return 0;
}


/**
 * urb完成时被调用的完成处理函数(控制传输)
 */
static void usb_kbd_led(struct urb *urb)
{
    unsigned long flags;
    /* 私有数据  */
    struct usb_kbd *kbd = urb->context;
    /* usb_device */
    struct usb_device *usbdev = kbd->usbdev;
    /* urb的当前状态 */
    if(urb->status)
    {
        hid_warn(usbdev,"led urb status %d received\n",urb->status);
    }

    /* 关中断、并获取自旋锁 */
    spin_lock_irqsave(&kbd->leds_lock,flags);

     if (*(kbd->leds) == kbd->newleds){
        kbd->led_urb_submitted = false;
        spin_unlock_irqrestore(&kbd->leds_lock, flags);
        return;
    }

    *(kbd->leds) = kbd->newleds;

    kbd->led->dev = usbdev;
    if (usb_submit_urb(kbd->led, GFP_ATOMIC)){
        /* urb请求提交失败 */
        hid_err(usbdev, "usb_submit_urb(leds) failed\n");
        kbd->led_urb_submitted = false;
    }

    /* 释放自旋锁、开中断 */
    spin_unlock_irqrestore(&kbd->leds_lock, flags);
}

/**
 * 用户usb接口设备和usb接口驱动匹配
 */
static struct usb_device_id usb_kbd_id_table[] = {
        { USB_INTERFACE_INFO(
                     USB_INTERFACE_CLASS_HID,                 //接口类:hid类
                     USB_INTERFACE_SUBCLASS_BOOT,             //子类:启动设备类
                     USB_INTERFACE_PROTOCOL_KEYBOARD) },      //USB协议:键盘协议
};


/*
 * 打开input_dev设备时执行
 */
static int usb_kbd_open(struct input_dev *dev)
{
   /* 获取驱动数据 */
   struct usb_kbd *kbd = input_get_drvdata(dev);
   kbd->irq->dev = kbd->usbdev;

   /* 使用usb_submit_urb提交urb */
   if (usb_submit_urb(kbd->irq, GFP_KERNEL))
        return -EIO;

   return 0;
}

/* 
 * 关闭input_dev设备时执行 
 */
static void usb_kbd_close(struct input_dev *dev)
{
    /* 获取驱动数据 */
    struct usb_kbd *kbd = input_get_drvdata(dev);

    /* 杀掉提交到内核中的urb */
    usb_kill_urb(kbd->irq);
}

/* 动态分配内存 */
static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)
{
        /* struct urb */
        if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))
                return -1;
        /* struct urb */
        if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))
                return -1;
        /* 申请内存空间用于数据传输,new 为指向该空间的地址,new_dma 则是这块内存空间的dma映射,
         * 即这块内存空间对应的 dma 地址。在使用 dma 传输的情况下,则使用 data_dma 指向的 dma 区域,
         * 否则使用 data 指向的普通内存区域进行传输。 */
        if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma)))
                return -1;
        if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL)))
                return -1;
        if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma)))
                return -1;

        return 0;
}

/* 释放内存 */
static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd)
{
        usb_free_urb(kbd->irq);
        usb_free_urb(kbd->led);
        usb_free_coherent(dev, 8, kbd->new, kbd->new_dma);
        kfree(kbd->cr);
        usb_free_coherent(dev, 1, kbd->leds, kbd->leds_dma);
}


/**
 * 当usb接口驱动和usb接口匹配成功之后,就会调用probe函数
 * 可以参考hub_probe实现
 */
static int usb_kbd_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
        /* 获取usb设备 */
        struct usb_device *dev = interface_to_usbdev(intf);
        /* 当前激活的接口配置 */
        struct usb_host_interface *interface;
        /* 当前usb接口下的端点0的端点描述符 */
        struct usb_endpoint_descriptor *endpoint;
        /* usb键盘设备 */
        struct usb_kbd *kbd;
        /* input_dev */
        struct input_dev *input_dev;
        /* 端点管道 */
        int pipe,maxp,i;
        int error = -ENOMEM;

        /* 当前激活的接口配置 */
        interface = intf->cur_altsetting;

        /* 从接口描述符获取端点个数,键盘只有1个 */
        if (interface->desc.bNumEndpoints != 1)
              return -ENODEV;

        /* 当前usb接口下的端点0的端点描述符 */
        endpoint = &interface->endpoint[0].desc;
        if (!usb_endpoint_is_int_in(endpoint))
              return -ENODEV;


        /* 通过usb_rcvintpipe创建一个端点管道  由设备地址[8:14]、端点号[15:18]、传输类型[30:31]、传输方向[7]构成 */
        pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
        /* 获取本端点接受或发送的最大信息包的大小  */
        maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

        // 打印VID,PID
        printk("VID=%x,PID=%x\n,endpointAddress=%d,maxPacketSize=%d",dev->descriptor.idVendor,dev->descriptor.idProduct,
            endpoint->bEndpointAddress,maxp);

        /* 动态分配内存 */
        kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);

        /* 分配一个input_dev结构体  */
        input_dev = input_allocate_device();

        if (!kbd || !input_dev)
              goto fail1;

        /* 初始化 */
        kbd->usbdev = dev;
        kbd->dev = input_dev;

        /* 动态分配内存空间 */
        if (usb_kbd_alloc_mem(dev,kbd))
              goto fail2;

        /* 初始化自旋锁 */
        spin_lock_init(&kbd->leds_lock);

        /* 获取键盘设备的名称 */
        if (dev->manufacturer)
              strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));

        if (dev->product) {
               if(dev->manufacturer)
                      strlcat(kbd->name, " ", sizeof(kbd->name));
               strlcat(kbd->name, dev->product, sizeof(kbd->name));
        }

        /* 如果键盘名没有 */
        if (!strlen(kbd->name))
                snprintf(kbd->name, sizeof(kbd->name),
                         "USB HIDBP Keyboard %04x:%04x",
                         le16_to_cpu(dev->descriptor.idVendor),
                         le16_to_cpu(dev->descriptor.idProduct));

       /*
        * 填充键盘设备结构体中的节点名。usb_make_path 用来获取usb设备在sysfs 中的路径,格式为:usb-usb总线号-路径名。
        */
        usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
        strlcat(kbd->phys, "/input0", sizeof(kbd->phys));

        /* 将键盘设备的名称赋给键盘设备内嵌的输入子系统结构体 */
        input_dev->name = kbd->name;
        /* 将键盘设备的设备节点名赋给键盘设备内嵌的输入子系统结构体 */
        input_dev->phys = kbd->phys;

       /*
        * input_dev中的input_id 结构体,用来存储厂商、设备类型和设备的编号,这个函数是将设备描述符
        * 中的编号赋给内嵌的输入子系统结构体
        */
        usb_to_input_id(dev, &input_dev->id);
        input_dev->dev.parent = &intf->dev;

        /* 设置上报事件,类型 */
        set_bit(EV_KEY,input_dev->evbit);    // 支持按键事件
        set_bit(EV_LED,input_dev->evbit);    // 支持LED事件
        set_bit(EV_REP,input_dev->evbit);    // 支持重复上报

        /* 设置上报EV_KEY类型事件的事件码 */
        for (i = 0; i < 255; i++)
            input_set_capability(input_dev,EV_KEY,usb_kbd_keycode[i]);   // 支持的按键
        clear_bit(KEY_RESERVED, input_dev->keybit);          //  清除EV_KEY事件类型下KEY_RESERVED事件码对应的bit位,也就是不传输这种事件

        /* 设置上报EV_LED类型事件的事件码 */
        input_set_capability(input_dev,EV_LED,LED_NUML);    
        input_set_capability(input_dev,EV_LED,LED_CAPSL);   
        input_set_capability(input_dev,EV_LED,LED_SCROLLL); 
        input_set_capability(input_dev,EV_LED,LED_COMPOSE);
        input_set_capability(input_dev,EV_LED,LED_KANA);

        /* 设置input_dev->dev->driver_data = kbd */
        input_set_drvdata(input_dev, kbd);

        /* 初始化input_dev */
        input_dev->event = usb_kbd_event;
        input_dev->open = usb_kbd_open;
        input_dev->close = usb_kbd_close;

        /* 填充@irq urb */
        usb_fill_int_urb (kbd->irq ,                      //urb结构体
                          kbd->usbdev,                    //usb设备
                          pipe,                           //端点管道
                          kbd->new,                       //缓存区地址
                          maxp,                           //数据长度
                          usb_kbd_irq,                    //中断函数
                          kbd,                            //urb完成函数上下文
                          endpoint->bInterval);           //中断间隔时间

         /* 因为我们S3C2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址 */
         kbd->irq->transfer_dma = kbd->new_dma;                  //设置DMA地址
         kbd->irq->transfer_flags  |= URB_NO_TRANSFER_DMA_MAP;    //设置使用DMA地址

        /* 构建控制请求数据 */
        kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;  // 请求类型  0x1<<5 |  0x01   类请求命令、接收者为接口
        kbd->cr->bRequest = 0x09;                                      // 请求值 USB_REQ_SET_CONFIGURATION  
        kbd->cr->wValue = cpu_to_le16(0x200);                          // 
        kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);   // 指定的接口编号
        kbd->cr->wLength = cpu_to_le16(1);                             //  数据长度

        /* 填充@led urb */
        usb_fill_control_urb(kbd->led,                         //urb结构体
                             dev,                              //usb设备
                             usb_sndctrlpipe(dev, 0),          //端点管道 使用端点0
                             (void *) kbd->cr,                 //pointer to the setup_packet buffer 
                             kbd->leds,                        //缓存区地址
                             1,                                //数据长度 
                             usb_kbd_led,                      //中断函数
                            kbd);                              //urb完成函数上下文
        kbd->led->transfer_dma = kbd->leds_dma;
        kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;


        /* 注册input_dev */
        error = input_register_device(kbd->dev);
        if (error) {
           printk("input device usb keyboard device registered failed\n");
           goto fail2;
        } else {
            printk("input device usb keyboard device registered successfully\n");
        }

        /* 设置intf->dev->driver_data = kbd */
        usb_set_intfdata(intf, kbd);
        return 0;

fail2:
        /* 释放动态申请的缓存区 */
        usb_kbd_free_mem(dev,kbd);
fail1:
        /* 释放input_dev */
        input_free_device(input_dev);
        kfree(kbd);
        return error;
}

/*
 * 卸载usb接口驱动时执行
 */
static usb_kbd_disconnect(struct usb_interface *intf)
{
    /* 获取intf->dev->driver_data */
     struct usb_kbd *kbd = usb_get_intfdata (intf);

     usb_set_intfdata(intf, NULL);

    if(kbd){
        /* 杀掉提交到内核中的urb */
        usb_kill_urb(kbd->irq);
        usb_kill_urb(kbd->led);
        /* 注销内核中的input_dev */
        input_unregister_device(kbd->dev);
        /* 释放input_dev */
        input_free_device(kbd->dev);
        /* 释放动态申请的缓存区 */
        usb_kbd_free_mem(interface_to_usbdev(intf),kbd);
        kfree(kbd);
    }
}

/**
 * usb键盘接口驱动
 */
static struct usb_driver usb_kbd_driver = {
        .name           = "usbkbd",
        .probe          = usb_kbd_probe,
        .disconnect     = usb_kbd_disconnect,
        .id_table       = usb_kbd_id_table,
};

/*
 *  usb键盘接口驱动模块入口
 */
static int usb_kbd_init(void)
{
   int ret;
   ret = usb_register(&usb_kbd_driver);
   if (ret){
       printk("usb interface driver registered failed\n");
   }else{
      printk("usb interface driver registered successfully\n");
   }
   return ret;
}

/*
 * usb键盘接口驱动模块出口
 */
static void __exit usb_kbd_exit(void)
{
    usb_deregister(&usb_kbd_driver);
    printk("usb interface driver deregistered successfully\n");
}

module_init(usb_kbd_init);
module_exit(usb_kbd_exit);
MODULE_LICENSE("GPL");

 

 

 

三、测试

3.1 编译usb键盘接口驱动

在15.usb_keyboard路径下编译:

root@zhengyang:/work/sambashare/drivers/15.usb_keyboard# cd /work/sambashare/drivers/15.usb_keyboard/
root@zhengyang:/work/sambashare/drivers/15.usb_keyboard# make

拷贝驱动文件到nfs文件系统:

root@zhengyang:/work/sambashare/drivers/15.usb_keyboard# cp /work/sambashare/drivers/15.usb_keyboard/keyboard_dev.ko /work/nfs_root/rootfs/

 

3.2 安装驱动

重启开发板,加载usb键盘接口驱动,执行如下命令:

insmod keyboard_dev.ko

运行结果如下:

[root@zy:/]# insmod keyboard_dev.ko
keyboard_dev: loading out-of-tree module taints kernel.
usbcore: registered new interface driver usbkbd
usb interface driver registered successfully

 

这里我没有多余的键盘,不然的话,键盘插入开发板后就可以看到usb键盘相关的信息输出。

3.3 编写应用程序

新建test文件夹,编写测试程序。

 

3.3.1 main.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char const *argv[])
{
    //打开设备文件
    int fd;
    int retval;
    fd_set readfds;
    struct timeval tv;
    if((fd = open("/dev/input/event0", O_RDONLY)) == -1)
    {
        perror("open error");
        return -1;
    }
    //读取文件内容
    struct input_event mykey;
    while(1){
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        if((retval = select(fd+1, &readfds, NULL, NULL, &tv)) == 1)
        {
           if(read(fd, &mykey, sizeof(mykey)) == sizeof(mykey)){      
           // 事件类型  鼠标或者按键
           if(mykey.type == EV_KEY)
           {
                printf("--------------------\n");
                printf("type = %u.\n", mykey.type);
                printf("code = %u.\n", mykey.code);
                printf("value = %u.\n", mykey.value); /* 按键是按下还是释放,0释放、1按下、2长按 */
                // 按下状态
                if(mykey.value == 1)
                {
                    printf("type:%#x, code:%d, value:%#x\n", mykey.type, mykey.code, mykey.value);
                    switch (mykey.code)
                    {
                    case 17:
                        puts("w键按下了");
                        break;
                    case 30:
                        puts("a键按下了");
                        break;
                    case 31:
                        puts("s键按下了");
                        break;
                    case 32:
                        puts("d键按下了");
                        break;
                    case 0x110:
                        puts("mouse left button down");
                        break;
                    case 0x111:
                        puts("mouse right button down");
                        break;
                    case 0x112:
                        puts("mouse middle button down");
                        break;
                    }
                }
          }
        }
      }  
   }
   return 0;
}
3.3.2 Makefile
all:
    arm-linux-gcc -march=armv4t -o main main.c
clean:
    rm -rf *.o main

参考文章

[1]21.Linux-写USB键盘驱动(详解)

标签:usb,urb,linux,dev,kbd,input,驱动,struct
From: https://www.cnblogs.com/zyly/p/16644624.html

相关文章

  • 通过DriverPack手动安装驱动记录
    DriverPack官网: https://driverpack.io/DriverPack类似驱动精灵,但是比驱动精灵驱动更全,驱动版本旧版本更多(很多老机器Win7/XP需要用旧版本驱动),DriverPack也有类似......
  • 基于 ESP8266_RTOS_SDK 驱动 DHT11
    概述DHT11模块使用一根data线实现信号触发以及数据反馈,信号格式参考如下https://zhuanlan.zhihu.com/p/347904660本文使用GPIO中断的方式采集反馈数据知识点:GPIO、中断......
  • Linux vim编辑器
    vim编辑器:setnu设置行号底行输入模式:wq储存后离开i进入输入模式Esc退出编辑模式,回到一般模......
  • Unix/Linux系统编程学习笔记-1
    笔记第一章引言一、概述:在第一章引言里,简单介绍了Unix的历史,包括贝尔实验室开发的Unix早期版本、AT&TSystemV以及Unix的其他版本,如BSD、HPUX、IBMAIX和Sun/Solari......
  • 《Unix/Linux系统编程》第一、二章读书笔记
    自学教材1、2章学习笔记一、任务要求学教材第1,2章,提交学习笔记(10分)知识点归纳以及自己最有收获的内容(3分)问题与解决思路(2分)实践内容与截图,代码链接(3分)知识的结构......
  • 《Unix/Linux系统编程》第1,2章学习笔记 20201209戴骏
    一.知识点归纳第一章引言1.本书目标编程背景知识详细介绍了程序开发步骤,包括汇报器、编译器、链接器等。动态数据结构的应用字符串标记化、搜索树节点、插入和......
  • Linux中 .gzip .tar .zip的区别
    目录.gzip.tar.zip引用gzip:一般是用于单文件的压缩。zip/tar:一般是用于整个目录/多文件的压缩.gzip不能保存原文件,不能压缩文件夹输入gzip1.txt后,原文件1.txt变为1.......
  • Linux基础——tmux&vim基础操作
    tmux&vimtmux分屏tmux可以开很多session,每个session可以开很多window,每个windo可以开很多panetmux功能:(1)分屏。(2)允许断开Terminal连接后,继续运行进程。......
  • docker序列之 linux namespace机制
    linuxnamespace机制linuxnamespace机制提供一种资源隔离和虚拟化特性,PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的namespace。每个namespace下的资源对......
  • Linux系统如何查看版本信息
    Linux系统如何查看版本信息-百度经验 https://jingyan.baidu.com/article/7908e85c725159af481ad2f7.html方法/步骤 输入"uname-a",可显示电脑以及操作系统的......