上一节勉勉强强把struct urb这个中心给说完,接着看那三个基本点。

第一个基本点,usb_alloc_urb函数,创建urb的专用函数,为一个urb申请内存并做初始化,在drviers/usb/core/urb.c里定义。

/**
* usb_alloc_urb - creates a new urb for a USB driver to use
* @iso_packets: number of iso packets for this urb
* @mem_flags: the type of memory to allocate, see kmalloc() for a list of
* valid options for this.
*
* Creates an urb for the USB driver to use, initializes a few internal
* structures, incrementes the usage counter, and returns a pointer to it.
*
* If no memory is available, NULL is returned.
*
* If the driver want to use this urb for interrupt, control, or bulk
* endpoints, pass '0' as the number of iso packets.
*
* The driver must call usb_free_urb() when it is finished with the urb.
*/
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
{
struct urb *urb; urb = kmalloc(sizeof(struct urb) +
iso_packets * sizeof(struct usb_iso_packet_descriptor),
mem_flags);
if (!urb) {
err("alloc_urb: kmalloc failed");
return NULL;
}
usb_init_urb(urb);
return urb;
}

这函数只做了两件事情,拿kmalloc来为urb申请内存,然后调用usb_init_urb进行初始化。usb_init_urb函数的作用就是初始化urb的引用计数,并用memset顺便把这里给urb申请的内存清零。没什么说的了么?usb_alloc_urb说:别看我简单,我也是很有内涵的。

先看第一个问题,它的第一个参数iso_packets,表示的是struct urb结构最后那个变长数组iso_frame_desc的元素数目,也就是应该与number_of_packets的值相同,所以对于控制/中断/批量传输,这个参数都应该为0。这也算是给咱们示范了下变长数组咋个用法,内核里到处都是C的示范工程。

第二个问题是参数mem_flags的类型gfp_t,早几个版本的内核,这里还是int,当然这里变成gfp_t是因为kmalloc参数里的那个标志参数的类型从int变成gfp_t了,你要用kmalloc来申请内存,就得遵守它的规则。咱们来调查一下它的背景。它在include/linux/types.h里定义。

typedef unsigned __bitwise__ gfp_t;

很显然,要了解gfp_t,关键是要了解__bitwise__,它也在types.h里定义。

#ifdef __CHECKER__

#define __bitwise__ __attribute__((bitwise))

#else

#define __bitwise__

#endif

__bitwise__的含义又取决于是否定义了__CHECKER__,如果没有定义__CHECKER__,那__bitwise__就啥也不是。哪里定义了__CHECKER__?咱们不找,因为内核代码里就没有哪个地方定义了__CHECKER__,它是有关Sparse工具的,内核编译时的参数决定了是不是使用Sparse工具来做类型检查。那Sparse又是什么?它是一种静态分析工具(static analysis tool), 用于在linux内核源代码中发现各种类型的漏洞,一直都是比较神秘的角色,最初由Linus Torvalds写的,后来linus没有继续维护,直到去年的冬天,它才又有了新的维护者Josh Triplett。有关Sparse再多的东东,咱们还是各自去研究吧,这里不多说了。

可能还会有第三个问题,usb_alloc_urb也没做多少事啊,它做的那些咱们自己很容易就能做了,为什么还说驱动里一定要使用它来创建urb那?按照C++的说法,它就是urb的构造函数,构造函数是创建对象的唯一方式。它将创建urb的工作给包装了,咱们只管调用就是了,孙子兵法里有,以不变应万变。

对应的,当然还会有个析构函数,销毁urb的,也在urb.c里定义。usb_free_urb只调用kref_put将urb的引用计数减一,减了之后如果变为0,也就是没人再用它了,就调用urb_destroy将它销毁掉。

接着看第二个基本点,usb_fill_control_urb函数,初始化刚才创建的控制urb,你要想使用urb进行usb传输,不是光为它申请点内存就够的,你得为它初始化。它是在include/linux/usb.h里定义的内联函数。

