《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

时间:2021-10-27 10:11:17

第10章 Linux USB 主机控制器和设备驱动


USB 的全称是 Universal Serial Bus,顾名思义:通用串行总线。 提到总线,联想一下,在你心目中总线总是用来干嘛的?还记得 I2C 总线? I2C 总线上挂有二条信号线,一条是 SCL、 SDA,这二货是干嘛滴?传输数据!对,就是用来传输数据的。换句话说,就是用来通信的。 既然是用来通信的,那自然就要用通信协议来规范通信。 在 USB 的世界里有一种协议叫 USB 协议, 这协议太复杂了,一时半载的难以消化,这里我们并不深入 USB 协议。


10.1 USB 总线简介


USB 最早的版本的是 USB1.0,后来随着人们的胃口越来越大, USB2.0,USB3.0 就问世了。 到今天为止,如果你看到你的 U 盘里面的底色是蓝色的(如图 10.1),那这个 U 盘的版本 90%是 USB3.0 的,还有 10%的可能性是山寨的。


USB 是一种“总线”,它与传统的外部设备与主机之间的连接方式不同,它允许将不同种类的外部设备混合连接到同一个接口上(这体现它的通用性),但是,它又和计算机内部的总线( 如 PCI 总线)不同, CPU 不能通过访问内指令或 I/O指令直接访问连接在 USB 上的设备,而要通过一个“USB 控制器”,间接地与连接在 USB 上的设备打交道, USB 总线存在于计算机的外部,所以说 USB 总线是外部总线。

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

10.1.1 USB 传输类型


USB 设备的数据传输在 USB 协议里分为 4 大传输类型:控制传输、中断传输、批量传输、实时传输。


10.1.1.1 控制传输


控制传输是每一个 USB 设备都必须支持的,它通常用来获取设备描述符、设置设备的状态等。 控制传输可靠、实时, USB 设备的识别过程就是控制传输。

10.1.1.2 中断传输

注意了,这里所谓的中断传输,并不是真正的“中断”, USB 设备并没有中断 USB 主机的本事,这里只是借用一下中断的概念,它其实是一种轮询的方式来完成数据的传输。 支持中断传输的 USB 设备多了去了,比如 USB 鼠标、 USB键盘等等。


10.1.1.3 批量传输


批量,顾名思义就是大量的数据拷贝,批量传输它能够保证数据的准确性,但是不能保证时间的实时性。支持批量传输的 USB 设备,最常见的莫过于 U 盘,还有移动硬盘。对吧,还记得曾经少年时期,里面放着多少苍老师的教育片?


10.1.1.4 实时传输


实时,顾名思义就是时间要求严格,对传输延时敏感。换句话说,对实时性要求比较高。 USB 摄像头是一个典型的代表,想到这个你就很好理解为什么实时性要求比较高了。 想象一下,某天你和某个 MM 进行 QQ 视频聊天,传过来的视频要是一卡一卡的,你不崩溃? 当然了,实时传输是一种不可靠的传输,还记得以前学网络编程说过的? QQ 视频传输是一种以 UDP 协议来传输数据的。


10.1.2 USB 的主从结构


所有的 USB 传输,都是从 USB 主机这方发起; USB 设备没有"主动"通知USB 主机的能力。 比如: USB 鼠标滑动一下立刻产生数据,但是它没有能力通知 PC 机来读数据,只能被动地等得 PC 机来读。 这就是为什么前面我们讲过的中断传输,并不是实际意义上的中断传输,因为它还没那个本事呢。

10.1.3 USB 传输的对象


说了那么多传输,到底传输的对象是什么? U 盘?鼠标?键盘?不,这是和非专业型人说的。专业点呢? 端点(endpoint)!比如: 我们说"读 U 盘"、 "写 U 盘", 换句话说:把数据写到 U 盘的端点 1 ,从 U 盘的端点 2 里读出数据。注意了,除了端点 0 外,每一个端点只支持一个方向的数据传输, 端点 0 用于控制传输,既能输出也能输入。 端点 1 、 2 等一般用作数据端点,存放主机与设备间往来的数据。

10.2 设备、配置、接口、端点


在 USB 设备的逻辑组织中,包含设备、配置、接口和端点 4 个层次。 这 4 者之间的关系不用多介绍,只需看图 10.2 即可明白。

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动
从上图可知,一个 USB 设备通常有一个或多个配置;配置通常又有一个或多个接口;接口通常又还有零个或多个端点。

10.2.1 设备描述符


在 Linux 系统中,这种层次化配置信息用一组标准的描述符来描述。 当你将一个 USB 设备插入电脑的时候,电脑很快就知道这个 USB 设备是什么东西了。有没有想过为什么它会那么聪明? 其实, USB 协议里规定了, PC 和 USB 设备都得遵守一定的规范。 PC 里事先安装有 USB 总线驱动程序,当 USB 插入电脑时, USB 总线驱动程序就会发出某些命令去获取设备的信息(描述符), USB 设备收到这个命令后,将设备描述符返回给 PC。


/* 参考 include\uapi\linux\usb\ch9.h */
struct usb_device_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型编号 */
__le16 bcdUSB; /* USB 版本号 */
__u8 bDeviceClass; /* USB 分配的设备类 code */
__u8 bDeviceSubClass; /* USB 分配的子类 code */
__u8 bDeviceProtocol; /* USB 分配的协议 code */
__u8 bMaxPacketSize0; /* endpoint0 最大包大小 */
__le16 idVendor; /* 厂商编号 */
__le16 idProduct; /* 产品编号 */
__le16 bcdDevice; /* 设备出厂编号 */
__u8 iManufacturer; /* 描述厂商字符串的索引 */
__u8 iProduct; /* 描述产品字符串的索引 */
__u8 iSerialNumber; /* 描述设备序列号字符串的索引 */
__u8 bNumConfigurations; /* 配置的数量 */
} __attribute__ ((packed));
#define USB_DT_DEVICE_SIZE 18


10.2.2 配置描述符


每个 USB 设备都提供了不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需从其中选定一个),配置由多个接口组成。


