程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

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

在前面的章节我们已经介绍了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/a,接收到的8个字节数据为:

00 00 04 00 00 00 00 00

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

00 00 04 16 00 00 00 00

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

我们找到输入子系统include/uapi/linux/input-event-codes.h中按键定义的值,可以看到按键A/a定义的值为30,按键S/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_driver结构体:

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_alloc_coherent申请usb缓冲区;
  • 通过usb_alloc_urb申请urb结构体;
  • 通过usb_fill_int_urb填充urb结构体;当键盘点击时触发urb处理完成函数,在urb处理完成函数进行输入事件上报;
  • 因为我们S3C2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址;
  • 使用usb_submit_urb提交urb

2.4 编写usb_kbd_disconnect

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

2.5 编写驱动模块出口函数

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

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;
                    }
                }
            }
        }
      }  
   }
   return 0;
}
3.3.2 Makefile
all:
    arm-linux-gcc -march=armv4t -o main main.c
clean:
    rm -rf *.o main
3.3.3 编译下载
root@zhengyang:/work/sambashare/drivers/15.usb_keyboard/test# make
arm-linux-gcc -march=armv4t -o main main.c
root@zhengyang:/work/sambashare/drivers/15.usb_keyboard/test# cp main /work/nfs_root/rootfs/

在开发板串口工具输入:

[root@zy:/]# ./main

随便按下键盘a、s、w、d输出如下:

--------------------
type = 1.
code = 30.
value = 1.
type:0x1, code:30, value:0x1
a键按下了
--------------------
type = 1.
code = 30.
value = 0.
--------------------
type = 1.
code = 31.
value = 1.
type:0x1, code:31, value:0x1
s键按下了
--------------------
type = 1.
code = 31.
value = 0.
--------------------
type = 1.
code = 17.
value = 1.
type:0x1, code:17, value:0x1
w键按下了
--------------------
type = 1.
code = 17.
value = 0.
--------------------
type = 1.
code = 32.
value = 1.
type:0x1, code:32, value:0x1
d键按下了
--------------------
type = 1.
code = 32.
value = 0.

四、使用内核自带usb键盘驱动

4.1 配置内核

我们需要重新配置内核,使用内核自带的usb键盘驱动,具体步骤如下。

我们切换到linux内核目录下:

root@zhengyang:~# cd /work/sambashare/linux-5.2.8/

查看drivers/hid/usbhid/Kconfig:

menu "USB HID Boot Protocol drivers"
        depends on USB!=n && USB_HID!=y && EXPERT

config USB_KBD
        tristate "USB HIDBP Keyboard (simple Boot) support"
        depends on USB && INPUT
        ---help---
          Say Y here only if you are absolutely sure that you don't want
          to use the generic HID driver for your USB keyboard and prefer
          to use the keyboard in its limited Boot Protocol mode instead.

          This is almost certainly not what you want.  This is mostly
          useful for embedded applications or simple keyboards.

          To compile this driver as a module, choose M here: the
          module will be called usbkbd.

          If even remotely unsure, say N.

config USB_MOUSE
        tristate "USB HIDBP Mouse (simple Boot) support"
        depends on USB && INPUT
        ---help---
          Say Y here only if you are absolutely sure that you don't want
          to use the generic HID driver for your USB mouse and prefer
          to use the mouse in its limited Boot Protocol mode instead.

          This is almost certainly not what you want.  This is mostly
          useful for embedded applications or simple mice.

          To compile this driver as a module, choose M here: the
          module will be called usbmouse.

          If even remotely unsure, say N.

endmenu

如果要使用内核自带的usb键盘驱动,USB配置为y,USB_HID不为y,必须要有EXPERT配置。我们编辑drivers/hid/usbhid/Kconfig文件,删除EXPERT。

在linux内核根目录下执行,生成默认配置文件.config:

make distclean
make s3c2440_defconfig    # 这个是之前我之前配置的

进行内核配置:

root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig

配置步骤如下:

 Device Drivers  --->

  • HID support --->
    • USB HID support
      • USB HID Boot Protocol drivers
        • <*>USB HIDBP Keyboard (simple Boot) support=

修改完配置后,保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:

mv s3c2440_defconfig ./arch/arm/configs/

此时重新执行:

make s3c2440_defconfig

4.2 编译内核和模块

如果修改了内核代码,则需要重新编译内核:

root@zhengyang:~# cd /work/sambashare/linux-5.2.8/
make V=1 uImage

将uImage复制到tftp服务器路径下:

 cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

4.3 烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。

设置开发板ip地址,从而可以使用网络服务:

SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # save
Saving Environment to NAND...
Erasing NAND...

Erasing at 0x40000 -- 100% complete.
Writing to NAND... OK
SMDK2440 # ping 192.168.0.200
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
host 192.168.0.200 is alive

设置tftp服务器地址,也就是我们ubuntu服务器地址:

set serverip 192.168.0.200
save

下载内核到内存,并写NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel
bootm

插入usb键盘,开发板内核启动后,输出usb键盘相关信息。

五、代码下载

Young / s3c2440_project[drivers]

参考文章

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

[2]Linux 键盘/鼠标 按键事件 编程

posted @ 2022-09-04 22:05  大奥特曼打小怪兽  阅读(758)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步