Linux之 USB驱动框架-usb 键盘驱动源码分析(7)

本文详细解析了USB键盘驱动中的关键部分,包括如何使用probe函数初始化设备,通过usb_kbd_alloc_mem动态分配内存用于中断和控制URB,以及中断处理函数usb_kbd_irq的工作流程。同时介绍了控制URB如何与input_dev交互,特别是通过event函数触发LED状态更改的处理过程。
摘要由CSDN通过智能技术生成

一、usbkbd.c

跟USB鼠标类型,USB键盘也属于HID类型,代码在/dirver/hid/usbhid/usbkbd.c下。USB键盘除了提交中断URB外,还需要提交控制URB。

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

module_usb_driver(usb_kbd_driver);
 

 usb_kbd_probe

static int usb_kbd_probe(struct usb_interface *iface,
                         const struct usb_device_id *id)
{
        struct usb_device *dev = interface_to_usbdev(iface);
        struct usb_host_interface *interface;
        struct usb_endpoint_descriptor *endpoint;
        struct usb_kbd *kbd;
        struct input_dev *input_dev;
        int i, pipe, maxp;
        int error = -ENOMEM;

        interface = iface->cur_altsetting;

        if (interface->desc.bNumEndpoints != 1)
                return -ENODEV;

        endpoint = &interface->endpoint[0].desc;
        if (!usb_endpoint_is_int_in(endpoint))
                return -ENODEV;

        pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);//建立中断输入端点
        maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));/获取返回字节大小

        kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); //分配私有数据空间
        input_dev = input_allocate_device();//分配input设备空间
        if (!kbd || !input_dev)
                goto fail1;

        if (usb_kbd_alloc_mem(dev, kbd))//分配urb空间和其他缓冲空间
                goto fail2;

        kbd->usbdev = dev;//给内嵌结构体赋值
        kbd->dev = input_dev;//给内嵌结构体赋值
        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(dev, kbd->phys, sizeof(kbd->phys));
        strlcat(kbd->phys, "/input0", sizeof(kbd->phys));

        input_dev->name = kbd->name;
        input_dev->phys = kbd->phys;
        usb_to_input_id(dev, &input_dev->id);//复制usb_driver的支持项给input的支持项
        input_dev->dev.parent = &iface->dev;

        input_set_drvdata(input_dev, kbd);//将kbd设置为input的私有数据

        input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
                BIT_MASK(EV_REP);
        input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
                BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |
                BIT_MASK(LED_KANA);

        for (i = 0; i < 255; i++)
                set_bit(usb_kbd_keycode[i], input_dev->keybit);
        clear_bit(0, input_dev->keybit);

        input_dev->event = usb_kbd_event;//定义event函数
        input_dev->open = usb_kbd_open;
        input_dev->close = usb_kbd_close;

        usb_fill_int_urb(kbd->irq, dev, pipe,
                         kbd->new, (maxp > 8 ? 8 : maxp),
                         usb_kbd_irq, kbd, endpoint->bInterval);//填充中断urb
        kbd->irq->transfer_dma = kbd->new_dma;
        kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //DMA传输方式

        kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; 
        kbd->cr->bRequest = 0x09;  //设置控制请求的格式
        kbd->cr->wValue = cpu_to_le16(0x200); cpu 数据格式转换为小端格式
        kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
        kbd->cr->wLength = cpu_to_le16(1);

        usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),
                             (void *) kbd->cr, kbd->leds, 1,
                             usb_kbd_led, kbd);//填充控制urb
        kbd->led->transfer_dma = kbd->leds_dma;
        kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;///设置dma和setup_dma有效

        error = input_register_device(kbd->dev); //注册input设备
        if (error)
                goto fail2;

        usb_set_intfdata(iface, kbd);
        device_set_wakeup_enable(&dev->dev, 1);
        return 0;

fail2:
        usb_kbd_free_mem(dev, kbd);