/* 参考 include\uapi\linux\usb\ch9.h */
struct usb_config_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型编号 */
__le16 wTotalLength; /* 配置所返回的所有数据的大小 */
__u8 bNumInterfaces; /* 配置所支持的接口数 */
__u8 bConfigurationValue; /* Set_Configuration 命令需要的参数值 */
__u8 iConfiguration; /* 描述该配置的字符串的索引值 */
__u8 bmAttributes;
__u8 bMaxPower;
} __attribute__ ((packed));
#define USB_DT_CONFIG_SIZE 9


10.2.3 接口描述符


接口是逻辑上的设备, 一个 USB 设备可以有多个接口,比如一个 USB 声卡设备, 它有录音接口,也有播放接口。
/* 参考 include\uapi\linux\usb\ch9.h */
struct usb_interface_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型编号 */
__u8 bInterfaceNumber; /* 接口的编号 */
__u8 bAlternateSetting; /* 备用的接口描述符编号 */
__u8 bNumEndpoints; /* 端点数,不包括端点 0 */
__u8 bInterfaceClass; /* 接口类 */
__u8 bInterfaceSubClass; /* 接口子类 */
__u8 bInterfaceProtocol; /* 接口所遵循的协议 */
__u8 iInterface; /* 描述该接口的字符串索引值 */
} __attribute__ ((packed));
#define USB_DT_INTERFACE_SIZE 9


10.2.4 端点描述符


端点是 USB 通信的最基本形式,每一个 USB 设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信,以使用设备的功能。在 USB系统中每一个端点都有惟一的地址,这是由设备地址和端点号给出的。每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。


struct usb_endpoint_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型编号 */
/* 端点地址: 0~3 位是端点号,第 7 位是方向(0-OUT,1 -IN) */
__u8 bEndpointAddress;
/* 端点属性: bit[0:1] 的值为 00 表示控制,
* 为 01 表示实时,为 02 表示批量,为 03 中断
*/
__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));
#define USB_DT_ENDPOINT_SIZE 7


10.3 USB 驱动程序


在 Linux 系统中,提供了主机侧和设备侧视角的 USB 驱动框架,这里,仅仅讲解主机侧角度看到的 USB 驱动框架。


从主机侧的角度而言,需要编写的 USB 驱动程序包括主机控制器驱动和设备驱动两类。 USB 主机控制器驱动程序控制插入其中的 USB 设备,而 USB 设备驱动程序控制该设备如何作为设备与主机通信。 在 USB 主机控制器驱动和USB 设备驱动之间还有一层叫 USB 核心层。 USB 核心负责 USB 驱动管理和协议处理工作,它通过定义一些数据结构、宏和功能函数,向上为 USB 设备驱动提供编程接口,向下为 USB 主机控制器驱动提供编程接口;通过全局变量维护整个系统的 USB 设备信息,完成设备热插拔控制、总线数据传输控制等。 说了那么多,无图无真相啊~~

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

10.3.1 S5PV210 主机控制器驱动的移植


USB 主机控制器有 3 种规范, UHCI(Universal Host Controller Interface),这种规范主要是 Intel、 Via 芯片公司提供支持 PC 主板的; OHCI(Open HostController Interface),这种规范是微软提出来的,主要应用在非 PC 系统上的嵌入式领域上的 USB 芯片; EHCI(Enhanced Host Controller Interface),这种后来为提高 USB 速度而提出的规范,它支持最高速度为 480Mbps。在《S5PV210_UM_REV1.1 》手册上搜索 OHCI 关键词,会发现下面一段话
《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动
这表明 S5PV210 这款 CPU 支持一个 USB 主机接口,同时支持 EHCI 和OHCI 这两种规范,支持 USB1.1 和 USB2.0 规范,支持最高的外设传输速率为480Mbps。 注意了,它并不支持 USB3.0 规范的 USB 设备,所以做测试的时候,千万不要拿 USB3.0 规范的 USB 设备去测试。

10.3.1.1 移植 


ohci-s5p 驱动打开内核目录:drivers\usb\host\,发现 Linux 系统提供了大量的主机控制器驱动,找遍所有平台,都没有找到 ohci-s5p.c 源码。 很遗憾, 3.8 的内核没有提供 S5PV210 的 USB HOST 控制器驱动程序。最好验证有没有提供的办法就是,烧写网蜂提供的第一版的 uImage进去,然后找个 U 盘、或者鼠标插入 Webee210开发板的 USB HOST 接口,看看串口有没有打印什么信息,结果是不会有任何反应的。 既然没有提供, 这就需要我们自己来编写了,这下不好办了吧?


不用紧张,仔细再找找,还是能发现一些类似的源码,可供我们移植的。 我们发现,内核虽然没有提供 ohci-s5p.c 源码,但是有提供 ehci-s5p.c 源码,还有 ohci 相关的其他平台的源码,比如 ohci-s3c2410.c、 ohci-exynos.c 供我们移植参考。

10.3.1.1.1 ohci-s5p.c的移花接木


内核既然没有 ohci-s5p.c,那我们使用其他平台的 ohci 源码,这里我们拷贝drivers\usb\host\目录下的 ohci-exynos.c 为 ohci-s5p.c。 然后将所有 exynos 字符串替换成 s5p, 由于有些地方是 exynos4,所以还需要将 s5p4 替换为 s5p。最后还需要修改一下头文件,将


#include <linux/platform_data/usb-exynos.h>

改为:
#include <linux/platform_data/usb-ohci-s5p.h>


10.3.1.1.2 usb-ohci-s5p.h的移花接木


打开内核目录 include\linux\platform_data\,然后 拷贝 usb-exynos.h 为usb-ohci-s5p.h。将所有的 exynos4 字符串替换为 s5p, 将 EXYNOS 替换为 S5P。最后添加平台数据:


static struct s5p_ohci_platdata s5p_ohci_platdata;


为了以后支持 EHCI 还添加 echi 的平台数据,最后 usb-ohci-s5p.h 修改为:


#ifndef __MACH_S5P_OHCI_H
#define __MACH_S5P_OHCI_H
/**************Add by Webee*******************/
#ifdef CONFIG_S5P_DEV_USB_EHCI
static struct s5p_ehci_platdata s5p_ehci_platdata;
#endif
static struct s5p_ohci_platdata s5p_ohci_platdata;
/**************Add by Webee*******************/
struct s5p_ohci_platdata {
int (*phy_init)(struct platform_device *pdev, int type);
int (*phy_exit)(struct platform_device *pdev, int type);
};
extern void s5p_ohci_set_platdata(struct s5p_ohci_platdata *pd);
#endif /* __MACH_S5P_OHCI_H */


