【usb】linux内核USB键盘驱动解析--LED灯的控制

一、概况

以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.10.tar.gz
文件路径:linux-5.10/drivers/hid/usbhid/usbkbd.c

二、探索

usb_kbd_event

  • 灯的主要逻辑在函数usb_kbd_event里面,usb_kbd_event被赋值给了input_dev->event
    input_dev->event = usb_kbd_event;也就是说usbkbd模块提供了控制等亮灭的方法,但是具体的到底哪个灯亮或者哪个灯灭的决定权在input模块。
  • 下面我们具体分析下usbkbd模块是如何控制灯的。其核心代码如下:
	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));
  • 这里面的test_bit的实现如下,其作用是获取addr这块内存的第nr位的值。

    • BITS_PER_LONG表示一个long类型所占用的bit位数量。在64位系统上BITS_PER_LONG就是64,32位系统上就是32。BIT_WORD(nr) 用nr整除BITS_PER_LONG就得到了nr这个比特位所在的元素下标。
    • BITS_PER_LONG-1意思是在一个unsigned long类型的变量里面,你最多只能将最左边的位移到最右边,也就是说你最多右移BITS_PER_LONG-1位,(nr & (BITS_PER_LONG-1))这里其实就是计算nr在其坐在的元素内要右移的bit位数。
    • 所以整体来看就是addr[BIT_WORD(nr)]找到第nr个bit位所在的元素,(nr & (BITS_PER_LONG-1))计算出第nr位在元素内的右移位数。(addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))将第nr位移到最右侧。1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)))完成位移后,将该位和1进行与操作,判断其是否是1。
    #ifdef CONFIG_64BIT
    #define BITS_PER_LONG 64
    #else
    #define BITS_PER_LONG 32
    #endif /* CONFIG_64BIT */
    
    #define BIT_WORD(nr)		((nr) / BITS_PER_LONG)
    /**
     * _test_bit - Determine whether a bit is set
     * @nr: bit number to test
     * @addr: Address to start counting from
     */
    static __always_inline bool
    _test_bit(unsigned long nr, const volatile unsigned long *addr)
    {
    	return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)));
    }
    
  • 清楚了test_bit,我们在来看看具体的业务逻辑。本次我们只关注LED_NUML, LED_CAPSL以及LED_SCROLLL。下面这块代码的意思就很明确了分别从dev->led这块内存的LED_SCROLLLLED_CAPSL,及LED_NUMLbit位取出值放在新的变量kbd->newleds的第2, 1, 0位。也就是说kbd->newleds变量的第2位如果是1,那么表示点亮键盘的Scroll Lock灯,如果是0则熄灭Scroll Lock等,而第1位表示Caps Lock,第0位表示Num Lock

    (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) |  (!!test_bit(LED_NUML,    dev->led))
    
  • 然后通过usb_submit_urb(kbd->led, GFP_ATOMIC)将新的led值发送给设备。

  • 我们知道USB有四种传输方式,控制传输(control)、批量传输(bulk)、中断传输(interrupt )以及等时传输(isochronous)。我们这里只是改了下leds的值,然后就提交了urb,那么我们这里到底用了什么传输方式呢?

usb_fill_control_urb

  • 回到我们的usb_kbd_probe函数里面,该函数在usbkbd这个驱动被加载时将会被调用;而我们前面的input_dev->event = usb_kbd_event;操作也是在这个函数里面完成的。

  • 下面是和灯相关的核心代码。控制灯使用的是USB控制传输。控制传输比较重要的就是其setup包,其结构如下:请添加图片描述

  • bRequestType字段,我们控制灯的请求,并非USB协议中定义的标准USB请求,而是输入设备类所定义的请求,所以是USB_TYPE_CLASS。我们这个请求过去之后是控制接口接收,所以接收者是USB_RECIP_INTERFACE。传输方向是主机向设备发送指令Host-to-device是零,所以不需要特意去设置,默认就好。

  • bRequest字段,因为我们这个是输入类设备特有的请求类型。所以需要查看usbhid协议,协议意思是控制LED灯使用Set_Report(Output)请求。而SET_REPORT对应的value是0x09,所以我们这里的bRequest也就是0x09表示这是一个SET_REPORT请求。
    请添加图片描述
    在这里插入图片描述

  • Set_Report(Output) 请求定义如下,其wValue字段包含Report Type Report IDReport Type在高字节,我们LED灯的控制指令是从host到device,也就是out,所以Report Type值是0x02,我们不用 Report ID,所以其值为零。所以我们的wValue字段的值就是0x0200
    请添加图片描述

    请添加图片描述

  • wIndex字段填写interface,直接填写控制接口的bInterfaceNumber字段即可。

  • wLength表示要传输的数据大小,我们传输表述灯状态的一个uint8_t类型,所以其大小为1。

  • usb_fill_control_urb就是把我们上面填进取的这些数据在填到urb里面,也就是kbd->led里面。

	kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	kbd->cr->bRequest = 0x09;
	kbd->cr->wValue = cpu_to_le16(0x200);
	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); 

三、总结

  • 通过usb_fill_control_urb事先准备好urb,然后在usb_kbd_event里面需要时,直接修改leds数据,然后将该urb发送即可。

四、参考资料

USB HID协议
USB 2.0 Specification

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
linux系统下USB键盘驱动源码+使用文档说明 如何编写Linux下的USB键盘驱动 1. 指定USB键盘驱动所需的头文件: #include /*内核头文件,含有内核一些常用函数的原型定义*/ #include /*定义内存分配的一些函数*/ #include /*模块编译必须的头文件*/ #include /*输入设备相关函数的头文件*/ #include /*linux初始化模块函数定义*/ #include /*USB设备相关函数定义*/ 2. 定义键盘码表数组: /*使用第一套键盘扫描码表:A-1E;B-30;C-2E…*/ static 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 }; 3. 编写设备ID表: static struct usb_device_id usb_kbd_id_table [] = { { USB_INTERFACE_INFO(3, 1, 1) },/*3,1,1分别表示接口类,接口子类,接口协议;3,1,1为键盘接口类;鼠标为3,1,2*/ { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);/*指定设备ID表*/ 4. 定义USB键盘结构体: struct usb_kbd { struct input_dev *dev; /*定义一个输入设备*/ struct usb_device *usbdev;/*定义一个usb设备*/ unsigned char old[8]; /*按键离开时所用之数据缓冲区*/ struct urb *irq/*usb键盘之中断请求块*/, *led/*usb键盘之指示请求块*/; unsigned char newleds;/*目标指定状态*/ char name[128];/*存放厂商名字及产品名字*/ char phys[64]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值