目录
2.1.1 USB设备描述符(usb_device_descriptor)
2.2.1 drivers/usb/core/hub.c的第2186行位于hub_port_init()函数里
2.2.3 继续搜索kick_khubd,发现被hub_irq()函数中调用
2.2.4 分析hub_port_connect_change()函数如何连接端口
2.2.5 进入hub_port_connect_change()->usb_alloc_dev(),看它是怎么设置usb_device
2.2.6 进入hub_port_connect_change()->choose_address(),看它是怎么分配地址编号的
2.2.7 再看hub_port_connect_change()->hub_port_init()函数是如何来实现连接USB设备的
2.2.8 再看hub_port_connect_change()->usb_new_device()函数是如何来创建USB设备的
2.2.9 再看usb_bus_type这个的成员usb_device_match函数是如何匹配的
1、几个常见疑惑?
usb协议不清楚的可以看一下
- 为什么一插上就会有提示信息?
是因为windows自带了USB总线驱动程序;
- 那USB总线驱动程序是干嘛用的?
- 识别USB设备;
- 给USB设备找到并安装对应的驱动程序;
- 提供USB的读写函数。
首先,新接入的USB设备的默认地址(编号)为0,再未分配新编号前,PC主机使用0地址和它通信。然后USB总线驱动程序都会给它分配一个地址(编号)。PC机想访问USB总线上某个USB设备时,发出的命令都含有对应的地址(编号)。
- USB的结构是怎样的?
- USB是一种主从结构。主机叫做Host,从机叫做Device,所有的USB传输,都是从USB主机这方发起;USB设备没有“主动”通知USB主机的能力。
- 例如:USB鼠标滑动一下立刻产生数据,但是它还没有能力通过OC机来读数据,只能被动地等待PC机来读。
- USB可以热拔插的硬件原理
- 在USB集线器(hub)的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。
- 而在USB设备端,在D+或者D-上接了1.5k欧姆上拉电阻,对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。
- 这样,当设备插入到集线器时,由1.5k的上拉电阻和15k的下拉电阻分呀,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。
- USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到告诉模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。
- USB的4大传输类型:
- 控制传输
- 是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。
- 中断传输
- 支持中断传输的典型设备有USB鼠标、USB键盘等等。中断传输不是说我的设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。interval是间隔时间的意思,表示我这个设备希望主机多长时间来轮询自己,只要这个值确定了之后,我主机就会周期性来查看有没有数据需要处理。
- 批量处理
- 支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。
- 实时传输
- USB摄像头就是实时传输设备的典型代表,它同样进行大量数据的传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高
- 控制传输
- USB端点:
- 每个USB设备与主机会有若干个通信的“端点”。每个端点都有个端点号,除了端点0外,每一个端点只能工作在一种传输类型(控制传输、中断传输、批量传输、实时传输)下,一个传输方向。
- 传输方向都是基于USB主机的立场说的,比如:鼠标的数据是从鼠标传到PC机,对应的端点称为“中断输入端点”。
- 端点0是设备的默认控制端点,既能输出也能输入,主要用于USB设备的识别过程。
- USB驱动整体框架:
- USB主机控制器类型
要想成为一个USB主机,硬件上就必须要有USB主机控制器,USB主机控制器又分为4种接口:
- OHCI(Open Host Controller Inerface):微软主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的硬件简单,软件复杂。
- UHCI(Universal Host Controller Interface):Intel主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),而UHCI接口的软件简单,硬件复杂。
- EHCI(Enhace Host Controller Interface):高速USB2.0(480Mbps)。
- xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9针脚设针,同时也支持USB2.0、1.1等。
在生活、工作中经常会接触到USB设备,如鼠标、键盘、摄像头、可移动硬盘、扫码枪等。这些设备通过USB接口连接到电脑上后,电脑会立刻提示“检测到新硬件...”、安装驱动等。这里需要强调下USB设备使用的是USB总线,window或Linux内核中都会自带usb总线驱动,所以接上USB设备后,主机能够立刻检测到,提醒需要安装设备驱动是指安装USB设备驱动。
USB设备驱动使用USB总线,所以很多操作由USB总线驱动帮我们完成了,我们只需要的按照总线、设备、驱动框架来实现USB设备的驱动既可。USB设备数据的读写操作由总线驱动现在,我们可以直接使用总线读取到的数据,然后解析这些数据的含义、再进行相关的操作就可以了(这里需要注意的一点是USB总线驱动只提供USB设备的读写操作函数,这函数是通用的,即里面的数据的含义总线驱动并不知道)。
2、USB总线驱动分析
2.1 USB描述符的层次及定义
- USB设备描述符(usb_device_descriptor)
- USB配置描述符(usb_config_descriptor)
- USB接口描述符(usb_interface_descriptor)
- USB端点描述符(usb_endpoint_descriptor)、
一个设备描述符可以有多个配置描述符;
一个配置描述符可以有多个接口描述符(比如声卡驱动就有两个接口:录音接口和播放接口)
一个接口描述符可以有多个端点描述符;
2.1.1 USB设备描述符(usb_device_descriptor)
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特定的数据结构
};
2.2.2 USB配置描述符
struct usb_config_descriptor {
__u8 bLength; //描述符的长度
__u8 bDescriptorType; //描述符类型的编号
__le16 wTotalLength; //配置所返回的所有数据的大小
__u8 bNumInterfaces; //配置所支持的接口个数, 表示有多少个接口描述符
__u8 bConfigurationValue; //Set_Configuration命令需要的参数值
__u8 iConfiguration; //描述该配置的字符串的索引值
__u8 bmAttributes; //供电模式的选择
__u8 bMaxPower; //设备从总线提取的最大电流
} __attribute__ ((packed));
2.2.3 接口描述符(逻辑设备)
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 这个成员结构体里,
- usb_interface结构体如下所示:
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;
... ...
}
- cur_altsetting成员的结构体是usb_host_interface,如下:
struct usb_host_interface {
struct usb_interface_descriptor desc; //当前被激活的接口描述符
struct usb_host_endpoint *endpoint; /* 这个接口的所有端点结构体的联合数组*/
char *string; /* 接口描述字符串 */
unsigned char *extra; /* 额外的描述符 */
int extralen;
};
2.2.4 端点描述符
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才能被提交到此端口
};
2.2 USB总线驱动如何识别设备
由于内核自带了USB驱动,所以我们先插入一个USB键盘到开发板上看打印信息发现以下字段:
如下图,找到第一段话是位于drivers/usb/core/hub.c的第2186行:
这个hub其实就是我们的USB主机控制器的集线器,用来管理多个USB接口
2.2.1 drivers/usb/core/hub.c的第2186行位于hub_port_init()函数里
它又是被谁调用的,如下图所示,我们搜索到它是通过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这个中断唤醒才行。
2.2.2 搜索”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);
}
2.2.3 继续搜索kick_khubd,发现被hub_irq()函数中调用
显然,就是当USB设备插入后,D+或D-就会被拉高,然后USB主机控制器就会产生一个hub_irq中断.
2.2.4 分析hub_port_connect_change()函数如何连接端口
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设备驱动连接
... ...
}
所以最终流程图如下:
2.2.5 进入hub_port_connect_change()->usb_alloc_dev(),看它是怎么设置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); //分配一个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结构体
}
- 设置device成员,注册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, //唤醒函数
};
2.2.6 进入hub_port_connect_change()->choose_address(),看它是怎么分配地址编号的
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键盘,也可以看出,如下图所示:
2.2.7 再看hub_port_connect_change()->hub_port_init()函数是如何来实现连接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); //重新获取设备描述符信息
... ...
}
- 上面第6行中,hub_set_address()函数主要是用来告诉USB设备新的地址编号,hub_set_address()函数如下:
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_control_msg()函数就是用来让USB主机控制器把一个控制报文发给USB设备,如果传输完成就返回0.其中参数udev表示目标设备;使用的管道为usb_sndaddr0pipe(),也就是默认的地址0加上控制端点号0; USB_REQ_SET_ADDRESS表示命令码,既设置地址; udev->devnum表示要设置目标设备的设备号;允许等待传输完成的时间为5秒,因为USB_CTRL_SET_TIMEOUT定义为5000。
- 上面第12行中,usb_get_device_descriptor()函数主要是获取目标设备描述符前8个字节,为什么先只开始读取8个字节?是因为开始时还不知道对方所支持的信包容量,这8个字节是每个设备都有的,后面再根据设备的数据,通过usb_get_device_descriptor()重读一次目标设备的设备描述结构.
其中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));
2.2.8 再看hub_port_connect_change()->usb_new_device()函数是如何来创建USB设备的
int usb_new_device(struct usb_device *udev)
{
... ...
err = usb_get_configuration(udev); //(1)获取配置描述块
... ...
err = device_add(&udev->dev); // (2)把device放入bus的dev链表中,并寻找对应的设备驱动
}
- 其中usb_get_configuration()函数如下,就是获取各个配置
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); //最后在解析每个配置块
}
... ...
}
- 其中device_add ()函数如下
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函数
2.2.9 再看usb_bus_type这个的成员usb_device_match函数是如何匹配的
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
2.2.10 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设备,也就是我们只需要实现设备驱动即可,设备由系统帮我们实现并挂载到总线上
3、 USB设备驱动分析
USB设备驱动的具体的代码可以参考内核中的/drivers/hid/usbhid/usbmouse.c,下面只会粗略分析
- 分配/设置usb_driver结构体,填充里面的.id_table、.probe、.disconnect成员。 调用usb_register把usb_driver结构体注册到内核中。
static struct usb_driver usbtouch_driver = {
.name = "myusb",
.probe = usbtouch_probe,
.disconnect = usbtouch_disconnect,
.id_table = usbtouch_devices,
};
static int __init usbtouch_init(void)
{
return usb_register(&usbtouch_driver);
}
static void __exit usbtouch_cleanup(void)
{
usb_deregister(&usbtouch_driver);
}
struct usb_driver中
- .id_table成员是用与和usb设备进行匹配的选项,表示这个驱动支持的设备。
- .probe成员为函数指针,就是在设备和驱动匹配成功的时候执行。
- .disconnect成员也为函数指针,在设备和驱动关联成功后再断开执行,如拔了USB设备或卸载USB设备驱动。
- .probe函数中我们需要处理的事情如下(这里以鼠标为例子):
创建input_dev设备 设置input_dev支持的类,需要支持按键类、相对位移类。 设置input_dev支持的事件,能够产生左、中、右、以及侧边附加按键事件,还有xy方向上的相对位移事件和滚轮滑动事件。 注册input_dev到input输入子系统中。 硬件相关的操作,不同的设备的操作存在差异 USB总线为组从结构,USB总线驱动只能轮询去获取USB设备上的数据,USB设备不能主动通知USB主机传输数据。在USB驱动中需要构建urb(usb request block)结构体,然后填充使用。urb中要指定USB设备数据存放到主机上的物理地址以及虚拟地址,还有USB数据传输的方向、长度,和设备通信的端点等。
- 使用usb_alloc_urb()函数分配urb结构体,结束后使用usb_free_urb()释放这个结构体。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
/*用于分配urb结构体给usb驱动使用*/
/* If the driver want to use this urb for interrupt, control, or bulk
* endpoints, pass '0' as the number of iso packets.
* The driver must call usb_free_urb() when it is finished with the urb.
*/
- USB设备的数据要存放在主机上的什么地方,USB设备驱动中需要指明。
void *usb_buffer_alloc(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma)
/*用于分配usb缓存,用于存放读写的数据*/
/*@dma: used to return DMA address of buffer 这里是内存的物理地址
*函数返回的是虚拟地址
*/
找到对应的设备端点和数据长度
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/*pipe为源地址*/
len = endpoint->wMaxPacketSize;
/*len 为一次传输的数据的大小,即包的长度*/
现在数据传输的三要素(源、长度、目的)都确定了,剩下的就是要进行填充urb结构体,即告诉主机数据传输的三要素。
static inline void usb_fill_int_urb (struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete_fn,
void *context,int interval)
/ * usb_fill_int_urb - macro to help initialize a interrupt urb
* @urb: pointer to the urb to initialize.
* @dev: pointer to the struct usb_device for this urb.
* @pipe: the endpoint pipe
* @transfer_buffer: pointer to the transfer buffer
* @buffer_length: length of the transfer buffer
* @complete_fn: pointer to the usb_complete_t function
* @context: what to set the urb context to.
* @interval: what to set the urb interval to, encoded like
* the endpoint descriptor's bInterval value.
*/
mouse->irq->transfer_dma = mouse->data_dma;
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
调用usb_fill_int_urb(),其中complete_fn相当于中断函数,即主机读取完数据会放到指定的内存data_dma中。然后通知CPU处理这些数据,这些数据只有USB设备驱动程序知道其含义。所以要在这个完成函数中处理这些数据,如上报。
最后就是提交usr请求块了。
usb_submit_urb(urb, GFP_ATOMIC);
到这里基本上就结束了,剩下的就是disconnect函数的操作。这个函数和.probe完全相反,申请了的资源需要在这里释放。
4、USB设备(鼠标)驱动编写
- 达到的目的:鼠标左键=按键L,鼠标右键=按键S,鼠标中键=回车键
- .probe函数的结构:
- 1 分配input_dev
- 2 设置
- 3 注册
- 硬件操作
- 使用USB总线驱动函数的读写函数来收发数据
- 怎么写USB设备驱动程序:
- 1 分配/设置usb_driver结构体
- .name
- .id_table
- .probe
- .disconnect
- 2 注册
- 1 分配/设置usb_driver结构体
- .probe函数的结构:
- 代码编写步骤如下:
0 定义全局变量 :usb_driver结构体、input_dev指针结构体 、虚拟地址缓存区、DMA地址缓存区
1 在入口函数中
- 通过usb_register()函数注册usb_driver结构体
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结构体
4.1 先写驱动主框架
/*
* 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");
4.2 编写.id_table
- 该USB设备驱动匹配的是接口描述符中的HID类 & BOOT子类 & MOUSE协议的设备。
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 */
};
4.3 编写.probe函数
- 开始的时候可以仅在该函数中打印一条语句,待整个架构测试通过后再逐步添加inputceng 。
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;
}
4.4 编写USB中断函数
当USB主机周期性(j间隔endpoint->bInterval)的从设备获得数据,并存入到usb buffer中,同时USB主机控制器会向CPU产生中断,并调用usb中断处理函数:
在usb设备驱动程序中,需要对数据进行解析。即usb总线获取到数据不会解析数据的含义,需要在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 usbtouch_irq(struct urb *urb)
{
#if 1
int status;
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设备驱动程序中,需要对数据进行解析。即usb总线获取到数据不会解析数据的含义
*需要在usb设备驱动中进行解析数据,我的usb数百data[1]为按键的值
*data[2]为鼠标在x方向上的相对位移值,data[3]为鼠标在y方向上的相对位移值
*data[4]为鼠标滚轮滑动产生的值
*/
input_report_key(input_dev, BTN_LEFT, data[1] & 0x01);
input_report_key(input_dev, BTN_RIGHT, data[1] & 0x02);
input_report_key(input_dev, BTN_MIDDLE, data[1] & 0x04);
input_report_key(input_dev, BTN_SIDE, data[0] & 0x08);
input_report_key(input_dev, BTN_EXTRA, data[0] & 0x10);
input_report_rel(input_dev, REL_X, data[2]);
input_report_rel(input_dev, REL_Y, data[3]);
input_report_rel(input_dev, REL_WHEEL, data[4]);
input_sync(input_dev);
resubmit:
/*上报后,重新提交urb请求*/
status = usb_submit_urb (urb, GFP_ATOMIC);
#else
/*打印USB总线驱动获取到的数据,分析数据。解析其数据含义*/
int i =0,status;
for(i = 0; i < len;i++){
printk("%x ",data[i]);
}
printk("\n");
status = usb_submit_urb (urb, GFP_ATOMIC);
#endif
}
4.5 编写.disconnect函数
- 开始的时候可以仅在该函数中打印一条语句,待整个架构测试通过后再逐步添加inputceng 。
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);
}
5、测试
5.1 去掉内核鼠标驱动支持
cd linux3.4.2
make menuconfig//图像界面配置,无法打开可以看我另外博客
->Device Drivers
->HID Devices
-> <>USB Human Interface Device (full HID) support
make uImage
5.2 使用新内核启动开发板
tftp 30000000 uImage //将新的内核复制到tftp共享目录后,下载到开发板内核
//nfs 30000000 主机IP:/nfs_root/uImage //或者使用nfs命令下载新内核
bootm 30000000 //从内核的30000000地址处启动内核
5.3 安装自己编译的USB设备驱动
mount -t nfs -o nolock 主机IP:/nfs_root /mnt //挂接网络文件系统
insmod usbmouse_as_key.ko //内核启动后,安装自己编译的USB设备驱动
5.4 测试
- 在开发板上接入、拔出USB鼠标,观察串口输出
- 将鼠标当作键盘,输入ls+回车命令
第一种解析方法
# 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(按住左键)
第二种解析方法
# hexdump /dev/event0
//序号 ---------------时间 类型 具体按键 对应值,可以看input架构相关知识
0000000 4998 0000 4b9a 000c 0001 0110 0001 0000
0000010 4998 0000 4bb6 000c 0000 0000 0000 0000
0000020 4998 0000 cc2f 000e 0001 0110 0000 0000
0000030 4998 0000 cc4b 000e 0000 0000 0000 0000
0000040 4999 0000 93c0 000d 0001 0111 0001 0000
0000050 4999 0000 93db 000d 0000 0000 0000 0000
0000060 499a 0000 2fe0 0001 0001 0111 0000 0000
0000070 499a 0000 2ff2 0001 0000 0000 0000 0000
0000080 499c 0000 b23e 0006 0002 0000 00ff 0000
0000090 499c 0000 b24e 0006 0002 0001 0001 0000
00000a0 499c 0000 b252 0006 0000 0000 0000 0000
00000b0 499e 0000 cf82 0007 0001 0112 0001 0000
00000c0 499e 0000 cf9d 0007 0000 0000 0000 0000
00000d0 499e 0000 59ad 000b 0001 0112 0000 0000
00000e0 499e 0000 59c2 000b 0000 0000 0000 0000
00000f0 499f 0000 df37 0007 0002 0008 00ff 0000
0000100 499f 0000 df4e 0007 0000 0000 0000 0000
0000110 499f 0000 cacc 000d 0002 0008 00ff 0000
0000120 499f 0000 cadf 000d 0000 0000 0000 0000
6、 代码
- usbmouse_as_key.c
- 第一个版本
/*
* 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");
- 第二个版本
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <linux/slab.h>
/*USB设备驱动可以使用input输入子系统框架来完成数据的读入即上报
*和传统的input子系统存在差异,传统的input输入子系统是在中断函数里面获取数据然后input_event上报数据
*在usb设备中,数据的读取由usb总线驱动完成,这个可以直接使用urb模块来实现。然后再urb的中断中上报数据
*/
static struct input_dev *input_dev;
static struct urb *urb;
static dma_addr_t data_dma;
static unsigned char *data;
static unsigned len;
static void usbtouch_irq(struct urb *urb)
{
#if 1
int status;
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设备驱动程序中,需要对数据进行解析。即usb总线获取到数据不会解析数据的含义
*需要在usb设备驱动中进行解析数据,我的usb数百data[1]为按键的值
*data[2]为鼠标在x方向上的相对位移值,data[3]为鼠标在y方向上的相对位移值
*data[4]为鼠标滚轮滑动产生的值
*/
input_report_key(input_dev, BTN_LEFT, data[1] & 0x01);
input_report_key(input_dev, BTN_RIGHT, data[1] & 0x02);
input_report_key(input_dev, BTN_MIDDLE, data[1] & 0x04);
input_report_key(input_dev, BTN_SIDE, data[0] & 0x08);
input_report_key(input_dev, BTN_EXTRA, data[0] & 0x10);
input_report_rel(input_dev, REL_X, data[2]);
input_report_rel(input_dev, REL_Y, data[3]);
input_report_rel(input_dev, REL_WHEEL, data[4]);
input_sync(input_dev);
resubmit:
/*上报后,重新提交urb请求*/
status = usb_submit_urb (urb, GFP_ATOMIC);
#else
/*打印USB总线驱动获取到的数据,分析数据。解析其数据含义*/
int i =0,status;
for(i = 0; i < len;i++){
printk("%x ",data[i]);
}
printk("\n");
status = usb_submit_urb (urb, GFP_ATOMIC);
#endif
}
static int usbtouch_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
int pipe;
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
interface = intf->cur_altsetting;
/*除端点0外的第一个端点,并不是指设备接口配置中的端点0*/
endpoint = &interface->endpoint[0].desc;
/*1.分配一个input_dev结果体*/
input_dev = input_allocate_device();
if(!input_dev){return -1;}
/*2.设置结构体中的支持的类以及事件。鼠标要支持案件类和相对位移类事件*/
input_dev->name = "usb-mouse";
input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
input_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
input_dev->relbit[0] = BIT(REL_X) | BIT(REL_Y);
input_dev->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
input_dev->relbit[0] |= BIT(REL_WHEEL);
/*3.注册input_dev到系统中*/
input_register_device(input_dev);
/*4.硬件相关的操作。主要是usb数据的获取以及上报*/
urb=usb_alloc_urb(0, GFP_KERNEL);
/*传输数据的来源,设备、端点、方向等组合*/
pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
/*和设备驱动交互一次传输的数据长度*/
len = endpoint->wMaxPacketSize;
/*USB总线驱动获取到的数据存放的内存*/
data = usb_buffer_alloc(udev, len,GFP_ATOMIC, &data_dma);
urb->dev = udev;
urb->transfer_dma = data_dma;
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/*上下文参数(这里使用的NULL),可以用来给完成函数提供参数*/
usb_fill_int_urb(urb, udev,pipe,data, len,usbtouch_irq, NULL, endpoint->bInterval);
/*提交urb请求*/
usb_submit_urb(urb, GFP_ATOMIC);
return 0;
}
static void usbtouch_disconnect(struct usb_interface *intf)
{
usb_kill_urb(urb);
input_unregister_device(input_dev);
usb_free_urb(urb);
usb_buffer_free(interface_to_usbdev(intf), len, data,data_dma);
input_free_device(input_dev);
}
/*定义设备驱动支持的设备*/
static struct usb_device_id usbtouch_devices [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
static struct usb_driver usbtouch_driver = {
.name = "myusb",
.probe = usbtouch_probe,
.disconnect = usbtouch_disconnect,
.id_table = usbtouch_devices,
};
static int __init usbtouch_init(void)
{
return usb_register(&usbtouch_driver);
}
static void __exit usbtouch_cleanup(void)
{
usb_deregister(&usbtouch_driver);
}
module_init(usbtouch_init);
module_exit(usbtouch_cleanup);
MODULE_LICENSE("GPL");
通用Makefile
ifneq ($(KERNELRELEASE),)
obj-m := usbmouse_as_key.o #具体修改只需要该.o之前的变量名和内核源码目录路径
else
KERNEL_DIR := /lib/modules/$(shell uname -r)/build # 取内核的build目录给KDIR,这里为内核源码目录路径
PWD := $(shell pwd) # 表示取当前的pwd值赋给PWD变量
all:
make -C $(KERNEL_DIR) M=$(PWD)
# -C:表示change,change到内核源码里面去编译
# M=$( ):指定了要编译驱动的源码目录;
#因此就该程序会再次进入我们这个makefile文件进行执行
#接下来继续去判断 KERNELRELEASE 变量.......
clean:
make -C $(KERN_DIR) M=$(PWD) clean
endif
原文链接:https://blog.csdn.net/liangzc1124/article/details/119333357