10.3.1.1.3添加 s5p_ohci_driver ohci-hcd.c


打开 drivers\usb\host\ ohci-hcd.c,在 CONFIG_USB_OHCI_EXYNOS 前面添加如下代码:


#ifdef CONFIG_USB_OHCI_S5P
#include "ohci-s5p.c"
#define PLATFORM_DRIVER s5p_ohci_driver
#endif


因为 S5PV210 USB HOST 控制器驱动由 drivers\usb\host\ ohci-hcd.c(支持各种 SoC 下的主机控制器驱动的通用部分)和 drivers\usb\host\ohci-s5p.c 共同完成。

10.3.1.1.4添加平台设备


前面我们移植 ohci-s5p.c 主要是围绕 platform_driver 来编程的,这又回到了平台驱动设备模型了。还记得我们移植 gpio-key 驱动了吗?里面就添加了平台设备来支持平台驱动。今天,我们同样需要添加平台设备来支持 s5p_ohci_driver这个平台驱动。 怎么添加呢?参考别人怎么写!


打开 arch\arm\plat-samsung\devs.c,找到 s5p_device_ehci 这个平台设备,模仿它来修改。


打开 arch\arm\mach-s5pv210\mach-smdkv210.c,在 smdkv210_devices[ ]前,添加如下代码:


