赞
踩
为什么一插上就会有提示信息?
是因为windows自带了USB总线驱动程序;
那USB总线驱动程序是干嘛用的?
首先,新接入的USB设备的默认地址(编号)为0,再未分配新编号前,PC主机使用0地址和它通信。然后USB总线驱动程序都会给它分配一个地址(编号)。PC机想访问USB总线上某个USB设备时,发出的命令都含有对应的地址(编号)。
USB的结构是怎样的?
USB可以热拔插的硬件原理
USB的4大传输类型:
USB端点:
USB驱动整体框架:
USB主机控制器类型
要想成为一个USB主机,硬件上就必须要有USB主机控制器,USB主机控制器又分为4种接口:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvFI8va0-1627907220913)(C:\Users\liang\AppData\Roaming\Typora\typora-user-images\image-20210731131536533.png)]
一个设备描述符可以有多个配置描述符;
一个配置描述符可以有多个接口描述符(比如声卡驱动就有两个接口:录音接口和播放接口)
一个接口描述符可以有多个端点描述符;
USB设备描述符结构体如下所示:
struct usb_device_descriptor { __u8 bLength; //本描述符的size __u8 bDescriptorType; //描述符的类型,这里是设备描述符DEVICE __u16 bcdUSB; //指明usb的版本,比如usb2.0 __u8 bDeviceClass; //类 __u8 bDeviceSubClass; //子类 __u8 bDeviceProtocol; //指定协议 __u8 bMaxPacketSize0; //端点0对应的最大包大小 __u16 idVendor; //厂家ID __u16 idProduct; //产品ID __u16 bcdDevice; //设备的发布号 __u8 iManufacturer; //字符串描述符中厂家ID的索引 __u8 iProduct; //字符串描述符中产品ID的索引 __u8 iSerialNumber; //字符串描述符中设备序列号的索引 __u8 bNumConfigurations; //配置描述符的个数,表示有多少个配置描述符 } __attribute__ ((packed));
USB设备描述符位于USB设备结构体usb_device中的成员descriptor中。同样地,配置、接口、端点描述符也是位于USB配置、接口、端点结构体中,不过这3个对于我们写驱动的不是很常用。
usb_device结构体如下所示:
struct usb_device { int devnum; //设备号,是在USB总线的地址 char devpath [16]; //用于消息的设备ID字符串 enum usb_device_state state; //设备状态:已配置、未连接等等 enum usb_device_speed speed; //设备速度:高速、全速、低速或错误 struct usb_tt *tt; //处理传输者信息;用于低速、全速设备和高速HUB int ttport; //位于tt HUB的设备口 unsigned int toggle[2]; //每个端点占一位,表明端点的方向([0] = IN, [1] = OUT) struct usb_device *parent; //上一级HUB指针 struct usb_bus *bus; //总线指针 struct usb_host_endpoint ep0; //端点0数据 struct device dev; //一般的设备接口数据结构 struct usb_device_descriptor descriptor; //USB设备描述符, struct usb_host_config *config; //设备的所有配置结构体,配置结构体里包含了配置描述符 struct usb_host_config *actconfig; //被激活的设备配置 struct usb_host_endpoint *ep_in[16]; //输入端点数组 struct usb_host_endpoint *ep_out[16]; //输出端点数组 char **rawdescriptors; //每个配置的raw描述符 unsigned short bus_mA; //可使用的总线电流 u8 portnum; //父端口号 u8 level; //USB HUB的层数 unsigned can_submit:1; //URB可被提交标志 unsigned discon_suspended:1; //暂停时断开标志 unsigned persist_enabled:1; //USB_PERSIST使能标志 unsigned have_langid:1; //string_langid存在标志 unsigned authorized:1; unsigned authenticated:1; unsigned wusb:1; //无线USB标志 int string_langid; //字符串语言ID /* static strings from the device */ //设备的静态字符串 char *product; //产品名 char *manufacturer; //厂商名 char *serial; //产品串号 struct list_head filelist; //此设备打开的usbfs文件 #ifdef CONFIG_USB_DEVICE_CLASS struct device *usb_classdev; //用户空间访问的为usbfs设备创建的USB类设备 #endif #ifdef CONFIG_USB_DEVICEFS struct dentry *usbfs_dentry; //设备的usbfs入口 #endif int maxchild; //(若为HUB)接口数 struct usb_device *children[USB_MAXCHILDREN]; //连接在这个HUB上的子设备 int pm_usage_cnt; //自动挂起的使用计数 u32 quirks; atomic_t urbnum; //这个设备所提交的URB计数 unsigned long active_duration; //激活后使用计时 #ifdef CONFIG_PM //电源管理相关 struct delayed_work autosuspend; //自动挂起的延时 struct work_struct autoresume; //(中断的)自动唤醒需求 struct mutex pm_mutex; //PM的互斥锁 unsigned long last_busy; //最后使用的时间 int autosuspend_delay; unsigned long connect_time; //第一次连接的时间 unsigned auto_pm:1; //自动挂起/唤醒 unsigned do_remote_wakeup:1; //远程唤醒 unsigned reset_resume:1; //使用复位替代唤醒 unsigned autosuspend_disabled:1; //挂起关闭 unsigned autoresume_disabled:1; //唤醒关闭 unsigned skip_sys_resume:1; //跳过下个系统唤醒 #endif struct wusb_dev *wusb_dev; //(如果为无线USB)连接到WUSB特定的数据结构 };
struct usb_config_descriptor {
__u8 bLength; //描述符的长度
__u8 bDescriptorType; //描述符类型的编号
__le16 wTotalLength; //配置所返回的所有数据的大小
__u8 bNumInterfaces; //配置所支持的接口个数, 表示有多少个接口描述符
__u8 bConfigurationValue; //Set_Configuration命令需要的参数值
__u8 iConfiguration; //描述该配置的字符串的索引值
__u8 bmAttributes; //供电模式的选择
__u8 bMaxPower; //设备从总线提取的最大电流
} __attribute__ ((packed));
USB接口只处理一种USB逻辑连接。一个USB接口代表一个逻辑上的设备,比如声卡驱动就有两个接口:录音接口和播放接口。这可以在windows系统中看出,有时插入一个USB设备后,系统会识别出多个设备,并安装相应多个的驱动。
struct usb_interface_descriptor {
__u8 bLength; //描述符的长度
__u8 bDescriptorType; //描述符类型的编号
__u8 bInterfaceNumber; //接口的编号
__u8 bAlternateSetting; //备用的接口描述符编号,提供不同质量的服务参数.
__u8 bNumEndpoints; //要使用的端点个数(不包括端点0), 表示有多少个端点描述符,比如鼠标就只有一个端点
__u8 bInterfaceClass; //接口类型,与驱动的id_table对应
__u8 bInterfaceSubClass; //接口子类型
__u8 bInterfaceProtocol; //接口所遵循的协议
__u8 iInterface; //描述该接口的字符串索引值
} __attribute__ ((packed)
它位于usb_interface->cur_altsetting->desc 这个成员结构体里,
struct usb_interface { /* 包含所有可用于该接口的可选设置的接口结构数组。每个 struct usb_host_interface 包含一套端点配置(即struct usb_host_endpoint结构所定义的端点配置。这些接口结构没有特别的顺序。*/ struct usb_host_interface *altsetting; /* 指向altsetting内部的指针,表示当前激活的接口配置*/ struct usb_host_interface *cur_altsetting; unsigned num_altsetting; /* 可选设置的数量*/ /* If there is an interface association descriptor then it will list the associated interfaces */ struct usb_interface_assoc_descriptor *intf_assoc; /* 如果绑定到这个接口的 USB 驱动使用 USB 主设备号, 这个变量包含由 USB 核心分配给接口的次设备号. 这只在一个成功的调用 usb_register_dev后才有效。*/ int minor; ... ... }
struct usb_host_interface {
struct usb_interface_descriptor desc; //当前被激活的接口描述符
struct usb_host_endpoint *endpoint; /* 这个接口的所有端点结构体的联合数组*/
char *string; /* 接口描述字符串 */
unsigned char *extra; /* 额外的描述符 */
int extralen;
};
struct usb_endpoint_descriptor { __u8 bLength; //描述符的长度 __u8 bDescriptorType; //描述符类型的编号 __u8 bEndpointAddress; //端点编号,比如端点1,就是1 __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));
比如端点0就位于usb_interface->cur_altsetting->desc->endpoint[0].desc
endpoint的结构体为usb_host_endpoint,如下所示:
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; //端点描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp; //超快速端点描述符
struct list_head urb_list; //本端口对应的urb链表
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled; //使能的话urb才能被提交到此端口
};
由于内核自带了USB驱动,所以我们先插入一个USB键盘到开发板上看打印信息发现以下字段:
如下图,找到第一段话是位于drivers/usb/core/hub.c的第2186行:
这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口
它又是被谁调用的,如下图所示,我们搜索到它是通过hub_thread()函数调用的
hub_thread()函数如下:
static int hub_thread(void *__unused)
{
do {
hub_events(); //执行一次hub事件函数
wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop()); //(1).每次执行一次hub事件,都会进入一次等待事件中断函数
try_to_freeze();
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}
从上面函数中得到, 要想执行hub_events(),都要等待khubd_wait这个中断唤醒才行。
找到该中断在kick_khubd()函数中唤醒,代码如下:
static void kick_khubd(struct usb_hub *hub)
{
unsigned long flags;
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); //唤醒khubd_wait这个中断
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}
显然,就是当USB设备插入后,D+或D-就会被拉高,然后USB主机控制器就会产生一个hub_irq中断.
static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
{
... ...
udev = usb_alloc_dev(hdev, hdev->bus, port1); //(1)注册一个usb_device,然后会放在usb总线上
usb_set_device_state(udev, USB_STATE_POWERED); //设置注册的USB设备的状态标志
... ...
choose_address(udev); //(2)给新的设备分配一个地址编号
status = hub_port_init(hub, udev, port1, i); //(3)初始化端口,与USB设备建立连接
... ...
status = usb_new_device(udev); //(4)创建USB设备,与USB设备驱动连接
... ...
}
所以最终流程图如下:
usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{
struct usb_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配一个usb_device设备结构体
... ...
device_initialize(&dev->dev); //初始化usb_device
dev->dev.bus = &usb_bus_type; //(1)设置usb_device的成员device->bus等于usb_bus总线
dev->dev.type = &usb_device_type; //设置usb_device的成员device->type等于usb_device_type
... ...
return dev; //返回一个usb_device结构体
}
在第17行上,设置device成员,主要是用来后面8.2小节,注册usb总线的device表上.
其中usb_bus_type是一个全局变量, 它和我们之前学的platform平台总线相似,属于USB总线, 是Linux中bus的一种.
如下图所示,每当创建一个USB设备,或者USB设备驱动时,USB总线都会调用match成员来匹配一次,使USB设备和USB设备驱动联系起来.
usb_bus_type结构体如下:
struct bus_type usb_bus_type = {
.name = "usb", //总线名称,存在/sys/bus下
.match = usb_device_match, //匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
.uevent = usb_uevent, //事件函数
.suspend = usb_suspend, //休眠函数
.resume = usb_resume, //唤醒函数
};
static void choose_address(struct usb_device *udev)
{
int devnum;
struct usb_bus *bus = udev->bus;
devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
//在bus->devnum_next~128区间中,循环查找下一个非0(没有设备)的编号
if (devnum >= 128) //若编号大于等于128,说明没有找到空余的地址编号,从头开始找
devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); //设置下次寻址的区间+1
if (devnum < 128) {
set_bit(devnum, bus->devmap.devicemap); //设置位
udev->devnum = devnum;
}
}
从上面代码中分析到每次的地址编号是连续加的,USB接口最大能接127个设备,我们连续插拔两次USB键盘,也可以看出,如下图所示:
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); //(1)设置地址,告诉USB设备新的地址编号
if (retval >= 0)
break;
msleep(200);
}
retval = usb_get_device_descriptor(udev, 8); //(2)获得USB设备描述符前8个字节
... ...
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //重新获取设备描述符信息
... ...
}
static int hub_set_address(struct usb_device *udev)
{
int retval;
... ...
retval = usb_control_msg(udev, usb_sndaddr0pipe(),USB_REQ_SET_ADDRESS,0, udev->devnum, 0,NULL, 0, USB_CTRL_SET_TIMEOUT); //(1.1)等待传输完成
if (retval == 0) { //设置新的地址,传输完成,返回0
usb_set_device_state(udev, USB_STATE_ADDRESS); //设置状态标志
ep0_reinit(udev);
}
return retval;
}
其中USB设备描述符结构体如下所示:
struct usb_device_descriptor { __u8 bLength; //本描述符的size __u8 bDescriptorType; //描述符的类型,这里是设备描述符DEVICE __u16 bcdUSB; //指明usb的版本,比如usb2.0 __u8 bDeviceClass; //类 __u8 bDeviceSubClass; //子类 __u8 bDeviceProtocol; //指定协议 __u8 bMaxPacketSize0; //端点0对应的最大包大小 __u16 idVendor; //厂家ID __u16 idProduct; //产品ID __u16 bcdDevice; //设备的发布号 __u8 iManufacturer; //字符串描述符中厂家ID的索引 __u8 iProduct; //字符串描述符中产品ID的索引 __u8 iSerialNumber; //字符串描述符中设备序列号的索引 __u8 bNumConfigurations; //可能的配置的数目 } __attribute__ ((packed));
int usb_new_device(struct usb_device *udev)
{
... ...
err = usb_get_configuration(udev); //(1)获取配置描述块
... ...
err = device_add(&udev->dev); // (2)把device放入bus的dev链表中,并寻找对应的设备驱动
}
int usb_get_configuration(struct usb_device *dev) { ... ... /* USB_MAXCONFIG 定义为8,表示设备描述块下有最多不能超过8个配置描述块 */ /*ncfg表示 设备描述块下 有多少个配置描述块 */ if (ncfg > USB_MAXCONFIG) { dev_warn(ddev, "too many configurations: %d, " "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG); dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG; } ... ... for (cfgno = 0; cfgno < ncfg; cfgno++){ //for循环,从USB设备里依次读入所有配置描述块 //每次先读取USB_DT_CONFIG_SIZE个字节,也就是9个字节,暂放到buffer中 result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,buffer, USB_DT_CONFIG_SIZE); ... ... //通过wTotalLength,知道实际数据大小 length = max((int) le16_to_cpu(desc->wTotalLength),USB_DT_CONFIG_SIZE); bigbuffer = kmalloc(length, GFP_KERNEL); //然后再来分配足够大的空间 ... ... //在调用一次usb_get_descriptor,把整个配置描述块读出来,放到bigbuffer中 result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,bigbuffer, length); ... ... //再将bigbuffer地址放在rawdescriptors所指的指针数组中 dev->rawdescriptors[cfgno] = bigbuffer; result = usb_parse_configuration(&dev->dev, cfgno,&dev->config[cfgno], bigbuffer, length); //最后在解析每个配置块 } ... ... }
int usb_get_configuration(struct usb_device *dev)
{
dev = get_device(dev); //使dev等于usb_device下的device成员
... ...
if ((error = bus_add_device(dev))) // 把这个设备添加到dev->bus的device表中
goto BusError;
... ...
bus_attach_device(dev); //来匹配对应的驱动程序
... ...
}
当bus_attach_device()函数匹配成功,就会调用驱动的probe函数
usb_device_match函数如下所示:
static int usb_device_match(struct device *dev, struct device_driver *drv) { if (is_usb_device(dev)) { //判断是不是USB设备 if (!is_usb_device_driver(drv)) return 0; return 1; } else { //否则就是USB驱动或者USB设备的接口 struct usb_interface *intf; struct usb_driver *usb_drv; const struct usb_device_id *id; if (is_usb_device_driver(drv)) //如果是USB驱动,就不需要匹配,直接return return 0; intf = to_usb_interface(dev); //获取USB设备的接口 usb_drv = to_usb_driver(drv); //获取USB驱动 id = usb_match_id(intf, usb_drv->id_table); //匹配USB驱动的成员id_table if (id) return 1; id = usb_match_dynamic_id(intf, usb_drv); if (id) return 1; } return 0; }
显然就是匹配USB驱动的id_table
id_table的结构体为usb_device_id,如下所示:
struct usb_device_id { __u16 match_flags; //与usb设备匹配那种类型?比较类型的宏如下: //USB_DEVICE_ID_MATCH_INT_INFO : 用于匹配设备的接口描述符的3个成员 //USB_DEVICE_ID_MATCH_DEV_INFO: 用于匹配设备描述符的3个成员 //USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION: 用于匹配特定的USB设备的4个成员 //USB_DEVICE_ID_MATCH_DEVICE:用于匹配特定的USB设备的2个成员(idVendor和idProduct) /* 以下4个用匹配描述特定的USB设备 */ __u16 idVendor; //厂家ID __u16 idProduct; //产品ID __u16 bcdDevice_lo; //设备的低版本号 __u16 bcdDevice_hi; //设备的高版本号 /*以下3个就是用于比较设备描述符的*/ __u8 bDeviceClass; //设备类 __u8 bDeviceSubClass; //设备子类 __u8 bDeviceProtocol; //设备协议 /* 以下3个就是用于比较设备的接口描述符的 */ __u8 bInterfaceClass; //接口类型 __u8 bInterfaceSubClass; //接口子类型 __u8 bInterfaceProtocol; //接口所遵循的协议 /* not matched against */ kernel_ulong_t driver_info; };
参考/drivers/hid/usbhid/usbmouse.c(内核自带的USB鼠标驱动),看它是如何使用的:
发现,它是通过**USB_INTERFACE_INFO()**这个宏定义的.该宏如下所示:
#define USB_INTERFACE_INFO(cl,sc,pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \ //设置id_table的.match_flags成员
.bInterfaceClass = (cl), .bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)
//设置id_table的3个成员,用于与匹配USB设备的3个成员
然后将上图里的usb_mouse_id_table []里的3个值代入宏**USB_INTERFACE_INFO(cl,sc,pr)**中:
.bInterfaceClass =USB_INTERFACE_CLASS_HID;
//设置匹配USB的接口类型为HID类, 因为USB_INTERFACE_CLASS_HID=0x03
//HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0X03
.bInterfaceSubClass =USB_INTERFACE_SUBCLASS_BOOT;
//设置匹配USB的接口子类型为启动设备
.bInterfaceProtocol=USB_INTERFACE_PROTOCOL_MOUSE;
//设置匹配USB的接口协议为USB鼠标的协议,等于2
//当.bInterfaceProtocol=1也就是USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议
如下图,我们也可以通过windows上也可以找到鼠标的协议号,也是2:
其中VID:表示厂家(vendor)ID
PID:表示产品(Product) ID
总结:当我们插上USB设备时,系统就会获取USB设备的设备、配置、接口、端点的数据,并创建新设备,所以我们的驱动就需要写id_table来匹配该USB设备。
达到的目的:鼠标左键=按键L,鼠标右键=按键S,鼠标中键=回车键
.probe函数的结构:
怎么写USB设备驱动程序:
代码编写步骤如下:
0 定义全局变量 :usb_driver结构体、input_dev指针结构体 、虚拟地址缓存区、DMA地址缓存区
1 在入口函数中
2 在usb_driver的probe函数中
分配一个input_dev结构体
设置input_dev支持L、S、回车、3个按键事件
注册input_dev结构体
设置USB数据传输:
->4.1)通过usb_rcvintpipe()创建一个接收中断类型的端点管道pipe,用于端点和数据缓冲区之间的连接
->4.2)通过usb_buffer_alloc()申请USB缓冲区
->4.3)申请并初始化urb结构体(urb:用来传输数据)
->4.4) 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址
->4.5)使用usb_submit_urb()提交urb
3 在鼠标中断函数中
1)判断缓存区数据是否改变,若改变则上传鼠标事件
2)使用usb_submit_urb()提交urb
4 在usb_driver的disconnect函数中
1)通过usb_kill_urb()杀掉提交到内核中的urb
2)释放urb
3)释放USB缓存区
4)注销input_device,释放input_device
5 在出口函数中
1)通过usb_deregister ()函数注销usb_driver结构体
/* * 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> /* 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");
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设备的厂家和产品ID,可以添加{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); //获得usb设备信息 /* 以下语句可以用来打印USB设备的厂家及产品id信息 *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); */ struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; int pipe; interface = intf->cur_altsetting; if (interface->desc.nNumEndpoints != 1) //除了端点0之外,鼠标的端点数如果不是1的话,则返回错误 return -ENODEV; endpoint = &interface->endpoint[0].desc; //得到第一个非零endpoint的描述符 if (!usb_endpoint_is_int_in(endpoint)) //如果不是中断输入型端点的话,返回错误 return -ENODEV; /* a. 分配一个input_dev */ uk_dev = input_allocate_device(); //在文件开头先定义输入设备(input_dev)指针uk_dev /* 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. 硬件相关操作 */ /* 数据传输“三要素”: 源,目的,长度 */ /* 1源: USB设备的某个端点 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 2长度: 要在文件开头定义一个长度变量,int len*/ len = endpoint->wMaxPacketSize; /* 3目的: (1.在文件开头定义一个字符串指针,char * usb_buf) * (2.在文件开头定义一个usb_buf的物理地址,dma_addr_t * usb_buf_phys) */ usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys); /* 使用"3要素" */ /* 1分配usb request block */ uk_urb = usb_alloc_urb(0, GFP_KERNEL); //先要在文件开头定义一个urb指针,即urb * uk_urb /* 2使用之前的三要素设置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; /* 3使用URB */ usb_submit_urb(uk_urb, GFP_KERNEL); return 0; }
当USB主机周期性(j间隔endpoint->bInterval)的从设备获得数据,并存入到usb buffer中,同时USB主机控制器会向CPU产生中断,并调用usb中断处理函数:
static void usbmouse_as_key_irq(struct urb *urb) { //为了调试,先打印usb_buf中的数据 #if 0 int i; static int cnt = 0; printk("data cnt %d: ", ++cnt); //usb_buf[]里存放的是鼠标按键值、x坐标、y坐标和滚轮的值 for (i = 0; i < len; i++) { printk("%02x ", usb_buf[i]); } printk("\n"); #endif //上报事件(USB总线驱动是不知道usb数据含义的,只有在USB设备驱动中加以解析) #if 1 /* USB鼠标数据含义 * data[0]: bit0-左键(1-按下, 0-松开) * bit1-右键(1-按下, 0-松开) * bit2-中键(1-按下, 0-松开) */ static unsigned char pre_val; if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0))) //如果上次数据的Bit0不等于现在的值 { /* 左键发生了变化 */ input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0); input_sync(uk_dev); } if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1))) { /* 右键发生了变化 */ input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0); input_sync(uk_dev); } if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2))) { /* 中键发生了变化 */ input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0); input_sync(uk_dev); } pre_val = usb_buf[0]; #endif /* 重新提交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);
//printk("disconnect usbmouse!\n");
usb_kill_urb(uk_urb); //杀死urb
usb_free_urb(uk_urb); //释放urb
usb_free_coherent(dev, len, usb_buf, usb_buf_phys); //释放usb buffer
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
cd linux3.4.2
make menuconfig
->Device Drivers
->HID Devices
-> <>USB Human Interface Device (full HID) support
make uImage
tftp 30000000 uImage //将新的内核复制到tftp共享目录后,下载到开发板内核
//nfs 30000000 主机IP:/nfs_root/uImage //或者使用nfs命令下载新内核
bootm 30000000 //从内核的30000000地址处启动内核
mount -t nfs -o nolock 主机IP:/nfs_root /mnt //挂接网络文件系统
insmod usbmouse_as_key.ko //内核启动后,安装自己编译的USB设备驱动
# ls /dev/event* //观察当前系统有没有其它event设备
ls:/dev/event*:No such file or directory
接上USB鼠标
usb 1-1:configuration #1 chosen from 1 choice
input:Unspecified device as /class/input/input0
# ls /dev/event*
/dev/event0 //对应我们刚接上去的鼠标
# cat /dev/tty1 //将鼠标作为键盘,观察tty1口输出
l(按下左键)s(按下右键)
(按下回车)
llllll(按住左键)
/* * 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 input_dev *uk_dev; static char *usb_buf; //使用虚拟地址的usb buffer static dma_addr_t usb_buf_phys; //使用物理地址的DMA缓冲区 static int len; //usb buffer 的大小 static struct urb *uk_urb; //usb请求块 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 void usbmouse_as_key_irq(struct urb *urb) { static unsigned char pre_val; #if 0 int i; static int cnt = 0; printk("data cnt %d: ", ++cnt); for (i = 0; i < len; i++) { printk("%02x ", usb_buf[i]); } printk("\n"); #endif /* USB鼠标数据含义 * data[0]: bit0-左键, 1-按下, 0-松开 * bit1-右键, 1-按下, 0-松开 * bit2-中键, 1-按下, 0-松开 * */ if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0))) { /* 左键发生了变化 */ input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0); input_sync(uk_dev); } if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1))) { /* 右键发生了变化 */ input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0); input_sync(uk_dev); } if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2))) { /* 中键发生了变化 */ input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0); input_sync(uk_dev); } pre_val = usb_buf[0]; /* 重新提交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_alloc_coherent(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); //printk("disconnect usbmouse!\n"); usb_kill_urb(uk_urb); usb_free_urb(uk_urb); usb_free_coherent(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");
KERN_DIR = /home/leon/linux-3.4.2
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += usbmouse_as_key.o
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。