USB驱动骨架usb_skeleton

在/drivers/usb/下有一个usb_skeleton.c文件是一个最简单的USB驱动程序的框架,写USB驱动程序可以以此为模版。

写一个USB的驱动程序最基本的要做四件事:驱动程序要支持的设备、注册USB驱动程序、探测和断开、提交和控制urb(USB请求块)(当然也可以不用urb来传输数据,下文我们会说到)。

1)驱动程序支持的设备:有一个结构体struct usb_device_id,这个结构体提供了一列不同类型的该驱动程序支持的USB设备,对于一个只控制一个特定的USB设备的驱动程序来说,struct usb_device_id表被定义为:

/* 驱动程序支持的设备列表 */

static struct usb_device_id skel_table [] = {

       { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },

       { }                               /* 终止入口 */

};

MODULE_DEVICE_TABLE (usb, skel_table);

MODULE_DEVICE_TABLE用来注册所支持的设备,platform_device_id结构体需要被导出到用户空间,使热插拔设备和模块装载系统知道什么模块对应什么设备。在稍后的内核构建过程中,depmod程序在所有的模块中搜索符号__mod_xxx_device_table,如果找到了该符号,他把数据从该模块中抽出,添加到文件/lib/module/KERNEL_VERSION/module.xxxmap中,当depmod结束后,内核模块支持的所有的xxx设备连同他们的模块名都在该文件中被列出。当内核告知一个新的xxx设备已经被发现时,热插拔系统使用module.xxxmap文件来寻找要加载的适当的驱动程序。module.xxxmap里面包含的是各个核心模块和它所支持的具体硬件设备的对应关系。(参考:http://www.360doc.com/content/11/1007/16/7473909_154074997.shtml)


关于MODULE_DEVICE_TABLE和module_platform_driver的理解见最后的介绍。


对于PC驱动程序,MODULE_DEVICE_TABLE是必需的,而且usb必需为该宏的第一个值,而USB_SKEL_VENDOR_ID和USB_SKEL_PRODUCT_ID就是这个特殊设备的制造商和产品的ID了,我们在程序中把定义的值改为我们这款USB的,如:

/* 定义制造商和产品的ID号 */

#define USB_SKEL_VENDOR_ID       0x1234

#define USB_SKEL_PRODUCT_ID     0x2345

这两个值可以通过命令lsusb,当然你得先把USB设备先插到主机上了。或者查看厂商的USB设备的手册也能得到,在我机器上运行lsusb是这样的结果:

Bus 004 Device 001: ID 0000:0000

Bus 003 Device 002: ID 1234:2345 Abc Corp.

Bus 002 Device 001: ID 0000:0000

Bus 001 Device 001: ID 0000:0000

得到这两个值后把它定义到程序里就可以了。


2)注册USB驱动程序:所有的USB驱动程序都必须创建的结构体是struct usb_driver。这个结构体必须由USB驱动程序来填写,包括许多回调函数和变量,它们向USB核心代码描述USB驱动程序。创建一个有效的struct usb_driver结构体,只须要初始化五个字段就可以了,在框架程序中是这样的:

static struct usb_driver skel_driver = {

.owner = THIS_MODULE,

.name =          "skeleton",

.probe = skel_probe,

.disconnect = skel_disconnect,

.id_table =      skel_table,

};

struct module *owner :指向该驱动程序的模块所有者的指针。USB核心使用它来正确地对该USB驱动程序进行引用计数,使它不会在不合适的时刻被卸载掉,这个变量应该被设置为THIS_MODULE宏。

const char *name:指向驱动程序名字的指针,在内核的所有USB驱动程序中它必须是唯一的,通常被设置为和驱动程序模块名相同的名字。

int (*probe) (struct usb_interface *intf,const struct usb_device_id *id):这个是指向USB驱动程序中的探测函数的指针。当USB核心认为它有一个接口(usb_interface)可以由该驱动程序处理时,这个函数被调用。

void (disconnect)(struct usb_interface *intf):指向USB驱动程序中的断开函数的指针,当一个USB接口(usb_interface)被从系统中移除或者驱动程序正在从USB核心中卸载时,USB核心将调用这个函数。

const struct usb_device_id *id_table:指向ID设备表的指针,这个表包含了一列该驱动程序可以支持的USB设备,如果没有设置这个变量,USB驱动程序中的探测回调函数就不会被调用。

在这个结构体中还有其它的几个回调函数不是很常用,这里就不一一说明了。以struct usb_driver 指针为参数的usb_register_driver函数调用把struct usb_driver注册到USB核心。一般是在USB驱动程序的模块初始化代码中完成这个工作的:

static int __init usb_skel_init(void)

{

       int result;

       /* 驱动程序注册到USB子系统中*/

       result = usb_register(&skel_driver);

       if (result)

              err("usb_register failed. Error number %d", result);

       return result;

}

当USB驱动程序将要被卸开时,需要把struct usb_driver从内核中注销。通过调用usb_deregister_driver来完成这个工作,当调用发生时,当前绑定到该驱动程序上的任何USB接口都被断开,断开函数将被调用:

static void __exit usb_skel_exit(void)

{

       /* 从子系统注销驱动程序 */

       usb_deregister(&skel_driver);

}


