一.USB理论
1. USB概念概述
USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB) USB2.0版本速度480Mbps(高速USB)
USB驱动由USB主机控制器驱动和USB设备驱动组成。USB主机控制器是用来控制USB设备和CPU之间通信的,USB主机控制器驱动主要用来驱动芯片上的主机控制器硬件。USB设备驱动主要是指具体的例如USB鼠标,USB键盘灯设备的驱动。
一般的通用的Linux设备,如U盘、USB鼠标、USB键盘,都不需要工程师再编写驱动,需要编写的是特定厂商、特定芯片的驱动,而且往往也可以参考内核中已经提供的驱动模板。USB只是一个总线,真正的USB设备驱动的主体工作仍然是USB设备本身所属类型的驱动,如字符设备、tty设备、块设备、输入设备等。
2. USB主机控制器
USB主机控制器属于南桥芯片的一部分,通过PCI总线和处理器通信。USB主机控制器分为UHCI(英特尔提出)、OHCI(康柏和微软提出)、 EHCI。其中OHCI驱动程序用来为非PC系统上以及带有SiS和ALi芯片组的PC主办上的USB芯片提供支持。UHCI驱动程序多用来为大多数其他PC主板(包括Intel和Via)上的USB芯片提供支持。ENCI兼容OHCI和UHCI。UHCI的硬件线路比OHCI简单,所以成本较低,但需要较复杂的驱动程序,CPU负荷稍重。主机控制器驱动程序完成的功能主要包括:解析和维护URB,根据不同的端点进行分类缓存URB;负责不同USB传输类型的调度工作;负责USB数据的实际传输工作;实现虚拟跟HUB的功能。
3. USB设备与USB驱动的匹配
USB设备与USB驱动怎么匹配的呢?实际上USB设备中有一个模块叫固件,是固件信息和USB驱动进行的匹配。固件是固化在集成电路内部的程序代码,USB固件中包含了USB设备的出厂信息,标识该设备的厂商ID、产品ID、主版本号和次版本号等。另外固件中还包含一组程序,这组程序主要完成USB协议的处理和设备的读写操作。USB设备固件和USB驱动之间通信的规范是通过USB协议来完成的。
4. USB设备的逻辑结构和端点的传输方式
USB设备的逻辑结构包括设备、配置、接口和端点,分别用usb_device、usb_host_config、 usb_interface、usb_host_endpoint表示。
端点的传输方式包括控制传输、中断传输、批量传输、等时传输。
控制传输主要用于向设备发送配置信息、获取设备信息、发送命令道设备,或者获取设备的状态报告。控制传输一般发送的数据量较小,当USB设备插入时,USB核心使用端点0对设备进行配置,另外,端口0与其他端点不一样,端点0可以双向传输。
中断传输就是中断端点以一个固定的速度来传输较少的数据,USB键盘和鼠标就是使用这个传输方式。这里说的中断和硬件上下文中的中断不一样,它不是设备主动发送一个中断请求,而是主机控制器在保证不大于某个时间间隔内安排一次传输。中断传输对时间要求比较严格,所以可以用中断传输来不断地检测某个设备,当条件满足后再使用批量传输传输大量的数据。
批量传输通常用在数据量大、对数据实时性要求不高的场合,例如USB打印机、扫描仪、大容量存储设备、U盘等。
等时传输同样可以传输大批量数据,但是对数据是否到达没有保证,它对实时性的要求很高,例如音频、视频等设备。
5. USB的URB请求块
USB请求块(USB request block,urb)是USB主机控制器和设备通信的主要数据结构,主机和设备之间通过urb进行数据传输。当主机控制器需要与设备交互时,只需要填充一个urb结构,然后将其提交给USB核心,由USB核心负责对其进行处理。
URB处理流程:
Step1:创建一个URB结构体 usb_alloc_urb()
Step2:初始化,被安排一个特定的USB设备的特定端点。fill_int/bulk/control_urb()
Step3:被USB设备驱动提交给USB核心usb_submit_urb(),注意GPF_ATOMIC,GPF_NOIO,GPF_KERNEL的使用区别。
Step4:提交由USB核心指定的USB主机控制器驱动,被主机控制器驱动处理,进行一次到USB设备的传输,该过程由USB核心和主机控制器完成,不受USB设备驱动控制
Step5:当urb完成,USB主机控制器驱动通知USB设备驱动。
简单的批量与控制URB
有时候USB驱动程序只是从USB设备上接收或发送一些简单的数据,这时候可以使用usb_bulk/control_msg()完成,这两个函数是同步的,因此不能在中断上下文和持有自旋锁的情况下使用。
6. USB的枚举过程
内核辅助线程khubd用来监视与该集线器连接的所有端口,通常情况下,该线程处于休眠状态,当集线器驱动程序检测到USB端口状态变化后,该内核线程立马唤醒。
USB的枚举过程:USB的枚举过程是热插拔USB设备的起始步骤,该过程中,主机控制器获取设备的相关信息并配置好设备,集线器驱动程序负责该枚举过程。枚举过程主要分如下几步:
Step1:根集线器报告插入设备导致的端口电流变化,集线器驱动程序检测到这一状态变化后,唤醒khubd线程。
Step2:khubd识别出电流变化的那个端口
Step3:khubd通过给控制端点0发送控制URB来实现从1-127中选出一个数作为插入设备的批量端点
Step4:khubd利用端口0使用的控制URB从插入的设备那里获得设备描述符,然后获得配置描述符,并选择一个合适的。
Step5:khubd请求USB核心把对应的客户驱动程序和该USB设备挂钩。
二.USB驱动分析
内核代码分析包括USB驱动框架、鼠标驱动、键盘驱动、U盘驱动。
USB驱动编写的主要框架usb-skeleton.c
USB鼠标驱动 usbmouse.c
USB键盘驱动usbkbd.c
USB Mass Storage是一类USB存储设备, U盘便是其中之一,主要分析的驱动文件是usb.c
1.USB驱动框架usb-skeleton.c
USB骨架程序可以被看做一个最简单的USB设备驱动的实例。
首先看看USB骨架程序的usb_driver的定义
static struct usb_driver skel_driver = {
.name = "skeleton",
.probe = skel_probe, //设备探测
.disconnect = skel_disconnect,
.suspend = skel_suspend,
.resume = skel_resume,
.pre_reset = skel_pre_reset,
.post_reset = skel_post_reset,
.id_table = skel_table, //设备支持项
.supports_autosuspend = 1,
};
#define USB_SKEL_VENDOR_ID 0xfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
static struct usb_device_id skel_table[] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE(usb, skel_table);
由上面代码可见,通过USB_DEVICE宏定义了设备支持项。
对上面usb_driver的注册和注销发送在USB骨架程序的模块加载和卸载函数中。
static int __init usb_skel_init(void)
{
int result;
result = usb_register(&skel_driver); //将该驱动挂在USB总线上
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
一个设备被安装或者有设备插入后,当USB总线上经过match匹配成功,就会调用设备驱动程序中的probe探测函数,向探测函数传递设备的信息,以便确定驱动程序是否支持该设备。
static int skel_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_skel *dev; //特定设备结构体
struct usb_host_interface *iface_desc; //设置结构体
struct usb_endpoint_descriptor *endpoint; //端点描述符
size_t buffer_size;
int i;
int retval = -ENOMEM;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配内存
if (!dev) {
err("Out of memory");
goto error;
}
kref_init(&dev->kref);
sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); //初始化信号量
mutex_init(&dev->io_mutex); //初始化互斥锁
spin_lock_init(&dev->err_lock); //初始化信号量
init_usb_anchor(&dev->submitted);
init_completion(&dev->bulk_in_completion); //初始化完成量
dev->udev = usb_get_dev(interface_to_usbdev(interface)); //获取usb_device结构体
dev->interface = interface; //获取usb_interface结构体
iface_desc = interface->cur_altsetting; //由接口获取当前设置
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { //根据端点个数逐一扫描端点
endpoint = &iface_desc->endpoint[i].desc; //由设置获取端点描述符
if (!dev->bulk_in_endpointAddr &&
usb_endpoint_is_bulk_in(endpoint)) { //如果该端点为批量输入端点
buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); //缓冲大小
dev->bulk_in_size = buffer_size; //缓冲大小
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; //端点地址
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); //缓冲区
if (!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); //分配urb空间
if (!dev->bulk_in_urb) {
err("Could not allocate bulk_in_urb");
goto error;
}
}
if (!dev->bulk_out_endpointAddr &&
usb_endpoint_is_bulk_out(endpoint)) { //如果该端点为批量输出端点
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; //端点地址
}
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {//都不是批量端点
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}
usb_set_intfdata(interface, dev); //将特定设备结构体设置为接口的私有数据
retval = usb_register_dev(interface, &skel_class); //注册USB设备
if (retval) {
err("Not able to get a minor for this device.");
usb_set_intfdata(interface, NULL);
goto error;
}
dev_info(&interface->dev,
"USB Skeleton device now attached to USBSkel-%d",
interface->minor);
return 0;
error:
if (dev)
kref_put(&dev->kref, skel_delete);
return retval;
}
通过上面分析,我们知道,usb_driver的probe函数中根据usb_interface的成员寻找第一个批量输入和输出的端点,将端点地址、缓冲区等信息存入USB骨架程序定义的usb_skel结构体中,并将usb_skel通过usb_set_intfdata传为USB接口的私有数据,最后注册USB设备.