static inline 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_fn,
void *context)
{
spin_lock_init(&urb->lock);
urb->dev = dev;
urb->pipe = pipe;
urb->setup_packet = setup_packet;
urb->transfer_buffer = transfer_buffer;
urb->transfer_buffer_length = buffer_length;
urb->complete = complete_fn;
urb->context = context;
}

这个函数基本上都是赋值语句,把你在参数里指定的值充实给刚刚创建的urb,urb的元素有很多,这里只是填充了一部分,剩下那些不是控制传输管不着的,就是自有安排可以不用去管的(别忘了我们是从usb_control_msg函数分析一路走到这里)。你想想,一个struct urb结构要应付四种传输类型,每种传输类型总会有点自己特别的要求,总会有些元素专属于某种传输类型,而其它传输类型不用管的。如果按C++的做法,这称不上是一个好的设计思想,应该有个基类urb,里面放点儿四种传输类型公用的,比如pipe,transfer_buffer等,再搞几个子类,control_urb,bulk_urb等等,专门应付具体的传输类型,如果不用什么虚函数,实际的时间空间消耗也不会增加什么。但是实在没必要这么搞,内核的结构已经够多了,你创建什么类型的urb,就填充相关的一些字段好了,usb core给咱们提供了不同传输类型的初始化函数,就像上面的usb_fill_control_urb,对于批量传输有usb_fill_bulk_urb,对于中断传输有usb_fill_int_urb,一般来说这也就够了,下面就看看usb_fill_control_urb函数的这俩孪生兄弟。

/**
* usb_fill_bulk_urb - macro to help initialize a bulk urb
* @urb: pointer to the urb to initialize.
* @dev: pointer to the struct usb_device for this urb.
* @pipe: the endpoint pipe
* @transfer_buffer: pointer to the transfer buffer
* @buffer_length: length of the transfer buffer
* @complete_fn: pointer to the usb_complete_t function
* @context: what to set the urb context to.
*
* Initializes a bulk urb with the proper information needed to submit it
* to a device.
*/
static inline 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_fn,
void *context)
{
spin_lock_init(&urb->lock);
urb->dev = dev;
urb->pipe = pipe;
urb->transfer_buffer = transfer_buffer;
urb->transfer_buffer_length = buffer_length;
urb->complete = complete_fn;
urb->context = context;
} /**
* usb_fill_int_urb - macro to help initialize a interrupt urb
* @urb: pointer to the urb to initialize.
* @dev: pointer to the struct usb_device for this urb.
* @pipe: the endpoint pipe
* @transfer_buffer: pointer to the transfer buffer
* @buffer_length: length of the transfer buffer
* @complete_fn: pointer to the usb_complete_t function
* @context: what to set the urb context to.
* @interval: what to set the urb interval to, encoded like
* the endpoint descriptor's bInterval value.
*
* Initializes a interrupt urb with the proper information needed to submit
* it to a device.
* Note that high speed interrupt endpoints use a logarithmic encoding of
* the endpoint interval, and express polling intervals in microframes
* (eight per millisecond) rather than in frames (one per millisecond).
*/
static inline void usb_fill_int_urb (struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context,
int interval)
{
spin_lock_init(&urb->lock);
urb->dev = dev;
urb->pipe = pipe;
urb->transfer_buffer = transfer_buffer;
urb->transfer_buffer_length = buffer_length;
urb->complete = complete_fn;
urb->context = context;
if (dev->speed == USB_SPEED_HIGH)
urb->interval = 1 << (interval - 1);
else
urb->interval = interval;
urb->start_frame = -1;
}

