Linux内核支持两种主要类型的USB驱动程序:宿主(host)系统上的驱动程序和设备(device)上的驱动程序。
一、USB设备基础
1.1、端点
USB通信最基本的形式是通过一个名为端点(endpoint)的东西。USB端点只能往一个方向传送数据,从主机到设备(称为输出端点)或者从设备到主机(称为输入端点)。端点可以看作是单向的管道。
USB端点有四种不同的类型,分别为:
控制:
控制端点用来控制对USB设备不同部分的访问。它们通常用于配置设备、获取设备信息、发送命令到设备,或者获取设备的状态报告。这些端点一般体积较小。每个USB设备都有一个名为“端点0”的控制端点,USB核心使用该端点在插入时进行设备的配置。USB协议保证这些传输始终有足够的保留带宽以传送数据到设备。
中断:
每当USB宿主要求设备传输数据时,中断端点就以一个固定的速率来传送少量的数据。这些端点是USB键盘和鼠标所使用的主要传输方式。他们通常还用于传输数据到USB设备以控制设备,不过一般不用来传输大量的数据。USB协议保证这些传输始终有足够的保留带宽以传送数据。
批量:
批量(bulk)端点传输大批量的数据。这些端点通常比中断端点大的多(他们可以一次持有更多的字符)。它们常见于需要确保没有数据丢失的传输的设备。USB协议不保证这些传输始终可以在特定的时间内完成。如果总线上的空间不足以发送整个批量包,它将被分割为多个包进行传输。这些端点通常出现在打印机、存储设备和网络设备上。
等时:
等时(isochronous)端点同样可以传送大批量的数据,但数据是否到达是没有保证的。这些端点用于可以应付数据丢失情况的设备,这类设备更注重于保持一个恒定的数据流。实时的数据收集(例如音频和视频设备)几乎毫无例外都使用这类端点。
内核中使用struct usb_host_endpoint结构体来描述USB端点。该结构体在另一个名为struct usb_endpoint_descriptor的结构体中包含了真正的端点信息。主要字段有:
bEndpointAddress:
这是特定端点的USB地址。这个8位的值中还包含了端点的方向。该字段可以结合位掩码USB_DIR_OUT和USB_DIR_IN来使用,以确定该端点的数据是传向设备还是主机。
bmAttributes:
这是端点的类型。该值可以结合位掩码USB_ENDPOINT_XFERTYPE_MASK来使用,以确定此端点的类型是USB_ENDPOINT_XFER_ISOC、USB_ENDPOINT_XFER_BULK还是USB_ENDPOINT_XFER_INT。这些宏分别表述等时、批量和中断端点。
wMaxPacketSize:
这是该端点一次可以处理的最大字节数。注意,驱动程序可以发送数量大于此值的数据到端点,但是在实际传输到设备的时候,数据将被分割为wMaxPacketSize大小的块。对于高速设备,通过使用高位中一些额外的位,该字段可以用来支持端点的高带宽模式。
bInterval:
如果端点是中断类型,该值是端点的时间设置----也就是说,端点的中断请求间隔时间。该值以毫秒为单位。
1.2、接口
USB端点被捆绑为接口。
内核使用struct usb_interface结构体来描述USB接口。USB核心把该结构体传递给USB驱动程序,之后由USB驱动程序来负责控制该结构体。该结构体重要字段有:
struct usb_host_interface *altsetting:
一个接口结构体数组,包含了所有可能用于该接口的可选设置。每个struct usb_host_interface结构体包含一套由上述struct usb_host_endpoint结构体定义的端点配置,注意,这些接口结构体没有特定的次序。
unsigned num_altsetting:
altsetting指针所指的可选设置的数量。
struct usb_host_interface *cur_altsetting:
指向altsetting数组内部的指针,表示该接口的当前活动设置。
int minor:
如果捆绑到该接口的USB驱动程序使用USB主设备号,这个变量包含USB核心分配给该接口的次设备号。这仅在一个成功的usb_register_dev调用之后才有效。
1.3、配置
USB接口本身被捆绑为配置。一个USB设备可以有多个配置,而且可以在配置之间切换以改变设备的状态。
Linux使用struct usb_host_config结构体来描述USB配置,使用struct usb_device结构体来描述整个USB设备。
逻辑单元之间的关系如下:
设备通常具有一个或者更多的配置。
配置经常具有一个或者更多的接口。
接口通常具有一个或者更多的设置。
接口没有或者具有一个以上的端点。
二、USB和Sysfs
三、USB urb
Linux内核中的USB代码通过一个称为urb(USB请求块)的东西和所有的USB设备通信。这个请求块使用struct urb结构体来描述。urb被用来以一种异步的方式往/从特定的USB设备上的特定USB端点发送/接收数据。USB设备驱动程序可能会为单个端点分配许多urb,也可能对许多不同的端点重用单个的urb。
3.1、struct urb结构体内容
3.2、创建和销毁urb
使用usb_alloc_urb函数创建。
struct usb *usb_alloc_urb(int ios_packets, int mem_flags);
iso_packets:是该urb应该包含的等时数据包的数量。如果不打算创建等时urb,该值应该设置为0。
mem_flags:和传递给用于从内核分配内存的kmalloc函数的标志有相同的类型。
释放urb:
void usb_free_urb(struct urb *urb);
3.2.1、中断urb
3.2.2、批量urb
3.2.3、控制urb
3.2.4、等时urb
3.3、提交urb
3.4、结束urb:结束回调处理例程
3.5、取消urb
四、编写USB驱动程序
驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否已经安装了硬件。
4.1、驱动程序支持哪些设备?
struct usb_device_id结构体提供了一列不同类型的该驱动程序支持的USB设备。
4.2、注册USB驱动程序
所有USB驱动程序都必须创建的主要结构体是struct usb_driver。该结构体必须由USB驱动程序来填写,包括许多回调函数和变量,它们向USB核心代码描述了USB驱动程序。
结构体成员内容如下:
int (*probe) (struct usb_interface *intf, const struct usb_device_id *id)
指向USB驱动程序中的探测函数的指针。当USB核心认为它有一个struct usb_interface可以由该驱动程序处理时,它将调用该函数。USB核心用来作判断的指向struct usb_device_id的指针也被传递给该函数。如果USB驱动程序确认传递给它的struct usb_interface,它应该恰当地初始化设备然后返回0。如果驱动程序不确认该设备,或者发生了错误,它应该返回一个负的错误值。
使用usb_register_driver函数将struct usb_driver注册到USB核心。
usb_deregister_driver注销USB设备驱动。
4.3、探测和断开的细节
当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用;探测函数应该检查传递给它的设备信息,确定驱动程序是否真的适合该设备。当驱动程序因为某种原因不应控制设备时,断开函数被调用,它可以做一些清理的工作。探测和断开回调函数都是在USB集线器内核线程的上下文中被调用的,因此在其中睡眠是合法的。
就在USB设备的断开回调函数被调用之前,所有正在传输到设备的urb都被USB核心取消,因此驱动程序不必要对这些urb显式地调用usb_kill_urb。在USB设备已经被断开之后,如果驱动程序试图通过调用usb_submit_urb来提交一个urb给它,提交将会失败并返回错误值-EPIPE。
4.4、提交和控制urb
当驱动程序有数据要发送到USB设备时(典型地发生在驱动程序的写函数中),必须分配一个urb来把数据传输给设备:
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
在urb被成功地分配之后,还应该创建一个DMA缓冲区来以最高效的方式发送数据到设备,传递给驱动程序的数据应该复制到该缓冲区中:
buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
if (!buf) {
retval = -ENOMEM;
goto error;
}
if (copy_from_user(buf, user_buffer, count)) {
retval = -EFAULT;
goto error;
}
一旦数据从用户空间正确复制到了局部缓冲区中,urb必须在可以被提交给USB核心之前被正确地初始化:
/* 正确地初始化urb */
usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), buf, count, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
现在urb被正确地分配了,数据被正确地复制了,urb被正确地初始化了,它就可以提交给USB核心以传输到设备:
/* 把数据从批量端口发出 */
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval) {
err(%s - failed submitting write urb, error %d", __FUNCTION__, retval);
goto error;
}
当urb回调函数正在运行时另一个urb被提交到设备是很常见的。这对于发送流式数据到设备很有用。不要忘了urb回调函数是运行在中断上下文中的,因此它不应该进行任何内存分配、持有任何信号量或者做任何其他可能导致进程睡眠的事情。当在回调函数内提交一个urb时,如果它在提交过程中需要分配新的内存块的话,使用GFP_ATOMIC标志来告诉USB核心不要睡眠。
4.5、不使用urb的USB传输
4.6、其他USB数据函数
阅读(3529) | 评论(0) | 转发(0) |