目录
一、概念介绍
例当USB设备接到PC时,右下角弹出"发现android phone",跳出一个对话框,提示安装驱动程序
提出问题:
- 既然还没有"驱动程序",为何能知道是"android phone"?答:windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone",提示安装的是"设备驱动程序"
- USB设备种类非常多,为什么一接入电脑,就能识别出来?答:PC和USB设备都得遵守一些规范,比如USB设备接入电脑后,PC机会发出"你是什么"?USB设备就必须回答"我是XXX",并且回答的语言必须是中文,USB总线驱动程序会发出某些命令想获取设备信息(描述符),USB设备必须返回"描述符"给PC
- USB接口只有4条线:5V,GND,D-,D+,PC机上接有非常多的USB设备,怎么分辨它们?答:每一个USB设备接入PC时,USB总线驱动程序都会给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)
- USB设备刚接入PC时,还没有编号,那么PC怎么把"分配的编号"告诉它?答:新接入的USB设备的默认编号是0,在末分配新编号前,PC使用0编号和它通信
- 为什么一接入USB设备,PC机就能发现它?答:PC的USB口内部,D-和D+接有15K的下拉电阻,未接USB设备时平时为低电平,USB设备的USB口内部,D-或D+接有1.5K的上拉电阻;它一接入PC,就会把PC USB口的D-或D+拉高,从硬件的角度通知PC有新设备接入,如果USB设备上是在D-接上1.5K电阻则是全速设备(12Mbps),如果是在D+接上1.5K电阻则是高速设备(480Mbps)
其他概念:
- USB是主从结构的,所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力,例:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等待PC机来读
- USB的传输类型:
a.控制传输:可靠,时间有保证,比如:USB设备的识别过程
b.批量传输:可靠,时间没有保证,比如:U盘(把数据拷到U盘后,弹出U盘时快时慢,文件可靠)
c.中断传输:可靠,实时,比如:USB鼠标(不希望几秒后鼠标才能反映,所以需要实时查询,借助中断概念,并没有中断能力,根据查询方式来实时查询)
d.实时传输:不可靠,实时,比如:USB摄像头(数据不可靠,有时候出现花屏,但实时传输)
- USB传输的对象:端点(endpoint)
我们说"读U盘"、"写U盘",可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据,除了端点0外,每个端点只支持一个方向的数据传输,端口0用于控制传输,既能输出也能输入
- 每一个端点都有传输类型,传输方向
- 术语里、程序里说的输入(IN)、输出(OUT)"都是"基于USB主机的立场说的,比如鼠标的数据是从鼠标传到PC机,对应的端点成为"输入端点"
- USB总线驱动程序的作用
a.识别USB设备 b.查到并安装对应的设备驱动程序 c.提供USB读写函数(不了解数据含义)
- libusb封装了接口函数,可跳过设备驱动程序来调用USB读写函数
- USB驱动程序框架:
app | |
内核 | USB设备驱动程序(知道数据含义) |
USB总线驱动程序(不知道数据含义) | |
硬件 | USB主机控制器 |
USB设备 |
- 对于PC的USB设备是主机控制器,USB总线驱动程序就是来支持USB主机控制器,可认为USB总线驱动程序就是USB主机控制器的驱动程序,主机控制器上面接有USB设备,设备驱动程序来支持USB设备,设备驱动程序访问USB设备,需要USB总线驱动程序提供的函数发给USB主机控制器,由USB主机控制器产生信号发给USB设备
二、分析USB总线驱动程序
- 对于USB主机控制器来说有三种规范
1.UHCI:intel规范,适用于低速(USB1.1、1.5Mbps)/全速(USB2.0、12Mbps)对于USB2.0还要分为全速还是高速
2.OHCI:microsoft规范,低速(USB1.1)/全速(USB2.0)
3.EHCI:高速(480Mbps)
- 对于个人单板S3C2440用的规范为OHCI,在内核中有ohci-2410.cUSB总线驱动程序
- USB总线驱动程序的作用中识别USB设备有三点:1.给USB设备分配地址 2.并告诉USB设备(set address) 3.发出命令获取描述符
- 在开发板上接入一个USB设备,看输出信息:
usb 1-1: new full speed USB device using s3c2410-ohci and address 2
usb 1-1: configuration #1 chosen from 1 choice
scsi0 : SCSI emulation for USB Mass Storage devices
scsi 0:0:0:0: Direct-Access HTC Android Phone 0100 PQ: 0 ANSI: 2
sd 0:0:0:0: [sda] Attached SCSI removable disk
拔掉:
usb 1-1: USB disconnect, address 2
再接上:
usb 1-1: new full speed USB device using s3c2410-ohci and address 3
usb 1-1: configuration #1 chosen from 1 choice
scsi1 : SCSI emulation for USB Mass Storage devices
scsi 1:0:0:0: Direct-Access HTC Android Phone 0100 PQ: 0 ANSI: 2
sd 1:0:0:0: [sda] Attached SCSI removable disk
- 新的全速设备接上,使用s3c2410-ohci和地址2,断开用地址2,重新接上又分配了地址3,根据这些信息来查看内核
- 在内核drivers目录下搜grep "USB device using" * -nR,找到以下内容
drivers/usb/core/hub.c:2186: "%s %s speed %sUSB device using %s and address %d\n",
- 在开发板中在USB主机控制器有hub上面接USB设备,每一个USB主机控制器都自带hub,hub可认为是特殊的USB设备
分析hub.c
在hub.c中出现打印语句
static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
...
dev_info (&udev->dev,
"%s %s speed %sUSB device using %s and address %d\n",
(udev->config) ? "reset" : "new", speed, type,
udev->bus->controller->driver->name, udev->devnum);
...
}
而hub_port_init在hub_port_connect_change被调用
static void hub_port_connect_change(struct usb_hub *hub, int port1,
u16 portstatus, u16 portchange)
{
...
status = hub_port_init(hub, udev, port1, i);
...
}
而hub_port_connect_change在hub_events被调用,而hub_events又被hub_thread线程调用,hub_thread线程平时是休眠的,在khubd_wait队列中休眠
static int hub_thread(void *__unused)
{
do {
hub_events();
wait_event_interruptible(khubd_wait,
!list_empty(&hub_event_list) ||
kthread_should_stop());
try_to_freeze();
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}
khubd_wait则在kick_khubd被唤醒
static void kick_khubd(struct usb_hub *hub)
{
unsigned long flags;
/* Suppress autosuspend until khubd runs */
to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
spin_lock_irqsave(&hub_event_lock, flags);
if (list_empty(&hub->event_list)) {
list_add_tail(&hub->event_list, &hub_event_list);
wake_up(&khubd_wait);
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}
同时kick_khubd被hub_irq中断调用,这是中断是主机控制器注册的中断,不是USB设备的中断,当我们接上USB设备,由硬件感知USB设备接入,就会产生hub_irq中断,一路下来调用hub_port_init函数来打印出新设备
/* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
{
struct usb_hub *hub = urb->context;
int status;
int i;
unsigned long bits;
switch (urb->status) {
case -ENOENT: /* synchronous unlink */
case -ECONNRESET: /* async unlink */
case -ESHUTDOWN: /* hardware going away */
return;
default: /* presumably an error */
/* Cause a hub reset after 10 consecutive errors */
dev_dbg (hub->intfdev, "transfer --> %d\n", urb->status);
if ((++hub->nerrors < 10) || hub->error)
goto resubmit;
hub->error = urb->status;
/* FALL THROUGH */
/* let khubd handle things */
case 0: /* we got data: port status changed */
bits = 0;
for (i = 0; i < urb->actual_length; ++i)
bits |= ((unsigned long) ((*hub->buffer)[i]))
<< (i*8);
hub->event_bits[0] = bits;
break;
}
hub->nerrors = 0;
/* Something happened, let khubd figure it out */
kick_khubd(hub);
resubmit:
if (hub->quiescing)
return;
if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
&& status != -ENODEV && status != -EPERM)
dev_err (hub->intfdev, "resubmit --> %d\n", status);
}
在hub_port_connect_change函数中调用了choose_address(udev)函数,给新设备分配编号(地址),find_next_zero_bit,如果编号被分配了相应位就设置为1,因此devnum如果大于128就返回重新分配,所以在USB设备重插上后地址为adress3,一个USB主机控制器最多可以接1-127即127个设备
static void choose_address(struct usb_device *udev)
{
int devnum;
struct usb_bus *bus = udev->bus;
/* If khubd ever becomes multithreaded, this will need a lock */
/* Try to allocate the next devnum beginning at bus->devnum_next. */
devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
bus->devnum_next);
if (devnum >= 128)
devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
if (devnum < 128) {
set_bit(devnum, bus->devmap.devicemap);
udev->devnum = devnum;
}
}
hub_port_init在hub_port_connect_change被调用,SET_ADDRESS_TRIES为设置地址的宏,在hub_port_init函数中调用hub_set_address函数,把编号(地址)告诉USB设备,USB设备以后就使用这个地址,并且调用了usb_get_device_descriptor函数获取设备描述符
static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
...
for (j = 0; j < SET_ADDRESS_TRIES; ++j) {
retval = hub_set_address(udev);
retval = usb_get_device_descriptor(udev, 8);
...
}
而在usb_get_device_descriptor中找到描述符的信息在ch9.h中定义,ch9为USB规范的第九章,描述符的信息可以在include\linux\usb\ch9.h看到,usb_device_descriptor为设备描述符,还有usb_config_descriptor配置描述符
struct usb_device_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__le16 bcdUSB; //USB版本号
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bMaxPacketSize0; //最大包大小,每个设备都有端点0,通过端点0来识别USB设备
__le16 idVendor; //厂家ID
__le16 idProduct; //产品ID
__le16 bcdDevice;
__u8 iManufacturer;
__u8 iProduct;
__u8 iSerialNumber;
__u8 bNumConfigurations; //配置的个数,即设备有多少种配置
} __attribute__ ((packed));
struct usb_config_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__le16 wTotalLength; //本配置下其他所有信息的总长度
__u8 bNumInterfaces; //接口的个数
__u8 bConfigurationValue;
__u8 iConfiguration;
__u8 bmAttributes;
__u8 bMaxPower;
} __attribute__ ((packed));
每个USB设备都有设备描述符,一个设备可能有多种配置,还有配置描述符,每个配置下面可能有多个接口,也可能只有一个接口,接口是逻辑上的设备,比如USB声卡,硬件只有一个,但逻辑上可能有两个功能,录音和播放,就可分为两个接口,两个逻辑设备,用两个接口描述符来分别描述,接口描述符如下,有端点就有端点描述符,写驱动程序的时候是给逻辑上的设备写的,所以一个USB硬件有可能安装多个驱动程序,一个接口描述符里有多个端点,描述符详解
struct usb_interface_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bInterfaceNumber;
__u8 bAlternateSetting;
__u8 bNumEndpoints; //USB传输的对象,除了端点0,有多少个端点
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
__u8 iInterface;
} __attribute__ ((packed));
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress; //端点地址,是端点1还是2,有编号(地址)
__u8 bmAttributes; //属性,哪种类型的端点,是输入还是输出,是实时端点还是批量端点
__le16 wMaxPacketSize; //最大包大小,一次性可以读多少数据
__u8 bInterval; //对于鼠标,查询的频繁
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
在usb_get_device_descriptor只获得8个字节,是因为不知道端点0一次性能传输多少数据,在设备描述符中第8个字节就可以知道一个包最大可以读多少,后面再根据最大包来获得设备描述符
static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
...
retval = usb_get_device_descriptor(udev, 8);
...
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
...
}
struct usb_device_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__le16 bcdUSB; //USB版本号
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bMaxPacketSize0;
..
}
继续分析hub_port_connect_change函数调用了usb_new_device函数,而在usb_get_configuration函数把所有的描述符都读出来,并解析,并且调用了device_add函数,回到了总线设备驱动模型,把device放入usb_bus_type的dev链表, 从usb_bus_type的driver链表里取出usb_driver
static void hub_port_connect_change(struct usb_hub *hub, int port1,
u16 portstatus, u16 portchange)
{
...
udev = usb_alloc_dev(hdev, hdev->bus, port1); //dev->dev.bus = &usb_bus_type;
/* Run it through the hoops (find a driver, etc) */
if (!status) {
status = usb_new_device(udev);
...
err = device_add(&udev->dev);
...
}
int usb_new_device(struct usb_device *udev)
{
int err;
/* Determine quirks */
usb_detect_quirks(udev);
err = usb_get_configuration(udev);
...
int usb_get_configuration(struct usb_device *dev)
{
...
result = usb_parse_configuration(&dev->dev, cfgno,
&dev->config[cfgno], bigbuffer, length);
...
}
在usb_alloc_dev函数分配了一个dever,在函数中dev->dev.bus = &usb_bus_type;出现了bus总线,在bus总线中match函数是根据id_table来比较usb_interface和usb_driver,如果能匹配,调用usb_driver的probe
struct usb_device *
usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
struct usb_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return NULL;
if (!usb_get_hcd(bus_to_hcd(bus))) {
kfree(dev);
return NULL;
}
device_initialize(&dev->dev);
dev->dev.bus = &usb_bus_type;
dev->dev.type = &usb_device_type;
dev->dev.dma_mask = bus->controller->dma_mask;
dev->state = USB_STATE_ATTACHED;
...
}
static int usb_device_match(struct device *dev, struct device_driver *drv)
{
/* devices and interfaces are handled separately */
if (is_usb_device(dev)) {
/* interface drivers never match devices */
if (!is_usb_device_driver(drv))
return 0;
/* TODO: Add real matching code */
return 1;
} else {
struct usb_interface *intf;
struct usb_driver *usb_drv;
const struct usb_device_id *id;
/* device drivers never match interfaces */
if (is_usb_device_driver(drv))
return 0;
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
id = usb_match_id(intf, usb_drv->id_table);
if (id)
return 1;
id = usb_match_dynamic_id(intf, usb_drv);
if (id)
return 1;
}
return 0;
}
更深了解参考《LINUX内核源代码情景分析》书籍
三、编写USB设备驱动程序
3.1 怎么写USB设备驱动程序
usb_interface在usb_bus_type总线当接上设备后会被USB主机控制器发现,需要我们来完善usb_driver
目的:把USB鼠标当成按键,左键为L健,右键为S健,滚轮按下为Enter健(输入子系统实现),1.分配input_dev结构体;2.设置结构体; 3.注册结构体; 4.硬件相关的操作(使用总线驱动程序提供的函数来收发数据)
- 写USB设备驱动程序过程如下,在probe函数中实现输入子系统
1. 分配/设置usb_driver结构体
.id_table
.probe
.disconnect
2. 注册
参考drivers\hid\usbhid\usbmouse.c,真正鼠标设备驱动程序
3.2 分配usb_driver结构体并设置注册
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
在usbmouse.c中id_table为一个USB_INTERFACE_INFO宏,该宏在usb.h中定义,只要USB设备接口描述符的类是USB_INTERFACE_CLASS_HID,子类是USB_INTERFACE_SUBCLASS_BOOT,协议是USB_INTERFACE_PROTOCOL_MOUSE,就能够支持,因此usbmouse_as_key_id_table函数就可以设置出
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
#define USB_INTERFACE_INFO(cl,sc,pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), \
.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
static struct usb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
如果想支持某款设备,只要 厂家ID,设备ID匹配就可以支持是可以的,在usb.h中定义了USB_DEVICE(vend,prod),根据需求修改id_table
#define USB_DEVICE(vend,prod) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \
.idProduct = (prod)
----------------------------
static struct usb_device_id usbmouse_as_key_id_table [] = {
//{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
在probe函数中参数一,逻辑上的设备就是usb_interface表示的,通过这个接口可以得到一个usb_device结构体
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
在usb_device结构体中有着设备描述符,因此我们可以打印出厂家ID和设备ID,写出probe函数和disconnect函数,完整代码如下
/*
* drivers\hid\usbhid\usbmouse.c
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
static struct usb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
printk("found usbmouse!\n");
printk("bcdUSB = %x\n", dev->descriptor.bcdUSB);
printk("VID = 0x%x\n", dev->descriptor.idVendor);
printk("PID = 0x%x\n", dev->descriptor.idProduct);
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
printk("disconnect usbmouse!\n");
}
/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int usbmouse_as_key_init(void)
{
/* 2. 注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
3.3 小测试
需要我们去掉原来的USB鼠标驱动,make menuconfig->Device Drivers->HID Devices-><> USB Human Interface Device (full HID) support ,并make uImage使用新的内核启动
加载驱动后,把PC的鼠标接在开发板,可以看到厂家ID和设备ID,与windows上的信息一致
3.4 完善probe函数
- 在usbmouse.c中probe前面是一些判断,判断端点是否为1个,是否是中断端点
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct input_dev *input_dev;
int pipe, maxp;
int error = -ENOMEM;
interface = intf->cur_altsetting;
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
...
- 分配一个input_dev结构体,并设置注册
static struct input_dev *uk_dev;
uk_dev = input_allocate_device();
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_REP, uk_dev->evbit);
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
input_register_device(uk_dev);
- 硬件相关的操作,使用总线驱动程序提供的函数来收发数据,数据传输三要素:源,目的,长度
源:USB设备的某个端点,usb_rcvintpipe宏包含了USB设备的地址还有端点的地址,pipe是个整数,含有端点的类型,端点的方向,还有设备地址端点地址
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
#define usb_rcvintpipe(dev,endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)static inline unsigned int __create_pipe(struct usb_device *dev,
unsigned int endpoint)
{
return (dev->devnum << 8) | (endpoint << 15);
}
目的用usb_buffer_alloc函数来分配,参数三为一个物理地址,而长度在端点描述符中
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;len = endpoint->wMaxPacketSize;
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
使用三要素,需要usb_alloc_urb函数来分配urb,即usb请求块,使用usb_fill_int_urb函数将三要素填充置urb,usbmouse_as_key_irq为查询函数,并不是CPU在查询,而是由主机控制器在查询,endpoint->bInterval为查询的频率,当主机控制器有数据后,就会存在usb_buf中从而usbmouse_as_key_irq函数被调用
static struct urb *uk_urb;
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
USB主机控制器得到数据后需要往某个物理地址去写,因此我们需要告诉它物理地址以及标记
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
使用urb
usb_submit_urb(uk_urb, GFP_KERNEL);
写出usbmouse_as_key_irq函数先将数据打印出来需要重新提交urb,并且完善disconnect函数
static void usbmouse_as_key_irq(struct urb *urb)
{
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
3.5 通过鼠标了解数据含义
完整代码如下
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;
static struct usb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
static void usbmouse_as_key_irq(struct urb *urb)
{
int i;
static int cnt = 0;
printk("data cnt %d: ", ++cnt);
for (i = 0; i < len; i++)
{
printk("%02x ", usb_buf[i]);
}
printk("\n");
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
/* a. 分配一个input_dev */
uk_dev = input_allocate_device();
/* b. 设置 */
/* b.1 能产生哪类事件 */
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_REP, uk_dev->evbit);
/* b.2 能产生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注册 */
input_register_device(uk_dev);
/* d. 硬件相关操作 */
/* 数据传输3要素: 源,目的,长度 */
/* 源: USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度: */
len = endpoint->wMaxPacketSize;
/* 目的: */
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int usbmouse_as_key_init(void)
{
/* 2. 注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
加载驱动,接上鼠标,按下左键右键滚轮健,可看到当数据中bit[8]为1则左键按下0松开,bit[9]为1则右键按下0松开,bit[10]为1则滚轮键按下0松开
3.6 根据含义完善驱动程序
- 将打印改成上报事件,根据上次的数据来和这次的数据比较,修改usbmouse_as_key_irq函数
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val;
/* USB鼠标数据含义
* data[0]: bit8-左键, 1-按下, 0-松开
* bit9-右键, 1-按下, 0-松开
* bit10-中键, 1-按下, 0-松开
*
*/
if ((pre_val & (1<<0)) != (usb_buf[1] & (1<<0)))
{
/* 左键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[1] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[1] & (1<<1)))
{
/* 右键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[1] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[1] & (1<<2)))
{
/* 中键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[1] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[1];
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
- 加载驱动,并测试,当接上鼠标后才会出现新设备event0,查看tty1,按下左键后按滚轮健出现l,按下右键后按滚轮健出现s,按下左键一会再按滚轮健,可以看到连续的l,也可以输入命令hexdump /dev/event0查看输出信息