负责批量传输的usb_fill_bulk_urb和负责控制传输的usb_fill_control_urb的相比,只是少初始化了一个setup_packet,因为批量传输里没有Setup包的概念,中断传输里也没有,所以usb_fill_int_urb里也没有初始化setup_packet这一说。不过usb_fill_int_urb比那两个还是多了点儿内容的,因为它有个interval,比控制传输和批量传输多了个表达自己期望的权利,另外还做了次判断,如果是高速就怎么怎么着,否则又怎么怎么着,主要是高速和低速/全速的间隔时间单位不一样,低速/全速的单位为帧,高速的单位为微帧,还要经过2的(bInterval-1)次方这么算一下。我们很快就能发现usb_fill_control_urb的孪生兄弟里,少了等时传输对应的那个初始化函数(比如usb_fill_iso_urb)。对于等时传输,urb里是可以指定进行多次传输的,你必须一个一个的对那个变长数组iso_frame_desc里的内容进行初始化,没人帮得了你。难道你能想出一个办法搞出一个适用于各种情况等时传输的初始化函数?我是不能。如果想不出来,使用urb进行等时传输的时候,还是老老实实的对里面相关的字段一个一个的填内容吧。如果想找个例子旁观一下别人是咋初始化的,可以去找个摄像头驱动,或者其它usb音视频设备的驱动看看,内核里也有一些的。

现在,你应该还记得咱们是因为要设置设备的地址,让设备进入Address状态,调用了usb_control_msg,才走到这里遇到usb_fill_control_urb的,参数里的setup_packet就是之前创建和赋好值的struct usb_ctrlrequest结构体,设备的地址已经在struct usb_ctrlrequest结构体wValue字段里了,这次控制传输并没有DATA transaction阶段,也并不需要urb提供什么transfer_buffer缓冲区,所以transfer_buffer应该传递一个NULL,当然transfer_buffer_length也就为0了。有意思的是这时候传递给usb_fill_control_urb的结束处理函数usb_api_blocking_completion,可以看一下当这次控制传输已经完成,设备地址已经设置好后,接着做了些什么,它的定义在drivers/usb/core/message.c里。这个函数usb_api_blocking_completion没有前面说的释放urb,也没有重新提交它,本来就想设置个地址就完事儿了,没必要再将它提交给HCD,你就是再提交多少次,设置多少次,也只能有一个地址。

最后就是第三个基本点,usb_start_wait_urb函数,将前面历经千辛万苦创建和初始化的urb提交给咱们的usb core,让它移交给特定的主机控制器驱动进行处理,然后等待HCD回馈的结果,如果等待的时间超过了预期的限度,它不会再等,它也在drivers/usb/core/message.c里定义。

/*
* Starts urb and waits for completion or timeout. Note that this call
* is NOT interruptible. Many device driver i/o requests should be
* interruptible and therefore these drivers should implement their
* own interruptible routines.
*/
static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length)
{
struct completion done;
unsigned long expire;
int status; init_completion(&done);
urb->context = &done;
urb->actual_length = 0;
status = usb_submit_urb(urb, GFP_NOIO);
if (unlikely(status))
goto out; expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;
if (!wait_for_completion_timeout(&done, expire)) { dev_dbg(&urb->dev->dev,
"%s timed out on ep%d%s len=%d/%d\n",
current->comm,
usb_pipeendpoint(urb->pipe),
usb_pipein(urb->pipe) ? "in" : "out",
urb->actual_length,
urb->transfer_buffer_length); usb_kill_urb(urb);
status = urb->status == -ENOENT ? -ETIMEDOUT : urb->status;
} else
status = urb->status;
out:
if (actual_length)
*actual_length = urb->actual_length; usb_free_urb(urb);
return status;
}

定义了一个struct api_context 结构体。api_context结构体里的成员completion是内核里一个比较简单的同步机制,一个线程可以通过它来通知另外一个线程某件事情已经做完了。你的代码执行到某个地方,需要再忙点儿其它的,就新开个线程,让它去忙活,然后自个接着忙自己的,想知道那边儿忙活的结果了,就停在某个地方等着,那边儿忙活完了会通知一下已经有结果了,于是你的代码又可以继续往下走。