fail1:
        input_free_device(input_dev);
        kfree(kbd);
        return error;
}
 

 在上面的probe中,我们主要是初始化一些结构体,然后提交中断urb和控制urb,并注册input设备。其中有几个地方需要细看下,其一,usb_kbd_alloc_mem的实现。其二,设置控制请求的格式。

其一:

usb_kbd_alloc_mem

static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)
{
        if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))//分配中断urb
                return -1;
        if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))//分配控制urb
                return -1;
        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)))//分配控制urb使用的控制请求描述符
                return -1;
        if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma)))//分配控制urb使用的缓冲区
                return -1;

        return 0;
}
 

 这里我们需要明白中断urb和控制urb需要分配不同的urb结构体,同时在提交urb之前,需要填充的内容也不同,中断urb填充的是缓冲区和中断处理函数,控制urb填充的是控制请求描述符和回调函数。

其二:设置控制请求的格式

cr是struct usb_ctrlrequest结构的指针,USB协议中规定一个控制请求的格式为一个8个字节的数据包,其定义如下
struct usb_ctrlrequest {
    __u8 bRequestType;//设定传输方向、请求类型等
    __u8 bRequest;//指定哪个请求,可以是规定的标准值也可以是厂家定义的值
    __le16 wValue;//即将写到寄存器的数据
    __le16 wIndex;//接口数量,也就是寄存器的偏移地址
    __le16 wLength;//数据传输阶段传输多少个字节
} __attribute__ ((packed));

USB协议中规定,所有的USB设备都会响应主机的一些请求,这些请求来自USB主机控制器,主机控制器通过设备的默认控制管道发出这些请求。默认的管道为0号端口对应的那个管道。

 

同样这个input设备首先由用户层调用open函数,所以先看看input中定义的open

static int usb_kbd_open(struct input_dev *dev)
{
        struct usb_kbd *kbd = input_get_drvdata(dev);

        kbd->irq->dev = kbd->usbdev;
        if (usb_submit_urb(kbd->irq, GFP_KERNEL))
                return -EIO;

        return 0;
}
 

 因为这个驱动里面有一个中断urb一个控制urb,我们先看中断urb的处理流程。中断urb在input的open中被提交后,当USB核心处理完毕,会通知这个USB设备驱动,然后执行回调函数,也就是中断处理函数usb_kbd_irq

static void usb_kbd_irq(struct urb *urb)
{
        struct usb_kbd *kbd = urb->context;
        int i;

        switch (urb->status) {
        case 0:                 /* success */
                break;
        case -ECONNRESET:       /* unlink */
        case -ENOENT:
        case -ESHUTDOWN:
                return;
        /* -EPIPE:  should clear the halt */
        default:                /* error */
                goto resubmit;//出错就再次提交中断urb
        }

        for (i = 0; i < 8; i++)
                input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);

        for (i = 2; i < 8; i++) {//向input子系统报告

                if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
                        if (usb_kbd_keycode[kbd->old[i]])
                                input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);
                        else
                                hid_info(urb->dev,
                                         "Unknown key (scancode %#x) released.\n",
                                         kbd->old[i]);
                }

                if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
                        if (usb_kbd_keycode[kbd->new[i]])
                                input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);
                        else
                                hid_info(urb->dev,
                                         "Unknown key (scancode %#x) pressed.\n",
                                         kbd->new[i]);
                }
        }

        input_sync(kbd->dev);

        memcpy(kbd->old, kbd->new, 8);

resubmit:
        i = usb_submit_urb (urb, GFP_ATOMIC);//再次提交中断urb
        if (i)
                hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d",
                        kbd->usbdev->bus->bus_name,
                        kbd->usbdev->devpath, i);
}

 这个就是中断urb的处理流程,跟前面讲的的USB鼠标中断处理流程类似。

接着看控制urb处理流程。

疑问:在probe中,我们填充了中断urb和控制urb,但是在input的open中,我们只提交了中断urb,那么控制urb什么时候提交呢?

