您的位置:首页 > 运维架构 > Linux

Linux环境下USB的原理、驱动和配置--本文由CSDN 特别约稿,作者为北京中科红旗软件技术有限公司 嵌入式工程师 梁国军

2012-03-01 10:00 573 查看
 
什么是 USB ?
USB 是英文 Universal Serial Bus 的缩写,意为通用串行总线。 USB 最初是为了替代许多不同的低速总线(包括并行、串行和键盘连接)而设计的,它以单一类型的总线连接 各种不同的类型的设备。 USB 的发展已经超越了这些低速的连接方式,它现在可以支持几乎所有可以连接到 PC 上的设备。最新的 USB 规范修订了理论上 高达 480Mbps 的高速连接。 Linux 内核支持两种主要类型的 USB 驱动程序:宿主系统上的驱动程序和设备上的驱动程序,从宿主的观点来看(一个普通的宿主也就是一个 PC 机),宿主系统的 USB 设备驱动程序控制
插入其中的 USB 设备,而 USB 设备的驱动程序控制该设备如何作为一个 USB 设备和主机通信。

 
USB 的具体构成
在动手写 USB 驱动程序这前,让我们先看看写的 USB 驱动程序在内核中的结构,如下图:

 


USB 驱动程序存在于不同的内核子系统和 USB 硬件控制器之间, USB 核心为 USB 驱动程序提供了一 个用于访问和控制 USB 硬件的接口,而不必考虑系统当前存在的各种不同类型的 USB 硬件控制器。 USB 是一个非常复杂的 设备, linux 内核为我们提供了一个称为 USB 的核心的子系统来处理大部分的复杂性, USB 设备包括配置 (configuration) 、接口( interface )和端点 (endpoint) , USB 设备绑定到接口上,而不是整个 USB 设备。如下图所示:


  
USB 通信最基本的形式是通过端点( USB 端点分中断、批 量、等时、控制四种,每种用途不同), USB端点只能往一个方向传送数据,从主机到设备或者从设备到主机,端点可以看作是单向的管道( pipe )。所以我们可 以这样认为:设备通常具有一个或者更多的配置,配置经常具有一个或者更多的接口,接口通常具有一个或者更多的设置,接口没有或具有一个以上的端点。驱动程 序把驱动程序对象注册到 USB 子系统中,稍后再使用制造商和设备标识来判断是否已经安装了硬件。 USB 核心使用一个列表 (是一个包含制造商 ID 和设备号 ID 的一个结构体)来判断对于一个设备该使用哪一个驱动程序,热插拨脚本使用它来确定当一个特定的设备
插入到系统时该自动装载哪一个驱动程序。

 

上面我们简要说明了驱动程序的基本理论,在写一个设备驱动程序之前,我们还要了解以下两个概念:模块 和设备文件。

 

模块 :是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是可以装载到系统中作为内核的一部分运行,从而可以动态扩充 内核的功能。模块最主要的用处就是用来实现设备驱动程序。Linux 下对于一个硬件的驱动,可以有两种方式:直接加载到内核代码中,启动内核时就会驱动此硬件设备。另 一种就是以模块方式,编译生成一个 .ko 文件 ( 在 2.4 以下内核中是用 .o 作模块文件,我们以 2.6 的内核为准,以下同 ) 。当应用程序需要时再加载到内核空间运行。所以我们所说的一个硬件的驱动程序,通常指的就是一个驱
动模块。

 

设备文件 :对于一个设备,它可以在 /dev 下面存在一个对应的逻辑设备节点,这个节点以文件的形式存在,但它不是普通意义上的文件,它是设备文 件,更确切的说,它是设备节点。这个节点是通过 mknod 命令建立的,其中指定了主设备号和次设备号。主设备号表明了某一类设备,一般对应着确定的驱动程 序;次设备号一般是区分不同属性,例如不同的使用方法,不同的位置,不同的操作。这个设备号是从 /proc/devices 文件中获得的,所 以一般是先有驱动程序在内核中,才有设备节点在目录中。这个设备号(特指主设备号)的主要作用,就是声明设备所使用的驱动程序。驱动程序和设备号是一一对
应的,当你打开一个设备文件时,操作系统就已经知道这个设备所对应的驱动程序。对于一个硬件, Linux 是这样来进行驱动的:首先,我们必 须提供一个 .ko 的驱动模块文件。我们要使用这个驱动程序,首先要加载它,我们可以用 insmod xxx.ko , 这样驱动就会根据自己的类型(字符设备类型或块设备类型,例如鼠标就是字符设备而硬盘就是块设备)向系统注册,注册成功系统会反馈一个主设备号,这个主设 备号就是系统对它的唯一标识。驱动就是根据此主设备号来创建一个一般放置在 /dev目录下的设备文件。在我们要访问此硬件时,就可以对设备文件通过 open 、 read 、 write 、 close 等命令进行。
而驱动就会接收到相应的 read 、 write 操作而根据自己的模块中的相应函数进行操作了。

 