#include <linux/platform_data/usb-ohci-s5p.h>
static struct resource s5p_ohci_resource[] = {
[0] = DEFINE_RES_MEM(0xEC300000, SZ_256),
[1] = DEFINE_RES_IRQ(S5P_IRQ_VIC1(23)),
};
static u64 samsung_device_dma_mask = DMA_BIT_MASK(32);
struct platform_device s5p_device_ohci = {
.name = "s5p-ohci",
.id = -1,
.num_resources = ARRAY_SIZE(s5p_ohci_resource),
.resource = s5p_ohci_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
void __init s5p_ohci_set_platdata(struct s5p_ohci_platdata *pd)
{
struct s5p_ohci_platdata *npd;

npd = s3c_set_platdata(pd, sizeof(struct s5p_ohci_platdata),&s5p_device_ohci);
if (!npd->phy_init)
npd->phy_init = s5p_usb_phy_init;
if (!npd->phy_exit)
npd->phy_exit = s5p_usb_phy_exit;
}


怎 么 确 定 s5p_ohci_resource 里 面 的 内 存 地 址 呢 ? 这 自 然 要 回 到《S5PV210_UM_REV1.1 》手册了,在 USB HOST 这章的寄存器介绍里面有这么一段描述:
《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

而 IRQ 的确定,则是找到下面这段话。


/* 参考 arch\arm\mach-s5pc100\include\mach\irqs.h */
#define IRQ_UHOST S5P_IRQ_VIC1(23)


然后将定义设置好的 s5p_device_ohci 添加到 smdkv210_devices[ ],如:


static struct platform_device *smdkv210_devices[] __initdata = {
&s5p_device_ohci, /* Add by Webee */
……
&webee210_button_device, /* Add by Webee */
};


最后,在 smdkv210_machine_init 函数中添加平台数据的设置函数。


#ifdef CONFIG_S5P_DEV_USB_OHCI
s5p_ohci_set_platdata(&s5p_ohci_platdata);
#endif


10.3.1.1.5移植 Kconfig


一、移植 drivers\usb\host\目录下的 Kconfig


打开 drivers\usb\host\目录下的 Kconfig,在 USB_OHCI_EXYNOS 前面添加 USB_OHCI_S5P 的配置支持。修改后如下:


# Add by Webee
config USB_OHCI_S5P
boolean "OHCI support for Samsung S5P SoC Series"
depends on USB_OHCI_HCD && PLAT_S5P
select S5P_DEV_USB_OHCI
help
Enable support for the Samsung S5P SOC's on-chip OHCI controller.
#Add by Webee
config USB_OHCI_EXYNOS

boolean "OHCI support for Samsung EXYNOS SoC Series"
depends on USB_OHCI_HCD && ARCH_EXYNOS
help
Enable support for the Samsung Exynos SOC's on-chip OHCI controller.


二、移植 arch\arm\plat-samsung 
录下的 Kconfig


打开 arch\arm\plat-samsung 目录下的 Kconfig,在 S5P_DEV_USB_EHCI后面添加 S5P_DEV_USB_OHCI 的配置支持,修改后如下:


config S5P_DEV_USB_EHCI
bool
help
Compile in platform device definition for USB EHCI
#Add by Webee
config S5P_DEV_USB_OHCI
bool
help
Compile in platform device definition for USB OHCI
#Add by Webee


三、移植 drivers\usb\目录下的 Kconfig


在 内 核 目 录 下 输 入 make menuconfig 配 置 内 核 时 , 搜 索 S5P_DEV_USB_OHCI发现如下现象,它表明 S5P_DEV_USB_OHCI 的配置需要先将 PLAT_S5P 配置上。

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

打开 drivers\usb\目录下的 Kconfig,在 USB_ARCH_HAS_OHCI 模块下添加如下内容:
default y if PLAT_S5P

10.3.1.1.6创建 setup-usb-phy.c 


文件在 arch\arm\mach-s5pv210\目录下创建 setup-usb-phy.c 文件,为什么要创建这么一个文件呢?还记得前面在 smdkv210_machine_init()函数里添加过s5p_ohci_set_platdata(&s5p_ohci_platdata);这个函数吗?


void __init s5p_ohci_set_platdata(struct s5p_ohci_platdata *pd)
{
struct s5p_ohci_platdata *npd;
npd = s3c_set_platdata(pd, sizeof(struct s5p_ohci_platdata),
&s5p_device_ohci);
if (!npd->phy_init)
npd->phy_init = s5p_usb_phy_init;
if (!npd->phy_exit)
npd->phy_exit = s5p_usb_phy_exit;
}


其中就会去设置 s5p_ohci_platdata 里的 phy_init、 phy_exit这两个成员函数。那么就需要实现, s5p_usb_phy_init 函数和 s5p_usb_phy_exit 函数。 最后将setup-usb-phy.c 文件添加如下代码:


#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <mach/regs-clock.h>
#include <mach/gpio.h>
#include <mach/regs-sys.h>
#include <plat/regs-usb-hsotg-phy.h>
#include <plat/usb-phy.h>
#include <plat/clock.h>
#include <plat/gpio-cfg.h>
int s5p_usb_phy_init(struct platform_device *pdev, int type)
{
int err;
struct clk *otg_clk;
if (type != S5P_USB_PHY_HOST)
return -EINVAL;
otg_clk = clk_get(&pdev->dev, "otg");
if (IS_ERR(otg_clk)) {
dev_err(&pdev->dev, "Failed to get otg clock\n");

return PTR_ERR(otg_clk);
}
err = clk_enable(otg_clk);
if (err) {
clk_put(otg_clk);
return err;
}
if (readl(S5PV210_USB_PHY_CON) & (0x1<<1)) {
clk_disable(otg_clk);
clk_put(otg_clk);
return 0;
}
__raw_writel(__raw_readl(S5PV210_USB_PHY_CON) | (0x1<<1),S5PV210_USB_PHY_CON);
__raw_writel((__raw_readl(S3C_PHYPWR)& ~(0x1<<7) & ~(0x1<<6)) | (0x1<<8) | (0x1<<5) | (0x1<<4),S3C_PHYPWR);
__raw_writel((__raw_readl(S3C_PHYCLK) & ~(0x1<<7)) | (0x3<<0),S3C_PHYCLK);
__raw_writel((__raw_readl(S3C_RSTCON)) | (0x1<<4) | (0x1<<3),S3C_RSTCON);
__raw_writel(__raw_readl(S3C_RSTCON) & ~(0x1<<4) & ~(0x1<<3),S3C_RSTCON);
/* "at least 10uS" for PHY reset elsewhere, 20 not enough here... */
udelay(50);
clk_disable(otg_clk);
clk_put(otg_clk);
return 0;
}
int s5p_usb_phy_exit(struct platform_device *pdev, int type)
{
if (type != S5P_USB_PHY_HOST)
return -EINVAL;
__raw_writel(__raw_readl(S3C_PHYPWR) | (0x1<<7)|(0x1<<6),S3C_PHYPWR);
__raw_writel(__raw_readl(S5PV210_USB_PHY_CON) & ~(1<<1),S5PV210_USB_PHY_CON);
return 0;
}


主要工作是获取 otg 时钟,设置 USB 相关的寄存器,具体为什么这么设置,Webee 也没有深入了解过,这是在外老网站上搜索到的代码。 有兴趣的小伙伴们,自己打开手册研究研究。

10.3.1.2 make menuconfig 


配置内核选项前面做了那么多工作,为谁而做?为配置选项而做!就好比,你读了那么多书,为谁而读?你别跟我扯,说什么为祖国而读,为中华的崛起而读书?在内核目录下输入 make menuconfig,进入内核配置菜单栏,配置如下:


Device Drivers ——>
 SCSI device support ——>
   <*> SCSI device support
   [ * ] legacy /proc/scsi/ support
   <*> SCSI disk support
   <*> SCSI CDROM support
   <*> SCSI generic support
 HID support ——>
   <*> Generic HID driver
 [ * ] USB support ——>
    <*> Support for Host-side USB
    <*> OHCI HCD support
   [ * ] OHCI support for Samsung S5P SoC Series
   [ * ] Generic OHCI driver for a platform device
   <*> USB mass Storage support
   [ * ] USB mass Storage verbose debug


完成以上所有移植后,就可以编译内核了,在内核目录下使用 make uImage命令编译内核。如果编译过程中出现说获取不到 usbhost 时钟,那么将 ohci-s5p.c里的 s5p_ohci_probe()函数里的:


s5p_ohci->clk = devm_clk_get(&pdev->dev, "usbhost");

改为:
s5p_ohci->clk = devm_clk_get(&pdev->dev, "usb-host");


10.3.2 S5PV210 主机控制器驱动的测试


某省*厅的厅长某天(已经商量好的日期)抽查某省某市的治安情况怎样怎样了。厅长只看你做的结果怎样(即使是作弊的),不看你做的过程怎样。前面做了那么多移植工作,我们也是时候来测试一下移植工作做的怎样了。只不过,我们的移植过程是非常有留念意义的哟。 将新的 uImage 烧到 webee210 开发板,启动内核,如果能够成功启动内核,并且出现如下信息:

ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
s5p-ohci s5p-ohci: S5PV210 OHCI Host Controller
s5p-ohci s5p-ohci: new USB bus registered, assigned bus number 1
s5p-ohci s5p-ohci: irq 87, io mem 0xec300000
hub 1 -0:1.0: USB hub found
hub 1 -0:1.0: 1 port detected
Initializing USB Mass Storage driver...
usbcore: registered new interface driver usb-storage
USB Mass Storage support registered.
mousedev: PS/2 mouse device common for all mice
i2c /dev entries driver
usbcore: registered new interface driver usbhid
usbhid: USB HID core driver


表明前面的移植工作没有白费,“厅长必须掌声鼓励一下,你小子,干的不错啊,接着马上小声说:晚上咱桑拿呢?还是沐浴呢?”

10.3.2.1 图说 OHCI 驱动之手机测试


使用 USB 线将手机与 webee210 开发板连接起来,观察打印信息。

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

移植 OHCI 驱动前,无论你插入神马东西,它都不会吐任何一个字,没有一点点反应,这是因为之前的内核还不支持 USB HOST 驱动。


移植 OHCI 驱动后,插入手机,立马弹出图 10.5 的信息。 里面信息告诉我们, webee210 开发板知道你插入的是什么 USB 设备,并且帮你找到相应的 USB
设备驱动程序并安装上。当你拔掉手机后,它弹出 disconnect 的信息,知道你已经将手机拔掉了。这就是牛逼哄哄的热插拔功能。 有些“后生仔”可能不认为这是一件很牛逼的事情,因为它没见识过,在热插拔技术未诞生之前,不能热插拔是多么痛苦的一件事。 我就不扯开了,自己去想象吧。

10.3.2.2 图说 OHCI 驱动之 U 盘测试


将 U 盘(注意,不要插 USB3.0 的 U 盘)插入 webee210 开发板的 USB HOST接口,观察打印信息(不同 U 盘信息可能不完全一致):

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

插入 U 盘的瞬间,就输出那么多的信息。从信息里,我们可以知道, webee210开发板已经识别出你插入的是 U 盘,并且知道你的 U 盘的容量有多大等等信息。那我们接下来测试一下 U 盘能不能读写呗~~先挂接 U 盘,然后进入 mnt 目录


[Webee210]\# mount /dev/sda /mnt
[Webee210]\# cd /mnt


然后就可以查看到 U 盘里面的文件数据了,我的 U 盘里只有二个文件夹,movie 和 music。至于是什么 movie,尽情的发挥你们的想象吧,骚年。

接下来我们来验证一下 U 盘能不能被读写。先创建一个 temp.txt 文件:


[Webee210]\# touch temp.txt


然后往 temp.txt 里写数据:


/* 注意了,使用 echo 命令,输入的内容不能有空格 */
[Webee210]\# echo hello,USB > temp.txt


最后检查一下内容写进去没,即检查可不可以被读:


[Webee210]\# cat temp.txt


《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

为了以防你说我作弊, Webee 还特意将 U 盘在 windows 下打开,看看里面的数据是否真的被读出写入了。如图 10.8 所示:

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

10.3.2.3 图说 OHCI 驱动之鼠标测试将鼠标


(注意,不要插 USB3.0 的鼠标)插入 webee210 开发板的 USB HOST接口,观察打印信息(不同鼠标信息可能不完全一致):

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动


Webee210 开发板也能识别出你插入的是鼠标,并帮你找到相应的鼠标驱动并安装上。 当然了, 接入不同的鼠标打印的信息可能会有差别。 包括后面的hexdump 命令输出的信息也有可能不同。


说到测试,怎么测试鼠标设备驱动呢?还记得输入子系统里怎么测试驱动了吗?还记得 input_event 结构体?对,鼠标设备驱动除了有与 USB 相关,还与输入子系统密切相关。这里,我们使用下面命令来测试鼠标。


[Webee210]\# hexdump /dev/event1

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

当你挪动鼠标,往左挪动、往右挪动;往前挪动、往后挪动;向前滚动鼠标中键、向后滚动鼠标中键;按下左键、按下右键;只要你对鼠标有动作,它都会上报,并将信息打印出来。具体这些数据有什么含义,自己去研究一下。 有意思的是,不同厂家的鼠标,滚动的动作在 input_event 结构体里的代表的意义有可能不一样。 到这里, USB HOST 相关的驱动就移植完了, USB 设备也可以正常使用了。


10.4 USB 核心


USB 核心负责 USB 驱动管理和协议处理工作,它通过定义一些数据结构、宏和功能函数,向上为 USB 设备驱动提供编程接口, 向下为 USB 主机控制器驱动提供编程接口;通过全局变量维护整个系统的 USB 设备信息,完成设备热插拔控制、总线数据传输控制等。


drivers\usb\目录下,有个目录叫 core 目录, 进去之后,发现里面有 22 个文件(如图 10.11 ), OMG,我该从哪里下手分析呀?看招!从 Makefile 开始? NO,这不是一个很好的办法。 那还有其他好的办法吗?

《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

10. 4.1 hub.c


细心的读者可能发现了 10.3.2 节中,手机、 U 盘、鼠标的测试的第一句打印信息都是相似的。如:


/* 手机 */
usb 1 -1:new full-speed USB device number 2 using s5p-ohci
/* U 盘 */
usb 1 -1:new full-speed USB device number 2 using s5p-ohci
/* 鼠标 */
usb 1 -1:new low-speed USB device number 2 using s5p-ohci


发现 3 种不同的 USB 设备,打印的信息都是极其类似的。那么,我们推测,这句应该是通用的。既然是通用的,应该是核心做的事情,核心嘛,就是想多管闲事,管管别人。 在 Source Insight 里搜索USB device number就会找到 hub.c文件里的 hub_port_init 函数里有那么一句:


if (udev->speed != USB_SPEED_SUPER)
dev_info(&udev->dev,"%s %s USB device number %d using %s\n",(udev->config) ? "reset" : "new", speed,devnum, udev->bus->controller->driver->name);


10. 4.2 USB 核心的事件处理流程


假如我们想弄清楚, USB 核心是怎么处理 USB 设备从插入到拔出, USB 核心做了什么工作,工作流程是怎样的?USB 设备都是通过插入上层 HUB 的一个 Port来连入系统并进而被系统发现的, 当 USB 设备插入一个 HUB 时,该 HUB 的那个 port 的状态就会改变, 从而系统就会知道这个改变, 此时会调用 hub_port_connect_change()函数。 这个函数在 drivers/usb/core/hub.c


subsys_initcall(usb_init); //drivers\usb\core\usb.c
usb_init //drivers\usb\core\usb.c
usb_hub_init
kthread_run(hub_thread, NULL, "khubd");
hub_thread
hub_events
hub_port_connect_change


hub_port_connect_change 函数主要做了二件事:


hub_port_connect_change
hub_port_init //复位设备,分配地址,获取设备描述符
usb_new_device(udev);


我们再来看看 hub_port_init 函数主要做了什么事?


hub_port_init
/* 给 USB 设备分配地址 */
hub_set_address(udev, devnum);
/* 获取 USB 设备描述符 */
usb_get_device_descriptor(udev, 8);
usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);


