【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

猜你喜欢

转载自blog.csdn.net/C2681595858/article/details/129900209