USB 驱动程序如何应用
了解了上述理论后,我们就可以动手写驱动程序,如果你基本功好,而且写过 linux 下的硬件驱动, USB 的硬件驱动和 pci_driver 很 类似,那么写 USB 的驱动就比较简单了,如果你只是大体了解了 linux 的硬件驱动,那也不要紧,因为在 linux 的内核源码中 有一个框架程序可以拿来借用一下,这个框架程序在 /usr/src/~ (你的内核版本,以下同) /drivers/usb 下,文件名为 usb-skeleton.c 。写一个 USB 的驱动程序最基本的要做四件事:驱动程序要支持的设备、注册 USB 驱动程序、探测和断
开、提交和控制 urb ( USB 请求块)(当然也可以不用urb 来传输数据,下文我们会说到)。

 

驱动程序支持的设备: 有一个结构体 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);

对于 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
得到这两个值后把它定义到程序里就可以了。
 
注册 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);
}
 
探测和断开: 当一个设备被安装而 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))
 
如果所有这些探测都通过了,驱动程序就知道它已经发现了正确的端点类型,可以把该端点的相关信息保存 到一个局部结构体中以便稍后用它来和端点进行通信:
 
                     /* 找到一个批量 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;
                     }
 
因为 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 :这个 值只有在端点类型是中断类型时才起作用,它是端点中断请求的间隔时间,以毫秒为单位。
 
提交和控制urb: 当驱动程序有数据要发送到 USB 设备时(大多数情况是在驱动程序的写函数中),要分配一个 urb 来把数据传输给设 备:
 
       /* 创建一个 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 就可以被提交给 USB 核心以传输到设备了:
 
       /* 把数据从批量 OUT 端口发出 */
       retval = usb_submit_urb(urb, GFP_KERNEL);
       if (retval) {
              err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
              goto error;
       }
 
当 urb 被成功传输到 USB 设备之后, urb 回调函数将被 USB 核心调用,在我们的例子中,我们初始化 urb ,使它指向 skel_write_bulk_callback 函数,以下就是该函数:
 
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
       struct usb_skel *dev;
 
       dev = (struct usb_skel *)urb->context;
 
       if (urb->status &&
           !(urb->status == -ENOENT ||
             urb->status == -ECONNRESET ||
              urb->status == -ESHUTDOWN)) {
              dbg("%s - nonzero write bulk status received: %d",
                  __FUNCTION__, urb->status);
       }
 
       /* 释放已分配的缓冲区 */
       usb_buffer_free(urb->dev, urb->transfer_buffer_length,
                     urb->transfer_buffer, urb->transfer_dma);
}
 
有时候 USB 驱动程序只是要发送或者接收一些简单的数据,驱动程序也可以不用 urb 来进行数据的传输, 这是里涉及到两个简单的接口函数: usb_bulk_msg 和 usb_control_msg ,在这个 USB 框架程序里读操作就是这样的一个应用:
 
/* 进行阻塞的批量读以 从设备获取数据 */
       retval = usb_bulk_msg(dev->udev,
                           usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                           dev->bulk_in_buffer,
                           min(dev->bulk_in_size, count),
                           &count, HZ*10);
 
       /* 如果读成功,复制到用户空间 */
       if (!retval) {
              if (copy_to_user(buffer, dev->bulk_in_buffer, count))
                     retval = -EFAULT;
              else
                     retval = count;
       }
usb_bulk_msg 接 口函数的定义如下:
int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,
void *data,int len,int *actual_length,int timeout);
 
其参数为:
 
struct usb_device *usb_dev :指向批量消息所发送的目标 USB 设备指针。
unsigned int pipe :批量消息所发送目标 USB 设备的特定端点,此值是调用 usb_sndbulkpipe 或者usb_rcvbulkpipe 来创建的。
 
void *data :如 果是一个 OUT 端点,它是指向即将发送到设备的数据的指针。如果是 IN 端点,它是指向从设备读取的数据应该存放 的位置的指针。
int len : data 参数所指缓冲区 的大小。
 
int *actual_length :指向保存实际传输字节数的位置的指针,至于是传输到设备还是从设备接收取决于端点的方向。
 
int timeout : 以 Jiffies 为单位的等待的超时时间,如果该值为 0 ,该函数一直等待消息的结束。
如果该接口函数调用成功,返回值为 0 ,否则返回一个负的错误值。
 
usb_control_msg 接口函数定义如下:
 
int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8    request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)
除了允许驱动程序发送和接收 USB 控制消息之外, usb_control_msg 函数的运作和 usb_bulk_msg 函数类似,其参数和 usb_bulk_msg 的参数有几个重要区别:
struct usb_device *dev :指向控制消息所发送的目标 USB 设备的指针。
unsigned int pipe :控制消息所发送的目标 USB 设备的特定端点,该值是调用 usb_sndctrlpipe 或usb_rcvctrlpipe 来创建的。
__u8 request : 控制消息的 USB 请求值。
__u8 requesttype :控制消息的 USB 请求类型值。
__u16 value : 控制消息的 USB 消息值。
__u16 index : 控制消息的 USB 消息索引值。
void *data :如 果是一个 OUT 端点,它是指身即将发送到设备的数据的指针。如果是一个 IN 端点,它是指向从设备读取的数据应该存 放的位置的指针。
__u16 size : data 参数所指缓冲区 的大小。
int timeout : 以 Jiffies 为单位的应该等待的超时时间,如果为 0 ,该函数将一直等待消息结束。
 