继续看看 usb_new_device 函数主要做了什么?


usb_new_device(udev);
usb_enumerate_device(udev); /* Read descriptors */
usb_get_configuration(udev); /* 获取设备描述符 */
usb_parse_configuration /* 解析设备描述符 */
device_add(&udev->dev);


device_add 函数在 drivers/base/core.c 里实现。


device_add(&udev->dev);
get_device(dev);
bus_add_device(dev);
bus_probe_device(dev);
device_attach(dev);
/*从总线上已注册的所有驱动中找出匹配的驱动程序.*/
bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
while ((drv = next_driver(&i)) && !error)
error = fn(drv, data);


bus_for_each_drv(dev->bus, NULL, dev, __device_attach)函数遍历 bus 上的所有驱动程序 , 并为每个驱动调用 fn()来查看是否匹配 . 这里的 fn 就是__device_attach.


__device_attach(struct device_driver *drv, void *data)
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->driver = drv;
else if (drv->probe)
ret = drv->probe(dev);


总结一下, device_add 函数把 device 放入 usb_bus_type 的 dev 链表, 从usb_bus_type 的 driver 链表里取出 usb_driver,把 usb_interface 和 usb_driver的 id_table 比较如果能匹配,调用 usb_driver 的 probe。

10. 5 USB 设备驱动之鼠标驱动