我们知道对于input子系统,如果有事件被响应,我们会调用事件处理层的event函数,而该函数最终调用的是input下的event。所以,对于input设备,我们在USB键盘驱动中只设置了支持LED选项,也就是ledbit项,这是怎么回事呢?刚才我们分析的那个中断urb其实跟这个input基本没啥关系,中断urb并不是像讲键盘input实现的那样属于input下的中断。我们在USB键盘驱动中的input子系统中只设计了LED选项,那么当input子系统有按键选项的时候必然会使得内核调用事件处理层的event函数,最终调用input下的event。

那我们来看看input下的event干了些什么。

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);

        if (type != EV_LED)
                return -1;//不是LED事件就返回

        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));//将当前的LED值保存在kbd->newleds中

        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))//提交控制urb
                pr_err("usb_submit_urb(leds) failed\n");
        else
                kbd->led_urb_submitted = true;

        spin_unlock_irqrestore(&kbd->leds_lock, flags);

        return 0;
}
 

 当在input的event里提交了控制urb后,经过URB处理流程,最后返回给USB设备驱动的回调函数,也就是在probe中定义的usb_kbd_led

static void usb_kbd_led(struct urb *urb)
{
        unsigned long flags;
        struct usb_kbd *kbd = urb->context;

        if (urb->status)//提交失败显示
                hid_warn(urb->dev, "led urb status %d received\n",
                         urb->status);

        spin_lock_irqsave(&kbd->leds_lock, flags);

        if (*(kbd->leds) == kbd->newleds){/比较kbd->leds和kbd->newleds,如果发生变化,则更新kbd->leds
                kbd->led_urb_submitted = false;
                spin_unlock_irqrestore(&kbd->leds_lock, flags);
                return;
        }

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

        kbd->led->dev = kbd->usbdev;
        if (usb_submit_urb(kbd->led, GFP_ATOMIC)){//再次提交控制urb
                hid_err(urb->dev, "usb_submit_urb(leds) failed\n");
                kbd->led_urb_submitted = false;
        }
        spin_unlock_irqrestore(&kbd->leds_lock, flags);

}
 

