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

/* may be called in any context with a valid urb->dev usecount
* caller surrenders "ownership" of urb
* expects usb_submit_urb() to have sanity checked and conditioned all
* inputs in the urb
*/
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
int status;
struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus);
struct usb_host_endpoint *ep;
unsigned long flags; if (!hcd)
return -ENODEV; usbmon_urb_submit(&hcd->self, urb); /*
* Atomically queue the urb, first to our records, then to the HCD.
* Access to urb->status is controlled by urb->lock ... changes on
* i/o completion (normal or fault) or unlinking.
*/ // FIXME: verify that quiescing hc works right (RH cleans up) spin_lock_irqsave (&hcd_data_lock, flags);
ep = (usb_pipein(urb->pipe) ? urb->dev->ep_in : urb->dev->ep_out)
[usb_pipeendpoint(urb->pipe)];
if (unlikely (!ep))
status = -ENOENT;
else if (unlikely (urb->reject))
status = -EPERM;
else switch (hcd->state) {
case HC_STATE_RUNNING:
case HC_STATE_RESUMING:
doit:
list_add_tail (&urb->urb_list, &ep->urb_list);
status = 0;
break;
case HC_STATE_SUSPENDED:
/* HC upstream links (register access, wakeup signaling) can work
* even when the downstream links (and DMA etc) are quiesced; let
* usbcore talk to the root hub.
*/
if (hcd->self.controller->power.power_state.event == PM_EVENT_ON
&& urb->dev->parent == NULL)
goto doit;
/* FALL THROUGH */
default:
status = -ESHUTDOWN;
break;
}
spin_unlock_irqrestore (&hcd_data_lock, flags);
if (status) {
INIT_LIST_HEAD (&urb->urb_list);
usbmon_urb_submit_error(&hcd->self, urb, status);
return status;
} /* increment urb's reference count as part of giving it to the HCD
* (which now controls it). HCD guarantees that it either returns
* an error or calls giveback(), but not both.
*/
urb = usb_get_urb (urb);
atomic_inc (&urb->use_count); if (urb->dev == hcd->self.root_hub) {
/* NOTE: requirement on hub callers (usbfs and the hub
* driver, for now) that URBs' urb->transfer_buffer be
* valid and usb_buffer_{sync,unmap}() not be needed, since
* they could clobber root hub response data.
*/
status = rh_urb_enqueue (hcd, urb);
goto done;
} /* lower level hcd code should use *_dma exclusively,
* unless it uses pio or talks to another transport.
*/
if (hcd->self.uses_dma) {
if (usb_pipecontrol (urb->pipe)
&& !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
urb->setup_dma = dma_map_single (
hcd->self.controller,
urb->setup_packet,
sizeof (struct usb_ctrlrequest),
DMA_TO_DEVICE);
if (urb->transfer_buffer_length != 0
&& !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP))
urb->transfer_dma = dma_map_single (
hcd->self.controller,
urb->transfer_buffer,
urb->transfer_buffer_length,
usb_pipein (urb->pipe)
? DMA_FROM_DEVICE
: DMA_TO_DEVICE);
} status = hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags);
done:
if (unlikely (status)) {
urb_unlink (urb);
atomic_dec (&urb->use_count);
if (urb->reject)
wake_up (&usb_kill_urb_queue);
usbmon_urb_submit_error(&hcd->self, urb, status);
usb_put_urb (urb);
}
return status;
}

usb_hcd_submit_urb目标就是将提交过来的urb指派给合适的主机控制器驱动程序。

看第一个函数,bus_to_hcd是用来获得struct usb_bus结构体对应的struct usb_hcd结构体,urb要去的那个设备所在的总线是在设备生命线的开头儿就初始化好了的,忘了可以再蓦然回首一下。

接下来就是一些必备的常规检验,如果usb_hcd都还是空的,返回吧。

usbmon_urb_submit就是与前面的usb Monitor有关的,如果你编译内核的时候没有配置上CONFIG_USB_MON,它就啥也不是,一个空函数。

spin_lock_irqsave去获得一把锁,这把锁在hcd.c的开头儿就已经初始化好了,所以说是把全局锁

* used when updating hcd data */