completion机制围绕struct completion结构去实现,有两种使用方式,一种是通过DECLARE_COMPLETION宏在编译时就创建好struct completion的结构体,另外一种就是上面的形式,运行时才创建的,先在定义一个struct api_context结构体(包含了struct completion结构体),然后用init_completion去初始化。光是创建struct completion的结构体没用,关键的是如何通知任务已经完成了,和怎么去等候完成的好消息。这里用来通知已经完成了的函数不止一个。

void complete(struct completion *c);

void complete_all(struct completion *c);

complete只通知一个等候的线程,complete_all可以通知所有等候的线程。然而针对不同的情况,等候的方式就有好几种,都在kernel/sched.c里定义

void wait_for_completion(struct completion *c);

unsigned long wait_for_completion_timeout(struct completion *x, unsigned long timeout);

int wait_for_completion_interruptible(struct completion *x);

unsigned long wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout);

上面usb_start_wait_urb函数里使用的就是wait_for_completion_timeout,设定一个时间限度,然后在那里候着,直到得到通知,或者超过时间。既然有等的一方,也总得有通知的一方吧,不然岂不是每次都超时?记得上面刚出现过的那个结束处理函数usb_api_blocking_completion不?不是吧,被窝都热乎着就不认人了啊,它里面的complete就是用来通知这里的wait_for_completion_timeout。有疑问的话听我慢慢分析:将刚刚初始过的api_context结构体done的地址赋值给了urb,wait_for_completion_timeout行等的就是这个done。在看usb_submit_urb行函数将这个控制urb提交给usb core,它是异步的,也就是说提交了之后不会等到传输完成了才返回。

现在就比较清晰了,usb_start_wait_urb函数将urb提交给usb core去处理后,就停在wait_for_completion_timeout行等候usb core和HCD的处理结果,而这个urb代表的控制传输完成之后会调用结束处理函数usb_api_blocking_completion,从而调用complete来通知usb_start_wait_urb说不用再等了,传输已经完成了,当然还有种可能是usb_start_wait_urb已经等的超过了时间限度仍然没有接到通知,不管是哪种情况,usb_start_wait_urb都可以不用再等,继续往下走了。

usb_submit_urb提交urb,并返回一个值表示是否提交成功了,显然,成功的可能性要远远大于失败的可能性,所以接下来的判断加上了unlikely。下一节会专门分析这个函数。

expire行,计算超时值。超时值在参数里存在了,为什么还要再计算?没错,你是在参数里是指定了自己能够忍受的最大时间限度,不过那是以ms为单位的,作为一个平头小百姓,咱们的时间概念里也只有分秒毫秒啊什么的,不过在linux里时间概念必须得加上一个号称jiffy的东东,因为函数wait_for_completion_timeout里的超时参数是必须以jiffy为单位的。Jiffy意思是短暂的时间跨度,短暂到什么程度?linux里它表示的是两次时钟中断的间隔,时钟中断是由定时器周期性产生的,关于这个周期,内核里有个巨直白巨形象的变量来描述,就是HZ,它是个体系结构相关的常数。内核里还提供了专门的计数器来记录从系统引导开始所度过的jiffy值,每次时钟中断发生时,这个计数器就增加1。既然你指定的时间和wait_for_completion_timeout需要的时间单位不一致,就需要转换一下,msecs_to_jiffies函数可以完成这个工作,它将ms值转化为相应的jiffy值。这一行里还剩个MAX_SCHEDULE_TIMEOUT比较陌生,在include/linux/sched.h里它被定义为LONG_MAX,最大的长整型值,你可能会好奇这个LONG_MAX是怎么来的,我也很好奇,所以咱们去include/linux/kernel.h里看看。

#define USHORT_MAX ((u16)(~0U))

#define SHORT_MAX ((s16)(USHORT_MAX>>1))

#define SHORT_MIN (-SHORT_MAX - 1)

#define INT_MAX ((int)(~0U>>1))

