深入,并且广泛
-沉默犀牛
有了第一篇文章的基础,我们这篇文章来看一下USB设备驱动的源码。与其他的Driver一样,USB的driver也表现为一个结构体:struct usb_driver
在编写新的USB设备驱动时,主要应该完成的工作是probe()和disconnect()函数,它们分别在Device被插入和拔出的时候调用,用于初始化和释放软硬件资源。usb_driver结构体中的id_table成员描述了这个USB驱动所支持的USB设备列表,它指向一个usb_device_id数组,实例如下:
static const struct usb_device_id skel_table[] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, skel_table);
static struct usb_driver skel_driver = {
.name = "skeleton",
.probe = skel_probe, //probe
.disconnect = skel_disconnect, //disconnent
.suspend = skel_suspend,
.resume = skel_resume,
.pre_reset = skel_pre_reset,
.post_reset = skel_post_reset,
.id_table = skel_table, //id_table
.supports_autosuspend = 1,
};
USB_DEVICE这个宏是可以根据Vendor ID和Product ID生成一个usb_device_id结构体的实例化。
当USB core检测到某个Device的属性和某个Drivre的usb_device_id结构体所携带的信息一致的时候,这个Driver的probe()函数就被执行(如果这个USB驱动是个模块的话,相关的.ko还应被Linux自动加载)。拔掉Device或者卸载Drivre模块后,USB核心就执行disconnect()函数来响应这个动作。
usb_driver结构体中的函数是USB设备驱动中与USB相关的部分,而USB只是一个总线,USB设备驱动真正的主体工作仍然是USB设备
本身所属类型的驱动,比如字符设备、tty设备、块设备、输入设备等。
因此USB设备驱动包含其作为
1.总线上挂接设备的驱动
2.本身所属设备类型的驱动两部分
尽管USB本身所属设备驱动的结构与其挂不挂在USB总线上没什么关系,但是据此在访问方式上却有很大的变化,例如,对于USB的TP而言,尽管仍然是中断服务函数、固件升级这些函数,但是在这些函数中,贯穿始终的是称为URB的USB请求块
举个书中的例子:
我们把整个USB构架(Host侧)看做一颗大叔,HostController是树根,树叶比作具体的USB设备,树干和树枝就是USB总线树叶本身与树枝是通过usb_driver结构体连接,而树叶本身的驱动(读写、控制)则需要通过树叶设备本身所属类设备驱动来完成。树根和树叶之间的“通信”依靠在树干和树枝中“流淌”的URB完成
由此可见,usb_driver本身只是有找到USB设备,管理USB设备连接和断开的作用,也就是说,它是公司入口处的“打卡机”,可以获得员工(USB设备)的上下班情况。树叶和员工一样,可以是研发工程师,也可以是销售,而作为USB设备的树叶可以是字符树叶、网络树叶或块树叶,因此必须实现相应设备类的驱动
根据上面的描述,我们知道了URB承载着USB设备和Host controller通信的核心数据,使用struct urb来描述:
仅注释了重要成员
struct urb {
/* private: usb core and host controller only fields in the urb */
struct kref kref;
void *hcpriv;
atomic_t use_count;
atomic_t reject;
int unlinked;
/* public: documented fields in the urb that can be used by drivers */
struct list_head urb_list;
struct list_head anchor_list;
struct usb_anchor *anchor;
struct usb_device *dev; //urb所要发送的目标 struct usb_device指针。该变量在urb可以被发送到
USB core之前,必须由USB驱动程序初始化
struct usb_host_endpoint *ep;
unsigned int pipe; //urb所要发送的特定目标struct usb_device的端点信息。该变量在urb可以
被发送到USB core之前,必须由USB驱动程序初始化
unsigned int stream_id;
int status; //当urb结束之后,或者正在被USB core处理时,该变量被设置为urb的当前状态
这是为了防止当urb被USB core处理时竞态的发生。
unsigned int transfer_flags; //该变量可以设为许多不同的位值,取决与USB驱动程序对urb的具体操作
详情见LDD3 P333
void *transfer_buffer; //指向用于发送数据到设备(OUT urb)或者从设备接受数据(IN urb)的缓冲区
的指针,为了使Host controller可以正确的访问该缓冲区,必须使用kmalloc
来创建它,而不是在栈中或者静态内存中。对于控制端点,该缓冲区用于传输数
据的中转
dma_addr_t transfer_dma; // 用于以DMA方式传输数据到USB设备的缓冲区
struct scatterlist *sg;
int num_mapped_sgs;
int num_sgs;
u32 transfer_buffer_length; //transfer_buffer或者transfer_dma变量所指向的缓冲区的大小(因为一个
urb 只能使用其中的一个)。若值为0,两个传输缓冲区都没有被USB core使用
u32 actual_length; //当urb结束之后,该变量被设置为urb所发出的数据(OUT urb)或者urb所接收
的数据(IN urb)的实际长度。对于IN urb,必须使用该变量而不是
transfer_buffer_length变量,因为所接收的数据可能小于整个缓冲区的尺寸
unsigned char *setup_packet; //指向控制urb的设置数据包的指针。它在传输缓冲区中的数据之前被传送。该变量
只对控制urb有效
dma_addr_t setup_dma; //控制urb用于设置数据包的DMA缓冲区。它在普通传输 缓冲区中的数据之前被传送
该变量只对控制urb有效
int start_frame; //设置或者返回初始的帧数量,用于等时传输
int number_of_packets; //仅对等时urb有效,指定该urb所助理的等时传输缓冲区的数量。对于等时urb,在
urb被发送到USB core之前,USB驱动程序必须设置该值
int interval; //urb被轮询的时间间隔。仅对中断或者等时urb有效
int error_count; //有USB core设置,仅用于等时urb结束之后。它表示报告了任何一种类型错误的等
时传输的数量
void *context; //指向一个可以被USB驱动程序设置的数据块。它可以在结束处理程序中,当urb被返
回到驱动程序时使用。
usb_complete_t complete; //指向一个结束处理程序的指针,让urb被完全传输 或者发生错误时,USB core将调
用该函数。在该函数中,USB驱动程序可以检查urb,释放它,或者把它重新提交到
另一个传输中去
struct usb_iso_packet_descriptor iso_frame_desc[0];
//仅对等时urb有效。
};
一个典型的urb生命周期如下:
1.由USB设备驱动程序创建
2.分配给一个特定的USB设备的特定端点
3.由USB设备驱动程序递交给USB core
4.由USB core递交到特定设备的特定USB Host controller驱动程序
5.由USB Host controller驱动程序处理,它从设备进行USB传送
6.当urb结束之后,USB Host controller驱动程序通知USB设备驱动程序
urb可以在任何时候被递交该urb的驱动程序取消掉,或者被USB核心取消,如果该设备已从系统中移除。urb被动态的创建,它包含一个内部引用计数,使得它们可以在最后一个使用者释放它们时自动地销毁
我们刚刚分析了USB驱动的整体结构,也说明了URB的用途和处理流程,接下来我们就找一个实际的例子来看看以下是kernel/drivers/input/touchscreen/usbtouchscreen.c文件中的probe函数
(只保留了有关input 和 urb处理的部分)
static int usbtouch_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usbtouch_usb *usbtouch;
struct input_dev *input_dev;
struct usb_endpoint_descriptor *endpoint;
struct usb_device *udev = interface_to_usbdev(intf);
struct usbtouch_device_info *type;
int err = -ENOMEM;
...
input_dev = input_allocate_device(); //input
...
usbtouch->type = type;
if (!type->process_pkt)
type->process_pkt = usbtouch_process_pkt; //之后会在urb完成处理函数中调用
...
usbtouch->irq = usb_alloc_urb(0, GFP_KERNEL); //urb的创建,(urb流程的第一步)
...
input_dev->name = usbtouch->name;
input_dev->phys = usbtouch->phys;
usb_to_input_id(udev, &input_dev->id);
input_dev->dev.parent = &intf->dev;
input_set_drvdata(input_dev, usbtouch);
input_dev->open = usbtouch_open;
input_dev->close = usbtouch_close;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, type->min_xc, type->max_xc, 0, 0);
input_set_abs_params(input_dev, ABS_Y, type->min_yc, type->max_yc, 0, 0);
if (type->max_press)
input_set_abs_params(input_dev, ABS_PRESSURE, type->min_press,
type->max_press, 0, 0)
...
//中间这一部分是关于input的设置
if (usb_endpoint_type(endpoint) == USB_ENDPOINT_XFER_INT)
usb_fill_int_urb(usbtouch->irq, udev, //分配urb给特定USB的特定端点(urb处理流程第二步)
usb_rcvintpipe(udev, endpoint->bEndpointAddress),
usbtouch->data, usbtouch->data_size,
usbtouch_irq, usbtouch, endpoint->bInterval); //usbtouch_irq就是urb处理完成函数
else
usb_fill_bulk_urb(usbtouch->irq, udev,
usb_rcvbulkpipe(udev, endpoint->bEndpointAddress),
usbtouch->data, usbtouch->data_size,
usbtouch_irq, usbtouch);
...
err = input_register_device(usbtouch->input); //注册到input子系统
...
if (usbtouch->type->irq_always) { //this can‘t fail 一定会进这个if
/* this can't fail */
usb_autopm_get_interface(intf);
err = usb_submit_urb(usbtouch->irq, GFP_KERNEL); //递交给USB core (urb处理流程第四步)
...
}
return 0;
...
}
上述urb处理流程的第4、 5步由USB core来处理,等到urb处理完,会调用urb处理完成函数,如下:
static void usbtouch_irq(struct urb *urb)
{
struct usbtouch_usb *usbtouch = urb->context;
struct device *dev = &usbtouch->interface->dev;
int retval;
switch (urb->status) { //在完成处理函数中,一般都会判断一下urb的状态
case 0:
/* success */
break;
case -ETIME:
/* this urb is timing out */
dev_dbg(dev,
"%s - urb timed out - was the device unplugged?\n",
__func__);
return;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
case -EPIPE:
/* this urb is terminated, clean up */
dev_dbg(dev, "%s - urb shutting down with status: %d\n",
__func__, urb->status);
return;
default:
dev_dbg(dev, "%s - nonzero urb status received: %d\n",
__func__, urb->status);
goto exit;
}
usbtouch->type->process_pkt(usbtouch, usbtouch->data, urb->actual_length);
//还记得上面说的拿个(要被urb完成处理函数调用的)函数吗?
...
}
以下就是usbtouch->type->process_pkt所指向的函数
static void usbtouch_process_pkt(struct usbtouch_usb *usbtouch,
unsigned char *pkt, int len)
{ //明显就是上报点的函数
struct usbtouch_device_info *type = usbtouch->type;
if (!type->read_data(usbtouch, pkt))
return;
input_report_key(usbtouch->input, BTN_TOUCH, usbtouch->touch);
if (swap_xy) {
input_report_abs(usbtouch->input, ABS_X, usbtouch->y);
input_report_abs(usbtouch->input, ABS_Y, usbtouch->x);
} else {
input_report_abs(usbtouch->input, ABS_X, usbtouch->x);
input_report_abs(usbtouch->input, ABS_Y, usbtouch->y);
}
if (type->max_press)
input_report_abs(usbtouch->input, ABS_PRESSURE, usbtouch->press);
input_sync(usbtouch->input);
}
这样我们就用这个usbtouchscreen.c文件中的代码理清了USB设备驱动的用法,一般的挂载在i2c总线上的TP是通过触发中断来执行中断处理函数,在中断处理函数的下半部完成报点,而挂载在USB总线后,是通过urb处理完成,来出发urb完成处理函数,在urb完成处理函数中,完成报点。