static DEFINE_SPINLOCK(hcd_data_lock);

现在简单介绍一下自旋锁,懂了的就请飘过。它和信号量,还有前面提到的completion一样都是linux里用来进行代码同步(或者同步资源,互斥访问,随你怎么叫),为什么要进行同步?你要知道在linux这个庞大负责的世界里,可能同时有多个线程,那么只要他们互相之间有一定的共享,就必须要保证一个人操作这个共享的时候让其它人知道。举个例子,你和另外一个人带领两只队伍去打土匪窝,结果你方武力值,智力值比较高,先占领了山头儿,你得插把旗子表示你已经占领了,让友军不要再打了,过来享受胜利果实吧,里边土匪太太,美酒美食一堆一堆的,如果你什么都不说,没插旗子也没其它什么暗号表示一下,只顾自个儿享受了,那边儿正打着过瘾谁知道在匪窝儿里的是你还是土匪啊。所以说同步是多重要啊,保持共享代码状态一致是多么重要啊。自旋锁身为同步机制的一种,自然也有它独特的本事,它可以用在中断上下文或者说原子上下文使用。上下文就是你代码运行的环境,linux的这个环境使用两种,能睡觉的环境和不能睡觉的环境。像信号量和completion就只能用在可以睡觉的环境,而自旋锁就用在不能睡觉的环境里。而咱们的usb_submit_urb还有usb_hcd_submit_urb必须得在两种环境里都能够使用,所以使用的是自旋锁,那在什么时候都不能睡觉了还有心情去调用它们那?想想urb的那个结束处理函数,它就是不能睡觉的,但它里面必须得能够重新提交urb。

那hcd_data_lock这把锁究竟是用来保护些什么的,为什么要使用它?主机控制器的struct usb_hcd结构体在它的驱动里早就初始化好了,就那么一个,但同一时刻是可能有多个urb向同一主机控制器申请进行传输,可能有多个地方都希望访问它里面的内容。

所以hcd_data_lock这把锁就是专门用来保护主机控制器的这个结构体的。

接着自旋锁往下看,知道了pipe,就可以从struct usb_device里的两个数组ep_in和ep_out里拿出对应端点的struct usb_host_endpoint结构体。

然后判断检验错误,如果一切正常,则继续往下走。

检验都通过了,usb_get_urb行就是增加urb的引用计数了。

下一行同时将urb的use_count也增加1,表示urb已经被HCD接受了,正在被处理着。你如果对这两个引用计数什么差别还有疑问,再蓦然回首一下看看说struct urb时讲的。

然后判断这个urb是不是流向root hub的,如果是,它就走向了root hub的生命线。如果这个主机控制器支持DMA,可你却没有告诉它URB_NO_SETUP_DMA_MAP或URB_NO_TRANSFER_DMA_MAP这两个标志,它就会认为你在urb里没有提供DMA的缓冲区,就会调用dma_map_single将setup_packet或transfer_buffer映射为DMA缓冲区。

hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags)行将urb扔给具体的主机控制器驱动程序了。下面的路就让urb去走吧

咱们说到这里也该回头看看,始终都不能忘怀自己是从设置设备地址,发送SET_ADDRESS请求给主机控制器开始,一路走到现在。如今设备已经可以进入Address状态,这桩心愿已了,该继续看设备的那条生命线了。

回顾已经分析的设备生命线过程,从Attached走到Powered只是弹指一挥间,从Powered再到Default虽说要复位一下,也算是三下五除二了,再从Default走到Address。接下来设备的目标当然就是Configured了。

要进入Configured状态,你得去配置设备,当然不能是盲目的去配置,要知道设备是可能有多个配置的,所以你要有选择有目的有步骤有计划的去配置。就要先去获得设备的设备描述符,message.c中的usb_get_device_descriptor()就是core里专门干这个的。这个函数比较流程大概是准备了一个struct usb_device_descriptor结构体,然后就用它去调用message.c里的usb_get_descriptor()获得设备描述符,获得之后再把得到的描述符复制到设备struct usb_device结构体的descriptor成员里。因此,这个函数成功与否的关键就在usb_get_descriptor()。其实对于写驱动的来说,眼里是只有usb_get_descriptor()没有usb_get_device_descriptor()的,不管你想获得哪种描述符都是要通过usb_get_descriptor(),而usb_get_device_descriptor()是专属内核用的接口。