#define INT_MIN (-INT_MAX - 1)

#define UINT_MAX (~0U)

#define LONG_MAX ((long)(~0UL>>1))

#define LONG_MIN (-LONG_MAX - 1)

#define ULONG_MAX (~0UL)

#define LLONG_MAX ((long long)(~0ULL>>1))

#define LLONG_MIN (-LLONG_MAX - 1)

#define ULLONG_MAX (~0ULL)

各种整型数的最大值最小值都在这里了, ‘~’是按位取反,‘UL’是无符号长整型,那么‘ULL’就是64位的无符号长整型,‘<<’左移运算的话就是直接一股脑的把所有位往左边儿移若干位,‘>>’右移运算比较容易搞混,主要是牵涉到怎么去补缺,有关空缺儿的事情在哪里都会比较的复杂,在C里主要就是无符号整数和有符号整数的之间的冲突。往右移多少位之后,空出来的那些空缺,对于无符号整数得补0,对于有符号的,得补上符号位。

还是拿LONG_MAX来说事儿,上边定义为((long)(~0UL>>1)),0UL按位取反之后全为1的无符号长整型,向右移1位,左边儿空出来的那个补0,这个数对于无符号的long来说不算什么,但是再经过long这么强制转化一下变为有符号的长整型。现在明白expire行的作用了吗?把指定的超时时间转化为相应的jiffy值,或者直接被设定为最大的long值。

现在我们需要知道怎么去判断等待的结果,也就是wait_for_completion_timeout的返回值代表什么意思?一般来说,一个函数返回了0代表了好消息,一切顺利,如果你这么想那可就错了。wait_for_completion_timeout返回0恰恰表示的是坏消息,表示直到超过了自己的忍耐的极限仍没有接到任何的回馈,而返回了一个大于0的值则表示接到通知了,那边儿不管是完成了还是出错了总归是告诉这边儿不用再等了,这个值具体的含义就是距你设定的时限提前了多少时间。为什么会这样?你去看看wait_for_completion_timeout的定义就知道了,我就不贴了,它是通过schedule_timeout来实现超时的,schedule_timeout的返回值就是这么个意思。

那么现在就很明显了,如果超时了,就打印一些调试信息提醒一下,然后调用usb_kill_urb终止这个urb,再将返回值设定一下。如果收到了通知,就简单了,直接设定了返回值,就接着往下走。

actual_length是用来记录实际传输的数据长度的,是上上层usb_control_msg函数需要的。有人说这个值urb里本来就有保存,何必再多次一举找个地儿去放?看到后面usb_free_urb函数了吗?把这个urb给销毁了啊,哪里还去找这个值。actual_length是从上头儿那里传递过来的一个指针,遇到指针一定要小心再小心啊,同志们。所以这里要先判断一下actual_length是不是空的。

现在,只剩一个usb_submit_urb在刚才分析时飘过。

