最近搞了一个stm32的usb设备与操作系统的通讯,在设备编码处理的同时进行与操作系统的数据通讯。

1、USB总线的列举:

USB功能设备是USB设备开发的的重点,USB功能设备满足USB总线接口技术协议。对于开发者开说,首先需要了解usb功能设备在设备总线上的列举过程。

在USB总技术接口协议中,USB总线使用列举操作来管理USB功能设备的连接和断开。当USB设备连接到USB主机时,USB主机自动对USB功能设备进行列举操作。

下面分别介绍USB功能设备在USB总线的连接和断开过程:

连接USB设备

在USB设备连接到USB总线上时,USB主机通过默认的控制传输管道向设备发出标准USB设备请求。

整个USB设备的连接过程分为如下几个步骤:

step1:当usb功能设备连接到usb主机或者usb集线器的下行usb端口后,usb总线立即为该设备提供电源

step2:USB主机检测到D-/D+线上的电压,确认其下行USB端口有USB功能设备连接

step3:USB集线器通过中断IN管道,向USB主机报告下行USB端口有USB功能设备连接

step4:USB主机接到USB集线器额通知后,通过USB设备类请求GetPortStatus获得关于USB功能设备的更多信息

step5:USB主机等待100ms,以确保USB功能设备的稳定连接

step6:USB主机发送USB集线器设备类请求SetPortStatus,对连接的USB功能设备执行复位操作

step7:USB功能设备复位结束后,USB功能设备进入默认状态,从USB总线获取小于100mA的电流,用于使用默认地址管道0的控制事物进行响应

step8:USB主机向USB功能设备发送GetDescriptor请求,获取默认控制管道的最大数据包长度

step9:USB主机发出Setddress请求,为连接的USB功能设备分配一个唯一的USB设备地址

step10:USB主机使用新的USB设备地址向USB功能设备发送GetDescriptor请求,并获取其设备信息的全部字段,包括产品PID,供应商VID等。

step11:USB主机循环发送GetDescriptor请求,获取完整的USB设备信息、包括配置描述符、接口描述符、端口描述符以及各种设备类定义描述符和供应商自定义信息。

step12:USB主机根据USB功能设备的配置信息,并获取其设备信息的全部字段,如产品PID,供应商VID等,为其选泽加载一个合适额主机驱动程序

step13:在正确加载驱动程序后,便可以进行各种配置的操作与以及数据传输操作等。

断开USB设备

当USB设备从USB总线中断开的时候,USB主机和USB集线器将自动处理断开USB的操作,具体过程如下:

step1:

当usb设备从usb集线器下行usb断口断开,usb集线器自动禁止该下行端口,并通过中断IN管道向USB主机报告其下行usb端口的变化

step2:

usb主机向usb集线器发送GetPortStatus请求了解更多信息,并处理该设备的断开操作

step:

USB驱动程序释放器占用所有系统资源,如内存空间、地址量

由于usb集线器也是一种特殊的usb功能设备,如果usb集线器从usb总线断开,则usb主机将对该usb集线器和usb集线器连接的所有设备进行断开操作,操作步骤同前面类似。

2、USB概念

USB驱动由USB主机控制器驱动和USB设备驱动组成。

USB主机控制器是用来控制USB设备和CPU之间通信的,USB主机控制器驱动主要用来驱动芯片上的主机控制器硬件。

USB设备驱动主要功能设备是指具体的例如USB鼠标,USB键盘灯设备的驱动

一般的通用的Linux设备,如U盘、USB鼠标、USB键盘,都不需要工程师再编写驱动,需要编写的是特定厂商、特定芯片的驱动,而且往往也可以参考内核中已经提供的驱动模板。USB只是一个总线,真正的USB设备驱动的主体工作仍然是USB设备本身所属类型的驱动,如字符设备、tty设备、块设备、输入设备等。

 3、USB主机控制器:

USB主机控制器属于南桥芯片的一部分,通过PCI总线和处理器通信USB主机控制器分为UHCI(英特尔提出)、OHCI(康柏和微软提出)、 EHCI。

