您的位置:首页 > 其它

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

2016-07-21 12:25 239 查看
    本文以 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不就自动提交了么? 会出现不相等的情况?

    此篇文章存在诸多疑问,如果有大神看到,还请解答一下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息