/*-------------------------------------------------------------------*/

/**
* usb_get_descriptor - issues a generic GET_DESCRIPTOR request
* @dev: the device whose descriptor is being retrieved
* @type: the descriptor type (USB_DT_*)
* @index: the number of the descriptor
* @buf: where to put the descriptor
* @size: how big is "buf"?
* Context: !in_interrupt ()
*
* Gets a USB descriptor. Convenience functions exist to simplify
* getting some types of descriptors. Use
* usb_get_string() or usb_string() for USB_DT_STRING.
* Device (USB_DT_DEVICE) and configuration descriptors (USB_DT_CONFIG)
* are part of the device structure.
* In addition to a number of USB-standard descriptors, some
* devices also use class-specific or vendor-specific descriptors.
*
* This call is synchronous, and may not be used in an interrupt context.
*
* Returns the number of bytes received on success, or else the status code
* returned by the underlying usb_control_msg() call.
*/
int usb_get_descriptor(struct usb_device *dev, unsigned char type, unsigned char index, void *buf, int size)
{
int i;
int result; memset(buf,0,size); // Make sure we parse really received data for (i = 0; i < 3; ++i) {
/* retry on length 0 or error; some devices are flakey */
result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
(type << 8) + index, 0, buf, size,
USB_CTRL_GET_TIMEOUT);
if (result <= 0 && result != -ETIMEDOUT)
continue;
if (result > 1 && ((u8 *)buf)[1] != type) {
result = -EPROTO;
continue;
}
break;
}
return result;
}

参数type就是用来区分不同的描述符的,协议里说了,GET_DESCRIPTOR请求主要就是适用于三种描述符,设备描述符,配置描述符和字符串描述符。参数index是要获得的描述符的序号,如果希望得到的这种描述符设备里可以有多个,你需要指定获得其中的哪个,比如配置描述符就可以有多个,不过对于设备描述符来说,是只有一个的,所以这里的index应该为0。参数buf和size就是描述你用来放置获得的描述符的缓冲区的。

这个函数主要就是调用了一个usb_control_msg(),现在仔细说说这个函数usb_control_msg()。

这里要说的第一个问题是它的一堆参数,这就需要认真了解一下spec 9.4.3里的这张表

GET_DESCRIPTOR请求的数据传输方向很明显是device-to-host的,而且还是协议里规定所有设备都要支持的标准请求,也不是针对端点或者接口什么的,而是针对设备的,所以bRequestType只能为0x80,就是上面表里的10000000B,也等于参数中的USB_DIR_IN。wValue的高位字节表示描述符的类型,低位字节表示描述符的序号,所以就有参数中的(type << 8) + index。wIndex对于字符串描述符应该设置为使用语言的ID,对于其它的描述符应该设置为0,所以参数中间的那个0。至于wLength,就是描述符的长度,对于设备描述符,一般来说你都会指定为USB_DT_DEVICE_SIZE吧。USB_CTRL_GET_TIMEOUT是定义在include/linux/usb.h里的一个宏,值为5000,表示有5s的超时时间。

第二个问题就是为什么会有3次循环。这个又要归咎于一些不守规矩的厂商了,搞出的设备古里古怪的,比如一些usb读卡器,一次请求还不定能成功,但是设备描述符拿不到接下来就没法子走了,所以这里多试几次,再不成功,就成鬼了。If判断的代码都是判断是不是成功得到请求的描述符,这个版本的内核这里的判断还比较混乱,就不多说了,你只要知道((u8 *)buf)[1] != type是用来判断获得描述符是不是请求的类型就可以了。

现在设备描述符已经有了,但是只有设备描述符是远远不够的,你从设备描述符里只能知道它一共支持几个配置,具体每个配置是是什么?所以接下来就要获得各个配置的配置描述符,并且拿结果去充实struct usb_device的config、rawdescriptors等相关元素。core内部并不直接调用上面的usb_get_descriptor()去完成这个任务,而是调用config.c里的usb_get_configuration()。