OHCI驱动程序用来为非PC系统上以及带有SiS和ALi芯片组的PC主办上的USB芯片提供支持。

UHCI驱动程序多用来为大多数其他PC主板(包括Intel和Via)上的USB芯片提供支持。

ENCI兼容OHCI和UHCI。UHCI的硬件线路比OHCI简单,所以成本较低,但需要较复杂的驱动程序,CPU负荷稍重。

主机控制器驱动程序完成的功能主要包括:

1解析和维护URB,根据不同的端点进行分类缓存URB;

2负责不同USB传输类型的调度工作;

3负责USB数据的实际传输工作;

4实现虚拟跟HUB的功能

4、USB设备与USB驱动的匹配

USB设备与USB驱动怎么匹配的呢?

USB设备中有一个模块叫固件,是固件信息和USB驱动进行的匹配。

固件是固化在集成电路内部的程序代码,USB固件中包含了USB设备的出厂信息,标识该设备的厂商ID、产品ID、主版本号和次版本号等。

另外固件中还包含一组程序,这组程序主要完成USB协议的处理和设备的读写操作。USB设备固件和USB驱动之间通信的规范是通过USB协议来完成的。

在进行的便携式项目中,stm32f103RC芯片的的usb引脚与主处理器的usb口进行连接,没有usb相应芯片,对stm32的usb编程处理,本人认为即相当于usb芯片的固件处理。usb芯片的固件就是FPGA的逻辑编程固化在芯片里面,非厂商的提供测方式顾客不能进行处理。

5、USB设备的逻辑结构和端点的传输方式

USB设备的逻辑结构包括设备、配置、接口和端点,分别用usb_device、usb_host_config、 usb_interface、usb_host_endpoint表示。
struct usb_skel {
structusb_device*udev;/* the usb device for this device */
structusb_interface*interface;/* the interface for this device */
struct semaphorelimit_sem;/* limiting the number of writes in progress */
struct usb_anchorsubmitted;/* in case we need to retract our submissions */
structurb*bulk_in_urb;/* the urb to read data with */
unsigned char           *bulk_in_buffer;/* the buffer to receive data */
size_tbulk_in_size;/* the size of the receive buffer */
size_tbulk_in_filled;/* number of bytes in the buffer */
size_tbulk_in_copied;/* already copied to user space */
__u8bulk_in_endpointAddr;/* the address of the bulk in endpoint */
__u8bulk_out_endpointAddr;/* the address of the bulk out endpoint */
interrors;/* the last request tanked */
intopen_count;/* count the number of openers */
boolongoing_read;/* a read is going on */
boolprocessed_urb;/* indicates we haven't processed the urb */
spinlock_terr_lock;/* lock for errors */
struct krefkref;
struct mutexio_mutex;/* synchronize I/O with disconnect */
structcompletionbulk_in_completion;/* to wait for an ongoing read */
};

usb_device->usb_host_config->usb_iinterface。
usb_device->usb_host_endpoint[16]

struct usb_device {
...
enum usb_device_statestate;
enum usb_device_speedspeed;
...
structusb_device *parent;
structusb_bus*bus;
structusb_host_endpointep0;

structdevicedev;

structusb_device_descriptordescriptor;
structusb_host_config*config;

struct usb_host_config *actconfig;
structusb_host_endpoint*ep_in[16];
structusb_host_endpoint*ep_out[16];

}

控制传输

主要用于向设备发送配置信息、获取设备信息、发送命令道设备,或者获取设备的状态报告。

控制传输一般发送的数据量较小,当USB设备插入时,USB核心使用端点0对设备进行配置,另外,端口0与其他端点不一样,端点0可以双向传输。

中断传输

中断端点以一个固定的速度来传输较少的数据,USB键盘和鼠标就是使用这个传输方式。

这里说的中断和硬件上下文中的中断不一样,它不是设备主动发送一个中断请求,而是主机控制器在保证不大于某个时间间隔内安排一次传输。