USB 设备驱动指的是从主机角度看到的,怎样访问被插入的 USB 设备。Linux 发展到今天, USB 设备驱动已经相当丰富,像 U 盘、 USB 鼠标、 USB 键盘几乎都不用驱动工程师再编写了,一般由芯片厂家提供。并且 USB 设备驱动在 Linux 里有大量的参考例子,只需要稍微修改一下就可以用了。 Linux3.8 下的usb 鼠标驱动在/drivers/hid/usbhid/usbmouse.c 中实现,这应该是一个通用的USB 鼠标驱动。


10. 5.1 四个重要的结构体


我们说过了 Linux 内核里,驱动程序几乎都是面向对象的编程。既然是面向对象,那么肯定有需要定义、设置、注册一个结构体。 USB 设备驱动也不例外,在 Linux 内核里,使用 usb_driver 结构体描述一个 USB 设备驱动。

10. 5.1.1 USB 设备驱动(usb_driver) 


usb_driver 结构体主要的成员“菜单”, 客官, 请慢用^_^……


struct usb_driver {
/* 驱动名字 */
const char *name;
/* 探测函数, USB 设备被插入的时候被调用 */
int (*probe) (struct usb_interface *intf,const structusb_device_id *id);
/* 断开函数, USB 设备被拔出的时候被调用 */
void (*disconnect) (struct usb_interface *intf);
/* id_table,描述了这个 USB 驱动所支持的 USB 设备列表 */
const struct usb_device_id *id_table;
......
};


10. 5.1.2 USB 设备匹配列表(usb_device_id) 


/* 参考 include\linux\Mod_devicetable.h */
struct usb_device_id {
__u16 match_flags; /* 标明要与哪些成员匹配 */
/* Used for product specific matches; range is inclusive */
__u16 idVendor;
__u16 idProduct;
__u16 bcdDevice_lo;
__u16 bcdDevice_hi;
/* Used for device class matches */
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
/* Used for interface class matches */
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
/* Used for vendor-specific interface matches */
__u8 bInterfaceNumber;
/* not matched against */
kernel_ulong_t driver_info
__attribute__((aligned(sizeof(kernel_ulong_t))));
};


一般在 USB 设备驱动程序里,使用下面几个宏来生成 usb_device_id 结构体的实例。

/* 参考 include\linux\usb.h */
USB_DEVICE(vend, prod)
USB_DEVICE_VER(vend, prod, lo, hi)
USB_DEVICE_INTERFACE_CLASS(vend, prod, cl)
USB_DEVICE_INTERFACE_PROTOCOL(vend, prod, pr)
USB_DEVICE_INTERFACE_NUMBER(vend, prod, num)
USB_DEVICE_INFO(cl, sc, pr)
USB_INTERFACE_INFO(cl, sc, pr)
USB_DEVICE_AND_INTERFACE_INFO(vend, prod, cl, sc, pr)
USB_VENDOR_AND_INTERFACE_INFO(vend, cl, sc, pr)


关于这 9 个宏如何使用,请参考 include\linux\usb.h源码。这里仅仅举个例子,比如在 usb_device_id 数组实例中,使用 USB_DEVICE(vend, prod)这个宏。表示,这个 USB 设备驱动所支持的制造商 ID、产品 ID 应该与 usb_device_id数组实例中的 idVendor、 idProduct 匹配。


当 USB 核心检测到插入的 USB 设备的属性与某个 USB 设备驱动的usb_device_id 结构体实例所携带的信息一致时,这个驱动程序的 probe()函数就会被调用。同理,当 USB 核心检测到 USB 设备被拔出时, disconnect()函数就来响应这个动作。所以, USB 设备驱动其实主要就是完成 probe() 和disconnect()函数,至于你在这两个函数里做什么事情,这个你决定。只打印一句话,都可以。 USB 总线已经完成它的本分工作了,剩下的就是 USB 本身所属类型的本分工作了。比如,你是一个 USB 串口,那么你就完成 tty 设备该完成的工作;你是一个字符设备,那么就完成字符设备该完成的工作;而 USB 鼠标是一个输入设备,那么很自然的,它就应该完成输入子系统那套工作。

10. 5.1. 3 USB 请求块(urb) 


USB 请求块(USB request block,urb)是 USB 设备驱动中用来描述 USB 设备通信所用的核心数据结构。 urb 结构体的成员实在是太多了,但是 webee 发现在很多 USB 设备驱动里,很多成员都是没有使用到的,也就是说很多成员使用率并不高。 urb 的成员,大多数使用 urb 封装函数来设置的。后面程序里会提及,这里暂且先跟大家说一下。


struct urb {
/* private: usb core and host controller only fields in the urb */
struct kref kref; /* URB 引用计数 */
void *hcpriv; /* host 控制器的私有数据 */
atomic_t use_count; /* 原子变量,用于并发传输计数 */
atomic_t reject; /* 原子变量,用于并发传输失败 */
int unlinked; /* unlink 错误码 */
/* public: documented fields in the urb that can be used by drivers */
struct list_head urb_list; /* URB 链表头 */
struct list_head anchor_list;
struct usb_device *dev; /* 内嵌的 USB 设备结构体 */
struct usb_host_endpoint *ep; /* (internal) pointer to endpoint */
unsigned int pipe; /* 管道信息 */
int status; /* URB 的当前状态 */
unsigned int transfer_flags; /* URB_SHORT_NOT_OK | ...*/
/* 发送数据到USB 设备,或从USB 设备里接收数据到缓冲区*/
void *transfer_buffer;
dma_addr_t transfer_dma; /* DMA方式向设备传输数据到缓冲区 */
......
/* transfer_buffer transfer_dma 指向缓冲区的大小 */
u32 transfer_buffer_length;
u32 actual_length; /* 实际发送或接收的长度 */
......
};