看这个函数代码前,先说点理论垫底。前面经过那么多努力,要想得到配置描述符,最终都不可避免的要向设备发送GET_DESCRIPTOR请求,这就需要以USB_DT_CONFIG为参数调用usb_get_descriptor函数,也就需要知道该为获得的描述符准备多大的一个缓冲区,本来这个长度应该很明确的为USB_DT_CONFIG_SIZE,它表示的就是配置描述符的大小,但是实际上不是这么回事儿,USB_DT_CONFIG_SIZE只表示配置描述符本身的大小,并不表示GET_DESCRIPTOR请求返回结果的大小。因为向设备发送GET_DESCRIPTOR请求时,设备并不单单返回一个配置描述符了事,而是一股脑儿的将这个配置下面的所有接口描述符,端点描述,还有class-或vendor-specific描述符都返回了给你(协议里这么说的,不是我胡诌哦)。那么这个总长度如何得到那?配置描述符里有这样一个神秘的字段wTotalLength,它里面记录的就是这个总长度,那么问题就简单了,可以首先发送USB_DT_CONFIG_SIZE个字节的请求过去,获得这个配置描述符的内容,从而获得那个总长度,然后以这个长度再请求一次,这样就可以获得一个配置下面所有的描述符内容了。上面的usb_get_configuration()采用的就是这个处理方法。

// hub-only!! ... and only in reset path, or usb_new_device()
// (used by real hubs and virtual root hubs)
int usb_get_configuration(struct usb_device *dev)
{
struct device *ddev = &dev->dev;
int ncfg = dev->descriptor.bNumConfigurations;
int result = -ENOMEM;
unsigned int cfgno, length;
unsigned char *buffer;
unsigned char *bigbuffer;
struct usb_config_descriptor *desc; if (ncfg > USB_MAXCONFIG) {
dev_warn(ddev, "too many configurations: %d, "
"using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
} if (ncfg < 1) {
dev_err(ddev, "no configurations\n");
return -EINVAL;
} length = ncfg * sizeof(struct usb_host_config);
dev->config = kzalloc(length, GFP_KERNEL);
if (!dev->config)
goto err2; length = ncfg * sizeof(char *);
dev->rawdescriptors = kzalloc(length, GFP_KERNEL);
if (!dev->rawdescriptors)
goto err2; buffer = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
if (!buffer)
goto err2;
desc = (struct usb_config_descriptor *)buffer; for (cfgno = 0; cfgno < ncfg; cfgno++) {
/* We grab just the first descriptor so we know how long
* the whole configuration is */
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
buffer, USB_DT_CONFIG_SIZE);
if (result < 0) {
dev_err(ddev, "unable to read config index %d "
"descriptor/%s\n", cfgno, "start");
dev_err(ddev, "chopping to %d config(s)\n", cfgno);
dev->descriptor.bNumConfigurations = cfgno;
break;
} else if (result < 4) {
dev_err(ddev, "config index %d descriptor too short "
"(expected %i, got %i)\n", cfgno,
USB_DT_CONFIG_SIZE, result);
result = -EINVAL;
goto err;
}
length = max((int) le16_to_cpu(desc->wTotalLength),
USB_DT_CONFIG_SIZE); /* Now that we know the length, get the whole thing */
bigbuffer = kmalloc(length, GFP_KERNEL);
if (!bigbuffer) {
result = -ENOMEM;
goto err;
}
result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
bigbuffer, length);
if (result < 0) {
dev_err(ddev, "unable to read config index %d "
"descriptor/%s\n", cfgno, "all");
kfree(bigbuffer);
goto err;
}
if (result < length) {
dev_warn(ddev, "config index %d descriptor too short "
"(expected %i, got %i)\n", cfgno, length, result);
length = result;
} dev->rawdescriptors[cfgno] = bigbuffer; result = usb_parse_configuration(&dev->dev, cfgno,
&dev->config[cfgno], bigbuffer, length);
if (result < 0) {
++cfgno;
goto err;
}
}
result = 0; err:
kfree(buffer);
dev->descriptor.bNumConfigurations = cfgno;
err2:
if (result == -ENOMEM)
dev_err(ddev, "out of memory\n");
return result;
}

在获得设备描述符的数目后,迎来了常规检查。飘过吧