中断传输对时间要求比较严格,所以可以用中断传输来不断地检测某个设备,当条件满足后再使用批量传输传输大量的数据。

批量传输(块传输)

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

等时传输(同步传输)

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

6、 USB的URB请求块

USB请求块(USB request block,urb)是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()完成,这两个函数是同步的,因此不能在中断上下文和持有自旋锁的情况下使用。

7、USB的枚举过程

内核辅助线程khubd用来监视与该集线器连接的所有端口,通常情况下,该线程处于休眠状态,当集线器驱动程序检测到USB端口状态变化后,该内核线程立马唤醒。
USB的枚举过程:

USB的枚举过程是热插拔USB设备的起始步骤,该过程中,主机控制器获取设备的相关信息并配置好设备,集线器驱动程序负责该枚举过程。

枚举过程主要分如下几步:
Step1:根集线器报告插入设备导致的端口电流变化,集线器驱动程序检测到这一状态变化后,唤醒khubd线程。
Step2:khubd识别出电流变化的那个端口
Step3:khubd通过给控制端点0发送控制URB来实现从1-127中选出一个数作为插入设备的批量端点
Step4:khubd利用端口0使用的控制URB从插入的设备那里获得设备描述符,然后获得配置描述符,并选择一个合适的。
Step5:khubd请求USB核心把对应的客户驱动程序和该USB设备挂钩。

8、USB驱动分析

内核代码分析包括USB驱动框架、鼠标驱动、键盘驱动、U盘驱动。
USB驱动编写的主要框架usb-skeleton.c
USB鼠标驱动 usbmouse.c
USB键盘驱动usbkbd.c
USB Mass Storage是一类USB存储设备, U盘便是其中之一,主要分析的驱动文件是usb.c

8.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      0x0483
#define USB_SKEL_PRODUCT_ID     0x5720
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结构体
/* ------------------ 注释代码------------------ */       
struct usb_skel {
struct usb_device*udev;/* the usb device for this device */
structusb_interface*interface;/* the interface for this device */
       ...
       ...
}
//usb_get_dev: increments the reference count of the usb device structure

//usb_get_dev将USB:将USB设备的引用计数  

struct usb_device*usb_get_dev(struct usb_device *dev)----->get_device(&dev->dev)

//kobj_to_dev提供上一级的回调函数

struct devcie * get_device(struct device *dev){

return dev? kobj_to_dev(kebject_get( &dev->kobj) ) : NULL;

}

static inline struct device * kobject_to_dev(struct koject *kobj){

returncontainer_of(kobj, struct device,  kobj)

}

interface_to_usbdev(structusb_interface  *intf) ----->return to_usb_device(intf->dev.parent)
 #define   to_usb_device(d)cotainer_of(d,structusb_interface,dev)   ...
 structusb_device {
...
enum usb_device_statestate;
enum usb_device_speedspeed;
...
struct usb_device *parent;
struct usb_bus *bus;
struct usb_host_endpoint ep0;
struct devicedev;
struct usb_device_descriptor descriptor;
struct usb_host_config *config;
struct usb_host_config *actconfig;
struct usb_host_endpoint *ep_in[16];
struct usb_host_endpoint *ep_out[16];
}
 struct device {
structdevice*parent;

structdevice_private*p;

struct kobject kobj;
const char*init_name; /* initial name of the device */
conststructdevice_type*type;

struct mutexmutex;/* mutex to synchronize calls to its driver.*/

struct bus_type*bus;/* type of bus device is on */
structdevice_driver*driver;/* which driver has allocated this
  device */
void*platform_data;/* Platform specific data, device  core doesn't touch it */
struct dev_pm_infopower;
struct dev_pm_domain*pm_domain;     
}

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);  //缓冲大小     
                  //将端点地址、缓冲区等信息存入USB骨架程序定义的usb_skel结构体中                    
                     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_driver的probe函数中根据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的定义

/* usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with the driver core
 */
static struct usb_class_driver skel_class = {
.name = "skel%d",
.fops = &skel_fops,
.minor_base = USB_SKEL_MINOR_BASE,
};

static const struct file_operationsskel_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_operations中open函数的实现

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);//获得次设备号----------

//MINOR(inode->i_rdev)===========

//根据usb_driver和次设备号获取设备的接口
interface = usb_find_interface(&skel_driver,subminor);//skel_driver为usb_driver结构体的对象

/*--find usb_interface pointer for driver and device*/

usb_find_interface(&skel_driver, subminor) -->

//查询总线bus_find_device

dev=bus_find_device(&usb_bus_type, NULL,  &agrp, _find_interface);//usb/core/usb.c定义bus_find_device(函数)-->

struct bus_type usb_bus_type = {//usb/core/driver.c定义整个结构体
.name =         "usb",
.match =        usb_device_match,
.uevent =       usb_uevent,
 };

put_device(dev);

return dev ? to_usb_interface(dev) : NULL;

#defineto_usb_interface(d)container_of(d,structusb_interface,dev)

dev是在structusb_interface结构体的成员

11

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

static inline void * usb_get_intfdata(struct usb_interface *intf){

return dev_get_drvdata(&intf->dev)

}

void * dev_get_drvdata(const struct device *dev){

if(dev && dev->p)

return dev->p->driver_data;

return NULL;

}

struct device {
struct device*parent;

struct device_private*p;

...

}
//struct device_private --------structure to hold the private to the driver core portions of the device structure
//device_private结构体------保持设备结构的驱动核心部分结构的私有化 -----base.h
struct device_private {
struct klist klist_children;//-----klist contain all children of this device
struct klist_node knode_parent;//-----node in sibling list
struct klist_node knode_driver;//-----node in driver list
struct klist_node knode_bus;//-----node in bus list
struct list_head deferred_probe;//-----entry in deferred_probe_list
//which is used to retry the binding of drivers which were unable to get all the resources needed by the device;
//typically because it depends on another driver getting probed first.

//private pointer for driver specific info.  Will turn into a list soon.---返回一个表列
void *driver_data;

//----pointer back to the struct class that this structure is associated with
struct device *device;
};

structklist_node{
void*n_klist;
structlist_head n_node;
structkerf n_ref;
}

//interface->dev->p->driver_data
if (!dev) {
retval = -ENODEV;
goto exit;
}

/* increment our usage count for the device */
kref_get(&dev->kref);

/* lock the device to allow correctly handling errors
* in resumption */
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;
}
} /* else { //uncomment this block if you want exclusive open
retval = -EBUSY;
dev->open_count--;
mutex_unlock(&dev->io_mutex);
kref_put(&dev->kref, skel_delete);
goto exit;
} */
/* prevent the device from being autosuspended */

/* save our object in the file's private structure */
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 = file->private_data; //获取文件的私有数据

/* verify that we actually have some data to write */
if (count == 0)
goto exit;

/*
* limit the number of URBs in flight to stop a user from using up all RAM
*/
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) {
/* any error is reported once */
dev->errors = 0;
/* to preserve notifications about reset */
retval = (retval == -EPIPE) ? retval : -EIO;
}
spin_unlock_irq(&dev->err_lock); //中断解锁---开中断
if (retval < 0)
goto error;

/* create a urb, and a buffer for it, and copy the data to the urb */
urb = usb_alloc_urb(0, GFP_KERNEL);  //分配urb
if (!urb) {
retval = -ENOMEM;
goto error;
}

buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL, &urb->transfer_dma); //分配写缓冲区--coherent(连贯一致,粘着的,相干的)
if (!buf) {
retval = -ENOMEM;
goto error;
}

if (copy_from_user(buf, user_buffer, writesize)) { //将用户空间数据拷贝到缓冲区
retval = -EFAULT;
goto error;
}