10. 5.1. 4 USB 鼠标(usb_mouse) 


usb_mouse 结构体代表一个 USB 鼠标设备,里面封装了 usb_device,input_dev 和 urb 三个重要的结构体。整个 USB 鼠标驱动就是围绕这个结构体来展开工作的。


struct usb_mouse {
char name[128]; /* 驱动名字 */
char phys[64]; /* 设备节点 */
struct usb_device *usbdev; /* 内嵌 USB 设备结构体 */
struct input_dev *dev; /* 内嵌 INPUT 设备结构体 */
struct urb *irq; /* USB 请求块 */
signed char *data; /* transfer_buffer 缓冲区 */
dma_addr_t data_dma; /* transfer _dma 缓冲区 */
};


10. 5.2 模块加载宏


打开 drivers/hid/usbhid/usbmouse.c 后,发现并没有模块加载函数了,很奇怪。也没有看到类似于 module_init(xxx_init)、 module_exit(xxx_exit)的修饰语句了,哪去了呢?只看到下面这句:


module_usb_driver(usb_mouse_driver);


不知道从 Linux 的 3.0 以后的哪个版本开始,就喜欢用类似于上面这种方法来代替以前的 module_init(xxx_init)、 module_exit(xxx_exit)了。深入一点,你会发现它与以前的入口函数、出口函数的功能是一样的,只不过是封装了一下。


/* 参考:include\linux\usb.h */
#define module_usb_driver(__usb_driver) \
module_driver(__usb_driver, usb_register, \
usb_deregister)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit); 


有兴趣的读者,自行展开宏,这有点考验你 C 语言的功底囖。

10. 5.3 usb_mouse_driver


static struct usb_driver usb_mouse_driver = {
.name = "usbmouse", /* 驱动名 */
.probe = usb_mouse_probe, /* 匹配方法 */
.disconnect = usb_mouse_disconnect,/* 拔出方法 */
.id_table = usb_mouse_id_table,/* 支持设备 ID 列表 */
};


10. 5. 4 usb_mouse_id_table


当插入鼠标时会根据 usb_mouse_id_table 去匹配创建 usb 设备。


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 */
};


宏展开之后:


static struct usb_device_id usb_mouse_id_table [] = {
{
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO,
.bInterfaceClass = USB_INTERFACE_CLASS_HID,
.bInterfaceSubClass = USB_INTERFACE_SUBCLASS_BOOT,
.bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE
},
{ } /* Terminating entry */
};


它表示这个 USB 鼠标驱动在判断支不支持 USB 鼠标时是根据接口信息来判断 的 。 如 果 USB 鼠 标 的 接 口 类 、 子 类 、 协 议 分 别 是USB_INTERFACE_CLASS_HID、 USB_INTERFACE_SUBCLASS_BOOTUSB_INTERFACE_PROTOCOL_MOUSE 的话,那么这个 USB 鼠标驱动就支持这个 USB 鼠标。 usb 插入枚举时候会获取 usb 鼠标的接口类型,获取其接口类信息,匹配成功的话会动态创建一个 usb_device.

10. 5.5 usb_mouse_probe


匹配成功了就会调用 probe 方法,它是整个 USB 鼠标驱动的主体函数,主要围绕 usb_mouse 结构体来展开工作。

static int usb_mouse_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
/* 由接口获取 usb_device */
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;
/* 由 usb_interface 实例获取 usb_host_interface 实例 */
interface = intf->cur_altsetting;
/* 鼠标端点(Endpoint)只有 1 个 */
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
/* 获取端点描述符,这里并不是获取端点 0 的描述符 */
endpoint = &interface->endpoint[0].desc;
/* 检查该端点是否是中断输入端点,根据 HID 规范,
* 鼠标唯一的端点应为中断端点
*/
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
/* 产生中断管道,驱动程序 buffer 和端点之间虚拟通道 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 返回该端点能够传输的最大的包长度,
* 鼠标的返回的最大数据包为 4 个字节
*/
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
/* 分配 usb_mouse 结构体大小的内存 */
mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
/* 鼠标是输入设备,所以要创建输入型设备 */
input_dev = input_allocate_device();
if (!mouse || !input_dev)
goto fail1;

/* 分配初始化 usb 鼠标数据缓冲区内存(默认 8 位数据) */
mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC,&mouse->data_dma);
if (!mouse->data)
goto fail1;
/* 分配 URB,第一个参数为 0,表明中断传输,不设置等时包 */
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
goto fail2;
mouse->usbdev = dev; /* 填充 mouse 的 usb_device 结构体 */
mouse->dev = input_dev; /* 填充 mouse 的 input 结构体 */
if (dev->manufacturer)
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}
if (!strlen(mouse->name))
snprintf(mouse->name, sizeof(mouse->name),"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
/* 设置设备节点 */
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
/* 输入设备的名字设置成 usb 鼠标的名字 */
input_dev->name = mouse->name;
/* 输入设备的路径设置成 usb 鼠标的路径 */
input_dev->phys = mouse->phys;
/* 设置输入设备的 bustype,vendor,product,version */
usb_to_input_id(dev, &input_dev->id);
input_dev->dev.parent = &intf->dev;
/* 设置鼠标能产生按键类和相对位移类事件 */
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);

