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驱动——键盘驱动(控制传输)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “胶原蛋白第一股”来了,巨子生物上市在即
- 下一篇: 8bit 1GS/s 高速数据采集卡