宏USB_MAXCONFIG是config.c理定义的,限制了一个设备最多只能支持8种配置拥有8个配置描述符,如果超出了这个限制,就强制它为这个最大值。不过如果设备里没有任何一个配置描述符,什么配置都没有就给你返回错误。

struct usb_device里的config表示的是设备拥有的所有配置,你设备有多少个配置就为它准备多大的空间。

rawdescriptors这是个字符指针数组里的每一项都指向一个使用GET_DESCRIPTOR请求去获取配置描述符时所得到的结果。

然后准备一个大小为USB_DT_CONFIG_SIZE的缓冲区,第一次发送GET_DESCRIPTOR请求要用的。

剩下的主要就是这个for循环了,获取每一个配置的那些描述符。

如同上面理论所说那样,首先发送USB_DT_CONFIG_SIZE个字节请求,获得配置描述符的内容。然后对返回的结果进行检验,知道为什么会判断结果是不是小于4么?答案尽在配置描述符中,里面的3,4字节就是wTotalLength,只要得到前4个字节,就已经完成任务能够获得总长度了。

既然总长度已经有了,那么这里就为接下来的GET_DESCRIPTOR请求准备一个大点的缓冲区。现在可以获得这个配置相关的所有描述符了。然后是对返回结果的检验,再然后就是将得到的那一堆数据的地址赋给rawdescriptors数组里的指针。

最后usb_parse_configuration函数将对前面GET_DESCRIPTOR请求获得的那堆数据做处理。

且让我们稍作休息,一根烟时间后再见,你觉得休息不够那就两支烟时间,如果还觉得不够,你就不要回来了。

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

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

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

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

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

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

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

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

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

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

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

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

    上一节勉勉强强把struct urb这个中心给说完,接着看那三个基本点. 第一个基本点,usb_alloc_urb函数,创建urb的专用函数,为一个urb申请内存并做初始化,在drviers/usb/ ...

  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-根据size截取屏幕中间矩形区域

    代码: RootViewController.m #import "RootViewController.h" @interface RootViewController () @ ...

  2. GET和POST请求

    GET与POST请求 简介 GET请求解释及语法格式 POST请求简介及语法 GET请求代码 POST请求代码 GET请求解释及语法格式: 网络请求默认是get 网络请求有很多种:GET查 POST改 ...

  3. IOS block 循环引用的解决

    在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...

  4. tomcat常见错误及解决方案

    一,tomcat启动时错误 1:The JAVA_HOME environment variable is not defined This environment variable is neede ...

  5. git diff的用法

    在git提交环节,存在三大部分:working tree(工作区), index file(暂存区:stage), commit(分支:master) working tree:就是你所工作在的目录, ...

  6. @OneToMany---ManyToOne

    http://blog.csdn.net/gebitan505/article/details/22619175 一对多,字段只是在多的一方,SQL数据库和JAVA中不同 SQL数据库表: 多的一方: ...

  7. Vmware快速安装linux虚拟机(SUSE)

    安装环境:Vmware 11.SUSE11 64位 vmware快速安装linux虚拟机的过程还是比较简单的,步骤如下: 1.点击文件,新建虚拟机. 2.选择典型安装. 3.在红框中选择想要安装的虚拟 ...

  8. Go语言异步服务器框架原理和实现

    Go语言类库中,有两个官方的服务器框架,一个HTTP,一个是RPC.使用这个两个框架,已经能解决大部分的问题,但是,也有一些需求,这些框架是不够的,这篇文章,我们先分析一下HTTP 和 RPC服务器的 ...

  9. ORA-00600: internal error code, arguments: [kcblasm_1], [103], [], [], [], [], [], []

    一ORACLE 10.2.0.5.0 标准版的数据库的告警日志出现ORA-00600错误,具体错误信息如下所示 Errors in file /u01/app/oracle/admin/SCM2/bd ...

  10. mysql源码解读之事务提交过程(一)

    mysql是一种关系型数据库,关系型数据库一个重要的特性就是支持事务,这是区别于no-sql产品的一个核心特性.当然了,no-sql产品支持键值查询,不能支持sql语句,这也是一个区别.今天主要讨论下 ...