/* 设置鼠标能产生按键类下的鼠标、左键、右键、中键 */
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
/* 设置鼠标能产生 X、 Y 方向的相对位移类事件 */
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
/* 设置鼠标能产生相对位移类下的鼠标、旁键、外部键*/
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA);
/* 设置鼠标能产生滚轮的相对位移类事件 */
input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);
/* usb 鼠标驱动文件作为输入设备的设备文件的驱动数据 */
input_set_drvdata(input_dev, mouse);
/* 设置输入事件的打开、关闭方法 */
input_dev->open = usb_mouse_open;
input_dev->close = usb_mouse_close;
/* 填充中断类型 urb 指定了 urb 的回调函数是 usb_mouse_irq */
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,(maxp > 8 ? 8 : maxp),usb_mouse_irq, mouse, endpoint->bInterval);
/* dma 数据缓冲区指向 usb 鼠标设备的 data_dma 成员 */
mouse->irq->transfer_dma = mouse->data_dma;
/* 无 DMA 映射 */
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 注册鼠标输入设备 */
error = input_register_device(mouse->dev);
if (error)
goto fail3;
/* 相当于 intf->dev->p->driver_data = mouse;
* 将 usb_mouse 实例放到 driver_data,供其他函数使用,
* 如:可通过 usb_get_intfdata (intf);取出 usb_mouse 实例
*/
usb_set_intfdata(intf, mouse);
return 0;
fail3:
usb_free_urb(mouse->irq);
fail2:

usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
fail1:
input_free_device(input_dev);
kfree(mouse);
return error;
}


10. 5.6 usb_mouse_disconnect


拔掉 usb 鼠标就会调用 disconnect 方法,其实 disconnect 只是 probe 的一个逆操作而已。


static void usb_mouse_disconnect(struct usb_interface *intf)
{
/* 通过接口获得 usb 鼠标设备 */
struct usb_mouse *mouse = usb_get_intfdata (intf);
/* intf->dev = NULL */
usb_set_intfdata(intf, NULL);
if (mouse) {
usb_kill_urb(mouse->irq); /* 取消已经提交的 urb */
input_unregister_device(mouse->dev);/* 注销输入设备 */
usb_free_urb(mouse->irq); /* 释放已经分配的 urb */
/* 清除传输数据缓冲区 */
usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data,
mouse->data_dma);
kfree(mouse); /* 释放 usb 鼠标设备 */
}
}


10. 5.7 usb_mouse_open


经过 probe 过程,注册了输入设备则会在/dev/input/目录下会产生对应的鼠标设备节点 , 应用程序可以打开该节点来控制 usb 鼠标设备,此时会调用usb_mouse_open 方法


static int usb_mouse_open(struct input_dev *dev)
{
/* 通过输入设备获取 usb 鼠标设备 */
struct usb_mouse *mouse = input_get_drvdata(dev);
/* 设置 urb 设备对应的 usb 设备 */
mouse->irq->dev = mouse->usbdev;

/* 提交 urb,通过 urb 提交之后,鼠标动作
* 通过 usb 传输数据就会交由 urb 去处理了
*/
if (usb_submit_urb(mouse->irq, GFP_KERNEL))
return -EIO;
return 0;
}


10. 5.7 usb_mouse_irq


当 操 作 鼠 标 的 时 候 , 会 引 起 urb 数 据 传 输 在 数 据 传 输 之 后 会 调 用usb_mouse_irq,注意了,它并不是一个真正的中断处理函数。我们说过了, USB设备没有中断主机控制器的能力,它只能轮询。


static void usb_mouse_irq(struct urb *urb)
{
/* 获取 usb 鼠标设备 */
struct usb_mouse *mouse = urb->context;
/* transfer_buffer 缓冲区 */
signed char *data = mouse->data;
struct input_dev *dev = mouse->dev;
int status;
/* 通过 urb->status 判断 URB 传输是否成功,
* 如果 urb->status = 0,则正确继续执行,错误时不会执行
*/
switch (urb->status) {
case 0: /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit;
}
/* 正确时报告鼠标按键情况,使用 input_report_key()
* 报告信息,使用 input_sync 表明报告完毕。
*/
input_report_key(dev, BTN_LEFT, data[0] & 0x01);

input_report_key(dev, BTN_RIGHT, data[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
input_report_key(dev, BTN_SIDE, data[0] & 0x08);
input_report_key(dev, BTN_EXTRA, data[0] & 0x10);
input_report_rel(dev, REL_X, data[1]);
input_report_rel(dev, REL_Y, data[2]);
input_report_rel(dev, REL_WHEEL, data[3]);
input_sync(dev);
resubmit:
/* 继续提交 urb */
status = usb_submit_urb (urb, GFP_ATOMIC);
if (status)
dev_err(&mouse->usbdev->dev,
"can't resubmit intr, %s-%s/input0, status %d\n",
mouse->usbdev->bus->bus_name,
mouse->usbdev->devpath, status);
}


usb 接口传来的数据会保存在 usb 鼠标 data 指针成员指向的缓冲区中, 这里可以看出 usb 鼠标传输的每次数据基本是 4 个字节。


第 0 个字节的第 1 位表示右键,第 2 位表示左键,第 3 位表示中键,第 4 位表示边键,第 5 为表示外部键;而第 1 个字节表示相对 x 坐标的位移,第 2 个字节表示相对 y 坐标的位移,第 3 个字节表示相对滚轮的位移。


当输入设备上报完 usb 接口接收来的数据后,需要调用 input_sync 同步事件消息,并调用 usb_submit_urb 提交 urb,使其继续监视处理 usb 鼠标设备传递的新数据。

10. 6 本章小结


一不小心,这章就写了将近 30 页的内容。花了可 webee 不少时间,如果你不好好学习这章内容的话,就应该要有文章有愧于马伊琍那种愧疚感,有木有?


好了,别闹了。来,总结一下本章学习了什么内容呢?回忆一下,本章首先介绍了什么是 USB 总线,接着大概对 USB 各种小知识科普了一下。然后还详细讲解了如何移植 ochi-s5p 驱动到 webee210 开发板,对移植的工作进行了各种测试。完了之后,还大致的讲解了 USB 核心怎么管理 USB 设备,工作流程是怎样的。最后,以 USB 鼠标驱动为例讲解了 USB 设备驱动实例。几乎全面讲解了 USB CORE、 USB HOST、 USB DEVICE DRIVER 三层知识。


看起来,好像讲解了很多东西。实际上,由于篇幅问题, webee 木有对 USB协议进行更深入的讨论了。 想对 USB 更深入了解的读者,建议去品读《Linux那些事儿之我是 USB》,当然,也欢迎跟 webee 探讨探讨。