总结下,键盘的控制urb走的是先由input的event提交,如果leds 变化了,则由控制urb的回调函数再次提交。好了,通过USB鼠标,我们已经知道了控制urb和中断urb的设计和处理流程 。

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 设备驱动开发详解 linuxdriver_code_tool |-- 03 | `-- 2.6内核升级工具 | |-- device-mapper-1.00.19-2.i386.rpm | |-- lvm2-2.00.25-1.01.i386.rpm | |-- mkinitrd-4.2.0.3.tar.tar | |-- module-init-tools-3.2.2.tar.bz2 | `-- modutils-2.4.5-1.src.rpm |-- 04 | |-- 内核模块参数范例 | | `-- book.c | |-- 内核模块导出符号 | | `-- export_symb.c | `-- 最简单的内核模块 | `-- hello.c |-- 05 | `-- udev代码 | `-- udev-114.tar.gz |-- 06 | |-- globalmem驱动 | | `-- globalmem.c | `-- 包含2个globalmem设备的驱动 | `-- globalmem_two.c |-- 07 | `-- 含并发控制的globalmem驱动 | `-- globalmem_lock.c |-- 08 | |-- globalfifo驱动 | | `-- globalfifo.c | `-- poll应用程序范例 | `-- pollmonitor.c |-- 09 | |-- 异步通知应用程序范例 | | `-- asyncmonitor.c | `-- 支持异步通知的globalfifo | `-- globalfifo_async.c |-- 10 | |-- S3C2410实时钟驱动 | | `-- s3c2410-rtc.c | `-- 秒设备驱动与应用程序 | |-- second.c | `-- second_test.c |-- 11 | |-- DMA范例 | | |-- 3c505.c | | |-- 3c505.h | | `-- dma.h | `-- 静态映射范例 | `-- mach-smdk2440.c |-- 12 | |-- NVRAM驱动 | | `-- generic_nvram.c | |-- 触摸屏驱动 | | |-- 作为input设备 | | | |-- s3c2410_ts.c | | | `-- s3c2410_ts.h | | `-- 作为普通字符设备 | | `-- s3c2410-ts.c | |-- 看门狗驱动 | | `-- s3c2410_wdt.c | `-- 平台设备 | `-- devs.c |-- 13 | |-- IDE驱动 | | |-- ide-disk.c | | `-- ide-h8300.c | `-- RAMDISK驱动 | `-- rd.c |-- 14 | |-- S3C2410串口驱动 | | |-- regs-gpio.h | | |-- regs-serial.h | | `-- s3c2410.c | `-- 串口核心层 | |-- serial_core.c | `-- serial_core.h |-- 15 | |-- S3C2410 I2C主机驱动 | | |-- i2c-s3c2410.c | | |-- iic.h | | |-- regs-gpio.h | | `-- regs-iic.h | `-- SAA711x I2C设备驱动 | `-- saa711x.c |-- 16 | `-- CS8900以太网设备驱动 | |-- cs89x0.c | `-- cs89x0.h |-- 17 | |-- ALSA工具及库 | | |-- alsa-driver-1.0.15.tar.bz2 | | |-- alsa-firmware-1.0.15.tar.bz2 | | |-- alsa-lib-1.0.15.tar.bz2 | | |-- alsa-oss-1.0.15.tar.bz2 | | |-- alsa-tools-1.0.15.tar.bz2 | | |-- alsa-utils-1.0.13.tar.bz2 | | `-- pyalsa-1.0.15.tar.bz2 | |-- ALSA驱动范例 | | |-- sa11xx-uda1341.c | | `-- uda1341.h | |-- ALSA应用程序范例 | | |-- pcm.c | | `-- pcm_min.c | |-- OSS驱动范例 | | `-- s3c2410-uda1341.c | `-- OSS应用程序范例 | |-- mixer.c | `-- sound.c |-- 18 | |-- FRAMEBUFFER应用程序范例 | | `-- fb_display | | |-- fb_display.c | | |-- fb_display.h | | |-- Makefile | | |-- README | | `-- test.c | `-- S3C2410 LCD驱动 | |-- s3c2410fb.c | `-- s3c2410fb.h |-- 19 | |-- busybox代码 | | `-- busybox-1.2.1.tar.bz2 | |-- MTD工具 | | `-- mtd-utils-1.0.0.tar.gz | |-- nand驱动范例 | | `-- s3c2410.c | |-- nor驱动范例 | | `-- s3c2410nor.c | `-- yaffs&yaffs2代码 | |-- yaffs.tar.gz | `-- yaffs2.tar.gz |-- 20 | |-- USB串口驱动 | | |-- usb-serial.c | | `-- usb-serial.h | |-- USB工具 | | `-- usbview-1.0.tar.tar | |-- USB骨架程序 | | `-- usb-skeleton.c | |-- USB键盘驱动 | | |-- input.h | | |-- usb_input.h | | `-- usbkbd.c | `-- usb主机控制器驱动范例 | |-- ohci-s3c2410.c | `-- usb-control.h |-- 21 | |-- PCI骨架程序 | | `-- pci-skeleton.c | `-- PCI驱动范例 | `-- i810_audio.c `-- 22 |-- 范例代码 | |-- oops范例 | | |-- oops_example.asm | | `-- oops_example.c | `-- proc范例 | `-- sim_proc.c `-- 内核调试工具 |-- ddd-3.3.11.tar.gz |-- gdbmod-2.4.bz2 |-- kdb-v4.4-2.6.15-rc5-common-1.bz2 |-- kdb-v4.4-2.6.15-rc5-common-2.bz2 |-- kdb-v4.4-2.6.15-rc5-i386-1.bz2 `-- linux-2.6.15.5-kgdb-2.4.tar.tar 73 directories, 91 files
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值