3)探测和断开:当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用,探测函数检查传递给它的设备信息,确定驱动程序是否真的适合该设备。当驱动程序因为某种原因不应该控制设备时,断开函数被调用,它可以做一些清理工作。探测回调函数中,USB驱动程序初始化任何可能用于控制USB设备的局部结构体,它还把所需的任何设备相关信息保存到一个局部结构体中,下面是探测函数的部分源码,我们加以分析。

/* 设置端点信息 */

       /* 只使用第一个批量IN和批量OUT端点 */

       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 &&

                  (endpoint->bEndpointAddress & USB_DIR_IN) &&

                  ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

                                   == USB_ENDPOINT_XFER_BULK)) {

                     /* 找到一个批量IN端点 */

                     buffer_size = 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;

                     }

              }

              if (!dev->bulk_out_endpointAddr &&

                  !(endpoint->bEndpointAddress & USB_DIR_IN) &&

                  ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

                                   == USB_ENDPOINT_XFER_BULK)) {

                     /* 找到一个批量OUT端点 */

                     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;

       }

在探测函数里,这个循环首先访问该接口中存在的每一个端点,给该端点一个局部指针以便以后访问:

for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {

endpoint = &iface_desc->endpoint[i].desc;

在一轮探测过后,我们就有了一个端点,在还没有发现批量IN类型的端点时,探测该端点方向是否为IN,这可以通过检查USB_DIR_IN是否包含在bEndpointAddress端点变量有确定,如果是的话,我们在探测该端点类型是否为批量,先用USB_ENDPOINT_XFERTYPE_MASK位掩码来取bmAttributes变量的值,然后探测它是否和USB_ENDPOINT_XFER_BULK值匹配:

if (!dev->bulk_out_endpointAddr &&

(endpoint->bEndpointAddress & USB_DIR_IN) &&

((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

== USB_ENDPOINT_XFER_BULK))

如果所有这些探测都通过了,驱动程序就知道它已经发现了正确的端点类型,可以把该端点的相关信息保存到一个局部结构体中以便稍后用它来和端点进行通信:

buffer_size = 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;

            }

因为USB驱动程序要在设备的生命周期的稍后时间获取和接口相关联的局部数据结构体,所以调用了usb_set_intfdata函数,把它保存到struct usb_interface结构体中以便后面的访问。

/* 把数据指针保存到这个接口设备中 */

usb_set_intfdata(interface, dev);

我们以后调用usb_set_intfdata函数来获取数据。当这一切都完成后,USB驱动程序必须在探测函数中调用usb_register_dev函数来把该设备注册到USB核心里:

/* 注册设备到USB核心 */

       retval = usb_register_dev(interface, &skel_class);

       if (retval) {

              /* 有些情况下是不允许注册驱动程序的 */

              err("Not able to get a minor for this device.");

              usb_set_intfdata(interface, NULL);

              goto error;

       }

当一个USB设备被断开时,和该设备相关联的所有资源都应该被尽可能的清理掉,在此时,如果已在探测函数中调用了注册函数来为该USB设备分配了一个次设备号,必须调用usb_deregister_dev函数来把次设备号交还给USB核心。在断开函数中,从接口获取之前调用usb_set_intfdata设置的任何数据也是很重要的。然后设置struct usb_interface结构体中的数据指针为NULL,以防任何不适当的对该数据的错误访问。

在探测函数中会对每一个接口进行一次探测,所以我们在写USB驱动程序的时候,只要做好第一个端点,其它的端点就会自动完成探测。在探测函数中我们要注意的是在内核中用结构体struct usb_host_endpoint来描述USB端点,这个结构体在另一个名为struct usb_endpoint_descriptor的结构体中包含了真正的端点信息,struct usb_endpoint_descriptor结构体包含了所有的USB特定的数据,该结构体中我们要关心的几个字段是:

bEndpointAddress:这个是特定的USB地址,可以结合USB_DIR_IN和USB_DIR_OUT来使用,以确定该端点的数据是传向设备还是主机。

bmAttributes:这个是端点的类型,这个值可以结合位掩码USB_ENDPOINT_XFERTYPE_MASK来使用,以确定此端点的类型是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)、USB_ENDPOINT_XFER_INT的哪一种。

wMaxPacketSize:这个是端点一次可以处理的最大字节数,驱动程序可以发送数量大于此值的数据到端点,在实际传输中,数据量如果大于此值会被分割。

bInterval:这个值只有在端点类型是中断类型时才起作用,它是端点中断请求的间隔时间,以毫秒为单位。


原文转自:http://hi.baidu.com/fearlesswang/item/cae983fd8ea87516ff358246



MODULE_DEVICE_ATBLE和module_platform_driver理解:


module_platform_driver()宏的作用就是定义指定名称的平台设备驱动注册函数和平台设备驱动注销函数,并且在函数体内分别通过platform_driver_register()函数和platform_driver_unregister()函数注册和注销该平台设备驱动。


MODULE_DEVICE_TABLE的理解是:depmod程序会将模块名和这个模块所支持的设备列表写到/lib/module/KERNEL_VERSION/module.xxxmap文件中,当热插拔系统检测到设备,会在这个文件中找到设备对应的模块,然后加载对应的驱动,module_platform_driver就是来加载这个模块的驱动。




你可能感兴趣的:(USB驱动骨架usb_skeleton)