/* this lock makes sure we don't submit URBs to gone devices */
mutex_lock(&dev->io_mutex);
if (!dev->interface) { /* disconnect() was called */
mutex_unlock(&dev->io_mutex);
retval = -ENODEV;
goto error;
}

/* initialize the urb properly */
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);

/* send the data out the bulk port */
retval = usb_submit_urb(urb, GFP_KERNEL); //提交urb
mutex_unlock(&dev->io_mutex);
if (retval) {
dev_err(&dev->interface->dev,"%s - failed submitting write urb, error %d\n", __func__, retval);
goto error_unanchor;
}

/*
* release our reference to this urb, the USB core will eventually free it entirely
*/
usb_free_urb(urb); //释放usb_alloc_urb

return writesize;

error_unanchor:
usb_unanchor_urb(urb);
error:
if (urb) {
usb_free_coherent(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被设置,如果控制urb有DMA缓冲区,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;

/* sync/async unlink faults aren't errors */
if (urb->status) {
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||  urb->status == -ESHUTDOWN))
dev_err(&dev->interface->dev,"%s - nonzero write bulk status received: %d\n",__func__, urb->status); //出错显示

spin_lock(&dev->err_lock);
dev->errors = urb->status;
spin_unlock(&dev->err_lock);
}

/* free up our allocated buffer */
usb_free_coherent(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 we cannot read at all, return EOF */
if (!dev->bulk_in_urb || !count) //正在写的时候禁止读操作
return 0;

/* no concurrent readers */
rv = mutex_lock_interruptible(&dev->io_mutex); //获得锁
if (rv < 0)
return rv;

if (!dev->interface) { /* disconnect() was called */
rv = -ENODEV;
goto exit;
}

/* if IO is under way, we must not touch things */
retry:
spin_lock_irq(&dev->err_lock); //中断上锁
ongoing_io = dev->ongoing_read; // a read is going on 正在进行读操作

struct usb_skel {
struct usb_device *udev; /* the usb device for this device */
struct usb_interface *interface; /* the interface for this device */
...
bool ongoing_read; /* a read is going on */
struct completion bulk_in_completion; /* to wait for an ongoing read */

}
spin_unlock_irq(&dev->err_lock); //中断解锁

if (ongoing_io) { //USB核正在读取数据中,数据没准备好
/* nonblocking IO shall not wait */
if (file->f_flags & O_NONBLOCK) { //如果为非阻塞,则结束
rv = -EAGAIN;
goto exit;
}
/*  IO may take forever hence wait in an interruptible state   IO 可能在此处(中断状态下)永远等待*/
rv = wait_for_completion_interruptible(&dev->bulk_in_completion);  // 等待
if (rv < 0)
goto exit;
/* by waiting we also semiprocessed the urb we must finish now */
dev->bulk_in_copied = 0; //拷贝到用户空间操作已成功
dev->processed_urb = 1; //目前已处理好urb
}

if (!dev->processed_urb) { //目前还没已处理好urb
/*  the URB hasn't been processed do it now  */
wait_for_completion(&dev->bulk_in_completion); //等待完成
dev->bulk_in_copied = 0; //拷贝到用户空间操作已成功
dev->processed_urb = 1; //目前已处理好urb
}

/* errors must be reported */
rv = dev->errors;
if (rv < 0) {
/* any error is reported once */
dev->errors = 0;
/* to preserve notifications about reset */
rv = (rv == -EPIPE) ? rv : -EIO;
/* no data to deliver */
dev->bulk_in_filled = 0;
/* report it */
goto exit;
}

/*  if the buffer is filled we may satisfy the read else we need to start IO  */
if (dev->bulk_in_filled) {  //缓冲区有内容
/* we had read data */
size_t available = dev->bulk_in_filled - dev->bulk_in_copied;
size_t chunk = min(available, count); //真正读取数据大小

if (!available) {
/* all data has been used actual IO needs to be done */
rv = skel_do_read_io(dev, count); //没可读数据则调用IO操作
if (rv < 0)
goto exit;
else
goto retry;
}
/* data is available chunk tells us how much shall be copied */
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 we are asked for more than we have, we start IO but don't wait */
if (available < count) //剩下可用数据小于用户需要的数据
skel_do_read_io(dev, count - chunk); //调用IO操作
} else {
/* no data in the buffer */
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;

/* prepare a read */
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), //填充urb
skel_read_bulk_callback,
dev);
/* tell everybody to leave the URB alone */
spin_lock_irq(&dev->err_lock);
dev->ongoing_read = 1; //标志正在读取数据中
spin_unlock_irq(&dev->err_lock);