usb驱动开发13之设备生命线的更多相关文章

  1. usb驱动开发14之设备生命线

    直接看代码吧. /*-------------------------------------------------------------------*/ /** * usb_submit_urb ...

  2. usb驱动开发17之设备生命线

    拜会完了山头的几位大哥,还记得我们从哪里来要到哪里去吗?时刻不能忘记自身的使命啊.我们是从usb_submit_urb()最后的那个遗留问题usb_hcd_submit_urb()函数一路走来,现在就 ...

  3. usb驱动开发16之设备生命线

    回到struct usb_hcd,继续努力的往下看. kref,usb主机控制器的引用计数.struct usb_hcd也有自己专用的引用计数函数,看hcd.c文件. static void hcd_ ...

  4. usb驱动开发15之设备生命线

    总算是进入了HCD的片儿区,既然来到一个片区,怎么都要去拜会一下山头几个大哥吧.,先回忆一些我们怎么到这里的?给你列举一个调用函数过程usb_control_msg->usb_internal_ ...

  5. usb驱动开发12之设备生命线

    函数usb_control_msg完成一些初始化后调用了usb_internal_control_msg之后就free urb.剩下的活,全部留给usb_internal_control_msg去做了 ...

  6. usb驱动开发18之设备生命线

    现在已经使用GET_DESCRIPTOR请求取到了包含一个配置里所有相关描述符内容的一堆数据,这些数据是raw的,即原始的,所有数据不管是配置描述符.接口描述符还是端点描述符都挤在一起,所以得想办法将 ...

  7. usb驱动开发11之设备生命线

    暂时先告别媒人,我们去分析各自的生命旅程,最后还会回到usb_device_match函数. 首先当你将usb设备连接在hub的某个端口上,hub检测到有设备连接了进来,它会为设备分配一个struct ...

  8. HarmonyOS USB DDK助你轻松实现USB驱动开发

    HDF(Hardware Driver Foundation)驱动框架是HarmonyOS硬件生态开放的基础,为开发者提供了驱动加载.驱动服务管理和驱动消息机制等驱动能力,让开发者能精准且高效地开发驱 ...

  9. usb驱动开发22之驱动生命线

    我们总是很喜欢高潮,不是吗?那就好好对待她哦.我们来看一下linux中的高潮部分设备是怎么从Address进入Configured的. usb_set_configuration函数的代码就不贴了,可 ...

随机推荐

  1. 【代码笔记】iOS-GTMBase64

    一,工程文件. 二,代码. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading th ...

  2. UITableView详细注释

    style //普通 UITableViewStylePlain, //分组 UITableViewStyleGrouped //表格视图 UITableView * tableView = [[UI ...

  3. Jquery mobile 中在列表项上使用单选按钮

      无意中发现可以在li上实现开关按钮 http://jsfiddle.net/Gajotres/pzfr2/                 触类旁通,终于实现了在li上增加单选按钮组   @mod ...

  4. Spark:利用Eclipse构建Spark集成开发环境

    前一篇文章“Apache Spark学习:将Spark部署到Hadoop 2.2.0上”介绍了如何使用Maven编译生成可直接运行在Hadoop 2.2.0上的Spark jar包,而本文则在此基础上 ...

  5. YUM源的简介,配置与使用

    A.yum 简介 yum,是Yellow dog Updater, Modified 的简称,是杜克大学为了提高RPM 软件包安装性而开发的一种软件包管理器.起初是由yellow dog 这一发行版的 ...

  6. XCLNetTools1.0(ASP.NET常用类库)

    版权声明:本文为博主原创文章,未经博主允许不得转载. 2016-01-01开放所有源代码: 项目地址:https://github.com/xucongli1989/XCLNetTools 下载地址: ...

  7. Java Gradle入门指南之插件管理(类型、导入及java plugin使用)

        上一篇随笔介绍了如何使用Gradle内建任务,介绍了自定义Gradle任务类的三种方法(build文件,buildSrc文件夹.新建groovy项目),一个任务是一个原子操作,即不可分割的.项 ...

  8. 【ASH】如何导出视图DBA_HIST_ACTIVE_SESS_HISTORY的查询结果数据

    [ASH]如何导出视图DBA_HIST_ACTIVE_SESS_HISTORY的查询结果数据 1.1  BLOG文档结构图 1.2  前言部分 1.2.1  导读和注意事项 各位技术爱好者,看完本文后 ...

  9. Js 关于console 在IE 下的兼容问题

    程序员在开发代码的过程中,使用console作为调试代码过程的一种手段. 发布到测试生产环境,发现IE8 出现加载错误.使用开发者工具调试,发现可以绕过问题. 通过网络搜索和在项目中进行修正. 以下办 ...

  10. Altium Desiner 警告 adding hidden net

    这是因为 一些元件 隐藏了 vcc GND 或者没有使用vcc GND ,用不着它也报警告了. 这里可以将 vcc GND删掉这个管脚.