• Linux下的USB总线驱动(一)


    版权所有,转载请说明转自 http://my.csdn.net/weiqing1981127

     

    一.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系统上以及带有SiSALi芯片组的PC主办上的USB芯片提供支持。UHCI驱动程序多用来为大多数其他PC主板(包括IntelVia)上的USB芯片提供支持。ENCI兼容OHCIUHCIUHCI的硬件线路比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_deviceusb_host_configusb_interfaceusb_host_endpoint表示。

     

    端点的传输方式包括控制传输、中断传输、批量传输、等时传输。

    控制传输主要用于向设备发送配置信息、获取设备信息、发送命令道设备,或者获取设备的状态报告。控制传输一般发送的数据量较小,当USB设备插入时,USB核心使用端点0对设备进行配置,另外,端口0与其他端点不一样,端点0可以双向传输。

     

    中断传输就是中断端点以一个固定的速度来传输较少的数据,USB键盘和鼠标就是使用这个传输方式。这里说的中断和硬件上下文中的中断不一样,它不是设备主动发送一个中断请求,而是主机控制器在保证不大于某个时间间隔内安排一次传输。中断传输对时间要求比较严格,所以可以用中断传输来不断地检测某个设备,当条件满足后再使用批量传输传输大量的数据。

     

    批量传输通常用在数据量大、对数据实时性要求不高的场合,例如USB打印机、扫描仪、大容量存储设备、U盘等。

     

    等时传输同样可以传输大批量数据,但是对数据是否到达没有保证,它对实时性的要求很高,例如音频、视频等设备。

     

    5.      USBURB请求块

    USB请求块(USB request blockurb)是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线程。

    Step2khubd识别出电流变化的那个端口

    Step3khubd通过给控制端点0发送控制URB来实现从1-127中选出一个数作为插入设备的批量端点

    Step4khubd利用端口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_driverprobe函数中根据usb_interface的成员寻找第一个批量输入和输出的端点,将端点地址、缓冲区等信息存入USB骨架程序定义的usb_skel结构体中,并将usb_skel通过usb_set_intfdata传为USB接口的私有数据,最后注册USB设备。

    我们来看看这个USB骨架程序定义的usb_skel结构体

    struct usb_skel {

           struct usb_device   *udev;                   //该设备的usb_device指针

           struct usb_interface       *interface;             //该设备的usb_interface指针

           struct semaphore    limit_sem;              //限制进程写的数量

           struct usb_anchor  submitted;            

           struct urb              *bulk_in_urb;        

           unsigned char           *bulk_in_buffer;   //接收数据缓冲区

           size_t                    bulk_in_size;            //接收数据大小

           size_t                    bulk_in_filled;       

           size_t                    bulk_in_copied;            

           __u8                     bulk_in_endpointAddr;       //批量输入端点地址

           __u8                     bulk_out_endpointAddr;    //批量输出端点地址

           int                  errors;           

           int                  open_count;          

           bool               ongoing_read;       

           bool               processed_urb;     

           spinlock_t              err_lock;       

           struct kref             kref;

           struct mutex          io_mutex;             

           struct completion   bulk_in_completion;       //完成量

    };

    好了看完了probe,我们再看看disconnect函数

    static void skel_disconnect(struct usb_interface *interface)

    {

           struct usb_skel *dev;

           int minor = interface->minor;  //获得接口的次设备号

           dev = usb_get_intfdata(interface);    //获取接口的私有数据

           usb_set_intfdata(interface, NULL);    //设置接口的私有数据为空

           usb_deregister_dev(interface, &skel_class);   //注销USB设备

           mutex_lock(&dev->io_mutex);

           dev->interface = NULL;          

           mutex_unlock(&dev->io_mutex);

           usb_kill_anchored_urbs(&dev->submitted);

           kref_put(&dev->kref, skel_delete);

           dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor);

    }

     

    我们在skel_probe中最后执行了usb_register_dev(interface, &skel_class)来注册了一个USB设备,我们看看skel_class的定义

    static struct usb_class_driver skel_class = {

           .name =          "skel%d",

           .fops =           &skel_fops,

           .minor_base = USB_SKEL_MINOR_BASE,

    };

    static const struct file_operations skel_fops = {

           .owner = THIS_MODULE,

           .read =           skel_read,

           .write =   skel_write,

           .open =          skel_open,

           .release = skel_release,

           .flush =   skel_flush,

    };

    根据上面代码我们知道,其实我们在probe中注册USB设备的时候使用的skel_class是一个包含file_operations的结构体,而这个结构体正是字符设备文件操作结构体。

    我们先来看看这个file_operationsopen函数的实现

    static int skel_open(struct inode *inode, struct file *file)

    {

           struct usb_skel *dev;

           struct usb_interface *interface;

           int subminor;

           int retval = 0;

           subminor = iminor(inode);   //获得次设备号

    //根据usb_driver和次设备号获取设备的接口

           interface = usb_find_interface(&skel_driver, subminor); 

           if (!interface) {

                  err("%s - error, can't find device for minor %d",

                       __func__, subminor);

                  retval = -ENODEV;

                  goto exit;

           }

           dev = usb_get_intfdata(interface);      //获取接口的私有数据usb_skel

           if (!dev) {

                  retval = -ENODEV;

                  goto exit;

           }

           kref_get(&dev->kref);

           mutex_lock(&dev->io_mutex);

           if (!dev->open_count++) {

                  retval = usb_autopm_get_interface(interface);

                         if (retval) {

                                dev->open_count--;

                                mutex_unlock(&dev->io_mutex);

                                kref_put(&dev->kref, skel_delete);

                                goto exit;

                         }

           }

           file->private_data = dev;            //usb_skel设置为文件的私有数据

           mutex_unlock(&dev->io_mutex);

    exit:

           return retval;

    }

    这个open函数实现非常简单,它根据usb_driver和次设备号通过usb_find_interface获取USB接口,然后通过usb_get_intfdata获得接口的私有数据并赋值给文件。

    好了,我们看看write函数,在write函数中,我们进行了urb的分配、初始化和提交的操作

    static ssize_t skel_write(struct file *file, const char *user_buffer,

                           size_t count, loff_t *ppos)

    {

           struct usb_skel *dev;

           int retval = 0;

           struct urb *urb = NULL;

           char *buf = NULL;

           size_t writesize = min(count, (size_t)MAX_TRANSFER);   //待写数据大小

           dev = (struct usb_skel *)file->private_data;    //获取文件的私有数据

           if (count == 0)

                  goto exit;

           if (!file->f_flags & O_NONBLOCK) {       //如果文件采用非阻塞方式

                  if (down_interruptible(&dev->limit_sem)) {  //获取限制读的次数的信号量

                         retval = -ERESTARTSYS;

                         goto exit;

                  }

           } else {

                  if (down_trylock(&dev->limit_sem)) {

                         retval = -EAGAIN;

                         goto exit;

                  }

           }

           spin_lock_irq(&dev->err_lock);    //关中断

           retval = dev->errors;

           if (retval < 0) {

                  dev->errors = 0;

                  retval = (retval == -EPIPE) ? retval : -EIO;

           }

           spin_unlock_irq(&dev->err_lock);  //开中断

           if (retval < 0)

                  goto error;

           urb = usb_alloc_urb(0, GFP_KERNEL);  //分配urb

           if (!urb) {

                  retval = -ENOMEM;

                  goto error;

           }

           buf = usb_buffer_alloc(dev->udev, writesize, GFP_KERNEL,

                                &urb->transfer_dma);  //分配写缓冲区

           if (!buf) {

                  retval = -ENOMEM;

                  goto error;

           }

           if (copy_from_user(buf, user_buffer, writesize)) {  //将用户空间数据拷贝到缓冲区

                  retval = -EFAULT;

                  goto error;

           }

           mutex_lock(&dev->io_mutex);

           if (!dev->interface) {            /* disconnect() was called */

                  mutex_unlock(&dev->io_mutex);

                  retval = -ENODEV;

                  goto error;

           }

           usb_fill_bulk_urb(urb, dev->udev,

                           usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),

                           buf, writesize, skel_write_bulk_callback, dev);  //填充urb

           urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;  //urb->transfer_dma有效  usb_anchor_urb(urb, &dev->submitted);

           retval = usb_submit_urb(urb, GFP_KERNEL);  //提交urb

           mutex_unlock(&dev->io_mutex);

           if (retval) {

                  err("%s - failed submitting write urb, error %d", __func__,

                      retval);

                  goto error_unanchor;

           }

           usb_free_urb(urb);

           return writesize;

    error_unanchor:

           usb_unanchor_urb(urb);

    error:

           if (urb) {

                  usb_buffer_free(dev->udev, writesize, buf, urb->transfer_dma);

                  usb_free_urb(urb);

           }

           up(&dev->limit_sem);

    exit:

           return retval;

    }

    首先说明一个问题,填充urb后,设置了transfer_flags标志,当transfer_flags中的URB_NO_TRANSFER_DMA_MAP被设置,USB核心使用transfer_dma指向的缓冲区而不是使用transfer_buffer指向的缓冲区,这表明即将传输DMA缓冲区。当transfer_flags中的URB_NO_SETUP_DMA_MAP被设置,如果控制urbDMA缓冲区,USB核心将使用setup_dma指向的缓冲区而不是使用setup_packet指向的缓冲区。

    另外,通过上面这个write函数我们知道,当写函数发起的urb结束后,其完成函数skel_write_bulk_callback会被调用,我们继续跟踪

    static void skel_write_bulk_callback(struct urb *urb)

    {

           struct usb_skel *dev;

           dev = urb->context;

           if (urb->status) {

                  if (!(urb->status == -ENOENT ||

                      urb->status == -ECONNRESET ||

                      urb->status == -ESHUTDOWN))

                         err("%s - nonzero write bulk status received: %d",

                             __func__, urb->status);  //出错显示

                  spin_lock(&dev->err_lock);

                  dev->errors = urb->status;

                  spin_unlock(&dev->err_lock);

           }

           usb_buffer_free(urb->dev, urb->transfer_buffer_length,

                         urb->transfer_buffer, urb->transfer_dma);   //释放urb空间

           up(&dev->limit_sem);

    }

    很明显,skel_write_bulk_callback主要对urb->status进行判断,根据错误提示显示错误信息,然后释放urb空间。

    接着,我们看看USB骨架程序的字符设备的read函数

    static ssize_t skel_read(struct file *file, char *buffer, size_t count,

                          loff_t *ppos)

    {

           struct usb_skel *dev;

           int rv;

           bool ongoing_io;

           dev = (struct usb_skel *)file->private_data;   //获得文件私有数据

           if (!dev->bulk_in_urb || !count)  //正在写的时候禁止读操作

                  return 0;

           rv = mutex_lock_interruptible(&dev->io_mutex);  //获得锁

           if (rv < 0)

                  return rv;

           if (!dev->interface) {           

                  rv = -ENODEV;

                  goto exit;

           }

    retry:

           spin_lock_irq(&dev->err_lock);

           ongoing_io = dev->ongoing_read; 

           spin_unlock_irq(&dev->err_lock);

           if (ongoing_io) {           //USB核正在读取数据中,数据没准备好

                  if (file->f_flags & O_NONBLOCK) { //如果为非阻塞,则结束

                         rv = -EAGAIN;

                         goto exit;

                  }

                  rv = wait_for_completion_interruptible(&dev->bulk_in_completion);  //等待

                  if (rv < 0)

                         goto exit;

                  dev->bulk_in_copied = 0;  //拷贝到用户空间操作已成功

                  dev->processed_urb = 1;  //目前已处理好urb

           }

           if (!dev->processed_urb) {     //目前还没已处理好urb

                  wait_for_completion(&dev->bulk_in_completion);    //等待完成

                  dev->bulk_in_copied = 0;   //拷贝到用户空间操作已成功

                  dev->processed_urb = 1;   //目前已处理好urb

           }

           rv = dev->errors;

           if (rv < 0) {

                  dev->errors = 0;

                  rv = (rv == -EPIPE) ? rv : -EIO;

                  dev->bulk_in_filled = 0;

                  goto exit;

           }

           if (dev->bulk_in_filled) {  //缓冲区有内容

           //可读数据大小为缓冲区内容减去已经拷贝到用户空间的数据大小

                  size_t available = dev->bulk_in_filled - dev->bulk_in_copied;

                  size_t chunk = min(available, count);  //真正读取数据大小

                  if (!available) {

                         rv = skel_do_read_io(dev, count);  //没可读数据则调用IO操作

                         if (rv < 0)

                                goto exit;

                         else

                                goto retry;

                  }

                  //拷贝缓冲区数据到用户空间

                  if (copy_to_user(buffer, dev->bulk_in_buffer + dev->bulk_in_copied,chunk))                             rv = -EFAULT;

                  else

                         rv = chunk;

                  dev->bulk_in_copied += chunk;  //目前拷贝完成的数据大小

                  if (available < count)           //剩下可用数据小于用户需要的数据

                         skel_do_read_io(dev, count - chunk);  //调用IO操作

           } else {

                  rv = skel_do_read_io(dev, count);  //缓冲区没数据则调用IO操作

                  if (rv < 0)

                         goto exit;

                  else if (!file->f_flags & O_NONBLOCK)

                         goto retry;

                  rv = -EAGAIN;

           }

    exit:

           mutex_unlock(&dev->io_mutex);

           return rv;

    }

    通过上面read函数,我们知道,在读取数据时候,如果发现缓冲区没有数据,或者缓冲区的数据小于用户需要读取的数据量时,则会调用IO操作,也就是skel_do_read_io函数。

     

    static int skel_do_read_io(struct usb_skel *dev, size_t count)

    {

           int rv;

           usb_fill_bulk_urb(dev->bulk_in_urb,dev->udev,usb_rcvbulkpipe(dev->udev,

                                dev->bulk_in_endpointAddr),dev->bulk_in_buffer,

                         min(dev->bulk_in_size, count),skel_read_bulk_callback,dev); //填充urb

           spin_lock_irq(&dev->err_lock);

           dev->ongoing_read = 1;   //标志正在读取数据中

           spin_unlock_irq(&dev->err_lock);

           rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);   //提交urb

           if (rv < 0) {

                  err("%s - failed submitting read urb, error %d",

                         __func__, rv);

                  dev->bulk_in_filled = 0;

                  rv = (rv == -ENOMEM) ? rv : -EIO;

                  spin_lock_irq(&dev->err_lock);

                  dev->ongoing_read = 0;

                  spin_unlock_irq(&dev->err_lock);

           }

           return rv;

    }

    好了,其实skel_do_read_io只是完成了urb的填充和提交,USB核心读取到了数据后,会调用填充urb时设置的回调函数skel_read_bulk_callback

    static void skel_read_bulk_callback(struct urb *urb)

    {

           struct usb_skel *dev;

           dev = urb->context;

           spin_lock(&dev->err_lock);

           if (urb->status) {          //根据返回状态判断是否出错

                  if (!(urb->status == -ENOENT ||

                      urb->status == -ECONNRESET ||

                      urb->status == -ESHUTDOWN))

                         err("%s - nonzero write bulk status received: %d",

                             __func__, urb->status);

                  dev->errors = urb->status;

           } else {

                  dev->bulk_in_filled = urb->actual_length;  //记录缓冲区的大小

           }

           dev->ongoing_read = 0;    //已经读取数据完毕

           spin_unlock(&dev->err_lock);

           complete(&dev->bulk_in_completion);   //唤醒skel_read函数

    }

    好了,到目前为止,我们已经把USB驱动框架usb-skeleton.c分析完了,总结下,其实很简单,在模块加载里面注册usb_driver,然后在probe函数里初始化一些参数,最重要的是注册了USB设备,这个USB设备相当于一个字符设备,提供file_operations接口。然后设计openclosereadwrite函数,这个open里基本没做什么事情,在write中,通过分配urb、填充urb和提交urb。注意读的urb的分配在probe里申请空间,写的urb的分配在write里申请空间。在这个驱动程序中,我们重点掌握usb_fill_bulk_urb的设计。

  • 相关阅读:
    数据结构与算法——认识O(NlogN)的排序(2)
    数据结构与算法——简单排序算法(2)
    数据结构与算法——认识O(NlogN)的排序(1)
    论文翻译——Attention Is All You Need
    论文翻译——Character-level Convolutional Networks for Text Classification
    研究NLP100篇必读的论文---已整理可直接下载
    kaggle——绝地求生游戏最终排名预测
    tab栏切换
    css兄弟选择器(+ 和 ~)的使用和区别
    cookie和web storage的区别
  • 原文地址:https://www.cnblogs.com/Zoran-/p/5819305.html
Copyright © 2020-2023  润新知