如果该接口函数调用成功,返回传输到设备或者从设备读取的字节数;如果不成功它返回一个负的错误值。
 
这两个接口函数都不能在一个中断上下文中或者持有自旋锁的情况下调用,同样,该函数也不能被任何其它 函数取消,使用时要谨慎。
 
我们要给未知的 USB 设备写驱动程序,只需要把这个框架程序稍做修改就可以用了,前面我们已经说过要修改制造商和产品的 ID 号,把 0xfff0 这两个值改 为未知 USB 的 ID 号。
  #define USB_SKEL_VENDOR_ID      0xfff0
     #define USB_SKEL_PRODUCT_ID     0xfff0
还有就是在探测函数中把需要探测的接口端点类型写好,在这个框架程序中只探测了批量(USB_ENDPOINT_XFER_BULK ) IN 和 OUT 端点,可以在此处使用掩码(USB_ENDPOINT_XFERTYPE_MASK )让其探测其它的端点类型,驱动程序会对 USB 设备的每一个接口进行一次探测,当探测成功后,驱动程序就被绑定到这个接口上。再有就是 urb 的初始化问题,如 果你只写简单的USB 驱动,这块不用多加考虑,框架程序里的东西已经够用了,这里我们简单介绍三个初始化 urb 的辅助函数:
 
usb_fill_int_urb : 它的函数原型是这样的:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buff,
int buffer_length,usb_complete_t complete,
void *context,int interval);
这个函数用来正确的初始化即将被发送到 USB 设备的中断端点的 urb 。
usb_fill_bulk_urb : 它的函数原型是这样的:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete)
这个函数是用来正确的初始化批量 urb 端点的。
usb_fill_control_urb : 它的函数原型是这样的:
void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);
这个函数是用来正确初始化控制 urb 端点的。
还有一个初始化等时 urb 的,它现在还没有初 始化函数,所以它们在被提交到 USB 核心前,必须在驱动程序中手工地进行初始化,可以参考内核源代码树下的 /usr/src/~/drivers/usb/media 下的 konicawc.c 文件。
 
驱动模块的编译、配置和使用
现在我们的驱动程序已经大体写好了,然后在 linux 下把它编译成 模块就可以把驱动模块插入到内核中运行了,编译的 Makefile 文件可以这样来写:
 
ifneq ($(KERNELRELEASE),)
       obj-m := xxx.o
else
       KERNELDIR ?= /lib/modules/$(shell uname -r)/build
       PWD := $(shell pwd)
default:
       $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
       rm -rf *.mod.* *.o *.ko .*.ko.* .tmp* .*.mod.o.* .*.o.*
其中 xxx 是源文件的文件名,在 linux 下直接执行 make 就可以生成驱动模块( xxx.ko )了。生成驱动模块后使用 insmod xxx.ko 就可以插入到内核中运行了,用 lsmod 可以看到你插入 到内核中的模块,也可以从系统中用命令 rmmod xxx 把模块卸载掉;如果把编译出来的驱动模块拷贝到 /lib/modules/~/kernel/drivers/usb/ 下,然后depmod 一下,那么你在插入 USB 设备的时候,系统就会自动为你加载驱动模块的;当然这个得有 hotplug 的支持;加
载驱动模块成功后就会在 /dev/ 下生成设备文件了,如果用命令 cat /proc/bus/usb/devices ,我们可以看到驱动程序已经绑定到接口上了:
 
T:  Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#=  2 Spd=12  MxCh= 0
D:  Ver= 1.10 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=1234 ProdID=2345 Rev= 1.10
C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr=  0mA
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=test_usb_driver /* 我们的驱动 */
E:  Ad=01(O) Atr=02(Bulk) MxPS=  64 Ivl=0ms
E:  Ad=82(I) Atr=02(Bulk) MxPS=  64 Ivl=0ms
 
此框架程序生成的是 skel0 (可以自由修改)的设备文件,现在就可以对这个设备文件进行打开、读写、关闭等的操作了。
 
面对层出不穷的新的 USB 设备,必须有人不断编写新的驱动程序以 便让这些设备能够在 linux 下正常的工作,从这个意义上讲,驱动程序的编写本身就是一件非常有意义的工作,本文只是起到一个抛砖 引玉的作用,帮助那些有志于写驱动程序的开发人员进一步了解 USB 驱动程序的设计思路,从而吸引更多的人加入到这个队伍中来。linux 不仅为我们提供 了一个顶级质量的操作系统,而且也为我们提供了参与到其未来开发过程的机会,我们完全可以从中得到无尽的快乐!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息