/* do it */
rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); //提交urb
if (rv < 0) {
dev_err(&dev->interface->dev,
"%s - failed submitting read urb, error %d\n",
__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);
/* sync/async unlink faults aren't errors */
if (urb->status) { //根据返回状态判断是否出错
if (!(urb->status == -ENOENT ||  urb->status == -ECONNRESET ||  urb->status == -ESHUTDOWN))
dev_err(&dev->interface->dev, "%s - nonzero write bulk status received: %d\n", __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函数

struct usb_skel {
struct usb_device *udev; /* the usb device for this device */
struct usb_interface *interface; /* the interface for this device */
...
size_t bulk_in_filled; /* number of bytes in the buffer */
size_t bulk_in_copied; /* already copied to user space */
bool ongoing_read; /* a read is going on */
struct completion bulk_in_completion; /* to wait for an ongoing read */

}

struct completion{
unsigned int done;
wait_queue_head_t wait;
}
//complete: - signals a single thread waiting on this completion---- 发送信号-当一个单线程等待完成
//@x:  holds the state of this particular completion -------保持这种特殊状态
//This will wake up a single thread waiting on this completion. Threads will be awakened in the same order in which they were queued
//在完成后这将唤醒一个单一的等待线程。线程将排队依次唤醒在相同的规则/秩序/次序
void complete(struct completion *x)
{
unsigned long flags;
spin_lock_irqsave(&x->wait.lock, flags);
x->done++;
__wake_up_common(&x->wait, TASK_NORMAL, 1, 0, NULL);
spin_unlock_irqrestore(&x->wait.lock, flags);
}
}

http://blog.csdn.net/weiqing1981127/article/details/8215708
http://blog.csdn.net/tankai19880619/article/details/11639185
http://blog.chinaunix.net/uid-20989763-id-1831319.html
http://blog.csdn.net/tommy_wxie/article/details/7663040

stm32usb功能设备以及在linux下的USB相关总线、设备驱动笔记相关推荐

  1. linux 挂载设备的目录,Linux下使用mount来挂载设备到目录

    一般情况下直接mount 设备路径 目录路径,就可以了.umount 设备名,就可以卸载这个设备了 使用lsblk -f可以查看挂载的设备,以及这些设备的文件系统. root@tao-PC:/boot ...

  2. linux平台实现USB虚拟总线驱动一(原理以及开发流程)

    by fanxiushu 2019-11-07 转载或引用请注明原始作者. 之前的文章阐述过在windows平台下,如何实现USB虚拟总线驱动, 以及如何在windows平台采集真实USB设备的数据, ...

  3. Linux下使用Java调用Hikvision设备网络SDK使用指南

    1 简介  由于在开发过程中,本来以为抓图项目会部署在Windows服务器上,但随着项目的进行发现项目需要部署在Linux系统,甚至是国产化平台银河麒麟上,但在部署时发现在国产化平台部署时出现缺包的问 ...

  4. Linux下按扇区读写块设备,Linux下按扇区读写块设备

    本文介绍Linux下按扇区读写块设备(示例TF卡),实际应用是在Android系统上,主要方法如下: 1.找到sdcard的挂载点,在android2.1系统下应该为/dev/block/mmcblk ...

  5. linux查看并口设备,如何在LINUX下实现硬件的自动检测(下)

    本文是<如何在LINUX下实现硬件的自动检测>一文的下部分,作者将继续向我们讲述如何自动检测另外几种总线类型硬件设备. 5 .USB设备的自动检测 5.1 USB设备检测的一般过程 USB ...

  6. Linux下查看硬盘序列号、设备序列号、操作系统版本和安装时间、系统启用时间等命令

    Linux下查看硬盘序列号.设备序列号.操作系统版本和安装时间.启用时间等命令 最近由于工作需要查询一些硬盘序列号.设备序列号.操作系统版本和安装时间.系统启用时间等信息.所以对用到的命令进行一下总结 ...

  7. linux u盘插入事件,Linux 下监控USB设备拔插事件

    Linux 下监控USB设备拔插事件 发布时间:2018-01-29 00:00, 浏览次数:1111 , 标签: Linux USB * 使用Netlink来实现 这是一个特殊的socket,可以接 ...

  8. Linux下的USB总线驱动 mouse

    Linux下的USB总线驱动(03)--USB鼠标驱动 usbmouse.c USB鼠标驱动 usbmouse.c 原文链接:http://www.linuxidc.com/Linux/2012-12 ...

  9. *Linux下的USB总线驱动 u盘驱动分析*

    Linux下的USB总线驱动(三) u盘驱动分析 版权所有,转载请说明转自 http://my.csdn.net/weiqing1981127 https://www.xuebuyuan.com/13 ...

  10. 如何编写Linux 下的 USB 键盘驱动

     如何编写Linux 下的 USB 键盘驱动 1. 指定 USB 键盘驱动所需的头文件: #include <linux/kernel.h>/*内核头文件,含有内核一些常用函数的原型定 ...

最新文章

  1. Nature | 复旦大学把衣服变成了显示器,能聊天能导航,水洗弯折都不怕
  2. 请选择JAVA中有效的标识_Java中有效的标识符是什么?
  3. java cache缓存_为什么 Java 中“1000==1000”为false,而”100==100“为true?
  4. webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)
  5. WordPress多用途电子商务博客新闻主题betheme 21.5.6版本
  6. 国内CDH的MAVEN代理
  7. 剑灵服务器延迟时间在哪看,怎么通过任务管理器看剑灵网络延迟
  8. bootloader功能介绍/时钟初始化设置/串口工作原理/内存工作原理/NandFlash工作原理...
  9. 编译支持iOS的libcurl+OpenSSL库(支持https IPv6)
  10. 论文笔记_S2D.59_2015-ICRA_V-LOAM_低漂移、鲁棒和快速的视觉-激光 里程计和建图
  11. 佐客牛排机器人餐厅_3分钟出餐!全球首家机器人餐厅开业
  12. vs 2017官网下载、QT下载
  13. 曾经,我们有一个芝麻大小的梦想
  14. 3DMAX安装包+安装教程
  15. 4月上旬国内域名商净增量Top10:联动天下跻身第10
  16. 腾讯云人脸识别 活体检测 人员库管理
  17. JZOJ5401. 【NOIP2017提高A组模拟10.8】Star Way To Heaven
  18. 【转】在数学的海洋中飘荡
  19. 3dsmax修改对象的旋转中心的位置
  20. “由于文件许可权错误,word无法完成保存操作”解决

热门文章

  1. ((亲测有效))安卓神器Xposed框架无ROOT使用指南
  2. html焦点图自动轮播,jQuery图片轮播(焦点图)插件jquery.slideBox
  3. 人力资源管理系统(大二数据库课设) spring boot,Mybatis+bootstap,ajax项目
  4. ArcGIS亚洲字体(CJK)垂直显示
  5. 【JS】388- 深入了解强大的 ES6 「 ... 」 运算符
  6. 电脑被流氓软件入侵?教你彻底清除
  7. APP定制开发之前,这6条铁律要牢记
  8. 2009英国电子工程学专业排名
  9. PotPlayer和MPC-HC挂载VSFilterMod加载外挂特效字幕的方法
  10. 国内DDOS防御的专业防火墙技术