欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

USB驱动——键盘驱动(控制传输)

发布时间:2023/12/20 34 豆豆
生活随笔 收集整理的这篇文章主要介绍了 USB驱动——键盘驱动(控制传输) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

 

USB驱动——键盘驱动(控制传输)

http://blog.csdn.net/lizuobin2/ https://blog.csdn.net/lizuobin2/article/details/51971393

    本文以 usbkbd.c 为例,分析 usb 键盘驱动程序。

 

  • static int __init usb_kbd_init(void)

  • {

  • int result = usb_register(&usb_kbd_driver);

  • if (result == 0)

  • printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"

  • DRIVER_DESC "\n");

  • return result;

  • }

  • static struct usb_driver usb_kbd_driver = {

  • .name = "usbkbd",

  • .probe = usb_kbd_probe,

  • .disconnect = usb_kbd_disconnect,

  • .id_table = usb_kbd_id_table,

  • };

  •     还是来看一下 id_table ,与鼠标相比,仅仅是协议不一样。

     

     

  • static 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 */

  • };

  •     下面来看probe函数

     

  • static int usb_kbd_probe(struct usb_interface *iface,

  • const struct usb_device_id *id)

  • {

  • /* 获得usb_device */

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

  •  
  • /* 如果当前设置的端点数量不是1,那么错误,返回 */

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

  •  
  • /*

  • kbd->irq = usb_alloc_urb(0, GFP_KERNEL)

  • kbd->led = usb_alloc_urb(0, GFP_KERNEL)

  • kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma)

  • kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma)

  • kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma)

  • */

  • if (usb_kbd_alloc_mem(dev, kbd))

  • goto fail2;

  •  
  • /* 填充键盘结构体。以及一些字符串 */

  • kbd->usbdev = dev;

  • kbd->dev = input_dev;

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

  • strlcpy(kbd->phys, "/input0", sizeof(kbd->phys));

  •  
  • /* 填充输入设备 */

  • input_dev->name = kbd->name;

  • input_dev->phys = kbd->phys;

  • usb_to_input_id(dev, &input_dev->id);

  • input_dev->dev.parent = &iface->dev;

  •  
  • input_set_drvdata(input_dev, kbd);

  •  
  • /* 设置它支持的事件类型和具体事件 1、按键类事件 2、LED灯(大小写灯等)3、重复上报*/

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

  •  
  • /* 对于LED类型的事件,首先会调用到dev->event 然后再调用事件处理层的event */

  • input_dev->event = usb_kbd_event;

  • input_dev->open = usb_kbd_open;

  • input_dev->close = usb_kbd_close;

  •  
  • /* 填充中断类型Urb */

  • usb_fill_int_urb(kbd->irq, dev, pipe,

  • kbd->new, (maxp > 8 ? 8 : maxp),

  • usb_kbd_irq, kbd, endpoint->bInterval);

  • kbd->irq->transfer_dma = kbd->new_dma;

  • kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

  •  
  • /*

  • * bit7 控制传输 data 阶段的方向 0 主机到设备 1设备到主机, 这里 0 主机到设备

  • * bit5-6 表示 request 类型,是标准的还是厂家定义的, 这里为hid class定义

  • * bit0-4 表示这个请求针对的是设备、接口还是端点 , 这里是接口

  • */

  • kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; // 0x01 << 5 |

  •  
  • /*

  • #define USB_REQ_GET_STATUS 0x00

  • #define USB_REQ_CLEAR_FEATURE 0x01

  • #define USB_REQ_SET_FEATURE 0x03

  • #define USB_REQ_SET_ADDRESS 0x05

  • #define USB_REQ_GET_DESCRIPTOR 0x06

  • #define USB_REQ_SET_DESCRIPTOR 0x07

  • #define USB_REQ_GET_CONFIGURATION 0x08

  • #define USB_REQ_SET_CONFIGURATION 0x09

  • #define USB_REQ_GET_INTERFACE 0x0A

  • #define USB_REQ_SET_INTERFACE 0x0B

  • #define USB_REQ_SYNCH_FRAME 0x0C

  • */

  • kbd->cr->bRequest = USB_REQ_SET_CONFIGURATION;

  •  
  • /* request 的参数 */

  • kbd->cr->wValue = cpu_to_le16(0x200);

  •  
  • /* bRequestType 中,针对接口、端点时,它表示那个接口或端点 */

  • kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);

  •  
  • /* data阶段数据长度 */

  • kbd->cr->wLength = cpu_to_le16(1);

  •  
  • /*

  • static inline void usb_fill_control_urb(

  • struct urb *urb,

  • struct usb_device *dev,

  • unsigned int pipe,

  • unsigned char *setup_packet,

  • void *transfer_buffer,

  • int buffer_length,

  • usb_complete_t complete_fn,

  • void *context)

  • */

  • /* 这里使用的是默认端点0 */

  • usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),(void *) kbd->cr, kbd->leds, 1, usb_kbd_led, kbd);

  • kbd->led->setup_dma = kbd->cr_dma;

  • kbd->led->transfer_dma = kbd->leds_dma;

  • kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);

  •  
  • error = input_register_device(kbd->dev);

  •  
  • usb_set_intfdata(iface, kbd);

  • return 0;

  • }

  •     这里与鼠标驱动程序相比,多了一个控制传输过程,而且,这个控制传输的 bRequestType 中说明了这个传输不是标准的请求,而是Class,我们的键盘是HID类型,因此还要看USB HID协议中,关于这个请求是如何定义的,才能知道wValue 、wIndex等是什么意思(参考:http://blog.csdn.net/leo_wonty/article/details/6721214),这就是就将knd->leds 内的一字节数据发送给从设备。在鼠标驱动程序中,中断 urb 是在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;

  • }

  •     中断传输完成之后会调用完成函数,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;

  • }

  • //报告usb_kbd_keycode[224..231]8按键状态

  • //KEY_LEFTCTRL,KEY_LEFTSHIFT,KEY_LEFTALT,KEY_LEFTMETA,

  • //KEY_RIGHTCTRL,KEY_RIGHTSHIFT,KEY_RIGHTALT,KEY_RIGHTMETA

  • for (i = 0; i < 8; i++)

  • input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);

  • //若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下

  • for (i = 2; i < 8; i++) {

  • //获取键盘离开的中断

  • //同时没有该KEY的按下状态

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

  • }

  • //获取键盘按下的中断

  • //同时没有该KEY的离开状态

  • 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) released.\n",

  • kbd->new[i]);

  • }

  • }

  •  
  • input_sync(kbd->dev); //同步设备,告知事件的接收者驱动已经发出了一个完整的报告

  •  
  • memcpy(kbd->old, kbd->new, 8); //防止未松开时被当成新的按键处理

  •  
  • resubmit:

  • i = usb_submit_urb (urb, GFP_ATOMIC);

  • if (i)

  • hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d",

  • kbd->usbdev->bus->bus_name,

  • kbd->usbdev->devpath, i);

  • }

  •     这里,都是上报的按键类事件,我们在前边的Probe函数中,设置了输入设备支持按键类事件,还有一个LED类事件,但是搜遍代码也没有找到,LED类事件是在哪里上报的。还有,probe函数中定义了一个dev->event函数,在浏览资料时发现,有些人说在input_event时,调用事件处理层的event函数的同时会调用dev->event,我认为这种说法是不正确的,看过input_event代码的同学应该不难发现,只有上报LED类等事件的时候才会触发dev->event,我们这里单纯上报的按键类事件并不会触发dev->event 。那么,probe 函数里定义的 dev->event 函数岂不是成了摆设么?确实,我暂时没发现它有什么用。手头没有usb键盘,这个后边实验证实。

     

     

  • static int usb_kbd_event(struct input_dev *dev, unsigned int type,

  • unsigned int code, int value)

  • {

  • struct usb_kbd *kbd = input_get_drvdata(dev);

  •  
  • if (type != EV_LED)

  • return -1;

  •  
  • 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->status == -EINPROGRESS)

  • return 0;

  •  
  • if (*(kbd->leds) == kbd->newleds)

  • return 0;

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

  • kbd->led->dev = kbd->usbdev;

  • if (usb_submit_urb(kbd->led, GFP_ATOMIC))

  • err_hid("usb_submit_urb(leds) failed");

  •  
  • return 0;

  • }

  •     这里的 dev->led 记录的是led的状态,比如,我们上报一个LED事件时,input_event 中会将对应的事件记录在 dev->led 中。这里检测LED事件是否发生,然后通过控制传输将1字节数据传送给usb键盘,Usb键盘的灯相应做出改变。但是,说到底,代码里没有上报过LED类事件,一切都是白扯。

  • static void usb_kbd_led(struct urb *urb)

  • {

  • struct usb_kbd *kbd = urb->context;

  •  
  • if (urb->status)

  • dev_warn(&urb->dev->dev, "led urb status %d received\n",

  • urb->status);

  •  
  • if (*(kbd->leds) == kbd->newleds)

  • return;

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

  • kbd->led->dev = kbd->usbdev;

  • if (usb_submit_urb(kbd->led, GFP_ATOMIC))

  • err_hid("usb_submit_urb(leds) failed");

  • }

  •     这里的控制传输完成函数也是个累赘?每次有LED事件上报的话,那么控制传输urb就自动提交了。那么,*(kbd->leds)== kbd->newleds 必然是相等的,除非又有新的事件上报了,但是新事件上报时,在usb_kbd_event 函数里urb不就自动提交了么? 会出现不相等的情况?

     

    总结

    以上是生活随笔为你收集整理的USB驱动——键盘驱动(控制传输)的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。