linux设备驱动之usb下载,linux设备驱动之USB数据传输分析(转载)

------------------------------------------

本文系本站原创,欢迎转载!

转载请注明出处:http://ericxiao.cublog.cn/

------------------------------------------

三:传输过程的实现

说到传输过程,我们必须要从URB开始说起,这个结构的就好比是网络子系统中的skb,好比是I/O中的bio.USB系统的信息传输就是打成URB结构,然后再过行传送的.

URB的全称叫USB request block.下面从它的接口说起.

3.1:URB的相关接口

1:URB的创建

URB的创建是由usb_alloc_urb()完成的.这个函数会完成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;

}

这个函数有两个参数,一个是iso_packets.仅仅用于ISO传输.表示ISO数据包个数,如果用于其它类型的传输,此参数为0.另一个是mem_flags.是分配内存的参数.

Usb_init_urb()如下:

void usb_init_urb(struct urb *urb)

{

if

(urb) {

memset(urb, 0, sizeof(*urb));

kref_init(&urb->kref);

INIT_LIST_HEAD(&urb->anchor_list);

}

}

由此可以看到,它的初始化只是初始化了引用计数和ahchor_list链表.这个链表在URB被锁定的时候会用到.

2:URB的初始化

USB2.0 spec中定义了四种传输,为别为ISO,INTER,BULK,CONTORL.linux

kernel为INTER,BULK,CONTORL的URB初始化提供了一些API,ISO的传输只能够手动去初始化.这些API如下:

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)

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)

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)

分别用来填充CONTORL,BULK,INT类型的URB.

观察他们的函数原型,发现有很多相的的参数.先对这些参数做一下解释:

Urb:是要初始化的urb

Dev:表示消息要被发送到的USB设备

Pipe:表示消息被发送到的端点

transfer_buffer:表示发送数据的缓冲区

length:就是transfer_buffer所表示的缓冲区大小

context:完成处理函数的上下文

complete_fn:传输完了之后要调用的函数.

usb_fill_control_urb()的setup_packet:即将被发送到端点的设备数据包

usb_fill_int_urb()中的interval:这个urb应该被调度的间隔.

函数的实际都是差不多的.以usb_fill_control_urb()为例:

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)

{

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相关的成员而已.

另外,关于ISO的URB初始化虽然没有可以调用的API,但它的初始化也很简单,对应就是填充几个成员而已.

另外,对于pipe的参数.有一系列辅助的宏.如下示:

#define

usb_sndctrlpipe(dev,endpoint) \

((PIPE_CONTROL << 30) |

__create_pipe(dev, endpoint))

#define

usb_rcvctrlpipe(dev,endpoint) \

((PIPE_CONTROL << 30) |

__create_pipe(dev, endpoint) | USB_DIR_IN)

#define

usb_sndisocpipe(dev,endpoint) \

((PIPE_ISOCHRONOUS << 30) |

__create_pipe(dev, endpoint))

#define

usb_rcvisocpipe(dev,endpoint) \

((PIPE_ISOCHRONOUS << 30) |

__create_pipe(dev, endpoint) | USB_DIR_IN)

#define

usb_sndbulkpipe(dev,endpoint) \

((PIPE_BULK << 30) |

__create_pipe(dev, endpoint))

#define

usb_rcvbulkpipe(dev,endpoint) \

((PIPE_BULK << 30) |

__create_pipe(dev, endpoint) | USB_DIR_IN)

#define

usb_sndintpipe(dev,endpoint) \

((PIPE_INTERRUPT << 30) |

__create_pipe(dev, endpoint))

#define

usb_rcvintpipe(dev,endpoint) \

((PIPE_INTERRUPT << 30) |

__create_pipe(dev, endpoint) | USB_DIR_IN)

这个宏都是根据usb2.0 spec的规范来设计的.

3:提交URB

提交urb的接口是usb_submit_urb().代码如下:

int usb_submit_urb(struct urb *urb, gfp_t mem_flags)

{

int xfertype, max;

struct

usb_device *dev;

struct

usb_host_endpoint *ep;

int is_out;

if

(!urb || urb->hcpriv ||

!urb->complete)

return -EINVAL;

dev =

urb->dev;

if

((!dev) || (dev->state <

USB_STATE_DEFAULT))

return -ENODEV;

//取得要传输的端口.对端地址是由方向+dev address+port number组成的

ep =

(usb_pipein(urb->pipe) ? dev->ep_in :

dev->ep_out)

[usb_pipeendpoint(urb->pipe)];

if

(!ep)

return -ENOENT;

urb->ep = ep;

urb->status = -EINPROGRESS;

urb->actual_length = 0;

//取得ep的传输类型

xfertype =

usb_endpoint_type(&ep->desc);

//如果是控制传输.端点0默认是控制传输

if

(xfertype == USB_ENDPOINT_XFER_CONTROL) {

//控制传输的urb如果没有setup_packet是非法的

struct usb_ctrlrequest *setup =

(struct usb_ctrlrequest *) urb->setup_packet;

if (!setup)

return -ENOEXEC;

//判断是否是out方向的传输

is_out = !(setup->bRequestType &

USB_DIR_IN) ||

!setup->wLength;

} else

{

//如果不是控制传输,在端点描述符的bEndportAddress的bit7 包含有端点的传输方向

is_out =

usb_endpoint_dir_out(&ep->desc);

}

//根据传输方向.置urb->transfer_flags的方向位

urb->transfer_flags =

(urb->transfer_flags &

~URB_DIR_MASK) |

(is_out ? URB_DIR_OUT : URB_DIR_IN);

//根据usb2.0 spec.除控制传输外的其它传输只有在config状态的时候才能进行

if

(xfertype != USB_ENDPOINT_XFER_CONTROL

&&

dev->state <

USB_STATE_CONFIGURED)

return -ENODEV;

//传送/接收的最大字节.如果这个最大巧若拙字节还要小于0,那就是非法的

max =

le16_to_cpu(ep->desc.wMaxPacketSize);

if (max

<= 0) {

dev_dbg(&dev->dev,

"bogus endpoint ep%d%s in %s (bad maxpacket %d)\n",

usb_endpoint_num(&ep->desc), is_out

? "out" : "in",

__FUNCTION__, max);

return -EMSGSIZE;

}

//如果是实时传输

if

(xfertype == USB_ENDPOINT_XFER_ISOC) {

int n, len;

//如果是高速传输.则要修正它的MAX值

//高速传输时, 一个微帧内可以修输多个数据.bit 11~bit12用来表示一个微帧内

//传输包的个数.

//在USB1.1中是不支持HIGH的

if (dev->speed == USB_SPEED_HIGH) {

int mult = 1 + ((max >> 11)

& 0x03);

max &= 0x07ff;

max *= mult;

}

//实现传输的数据包数目不能小于等于0

if (urb->number_of_packets <=

0)

return -EINVAL;

//urb->number_of_packets:

实时数据包个数.每个实时数据包对应urb->iso_frame_desc[]中的一项

for (n = 0; n <

urb->number_of_packets; n++) {

len = urb->iso_frame_desc[n].length;

if (len < 0 || len > max)

return -EMSGSIZE;

urb->iso_frame_desc[n].status = -EXDEV;

urb->iso_frame_desc[n].actual_length = 0;

}

}

//如果要传输的缓存区大小小于0.非法

if

(urb->transfer_buffer_length <

0)

return -EMSGSIZE;

#ifdef DEBUG

{

unsigned int orig_flags = urb->transfer_flags;

unsigned int allowed;

allowed

= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP |

URB_NO_INTERRUPT | URB_DIR_MASK | URB_FREE_BUFFER);

switch

(xfertype) {

case

USB_ENDPOINT_XFER_BULK:

if (is_out)

allowed |= URB_ZERO_PACKET;

case

USB_ENDPOINT_XFER_CONTROL:

allowed |= URB_NO_FSBR;

default:

if (!is_out)

allowed |= URB_SHORT_NOT_OK;

break;

case

USB_ENDPOINT_XFER_ISOC:

allowed |= URB_ISO_ASAP;

break;

}

urb->transfer_flags &=

allowed;

if

(urb->transfer_flags != orig_flags) {

err("BOGUS urb flags, %x --> %x",

orig_flags, urb->transfer_flags);

return -EINVAL;

}

}

#endif

//关于实时传输和中断传输的interval处理

switch

(xfertype) {

case

USB_ENDPOINT_XFER_ISOC:

case

USB_ENDPOINT_XFER_INT:

//interval不能小于或等于0

if (urb->interval <= 0)

return -EINVAL;

switch (dev->speed) {

case

USB_SPEED_HIGH:

if (urb->interval > (1024 * 8))

urb->interval = 1024 * 8;

max = 1024 * 8;

break;

case

USB_SPEED_FULL:

case USB_SPEED_LOW:

if (xfertype == USB_ENDPOINT_XFER_INT) {

if (urb->interval > 255)

return -EINVAL;

max = 128;

} else {

if (urb->interval > 1024)

urb->interval = 1024;

max = 1024;

}

break;

default:

return -EINVAL;

}

urb->interval = min(max, 1

<<

ilog2(urb->interval));

}

return

usb_hcd_submit_urb(urb, mem_flags);

}

这段代码虽然很长,但逻辑很清楚.对照代码中的注释理解应该是没有问题的.在这里要注意,UHCI是属于USB1.1的,它不支持HIGH传输.

对URB进行一系列处理之后,就会将urb丢给hcd进行处理了.usb_hcd_submit_urb()代码如下:

int usb_hcd_submit_urb (struct urb *urb, gfp_t

mem_flags)

{

int status;

//从usb_bus的地址取得usb_hcd的地址

struct

usb_hcd *hcd =

bus_to_hcd(urb->dev->bus);

//增加有关的引用计数,usbmon*系列的函数是编译选择的.忽略

usb_get_urb(urb);

atomic_inc(&urb->use_count);

atomic_inc(&urb->dev->urbnum);

usbmon_urb_submit(&hcd->self,

urb);

//对传输的缓存区进行DMA映射

status

= map_urb_for_dma(hcd, urb, mem_flags);

//出现错误,返回

if

(unlikely(status)) {

usbmon_urb_submit_error(&hcd->self,

urb, status);

goto error;

}

//如果是root hub

if

(is_root_hub(urb->dev))

status = rh_urb_enqueue(hcd, urb);

else

//如果是一般的设备

status =

hcd->driver->urb_enqueue(hcd, urb,

mem_flags);

if

(unlikely(status)) {

usbmon_urb_submit_error(&hcd->self,

urb, status);

unmap_urb_for_dma(hcd, urb);

error:

urb->hcpriv = NULL;

INIT_LIST_HEAD(&urb->urb_list);

atomic_dec(&urb->use_count);

atomic_dec(&urb->dev->urbnum);

if (urb->reject)

wake_up(&usb_kill_urb_queue);

usb_put_urb(urb);

}

return

status;

}

在这里函数里要注意到,urb->transfer_buffer是一个虚拟地址,用于UHCI的时候,必须要将其映射物理地址,以供设备使用.这也就是map_urb_for_dma()要完成的工作.

map_urb_for_dma()函数比较简单,这里就不做详细分析.

可能有人会有这样的疑惑,对于root hub的情况,为什么不用对传输缓存区进行DMA映射呢?

在后面的处理中我们可以看到,其实对于root hub

,它不需要进行实际的物理传输,linux按照spec上的规定,将它静态放置在内存中,在进行相关操作的时候,只要直接copy过去就可以了.

其次,要注意,这个函数不能用于中断上下文,因为该函数是同步的,会引起睡眠.

在这里,我们看到,流程最终转入到了下面的代码片段中:

//如果是root hub

if

(is_root_hub(urb->dev))

status = rh_urb_enqueue(hcd, urb);

else

//如果是一般的设备

status =

hcd->driver->urb_enqueue(hcd, urb,

mem_flags);

下面,就分情况来剖析,各种传输到底是怎么完成的.

3.2:控制传输过程

1:root hub的控制传输

在前面看到,对于root hub的情况,流程会转入rh_urb_enqueue().代码如下:

static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb

*urb)

{

//如果是中断传输的端点

if

(usb_endpoint_xfer_int(&urb->ep->desc))

return rh_queue_status (hcd, urb);

//如果是控制传输的端点

if

(usb_endpoint_xfer_control(&urb->ep->desc))

return rh_call_control (hcd, urb);

return

-EINVAL;

}

对应是控制传输的时,流程转入了rh_call_control()中:

static int rh_call_control (struct usb_hcd *hcd, struct urb

*urb)

{

struct

usb_ctrlrequest *cmd;

u16 typeReq, wValue, wIndex, wLength;

u8 *ubuf = urb->transfer_buffer;

u8 tbuf [sizeof (struct usb_hub_descriptor)]

__attribute__((aligned(4)));

const

u8 *bufp =

tbuf;

int len = 0;

int patch_wakeup = 0;

int status;

int n;

might_sleep();

spin_lock_irq(&hcd_root_hub_lock);

//将urb加到ep的urb传输链表

status

= usb_hcd_link_urb_to_ep(hcd, urb);

spin_unlock_irq(&hcd_root_hub_lock);

if

(status)

return status;

//将urb的私有域指向了hcd

urb->hcpriv = hcd;

cmd =

(struct usb_ctrlrequest *) urb->setup_packet;

typeReq = (cmd->bRequestType

<< 8) |

cmd->bRequest;

wValue = le16_to_cpu

(cmd->wValue);

wIndex = le16_to_cpu

(cmd->wIndex);

wLength = le16_to_cpu

(cmd->wLength);

if

(wLength >

urb->transfer_buffer_length)

goto error;

urb->actual_length = 0;

switch

(typeReq) {

case

DeviceRequest | USB_REQ_GET_STATUS:

tbuf [0] =

(device_may_wakeup(&hcd->self.root_hub->dev)

<< USB_DEVICE_REMOTE_WAKEUP)

| (1 <<

USB_DEVICE_SELF_POWERED);

tbuf [1] = 0;

len = 2;

break;

case

DeviceOutRequest | USB_REQ_CLEAR_FEATURE:

if (wValue == USB_DEVICE_REMOTE_WAKEUP)

device_set_wakeup_enable(&hcd->self.root_hub->dev,

0);

else

goto error;

break;

case

DeviceOutRequest | USB_REQ_SET_FEATURE:

if

(device_can_wakeup(&hcd->self.root_hub->dev)

&& wValue ==

USB_DEVICE_REMOTE_WAKEUP)

device_set_wakeup_enable(&hcd->self.root_hub->dev,

1);

else

goto error;

break;

case

DeviceRequest | USB_REQ_GET_CONFIGURATION:

tbuf [0] = 1;

len = 1;

case

DeviceOutRequest | USB_REQ_SET_CONFIGURATION:

break;

case

DeviceRequest | USB_REQ_GET_DESCRIPTOR:

switch (wValue & 0xff00) {

case USB_DT_DEVICE << 8:

if (hcd->driver->flags

& HCD_USB2)

bufp = usb2_rh_dev_descriptor;

else if (hcd->driver->flags

& HCD_USB11)

bufp = usb11_rh_dev_descriptor;

else

goto error;

len = 18;

break;

case USB_DT_CONFIG << 8:

if (hcd->driver->flags

& HCD_USB2) {

bufp = hs_rh_config_descriptor;

len = sizeof hs_rh_config_descriptor;

} else {

bufp = fs_rh_config_descriptor;

len = sizeof fs_rh_config_descriptor;

}

if

(device_can_wakeup(&hcd->self.root_hub->dev))

patch_wakeup = 1;

break;

case USB_DT_STRING << 8:

n = rh_string (wValue & 0xff, hcd, ubuf,

wLength);

if (n < 0)

goto error;

urb->actual_length = n;

break;

default:

goto error;

}

break;

case

DeviceRequest | USB_REQ_GET_INTERFACE:

tbuf [0] =

0;

len = 1;

case

DeviceOutRequest | USB_REQ_SET_INTERFACE:

break;

case

DeviceOutRequest | USB_REQ_SET_ADDRESS:

// wValue == urb->dev->devaddr

dev_dbg (hcd->self.controller, "root hub device

address %d\n",

wValue);

break;

case

EndpointRequest | USB_REQ_GET_STATUS:

// ENDPOINT_HALT flag

tbuf [0] = 0;

tbuf [1] = 0;

len = 2;

case

EndpointOutRequest | USB_REQ_CLEAR_FEATURE:

case

EndpointOutRequest | USB_REQ_SET_FEATURE:

dev_dbg (hcd->self.controller, "no endpoint features

yet\n");

break;

default:

switch (typeReq) {

case GetHubStatus:

case GetPortStatus:

len = 4;

break;

case GetHubDescriptor:

len = sizeof (struct usb_hub_descriptor);

break;

}

status = hcd->driver->hub_control

(hcd,

typeReq, wValue, wIndex,

tbuf, wLength);

break;

error:

status = -EPIPE;

}

//如果发生了错误,将len置为0,表示没有数据要返回的

if

(status) {

len = 0;

if (status != -EPIPE) {

dev_dbg (hcd->self.controller,

"CTRL: TypeReq=0x%x val=0x%x "

"idx=0x%x len=%d ==> %d\n",

typeReq, wValue, wIndex,

wLength, status);

}

}

//copy返回的数据到transfer_buffer

if

(len) {

if (urb->transfer_buffer_length <

len)

len = urb->transfer_buffer_length;

urb->actual_length = len;

// always USB_DIR_IN, toward host

memcpy (ubuf, bufp, len);

if (patch_wakeup &&

len > offsetof (struct usb_config_descriptor,

bmAttributes))

((struct usb_config_descriptor

*)ubuf)->bmAttributes

|= USB_CONFIG_ATT_WAKEUP;

}

spin_lock_irq(&hcd_root_hub_lock);

//处理完成了,可以将urb从ep->urb_list上脱落了

usb_hcd_unlink_urb_from_ep(hcd, urb);

spin_unlock(&hcd_root_hub_lock);

//URB已经使用完了,对它进行后续处理.包括调用complete唤醒调用进程

usb_hcd_giveback_urb(hcd, urb, status);

spin_lock(&hcd_root_hub_lock);

spin_unlock_irq(&hcd_root_hub_lock);

return

0;

}

从这里看到,它只是将值是保存在内存中的或者是调用驱动的hub_control接口,去读取/设置相关寄存器.这些过程就不再详细分析了.

2:非root_hub的控制传输

对于非root_hub的情况,流程会转入status =

hcd->driver->urb_enqueue(hcd, urb,

mem_flags);

对于UHCI,它的对应接口为uhci_urb_enqueue().代码如下:

static int uhci_urb_enqueue(struct usb_hcd *hcd,

struct urb *urb, gfp_t mem_flags)

{

int

ret;

struct

uhci_hcd *uhci = hcd_to_uhci(hcd);

unsigned long flags;

struct

urb_priv *urbp;

struct

uhci_qh *qh;

spin_lock_irqsave(&uhci->lock,

flags);

//将urb加到通信端点的传输链表 urb->urb_list

加至urb->ep->urb_list

ret =

usb_hcd_link_urb_to_ep(hcd, urb);

if

(ret)

goto done_not_linked;

ret =

-ENOMEM;

//初始化urb->hcpriv,它的结构为struct urb_priv

//urb->hcpriv = urbp

//urbp->urb = urb

urbp =

uhci_alloc_urb_priv(uhci, urb);

if

(!urbp)

goto done;

//创建QH

if

(urb->ep->hcpriv)

qh = urb->ep->hcpriv;

else

{

qh = uhci_alloc_qh(uhci, urb->dev,

urb->ep);

if (!qh)

goto err_no_qh;

}

urbp->qh = qh;

//各类型的不同操作

switch

(qh->type) {

case

USB_ENDPOINT_XFER_CONTROL:

ret = uhci_submit_control(uhci, urb, qh);

break;

case

USB_ENDPOINT_XFER_BULK:

ret = uhci_submit_bulk(uhci, urb, qh);

break;

case

USB_ENDPOINT_XFER_INT:

ret = uhci_submit_interrupt(uhci, urb, qh);

break;

case

USB_ENDPOINT_XFER_ISOC:

urb->error_count = 0;

ret = uhci_submit_isochronous(uhci, urb, qh);

break;

}

if (ret

!= 0)

goto err_submit_failed;

urbp->qh = qh;

//将urbp加到qh的queue链表中

list_add_tail(&urbp->node,

&qh->queue);

//qh的queue中只有一个urbp的时候,且QH没有被禁用

if

(qh->queue.next ==

&urbp->node

&& !qh->is_stopped)

{

//启用这个QH进行传输了

uhci_activate_qh(uhci, qh);

//fsbr:高速传输

uhci_urbp_wants_fsbr(uhci, urbp);

}

goto

done;

err_submit_failed:

if

(qh->state == QH_STATE_IDLE)

uhci_make_qh_idle(uhci,

qh);

err_no_qh:

uhci_free_urb_priv(uhci, urbp);

done:

if

(ret)

usb_hcd_unlink_urb_from_ep(hcd, urb);

done_not_linked:

spin_unlock_irqrestore(&uhci->lock,

flags);

return

ret;

}

Urbp是urb的一个扩展区结构,这个urbp最后包含的很多信息,具体如下:

Urbp->urb:指向了要传输的urb

Urbp->qh:指向传输的QH

在进行传输的时候,先创建一个QH结构,然后将数据打成TD的形式,再将TD挂到QH上面.

对于QH的创建,有必要跟进去看一下:

static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd

*uhci,

struct usb_device *udev, struct usb_host_endpoint *hep)

{

dma_addr_t dma_handle;

struct

uhci_qh *qh;

qh =

dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC,

&dma_handle);

if

(!qh)

return NULL;

memset(qh, 0, sizeof(*qh));

qh->dma_handle = dma_handle;

qh->element = UHCI_PTR_TERM;

qh->link = UHCI_PTR_TERM;

INIT_LIST_HEAD(&qh->queue);

INIT_LIST_HEAD(&qh->node);

if

(udev)

{

qh->type = hep->desc.bmAttributes

& USB_ENDPOINT_XFERTYPE_MASK;

if (qh->type != USB_ENDPOINT_XFER_ISOC) {

qh->dummy_td = uhci_alloc_td(uhci);

if (!qh->dummy_td) {

dma_pool_free(uhci->qh_pool, qh, dma_handle);

return NULL;

}

}

qh->state = QH_STATE_IDLE;

qh->hep = hep;

qh->udev = udev;

hep->hcpriv = qh;

//如果是中断传输或者者是实时传输.计算数据传输所耗的总线时间

if (qh->type == USB_ENDPOINT_XFER_INT ||

qh->type == USB_ENDPOINT_XFER_ISOC)

qh->load =

usb_calc_bus_time(udev->speed,

usb_endpoint_dir_in(&hep->desc),

qh->type == USB_ENDPOINT_XFER_ISOC,

le16_to_cpu(hep->desc.wMaxPacketSize))

/ 1000 + 1;

} else

{

qh->state = QH_STATE_ACTIVE;

qh->type = -1;

}

return

qh;

}

这个函数在UHCI的start函数中已经看到过,不过在那个地方,它的udev参数是空的.这只是一个框架,并不会用来实际的传输.

而对于实际的传输,也就是代码中注释到的所谓”Normal QH”的情况.

我们在代码中看到.如果不是一个实时传输.它还会创建一个dummy_td,这个TD是用来做什么,我们暂且不要去管,只知道有这么回事就可以了.另外,对于中断传输和实现传输,还要计算传输字节所耗的总线带宽.

返回到uhci_urb_enqueue()中,对于控制传输的情况,流程会转入到uhci_submit_control()中,这个函数的代码如下所示:

static int uhci_submit_control(struct uhci_hcd *uhci, struct

urb *urb,

struct uhci_qh *qh)

{

struct

uhci_td *td;

unsigned long destination, status;

int

maxsze =

le16_to_cpu(qh->hep->desc.wMaxPacketSize);

int len

= urb->transfer_buffer_length;

dma_addr_t data = urb->transfer_dma;

__le32

*plink;

struct

urb_priv *urbp = urb->hcpriv;

int

skel;

//dev_num + end_num

destination = (urb->pipe &

PIPE_DEVEP_MASK) | USB_PID_SETUP;

//设置TD描述符的Status的C_ERR字段为3.如果失败次数超过了3.则将TD置为inactive

status

= uhci_maxerr(3);

//如果是低速设备.设置STATUS的LS位,表示是一个低速设备

if

(urb->dev->speed ==

USB_SPEED_LOW)

status |= TD_CTRL_LS;

//SETUP包

td =

qh->dummy_td;

//将TD放到urbp的td_list链表

uhci_add_td_to_urbp(td, urbp);

//SETUP阶段的数据信息包只有8个字节

uhci_fill_td(td, status, destination | uhci_explen(8),

urb->setup_dma);

plink =

&td->link;

status

|= TD_CTRL_ACTIVE;

//判断是往哪一个方向的

if

(usb_pipeout(urb->pipe) || len == 0)

destination ^= (USB_PID_SETUP ^ USB_PID_OUT);

else

{

destination ^= (USB_PID_SETUP ^ USB_PID_IN);

//如果是IN方向的,必须要置SPD位

//如果是输入方向且数据包被成功传递,但长度小于最大长度,就会将TD置为inactive

//如果短包中断被使用,就会上报一个中断

//SPD是OUT方向是无意义的

status |= TD_CTRL_SPD;

}

//数据传输阶段

while

(len > 0) {

int pktsze = maxsze;

//是后的一个包,确实是短包了,清除掉SPD

if (len <= pktsze)

{

pktsze = len;

status &= ~TD_CTRL_SPD;

}

td = uhci_alloc_td(uhci);

if (!td)

goto nomem;

*plink = LINK_TO_TD(td);

//交替TOGGLE位,是用来同步的, usb2.0 spec上有详细说明

destination ^= TD_TOKEN_TOGGLE;

//将TD添加到td_list

uhci_add_td_to_urbp(td, urbp);

uhci_fill_td(td, status, destination | uhci_explen(pktsze),

data);

plink = &td->link;

data += pktsze;

len -= pktsze;

}

//状态阶段

td =

uhci_alloc_td(uhci);

if

(!td)

goto nomem;

*plink

= LINK_TO_TD(td);

//状态阶段的方向跟前一笔事务是相反的方向

destination ^= (USB_PID_IN ^ USB_PID_OUT);

//限定为DATA1

destination |=

TD_TOKEN_TOGGLE;

uhci_add_td_to_urbp(td, urbp);

//状态阶段中,只需要传送0长度的数据

//IOC置位了,即传输完成了就会产生中断

uhci_fill_td(td, status | TD_CTRL_IOC,

destination | uhci_explen(0), 0);

plink =

&td->link;

td =

uhci_alloc_td(uhci);

if

(!td)

goto nomem;

*plink

= LINK_TO_TD(td);

//起个标识作用,表示该TD已经结尾了,这个包是不会参与传输的,因为它没有置ACTIVE标志

uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);

wmb();

//将dummy_td置为ACTIVE

qh->dummy_td->status |=

__constant_cpu_to_le32(TD_CTRL_ACTIVE);

//将dummy_td指向新创建的TD

qh->dummy_td = td;

//确定QH是属于低速还是高速

if

(urb->dev->speed == USB_SPEED_LOW

||

urb->dev->state !=

USB_STATE_CONFIGURED)

skel = SKEL_LS_CONTROL;

else

{

skel = SKEL_FS_CONTROL;

uhci_add_fsbr(uhci, urb);

}

//QH的初始状态是为QH_STATE_IDLE

if

(qh->state != QH_STATE_ACTIVE)

qh->skel = skel;

//实际传输的长度置-8.正好是SETUP的包大小,以后返回的actual_length就是单纯的数据传输长度了

urb->actual_length =

-8;

return

0;

nomem:

uhci_remove_td_from_urbp(qh->dummy_td);

return

-ENOMEM;

}

理解这个函数需要参照usb2.0

spec.控制传输包含了三个阶段,分别是SETUP,DATA和HANDSHAKE.

Setup阶段的token信息包的PID为SETUP(1101B),数据信息包就是urb->

setup_packet部份,它的物理地址是urb-> setup_dma.为八个字节.

DATA阶段的token信息包的PID为IN/OUT(要根据传输方向来确定),后面跟的是具体的数据的数据信息包.另外,DATA阶段的数据信息包中的PID起同步作用,DATA1/DATA0轮流交换.

HANDSHAKE阶段的信息包的PID方向跟DATA阶段是相反的,数据信息包的PID值固定为DATA1,传输的一个长度为0的数据.

另外,虽然每一笔事务都有token信息包,数据信息包和联络信息包,联络信息包UHCI会自动管理,并不需要驱动进行操作.设备返回的联络信息包会反应到对应TD的STATUS段中.

其实,最后还附带有一个TQ,因为它没有置ACTIVE位,实际上他不会参与传输,只是起一个标识结尾的作用.

还有一个值得注意的地方:如果是全速的控制传输,将skel置为SKEL_FS_CONTROL后,会调用uhci_add_fsbr().这个函数代码如下:

static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb

*urb)

{

struct

urb_priv *urbp = urb->hcpriv;

if

(!(urb->transfer_flags &

URB_NO_FSBR))

urbp->fsbr = 1;

}

从上面的代码看到,如果URB没有带URB_NO_FSBR的标志,就会将urbp->fsbr置为1.这在后面关于FSBR的分析是会用到的.在这里特别提一下.

请自行对照代码中的注释进行阅读,这里就不进行详细分析了

经过上面的操作之后,所有的TD都链在了urbp->urb_list中.qh->

dummy_td指向它的最后一个TD.

返回到uhci_urb_enqueue().流程会转入uhci_activate_qh().这个函数是各种传输所共用的.

代码如下:

static void uhci_activate_qh(struct uhci_hcd *uhci, struct

uhci_qh *qh)

{

//qh->queure为空,也就是说QH没有链接到urbp->node

WARN_ON(list_empty(&qh->queue));

//QH的初始值是qh->element和qh->link都是UHCI_PTR_TERM

if

(qh_element(qh) == UHCI_PTR_TERM) {

//从qh导出urbp

struct urb_priv *urbp =

list_entry(qh->queue.next,

struct urb_priv, node);

//经过前面的分析,所有td都是挂在td_list上面的

struct uhci_td *td =

list_entry(urbp->td_list.next,

struct uhci_td, list);

//将TD挂到QH上面

qh->element = LINK_TO_TD(td);

}

qh->wait_expired = 0;

qh->advance_jiffies = jiffies;

if

(qh->state == QH_STATE_ACTIVE)

return;

qh->state = QH_STATE_ACTIVE;

//刚开始的时候uhci->next_qh为NULL

if (qh

== uhci->next_qh)

uhci->next_qh =

list_entry(qh->node.next, struct uhci_qh,

node);

//初始化时qh->node为空

list_del(&qh->node);

//ISO传输

if

(qh->skel == SKEL_ISO)

link_iso(uhci, qh);

//INTER传输

else if

(qh->skel < SKEL_ASYNC)

link_interrupt(uhci, qh);

else

//其它类型的

link_async(uhci, qh);

}

上面的代码中的注释注明了各项操作.

QH在初始化时,将qh->element和qh->link都设置为了UHCI_PTR_TERM.也就是说它下面没有挂上TD,也没有链接其它的QH.在这个函数中,首先将QH和TD要关联起来,即,把TD连接到QH->element.

对于控制传输,流程最后转入link_async()中,是到QH和UHCI的frame list关联起来的时候了.

代码如下示:

static void link_async(struct uhci_hcd *uhci, struct uhci_qh

*qh)

{

struct

uhci_qh *pqh;

__le32

link_to_new_qh;

//uhci->skel_async_qh:skelqh[9]

//各钟ASYNC的QH按照从小到大的顺序链接在skelqh[9]->node上

//注意了,list_for_each_entry_reverse()是一个反向搜索

list_for_each_entry_reverse(pqh,

&uhci->skel_async_qh->node,

node) {

if (pqh->skel <=

qh->skel)

break;

}

//在skelqh[9]->node中找到一个合适的位置,将QH插下去

list_add(&qh->node,

&pqh->node);

//接QH插入到调度队列

qh->link = pqh->link;

//这里涉及到物理地址的操作,为了防止编译器优化,插上内存屏障

wmb();

link_to_new_qh = LINK_TO_QH(qh);

pqh->link = link_to_new_qh;

//如果是第1个fsbr,将skelqh[10]指向这个QH

//skel_term_qh: skelqh[10]

if

(pqh->skel < SKEL_FSBR

&& qh->skel

>= SKEL_FSBR)

uhci->skel_term_qh->link =

link_to_new_qh;

}

Async类型的传输包括控制传输和批量传输.这样的传输是不会保证实时性的,只有在带宽空闲的时候才会进行.从上面的代码中可以看到,它们的QH都是挂在skeqh[9].即是挂在int1的QH后面的.根据我们之前对UHCI调度初始化的分析.int1是排在最后的一个QH.所以,挂在int1后的QH,要等前面的QH运行完之后才能够运行.

而对于Control传输.根据设备的传输速度又有了SKEL_LS_CONTROL和SKEL_FS_CONTROL.对于BULK有22

这几个宏的定义如下:

#define

SKEL_LS_CONTROL 20

#define

SKEL_FS_CONTROL 21

#define

SKEL_FSBR SKEL_FS_CONTROL

#define

SKEL_BULK 22

事实上,我们希望他们传殊的优先级顺序是SKEL_LS_CONTROL >

SKEL_FS_CONTROL > SKEL_BULK

在这里,要特别注意上面的搜索是从后面往前面搜索的.不要被它弄迷糊了.

在最后,还将skel_term_qh指向第一个SKEL_FS_CONTROL的QH.在前面UHCI调度初始化中曾分析过.这个skel_term_qh实际上并挂在frame

list中.在这里,

skel_term_qh->link指向第一个SKEL_FS_CONTROL是在做什么呢?暂且把这个问题放到这里.我们马上就会涉及到这部份内容了.

从uhci_activate_qh()返回之后,流程会进入到uhci_urbp_wants_fsbr().

在分析之个函数之前.先介绍一下什么叫FSBR.FSBR的全名叫: Full Speed Bus

Reclamtion.它是一种带宽回收机制.只有在全速传输中才会用到.所谓带宽回收,其实是跟UHCI默认遍历QH的方式有关的.在UHCI

的spec中,定义了两种遍历QH的方式.分别是广度优先搜索和深度优先搜索.深度优先搜索在PCI驱动结构中大量被用到.它类似于二叉树的先根遍历.深度优先搜索类似于二叉树的层遍历.

具体到UHCI的QH遍历方式上:

深度优先:UHCI先处理完QH下面的所有TD,然后再处理下一个QH.很显然,这样的方式不会有带宽浪费.但对挂在后面的QH来说很不公平.

广度优先:UHCI先取QH下挂的TD(qh->element).先将这个TD处理完.然后再它后面链接的QH(qh->link).这样的方式对挂在后面的QH都是公平的.但是遍历完所有QH之后.如果时间还有剩余,UHCI什么事都不会干.但很显然,每个QH下的TD并不是都处理完了.

所以,针对广度优先搜索,出现了带宽回收.具体怎么样回收呢?我们来看uhci_urbp_wants_fsbr()的代码:

static void uhci_urbp_wants_fsbr(struct uhci_hcd *uhci, struct

urb_priv *urbp)

{

if

(urbp->fsbr) {

uhci->fsbr_is_wanted = 1;

if (!uhci->fsbr_is_on)

uhci_fsbr_on(uhci);

else if (uhci->fsbr_expiring) {

uhci->fsbr_expiring = 0;

del_timer(&uhci->fsbr_timer);

}

}

}

记得在uhci_submit_control()分析的时候,提到过,如果是全速控制传输会调用uhci_add_fsbr()将urbp->fsbr置为1.

初始化的时候,会将uhci->fsbr_is_on设置为0.也就是说,在这个地方,流程会转入到uhci_fsbr_on().代码如下:

static void uhci_fsbr_on(struct uhci_hcd *uhci)

{

struct

uhci_qh *lqh;

uhci->fsbr_is_on = 1;

lqh =

list_entry(uhci->skel_async_qh->node.prev,

struct uhci_qh, node);

lqh->link =

LINK_TO_QH(uhci->skel_term_qh);

}

在这个函数中,先将fsbr_is_on设置为1.然后,取得挂在skel_async_qh上的最后一个QH.然后将这个QH的指向skel_term_qh.

结合uhci_activate_qh()中对于skel_term_qh的操作.得出以下的示意图:

a4c26d1e5885305701be709a3d33442f.png

在uhci_activate_qh()的分析中,我们了解到:按照SKEL_LS_CONTROL

> SKEL_FS_CONTROL >

SKEL_BULK优先级链接在skel_async_qh->node中.所以SKEL_FS_CONTROL后面可能还会跟有SKEL_BULK类型的传送包.

如上图所示:skel_term_qh起一个中间桥的作用,将SKEL_FS_CONTRO后的QH链接起来.这样,在UHCI有空闲带宽的时候就不会傻呆着无所事事了.它会循环处理SKEL_FS_CONTRO和SKEL_BULK的包.

另外,从上图中也可以看出.只有全速控制传和批量传输会用到FSBR.另外,对于批量传输,它无所谓低速批量传输.因为所有的BULK都会链接在上面的圆环中.

既然说到了uhci_fsbr_on().顺便说一下它的对立函数uhci_fsbr_off():

static void uhci_fsbr_off(struct uhci_hcd *uhci)

{

struct

uhci_qh *lqh;

uhci->fsbr_is_on = 0;

lqh =

list_entry(uhci->skel_async_qh->node.prev,

struct uhci_qh, node);

lqh->link = UHCI_PTR_TERM;

}

它将fsbr_is_on置为0.然后断开了上图中所示的圆环.

到这里之后,TD能够被UHCI调度了.那TD调度完了之后,驱动要怎样才能够知道呢?

回忆之前,我们在最后的一个有效TD的STATUS中,置位了IOC.也就是说传输完成之后,会上报一个中断.所以,控制传输完成之后是由中断处理函数进行处理的.事实上,无论哪一种传输方式到传输完成的时候,就会由中断处理函数都行后续的处理工作.所以中断处理这部份放到最后统一进行分析.

上面的分析中涉及到了BULK传输.那就来看一下BULK传输的实现.

3.3:批量传输过程

Root

hub没有批量传输.按照控制传输的流程.批量传输最终也会交给uhci_urb_enqueue()处理.与前面分析的控制传输不同的是,在switch的判断中,流程会转向uhci_submit_bulk().

static int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb

*urb,

struct uhci_qh *qh)

{

int

ret;

//如果是低速设备,退出.BULK不能在低速设备中使用

if

(urb->dev->speed ==

USB_SPEED_LOW)

return -EINVAL;

//qh的初始化state是QH_STATE_IDLE

if

(qh->state != QH_STATE_ACTIVE)

qh->skel = SKEL_BULK;

ret =

uhci_submit_common(uhci, urb, qh);

//uhci_add_fsbr():判断urb是否支持FSBR

if (ret

== 0)

uhci_add_fsbr(uhci, urb);

return

ret;

}

首先一个低速设备是不能用做BULK传输的.另外,在初始化qh的时候,将它的状态初始化成QH_STATA_IDLE.代码中再判断一次,是为了防止操作的QH是已经挂在UHCI调度的QH.然后再调用uhci_submit_common()去填充这次传输所需要的TD.最后同控制传输的情况一下,也要进行FSBR的判断.

uhci_submit_common()代码如下:

static int uhci_submit_common(struct uhci_hcd *uhci, struct

urb *urb,

struct uhci_qh *qh)

{

struct

uhci_td *td;

unsigned long destination, status;

int

maxsze =

le16_to_cpu(qh->hep->desc.wMaxPacketSize);

int len

= urb->transfer_buffer_length;

dma_addr_t data = urb->transfer_dma;

__le32

*plink;

struct

urb_priv *urbp = urb->hcpriv;

unsigned int toggle;

if (len

< 0)

return -EINVAL;

destination = (urb->pipe &

PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);

//取得toggle位

toggle

= usb_gettoggle(urb->dev,

usb_pipeendpoint(urb->pipe),

usb_pipeout(urb->pipe));

//设置uhci spec规定的TD第二个寄存器中的C_ERR位,当错误次数超过3次,就会认为TD无效

status

= uhci_maxerr(3);

//如果是低速设备,设置STATUS段的LS位,表明这是一个低速传输

if

(urb->dev->speed ==

USB_SPEED_LOW)

status |= TD_CTRL_LS;

//如果是输入管道,设置bit27的SPD

//SPD只有在IN方向才有效

if

(usb_pipein(urb->pipe))

status |= TD_CTRL_SPD;

//批量传输的数据阶段TD

plink =

NULL;

td =

qh->dummy_td;

do

{

int pktsze = maxsze;

//最后的分包了

if (len <= pktsze)

{

pktsze = len;

//如果URB的标志设置了URB_SHORT_NOT_OK.则表示短包是不可以接受的

if (!(urb->transfer_flags &

URB_SHORT_NOT_OK))

//允许短包,清除SPD

status &= ~TD_CTRL_SPD;

}

if (plink) {

td = uhci_alloc_td(uhci);

if (!td)

goto nomem;

*plink = LINK_TO_TD(td);

}

uhci_add_td_to_urbp(td, urbp);

uhci_fill_td(td, status,

destination | uhci_explen(pktsze) |

(toggle <<

TD_TOKEN_TOGGLE_SHIFT),

data);

plink = &td->link;

//设置ACTIVE位,则表示该TD有效

status |= TD_CTRL_ACTIVE;

data += pktsze;

len -= maxsze;

toggle ^= 1;

} while

(len > 0);

//判断是URB是否设置了URB_ZERO_PACKET.

//如果该位被置,而且是OUT方向.且数据长度是最大允许长度的整数位

//就需要传输一个0长度的数据包来结速此次传输

if

((urb->transfer_flags &

URB_ZERO_PACKET) &&

usb_pipeout(urb->pipe)

&& len == 0

&&

urb->transfer_buffer_length > 0)

{

td = uhci_alloc_td(uhci);

if (!td)

goto nomem;

*plink = LINK_TO_TD(td);

uhci_add_td_to_urbp(td, urbp);

uhci_fill_td(td, status,

destination | uhci_explen(0) |

(toggle <<

TD_TOKEN_TOGGLE_SHIFT),

data);

plink = &td->link;

toggle ^= 1;

}

//设置IOC位.表示执行传输完后,就会触发一次中断

td->status |=

__constant_cpu_to_le32(TD_CTRL_IOC);

//队列结束的TD

td =

uhci_alloc_td(uhci);

if

(!td)

goto nomem;

*plink

= LINK_TO_TD(td);

uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);

wmb();

qh->dummy_td->status |=

__constant_cpu_to_le32(TD_CTRL_ACTIVE);

qh->dummy_td = td;

//设置usb_dev的toggle数组

usb_settoggle(urb->dev,

usb_pipeendpoint(urb->pipe),

usb_pipeout(urb->pipe), toggle);

return

0;

nomem:

uhci_remove_td_from_urbp(qh->dummy_td);

return

-ENOMEM;

}

这里的操作和控制传输的的相应操作类似,只是对于BULK来说,它只有数据阶段.并不像CONTORL那样有三个相位.

另外,对于BULK来说,它的数据信息包PID也是DATA0和DATA1轮流反转的.用来做同步和错误检测.也就是对应上面代码中的toggle操作,关于toggle[]数组,在之前已经分析过了.

另外,对于设置了URB_ZERO_PACKET标志的情况,如果传输长度刚好是所允许最大长度整数倍,且为OUT方向,就需要发送一个0长度的数据包,表示本次传输已经结束了.

对于传输长度不是允许最大长度整数倍的情况,设备检测到传输长度小于所允许的最大长度就认为本次传输已经完成了.

返回到uhci_urb_enqueue()中,后面的操作都跟控制传输完全一样了.

3.4:中断传输过程

1:root hub的中断传输

在usb_hcd_submit_urb()àrh_urb_enqueue()中:

static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb

*urb)

{

if

(usb_endpoint_xfer_int(&urb->ep->desc))

return rh_queue_status (hcd, urb);

if

(usb_endpoint_xfer_control(&urb->ep->desc))

return rh_call_control (hcd, urb);

return

-EINVAL;

}

如果是中断传输,流程会转到rh_queue_status()中:

static int rh_queue_status (struct usb_hcd *hcd, struct urb

*urb)

{

int retval;

unsigned long flags;

int len = 1 + (urb->dev->maxchild /

8);

spin_lock_irqsave (&hcd_root_hub_lock,

flags);

//如果root hub已经在处理中断传输或者是urb非法

if

(hcd->status_urb ||

urb->transfer_buffer_length < len)

{

dev_dbg (hcd->self.controller, "not queuing rh

status urb\n");

retval = -EINVAL;

goto done;

}

//将urb添加到urb->ep_>urb_list

retval

= usb_hcd_link_urb_to_ep(hcd, urb);

if

(retval)

goto done;

hcd->status_urb = urb;

urb->hcpriv = hcd;

//uhci_start(),会将uses_newpolling设为了1

if

(!hcd->uses_new_polling)

mod_timer(&hcd->rh_timer,

(jiffies/(HZ/4) + 1) * (HZ/4));

else if

(hcd->poll_pending)

mod_timer(&hcd->rh_timer,

jiffies);

retval

= 0;

done:

spin_unlock_irqrestore (&hcd_root_hub_lock,

flags);

return

retval;

}

从上面的代码可以看到,urb最终会交给hcd->rh_timer这个定时器进行处理.在前面的分析中,我们看到了在uhci_start()中设置了usrs_new_polling.搜索整个linux源代码,发现其它地方没有更改这个值.所以,上面代码中的if判断是不会满足的.那hcd->poll_pending会不会满足呢?

要回答这个问题,我们得返回看一下usb_add_hcd()的处理,在那里我们忽略了一个函数的处理.在这个函数中,有这样的代码片段:

int usb_add_hcd(struct usb_hcd *hcd,

unsigned int irqnum, unsigned long irqflags)

{

......

......

if (hcd->uses_new_polling

&&

hcd->poll_rh)

usb_hcd_poll_rh_status(hcd);

return retval;

......

}

在start_rh()中,会将hcd->poll_rh设为1,那在初始化阶段,usb_hcd_poll_rh_status()会得到运行:

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)

{

struct

urb *urb;

int length;

unsigned long flags;

char buffer[4];

if

(unlikely(!hcd->rh_registered))

return;

if

(!hcd->uses_new_polling

&&

!hcd->status_urb)

return;

length

= hcd->driver->hub_status_data(hcd,

buffer);

if

(length > 0) {

spin_lock_irqsave(&hcd_root_hub_lock, flags);

urb = hcd->status_urb;

if (urb) {

hcd->poll_pending = 0;

hcd->status_urb = NULL;

urb->actual_length = length;

memcpy(urb->transfer_buffer, buffer, length);

usb_hcd_unlink_urb_from_ep(hcd, urb);

spin_unlock(&hcd_root_hub_lock);

usb_hcd_giveback_urb(hcd, urb, 0);

spin_lock(&hcd_root_hub_lock);

} else {

length = 0;

hcd->poll_pending = 1;

}

spin_unlock_irqrestore(&hcd_root_hub_lock,

flags);

}

if

(hcd->uses_new_polling ? hcd->poll_rh

:

(length == 0 &&

hcd->status_urb != NULL))

mod_timer (&hcd->rh_timer,

(jiffies/(HZ/4) + 1) * (HZ/4));

}

首先进行一些合法性检测.如果hcd没有注册root hub,会出错退出.实际上,这个函数就是为了周期性检测root

hub的状态而设的.

之后会调用length =

hcd->driver->hub_status_data(hcd,

buffer);

它会回溯到UHCI驱动的hub_status_data().这个函数会去取root

hub的状态,如果状态发生改变,则将改变的状态存放在buffer中.然后返回1.如果没有必变,则返回0.

hcd->status_urb终于对应到前面rh_queue_status()的分析了.我们知道,如果主机发出中断传输,会将hcd->status_urb指向这个urb.

那,这个函数对应的操作为:

如果主机有发了中断传输来获得root

hub的状态,就将状态值赋值到urb的传输缓存区.如果没有,状态却发生了改变,将hcd->poll_pending设为1.

函数后面的if判断是肯定会成功的.因为hcd->uses_new_polling

,hcd->poll_rh都为真.然后,启动了定时器hcd->rh_timer.

这个定时器的初始化如下:

在usb_create_hcd()中:

......

init_timer(&hcd->rh_timer);

hcd->rh_timer.function = rh_timer_func;

hcd->rh_timer.data = (unsigned long) hcd;

......

也就是说,这个定时器的处理函数为rh_timer_func().参数为hcd.

处理函数代码如下:

static void rh_timer_func (unsigned long _hcd)

{

usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);

}

看到了吧.这个定时器的处理函数就是上面分析的usb_hcd_poll_rh_status.

也就是说,从系统初始化后,这个定时器就会一直在运行了.

OK.返回到rh_queue_status().如果root_hub的状态发生了改变,就会重新设定定时器hcd->rh_timer.这样做是为了防止某些情况下(例如改变了hcd->uses_new_polling的值)定时器停止.

我们在上面分析过了这个定时器的处理函数.不过还留下了一个尾巴,也就是hcd->driver->hub_status_data().下面就来分析一下这个函数.

这个函数指针的接口为

static int uhci_hub_status_data(struct usb_hcd *hcd, char

*buf)

{

struct

uhci_hcd *uhci = hcd_to_uhci(hcd);

unsigned long flags;

int

status = 0;

spin_lock_irqsave(&uhci->lock,

flags);

//扫描调度队列,将一些已经运先完毕的TD,QH删除掉

uhci_scan_schedule(uhci);

//如果hcd不处于HCD_FLAG_HW_ACCESSIBLE状态,或者处于"死"状态

//即不可操作状态

if

(!test_bit(HCD_FLAG_HW_ACCESSIBLE,

&hcd->flags) ||

uhci->dead)

goto done;

//检查UHCI的端口,也即root hub的端口

uhci_check_ports(uhci);

//判断端口状态是否改变

status

= get_hub_status_data(uhci, buf);

//端口状态改变的相应处理

//uhci->rh_state,是root hub以前的状态

//如果status为1,则状态发生了改变

switch

(uhci->rh_state) {

//电源管理部份,忽略

case

UHCI_RH_SUSPENDING:

case

UHCI_RH_SUSPENDED:

if (status)

usb_hcd_resume_root_hub(hcd);

break;

case

UHCI_RH_AUTO_STOPPED:

//如果旧状态是stop,且发生了改变,那就将root hub启动了

if (status)

wakeup_rh(uhci);

break;

//如果旧状态是RH_RUNNING.则判断root hub下面的端口状态.如果端口末连接

//将其置为UHCI_RH_RUNNING_NODEVS.且设置auto_stop_time为 jiffies + HZ

case

UHCI_RH_RUNNING:

if (!any_ports_active(uhci)) {

uhci->rh_state = UHCI_RH_RUNNING_NODEVS;

uhci->auto_stop_time = jiffies + HZ;

}

break;

//如果旧状态是UHCI_RH_RUNNING_NODEVS.判断是否有设备连上了root hub

//如果有连上,将其状态置为UHCI_RH_RUNNING

//如果还是超时还是没连上,将端口suspend

case

UHCI_RH_RUNNING_NODEVS:

if (any_ports_active(uhci))

uhci->rh_state = UHCI_RH_RUNNING;

else if (time_after_eq(jiffies,

uhci->auto_stop_time))

suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);

break;

default:

break;

}

done:

spin_unlock_irqrestore(&uhci->lock,

flags);

return

status;

}

这个函数的操作逻辑还是比较清淅,不过大量状态的判断让人觉得头昏眼花.

首先函数调用uhci_scan_schedule()去扫描挂在UHCI上面的调度队列,如果有TD或者QH被执行完,就应该将它们从调度队列中删除.这个函数在中断处理过程还会涉及到.到那时再进行详细的分析.在这里只要知道它是干什么的就可以了.它不会影响后续的流程.

然后,会调用uhci_check_ports()去检查端口.它主要是对超时末完成的Reset状态和resume信号的处理

接着,调用get_hub_status_data()去判断端口状态是否发生了改变,如果发生了改变,返回值为1.且将改变状态的端口在buf中置位.

最后,就是一个很长的switch语句来处理对应状态的改变.

如果之前的状态是UHCI_RH_SUSPENDING和UHCI_RH_SUSPENDED,那表明之前root

hub处于挂起状态.如果状态发生了改变,就要将它从挂起状态中恢复过来.这一个过程涉及到了电源管理.暂且忽略掉这一倍份.

如果之前是UHCI_RH_AUTO_STOPPED状态.表明设备之前是STOP的,现在状改变了,当然要将它重新启用了.

如果之前是UHCI_RH_RUNNING状态,表示root hub处于正常的联连.那就去判断这个root

hub上是否有设备相连.如果没有.则将其置为UHCI_RH_RUNNING_NODEVS.假设root

hub上之前挂了一个U盘.此时的状态是UHCI_RH_RUNNING_RUNING的,之后我将U盘拨下,这时候端口会转变为UHCI_RH_RUNNING_NODEVS状态.

如果之前是UHCI_RH_RUNNING_NODEVS,去判断hub下面是否有设备相联.如果有,则将它转换到UHCI_RH_RUNNING_RUNING.如果还是没有设备相联,且没有设备相联的时候超过了一个HZ.将就端口suspend.用来节省电源.

下面,挨个分析这个函数的几个子函数.

第一个要分析的子函数是: uhci_check_ports()

static void uhci_check_ports(struct uhci_hcd *uhci)

{

unsigned int port;

unsigned long port_addr;

int

status;

//遍历UHCI下面的端口

for

(port = 0; port < uhci->rh_numports;

++port) {

port_addr = uhci->io_addr + USBPORTSC1 + 2 *

port;

status = inw(port_addr);

//端口处于Reset状态

if (unlikely(status & USBPORTSC_PR)) {

//在用户将root hub reset时,会将ports_timeout设置成iffies +

//msecs_to_jiffies(50)

//如果超时之后,RESET过程还末完成

if (time_after_eq(jiffies, uhci->ports_timeout))

{

//将一些状态位和PR位清了

CLR_RH_PORTSTAT(USBPORTSC_PR);

udelay(10);

//HP主机的特殊处理

if (to_pci_dev(uhci_dev(uhci))->vendor ==

PCI_VENDOR_ID_HP)

wait_for_HP(port_addr);

//清除掉CSC,PEC,并设置PE

CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC);

SET_RH_PORTSTAT(USBPORTSC_PE);

}

}

//端口探测到了Resume 信号

if (unlikely(status & USBPORTSC_RD)) {

//对于Resume,有两个可能的情况,一种是用户设置端口成Resume

//另外的一种是状态检测到端口变为Resume

//如果用户设置,会将port在resuming_ports置位.否则,便是状态检测到端口变为Resume

if (!test_bit(port,

&uhci->resuming_ports)) {

//如果是轮询检测的.将其在resuing_port中置位.

//并且设置port_timeout为iffies +msecs_to_jiffies(20)

//将状态检测定时器设置在prot_timeout后运行

set_bit(port,

&uhci->resuming_ports);

uhci->ports_timeout = jiffies +

msecs_to_jiffies(20);

mod_timer(&uhci_to_hcd(uhci)->rh_timer,

uhci->ports_timeout);

}

//如果在超时过后,还是处于Resume.

//需要软件来清除这个状态

else if (time_after_eq(jiffies,

uhci->ports_timeout)) {

uhci_finish_suspend(uhci, port, port_addr);

}

}

}

}

第一个要处理的是RESET状态,用户可以通过控制传输将root hub

进行Rest操作,如果Reset过程超过了50ms.这就需要软件来处理了,驱动将一些状态标志清除之后,设置PE位,PE即port

enable的意思.

第二个要处理的是Resume信息.本来设备在收到这个信号的时候,应该将设备从挂起状态恢复到正常状态.但是如果20ms之后,恢复过程还没有结束,就需要软件去处理了.在代码中我们可以看到,它会调用uhci_finish_suspend()去完成软件处理的这一过程.代码如下:

static void uhci_finish_suspend(struct uhci_hcd *uhci, int

port,

unsigned long port_addr)

{

int

status;

int

i;

//如果端口处于恢复或者是Resum状态

if

(inw(port_addr) & SUSPEND_BITS) {

//将SUSP和RD位清除

CLR_RH_PORTSTAT(SUSPEND_BITS);

//如果端口在resuming被置,将其在suspend置位,表示是刚刚恢复过来的port

if (test_bit(port,

&uhci->resuming_ports))

set_bit(port,

&uhci->port_c_suspend);

//等待UHCI清降RD位

for (i = 0; i < 10; ++i) {

if (!(inw(port_addr) & SUSPEND_BITS))

break;

udelay(1);

}

}

//在resuming_ports中将对应位清除

clear_bit(port,

&uhci->resuming_ports);

}

从上面的处理我们可以看到,它先将SUSP和RD位清除,从代码的注释中可以看到,UHCI要发送一个EOP包之后,才会改变它的状态.因此在这里一直延时等待它的状态改变完成.

在这里,注意到刚刚恢复的端口会在port_c_suspend位图中置位.那要到什么时候才会将它在port_c_suspend位图中清除呢?

必须要等待UHCI向root hub发送CLEAR_FEATURE才会将其清除.

只是这个过程我们在分析控制传输的时候将它忽略过去了,:-)

到这里, uhci_check_ports()函数就分析完了.

第二个要分析的函数是get_hub_status_data().它会判断端口状态是否发生了改变.代码如下:

static inline int get_hub_status_data(struct uhci_hcd *uhci,

char *buf)

{

int

port;

//mask设定为了SC1的RWC位.这些RWC属性的位其实都代表着端口状态的改变

int

mask = RWC_BITS;

//有的主板会误报错误,使mask去掉这几位的匹配

if

(ignore_oc)

mask &= ~USBPORTSC_OCC;

*buf =

0;

//如果端口发生了改变,则将buf中的相应位置1

for

(port = 0; port < uhci->rh_numports;

++port) {

if ((inw(uhci->io_addr + USBPORTSC1 + port * 2)

& mask) ||

//或者是端口刚恢复过来

test_bit(port,

&uhci->port_c_suspend))

*buf |= (1 << (port + 1));

}

//如果buf不为空,返回真

return

!!*buf;

}

从UHCI的spec中可以看到,对应PORTSC寄存器的RW/C属性的位,其实都是状态改变位,判断这些位是否被置就可以得知端口状态是否发生了改变.

在这里要注意buf中置位操作,它是*buf |=

(1<

系统中的端口号是从1起计数的.

疑问:在这里通过读取PORTSC的值来判断ROOT

HUB的状态是否发生了改变,那代码中又没看到什么地方对它清位.这是为什么呢???有一位高人告诉我,是在hub

driver中完成的.初想之下似乎有点不可思议,不过,到分析HUB驱动的时候再来解答这个疑问好了:-(

第三个要分析的函数是wakeup_rh().这个函数将root hub重新运行起来,代码如下:

static void wakeup_rh(struct uhci_hcd *uhci)

__releases(uhci->lock)

__acquires(uhci->lock)

{

dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,

"%s%s\n", __FUNCTION__,

uhci->rh_state == UHCI_RH_AUTO_STOPPED ?

" (auto-start)" : "");

//如果UHCI处于挂起状态,将其恢复

if

(uhci->rh_state == UHCI_RH_SUSPENDED) {

//将将态设为UHCI_RH_RESUMING.表示正在恢复

uhci->rh_state = UHCI_RH_RESUMING;

//置FGD,EGSM,CF位

outw(USBCMD_FGR | USBCMD_EGSM | USBCMD_CF,

uhci->io_addr + USBCMD);

spin_unlock_irq(&uhci->lock);

//延迟20s

msleep(20);

spin_lock_irq(&uhci->lock);

//如果uhci dead,非法

if (uhci->dead)

return;

//再次写入CF

outw(USBCMD_CF, uhci->io_addr + USBCMD);

mb();

udelay(4);

//照spec上的说法,20s过后,FGR标志应该是会清除的,如果没有,打印出警告

if (inw(uhci->io_addr + USBCMD) &

USBCMD_FGR)

dev_warn(uhci_dev(uhci), "FGR not stopped yet!\n");

}

//启动RH

start_rh(uhci);

mod_timer(&uhci_to_hcd(uhci)->rh_timer,

jiffies);

}

按照UHCI

spec上的说法,如果是处于挂起状态的设备,可以给它发送一个强制的RESUME信号.当UHCI检测到USBCMD_FGR位被设置之后,会让设备处于活跃状态.发送强制的RESUME会有20s的时间.

之后的start_rh()我们在之前分析过,这里不再赘述.

第四个要分析的函数是any_ports_active().它会检测设备的端口状态.状态如下:

static int any_ports_active(struct uhci_hcd *uhci)

{

int

port;

for

(port = 0; port < uhci->rh_numports;

++port) {

if ((inw(uhci->io_addr + USBPORTSC1 + port * 2)

&

(USBPORTSC_CCS | RWC_BITS)) ||

//或者是端口刚刚恢复过来

test_bit(port,

&uhci->port_c_suspend))

return 1;

}

return

0;

}

跟前面分析的一样,还是取PORTSC的值,如果状态有改变或者CCS被置,就认为端口上已经连上设备了.CCS位表示Current

Connect Status.

第五个要分析的函数是suspend_rh().

static void suspend_rh(struct uhci_hcd *uhci, enum

uhci_rh_state new_state)

__releases(uhci->lock)

__acquires(uhci->lock)

{

int

auto_stop;

int

int_enable, egsm_enable;

auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);

dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,

"%s%s\n", __FUNCTION__,

(auto_stop ? " (auto-stop)" : ""));

//如果已经在STOP状态了,不需要进行任何处理了

if

(uhci->rh_state == UHCI_RH_AUTO_STOPPED) {

uhci->rh_state = new_state;

return;

}

egsm_enable = USBCMD_EGSM;

uhci->working_RD = 1;

int_enable = USBINTR_RESUME;

//选择编译函数

if

(remote_wakeup_is_broken(uhci))

egsm_enable = 0;

//这代码段也可以忽略,是用来修正某些设备的

if

(resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||

!device_may_wakeup(

&uhci_to_hcd(uhci)->self.root_hub->dev))

uhci->working_RD = int_enable = 0;

//置RESUME位,表示如果有Resume信号,将会引发中断

outw(int_enable, uhci->io_addr + USBINTR);

//置位EGSM和CF

outw(egsm_enable | USBCMD_CF, uhci->io_addr +

USBCMD);

mb();

udelay(5);

//调用这个函数的new_start有两种可能,UHCI_RH_SUSPENDED和UHCI_RH_AUTO_STOPPED

//如果UHCI还没有halted?.再等1s

if

(!auto_stop &&

!(inw(uhci->io_addr + USBSTS) &

USBSTS_HCH)) {

//将状态置为SUSPENDING.表示正在进行挂起状态

uhci->rh_state = UHCI_RH_SUSPENDING;

spin_unlock_irq(&uhci->lock);

msleep(1);

spin_lock_irq(&uhci->lock);

if (uhci->dead)

return;

}

//如果还没halted,打印出警告

if

(!(inw(uhci->io_addr + USBSTS) &

USBSTS_HCH))

dev_warn(&uhci_to_hcd(uhci)->self.root_hub->dev,

"Controller not stopped yet!\n");

//得到当前传输的帧号

uhci_get_current_frame_number(uhci);

//更新状态,设is_stopped为1,在正常情况下,poll_rh会设为0.rh_timer也停止了

uhci->rh_state = new_state;

uhci->is_stopped = UHCI_IS_STOPPED;

uhci_to_hcd(uhci)->poll_rh = !int_enable;

//更新一下调度队列

uhci_scan_schedule(uhci);

//停止FSBR

uhci_fsbr_off(uhci);

}

首先,调用这个函数的时候有两种情况,一种是以UHCI_RH_SUSPENDED为参数进行调用,另外的一种是以UHCI_RH_AUTO_STOPPED进行调用.即对root

hub进行挂起或者停止操作.

然后,在命令寄存器中置位USBCMD_EGSM.这样,设备就会进入EGSM模式.再启用Resume中断.如果以后有resume就会进入到中断处理程序.

最后,设置poll_rh为0,rh_timer定时器随之停止了.

可能会有这样的疑问,既然root hub的轮询状态定时器已经停止,那之后要怎么去更改root

hub的状态呢?我们不是在前面打开了Resume中断么?然后在中断处理中,又会将rh_timer唤起.

到这里,

uhci_hub_status_data()函数已经分析完了.相应的,root_hub的中断传输返回了.

2:非root_hub的中断传输

对于非root_hub的中断传输中,流程会转向到hcd->driver->urb_enqueue()进行处理.

首先,在uhci_alloc_qh()对中断传输就有一些特殊的处理.如下代码片段如示 :

static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd

*uhci,

struct usb_device *udev, struct usb_host_endpoint *hep)

{

......

......

//计算传输所占的总线时间,以微秒为单位

if (qh->type == USB_ENDPOINT_XFER_INT ||

qh->type == USB_ENDPOINT_XFER_ISOC)

qh->load =

usb_calc_bus_time(udev->speed,

usb_endpoint_dir_in(&hep->desc),

qh->type == USB_ENDPOINT_XFER_ISOC,

le16_to_cpu(hep->desc.wMaxPacketSize))

/ 1000 + 1;

......

}

在代码中判断,如果是中断传输或者是实时传输,就计算传输数据所耗的总线时间.这些计算公式都是由usb2.0

spec上规定的.

细心一点朋友可能发现了,在这里计算传输的字节数是hep->desc.wMaxPacketSize,即一次传输的数据值,但是传输数据总量不是hep->desc.wMaxPacketSize,它有可能会打成多个包嘛?

嗯,不错,有可能中断传输需要多个包,它的数据总量也并不是hep->desc.wMaxPacketSize.对应到UHCI的调度来说,他需要多个TD.前面我们分析FSBR机制的时候就说过,在一个frame内,默认用深度扫描的方式去扫描各个QH,然后处理一个挂下它下面的TD,然后就去处理另外的QH.从这里,我们知道,在一个frame内.只会传输一个数据包的,这个数据包的数据最大值就是hep->desc.wMaxPacketSize,那也就是说,一个frame内,所消耗的总线时间就是hep->desc.wMaxPacketSize大小的耗时.

哪为什么要计算它所需的总线时间呢?因为USB2.0

spec规定,实时传输加上中断传输所耗的带宽不能超过总带宽的90%.对应到总线时间上,每一个frmae内,

实时传输加上中断传输的时间不能超过900微秒.因为一个frame是1毫秒,也就是1000微秒.

返回到uhci_urb_enqueue(),在switch的判断中,会进入到uhci_submit_interrupt().代码如下:

static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct

urb *urb,

struct uhci_qh *qh)

{

int

ret;

//QH在初始化的时候会将bandwidth_reserved置为0

if

(!qh->bandwidth_reserved) {

int exponent;

//计算合适的周期值

for (exponent = 7; exponent >= 0; --exponent)

{

if ((1 << exponent) <=

urb->interval)

break;

}

if (exponent < 0)

return -EINVAL;

//qh->period:调度QH的周期

qh->period = 1 <<

exponent;

//QH所在的frame 项

qh->skel = SKEL_INDEX(exponent);

//检查带宽是否足够

qh->phase = (qh->period / 2)

& (MAX_PHASE - 1);

ret = uhci_check_bandwidth(uhci, qh);

if (ret)

return ret;

} else

if (qh->period >

urb->interval)

return

-EINVAL;

//将数据保打成TD,然后挂以QH上

ret =

uhci_submit_common(uhci, urb, qh);

if (ret

== 0) {

urb->interval = qh->period;

//分得所需要的带宽

if (!qh->bandwidth_reserved)

uhci_reserve_bandwidth(uhci, qh);

}

return

ret;

}

首先为传输选择一个合适的中断值.在前面我们分析过,UHCI的调度里,分为了从int1,int2,int4...int128这8种类型的中断.那在urb中的间隔值可能没有落在这几个点上,例如,urb中的间隔值是34.落在int32和int64之间,所以要选择32做为它的周期.

SKEL_INDEX()是将周期值转换为skelqh中的序号.例如,int128对应的就是skelqh[2].

在这里注意到,打成TD的过程是调用uhci_submit_common().这和批量传输过程是一样的,在这里就不再做分析了.

在这里,要特别分析一下两个函数,一个是用来计算带宽是否足够的函数uhci_check_bandwidth().另一个是用来设置占用带宽的函数uhci_reserve_bandwidth().

先来分析uhci_check_bandwidth(),代码如下:

static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct

uhci_qh *qh)

{

int

minimax_load;

if

(qh->phase >= 0)

minimax_load = uhci_highest_load(uhci, qh->phase,

qh->period);

else

{

int phase, load;

int max_phase = min_t(int, MAX_PHASE,

qh->period);

qh->phase = 0;

minimax_load = uhci_highest_load(uhci, qh->phase,

qh->period);

for (phase = 1; phase < max_phase; ++phase) {

load = uhci_highest_load(uhci, phase,

qh->period);

if (load < minimax_load) {

minimax_load = load;

qh->phase = phase;

}

}

}

if

(minimax_load + qh->load > 900)

{

dev_dbg(uhci_dev(uhci), "bandwidth allocation failed: "

"period %d, phase %d, %d + %d us\n",

qh->period, qh->phase, minimax_load,

qh->load);

return -ENOSPC;

}

return

0;

}

注意到,在uhci_submit_interrupt()中,将qh->phase设置如下:

qh->phase = (qh->period / 2)

& (MAX_PHASE - 1);

这个phase肯定是大于或者等于0的.

根据上面的计算方式,int1,int2...int128分别对应的phase值是:

0,1,2,4,8,16,0,0

那这个phase代表的是什么呢?不着急,先看完整个过程的分析.

先撇开其它的,只看后面的一个if语句:

if (minimax_load + qh->load >

900)

......

根据注释和打印消息的提示,这里是带宽不够的情况,qh->load是我们在前面计算出来的,这次传输在frame所占的总线时间.以微秒为单位.再根据注释的说明,再加上我们之前的分析:等时传输加上中断传输的带宽不能超过90%.也就是一个frame的900微秒.据此,我们可以肯定minimax_load就是frame中已经被消耗的时间.

跟踪到uhci_highest_load():

static int uhci_highest_load(struct uhci_hcd *uhci, int phase,

int period)

{

int

highest_load = uhci->load[phase];

for

(phase += period; phase < MAX_PHASE; phase +=

period)

highest_load = max_t(int, highest_load,

uhci->load[phase]);

return

highest_load;

}

这个函数用来计算中断传输所属的frame的负载情况.uhci有一个32项的数组.其实就是代表32个frame.在前面分析可以知道,UHCI调度中总共有1024项.这是就是以32项来表示1024项frame

的负载情况.

这个函数本身还是好懂,就是以phase为起点,以period为周期,找出数组项的最大值.

结合上面的分析:

int1,int2...int128分别对应的phase值是:

0,1,2,4,8,16,0,0

0,1,2,4,8,16这几项倒还是好理解,就是对应int1,int2,int4...int32在uhci->frame[]中的起始位置.这几个间隔在前32项数组里呈一个周期重复的关系,用32项数组来表示他们的负载情况毫无疑问是可以的.

Int1所占的位置是0.任何间隔后面都可以跟int1.因此,在这里,起始位置0也可以被其它间隔重用.

uhci_reserve_bandwidth()的代码如下:

static void uhci_reserve_bandwidth(struct uhci_hcd *uhci,

struct uhci_qh *qh)

{

int

i;

int

load = qh->load;

char *p

= "??";

for (i

= qh->phase; i < MAX_PHASE; i +=

qh->period) {

uhci->load[i] += load;

uhci->total_load += load;

}

uhci_to_hcd(uhci)->self.bandwidth_allocated =

uhci->total_load / MAX_PHASE;

switch

(qh->type) {

case

USB_ENDPOINT_XFER_INT:

++uhci_to_hcd(uhci)->self.bandwidth_int_reqs;

p = "INT";

break;

case

USB_ENDPOINT_XFER_ISOC:

++uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs;

p = "ISO";

break;

}

qh->bandwidth_reserved = 1;

dev_dbg(uhci_dev(uhci),

"%s dev %d epx-%s, period %d, phase %d, %d us\n",

"reserve", qh->udev->devnum,

qh->hep->desc.bEndpointAddress,

p,

qh->period, qh->phase, load);

}

首先,更新load[]数组,因为要传输一个中断数据包.会占据总线时间.在传输完成这个,同样也会更新load[].只不过,到时候是将它的值减下来.因为传输完成了,释放总线时间.

然后,更新uhci中的各项计算.

最后,将qh->bandwidth_reserver置为1.表示已经为这个QH分配带宽了.

就这样,

uhci_submit_interrupt()就处理完了.流程返回到uhci_urb_enqueue().

同之前分析过的其它类型的传输一样,流程会进入到uhci_activate_qh().只不过,在函数里调用的是link_interrupt()用来跟具体的调度frame关联起来.代码如下:

static void link_interrupt(struct uhci_hcd *uhci, struct

uhci_qh *qh)

{

struct

uhci_qh *pqh;

//挂到skelqh[qh->skel]的最后面

list_add_tail(&qh->node,

&uhci->skelqh[qh->skel]->node);

//找到链接在最末尾的QH

pqh =

list_entry(qh->node.prev, struct uhci_qh,

node);

//链接到这个QH的后面

qh->link = pqh->link;

wmb();

pqh->link = LINK_TO_QH(qh);

}

很简单的操作,就是将qh插到对应的间隔的frame就可以了.

在这里,要注意,流回返回uhci_urb_enqueue之后,

uhci_urbp_wants_fsbr()是什么都不会做的.

因为中断传输并没有调用uhci_add_fsbr()将fsbr开启.

到这里,中断传输已经分析完了.

3.5:实时传输过程

Root hub是不支持实时传输的.困此,流程一直流到uhci_urb_enqueue()里面.

对于实时传输, uhci_alloc_qh()有特殊的处理.如下面的代码片段如示:

static int uhci_urb_enqueue(struct usb_hcd *hcd,

struct urb *urb, gfp_t mem_flags)

{

......

......

if (qh->type != USB_ENDPOINT_XFER_ISOC) {

qh->dummy_td = uhci_alloc_td(uhci);

if (!qh->dummy_td) {

dma_pool_free(uhci->qh_pool, qh, dma_handle);

return NULL;

}

}

qh->state = QH_STATE_IDLE;

qh->hep = hep;

qh->udev = udev;

hep->hcpriv = qh;

//计算传输所占的总线时间,以微秒为单位

if (qh->type == USB_ENDPOINT_XFER_INT ||

qh->type == USB_ENDPOINT_XFER_ISOC)

qh->load =

usb_calc_bus_time(udev->speed,

usb_endpoint_dir_in(&hep->desc),

qh->type == USB_ENDPOINT_XFER_ISOC,

le16_to_cpu(hep->desc.wMaxPacketSize))

/ 1000 + 1;

......

}

如上面的代码所示,它并不会像其它的传输那样去创建qh->dummy_td.它也会跟中断传输一样去计算它所耗的总线时间.

在uhci_urb_enqueue()中的switch判断中,流程转入到uhci_submit_isochronous(),在分析代码之前,先来看一下中断传输的调度周期和实时传输的调度周期的差别.

首先是调度周期的范围不一样,中断传输的调度周期是从int1到int128,而实时传输的调度周期是从int1到int1024,不过两种传输的调度周期都有一个规律,就是它的周期值都是2的n次方的形式.也就是说它的低n位是对齐的.

再者,他们调度的起始点不一样,从前面UHCI调度架构初始化部份可以看到,UHCI的1024个frmame,全指向的int1到int128,也即uhci->skelqh[2]~uhci->skelqh[9]项.这些间隔的起始位置在frame中都是被限定好了的.而对于实时传输,它的调度起点可以任意定.

好了,可以跟进代码了,分段分析,如下示:

static int uhci_submit_isochronous(struct uhci_hcd *uhci,

struct urb *urb,

struct uhci_qh *qh)

{

struct

uhci_td *td = NULL;

int i,

frame;

unsigned long destination, status;

struct

urb_priv *urbp = (struct urb_priv *)

urb->hcpriv;

//间隔时间不能超过1024,数据包个数不能超为1024

if

(urb->interval >= UHCI_NUMFRAMES

||

urb->number_of_packets >=

UHCI_NUMFRAMES)

return -EFBIG;

if

(!qh->bandwidth_reserved) {

qh->period = urb->interval;

//URB_ISO_ASAP这个flag是专门给等时传输用的,

//它的意思就是告诉驱动程序,只要带宽允许,那么就从此点开始设置这个urb的start_frame变//量

if (urb->transfer_flags &

URB_ISO_ASAP) {

qh->phase =

-1;

//找到一个最合适的phase

i = uhci_check_bandwidth(uhci, qh);

if (i)

return i;

//取得当前的frame

uhci_get_current_frame_number(uhci);

//延迟10ms.让它有足够的时候分配内存

frame = uhci->frame_number + 10;

//找到frame下一个周期的起始位置

urb->start_frame = frame +

((qh->phase - frame) &

(qh->period - 1));

} else {

//指定的起始帧不能在扫描帧的前面,扫描帧,也就是当前UHCI调度的帧号

//起始帧不能放到被调度帧的前面.这样避免要传输的ISO数据的前面的帧要晚于后面的帧调//度

i = urb->start_frame -

uhci->last_iso_frame;

if (i <= 0 || i >=

UHCI_NUMFRAMES)

return -EINVAL;

//计算phase

//start_frame要落在这个周期点上

qh->phase = urb->start_frame

& (qh->period - 1);

i = uhci_check_bandwidth(uhci, qh);

if (i)

return i;

}

}

该函数先对入口参数进行有效性判断.在UHCI的架构上,进入到这个函数时,qh是刚刚分配初始化好的,它的bandwidth_reserved肯定是0,所以上面的if判断是肯定会满足的.

Urb->interval是驱动程序在提交实时传输时设置的传输间隔,也就是ISO数据包的调度周期,如果用户指明了URB_ISO_ASAP,则表示该ISO数据包要尽快的传送.

对于指定了URB_ISO_ASAP的情况,就是为传输选定一个周期始点和ISO数据的起始帧号.如果没有指定这个标志,那就使用用户自定义的起始帧号,但是请注意,这个起始帧号不能在当前调度帧的前面.为什么?

如果起始帧在当前调度帧的前面,那有可能,ISO数据包后面的帧会在当前调度帧的后面.那有可能后面的ISO数据反而要比前面的ISO数据先进行调度.

这段代码涉及到phase和start_frame计算比较涩晦,稍后再进行详细分析.

else if (qh->period !=

urb->interval) {

return

-EINVAL;

} else

{

if (list_empty(&qh->queue)) {

frame = qh->iso_frame;

} else {

struct urb *lurb;

lurb = list_entry(qh->queue.prev,

struct urb_priv, node)->urb;

frame = lurb->start_frame +

lurb->number_of_packets *

lurb->interval;

}

if (urb->transfer_flags &

URB_ISO_ASAP) {

uhci_get_current_frame_number(uhci);

if (uhci_frame_before_eq(frame, uhci->frame_number))

{

frame = uhci->frame_number + 1;

frame += ((qh->phase - frame)

&

(qh->period - 1));

}

}

urb->start_frame = frame;

}

//数据不能超长

if

(uhci_frame_before_eq(uhci->last_iso_frame +

UHCI_NUMFRAMES,

urb->start_frame +

urb->number_of_packets *

urb->interval))

return -EFBIG;

后面的这几个elseif

,if肯定是不会进去的,可以直接跳过.一次传输的ISO数据包也不能够太长,从上面的计算,可以看出,一次ISO传输不能超过1023个包.

//置ACTIVE和IOS

status

= TD_CTRL_ACTIVE | TD_CTRL_IOS;

destination = (urb->pipe &

PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);

for (i

= 0; i < urb->number_of_packets; i++)

{

td = uhci_alloc_td(uhci);

if (!td)

return -ENOMEM;

uhci_add_td_to_urbp(td, urbp);

uhci_fill_td(td, status, destination |

uhci_explen(urb->iso_frame_desc[i].length),

urb->transfer_dma +

urb->iso_frame_desc[i].offset);

}

//最后的一个TD置IOC

td->status |=

__constant_cpu_to_le32(TD_CTRL_IOC);

到这里的话,就是要将数据打成TD了.首先,对于每一个TD,都要置ACTIVE和IOS位,表示TD是一个可被调度的实时传输型的数据包.然后,对于最后的一个TD还要设置IOC位,这样,速个数据传输完成之后就会产生中断.

对于urb->iso_frame_desc数组,这是驱动程序在提交URB的时候,就已经设置了相关项的.上面代码中的urb->iso_frame_desc[i].length,

urb->iso_frame_desc[i].offset分别表示该帧的长度和在整个数据中的偏移值.

这样,ISO对应的TD包就全部在urbp->td_list上面了

//将TD挂到相应的frame上

frame =

urb->start_frame;

list_for_each_entry(td,

&urbp->td_list, list) {

uhci_insert_td_in_frame_list(uhci, td, frame);

frame += qh->period;

}

if

(list_empty(&qh->queue)) {

qh->iso_packet_desc =

&urb->iso_frame_desc[0];

qh->iso_frame =

urb->start_frame;

}

//将skel置为SKEL_ISO

qh->skel = SKEL_ISO;

//设置占用带宽

if

(!qh->bandwidth_reserved)

uhci_reserve_bandwidth(uhci, qh);

return

0;

}

创建好了TD之后就要跟UHCI的调度系统关联起来了.从上面的代码中可以看到,它是每隔特定的周期就在uhci->frame[]数组里插入对应的TD.

从前面的流程中看来,

list_empty(&qh->queue)是肯定会满足的,就这样,

qh->iso_packet_desc指定了urb->iso_frame_desc[]的首地址.qh->iso_frame就是它的起始帧位置.

经过上面的分段分析之后,流程就很清晰了.在代码中,有几个情况必须要详细的分析一下.

1:在指定URB_ISO_ASAP标志的情况下,phase和start_frame的计算

正如之前在分析中断传输的过程一样,phase就是指在frame[]中起始位置.在中断传输中,指定间隔的起始位置都是固定好的.但在ISO传输中,就没必要了,因为它对数据的实时性要求很高,只要能够传输ISO,就马上让它传输出去,没必须要等到指定的间隔位置到来之后才去调度它.

在代码中,先将phase置为-1.然后调用uhci_check_bandwidth()来选择一个合适的phase值.这个函数在之前分析过,在这里将其关部份列出:

static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct

uhci_qh *qh)

{

......

......

if (qh->phase >= 0)

minimax_load = uhci_highest_load(uhci, qh->phase,

qh->period);

else

{

int phase, load;

int max_phase = min_t(int, MAX_PHASE,

qh->period);

qh->phase = 0;

minimax_load = uhci_highest_load(uhci, qh->phase,

qh->period);

for (phase = 1; phase < max_phase; ++phase) {

load = uhci_highest_load(uhci, phase,

qh->period);

if (load < minimax_load) {

minimax_load = load;

qh->phase = phase;

}

}

}

......

}

从代码中很容易很出,它就是在找到一个负载最轻的项.

找到合适的phase之后,将选择start_frame的基准,也就是frame变量,在当前调度帧的基础上后移十个帧.注释中说的很明白,这样就是为了有足够的时候让它分配内存.

最终关于start_frame的计算如下:

urb->start_frame = frame +

((qh->phase - frame) &

(qh->period - 1));

start_frame要满足两上条件:

1:start_frame在frame的后面.这点是没什么疑问的.因为我们希望这些ISO数据可以尽快被调度到.

2:start_frame必须要满足:start_frame =

phase+K*period(k=0,1,2...).也就是说,start_frame必须要落在它的周期点上.

对应上面的计算就是为了找到frame后的第一个周期点.这里的计算方式很隐晦,下面详细分析一下这个算法.

1:对于phase=frame的情况.上面的计算是满足的.

2:对于phase>frame的情况.

如下图示:

a4c26d1e5885305701be709a3d33442f.png

首先,先提醒一下,phase之前,不能还有一个周期的长度,也就是说,phase是周期起点,这必须要满足,phase

<

period.想一想为什么?如果phase>period的话,那周期起点就不是phase,面是phase-N*period(N=0,1,2...)了.

另外,还是注意这个period是2的n次方形式的,也就低n是为0的.

在这种情况一,(phase-frame)&(

period-1)就是等于phase-frame. Frame+(phase-frame)&(

period-1)=period.很显然是满足的.

3:对于phase

因为phase-frmae的值要小于0.这样给我们的计算造成了不便,所以不能用常规的方法去分析它.

A+(-A)=0

那A的后面,第一个为period的整数倍的值为

A+(-A)&(period-1)

为什么呢?我们假设period=1<

(M=0,1,2...).如下图所示:

a4c26d1e5885305701be709a3d33442f.png

如上图中分析的,A的两种情况:

1:一种是A的后M位为0.显然,A是period的倍数,

A+(-A)&(period-1)还是等于A.显然是正确的

2:另一种A的后M位不为0,

A+(-A)&(period-1)之后,低M为0,而第M+1有进位,显然结果就是A后面最小的period的倍数值.

假设,现在是从位置0开始计算的周期,那frame后的周期点就是frame+(-frame)&(period-1).现在周期是从phase开始计算的,那,frmae后的周期点就要加上phase,即frame+(-frame)&(period-1)+phase.根据前面对于phase的要求,有phase=(phase)&(period-1).那么就有了下面的式子:

frame+(-frame)&(period-1)+(phase)&(period-1)

因为(X+Y)&Z =

X&Z+Y&Z

所以下面的式子就等价于:

Frame+(period-frame)&(period-1)

很显然,他们也是满足的

就这样,就确定了urb->start_frame的位置.

2:在没有指定URB_ISO_ASAP的情况下,phase和start_frame的计算

如果没有指定URB_ISO_ASAP,那start_frame就使用用户指定的start_frame.剩下的工作就是计算phase值了.这样的工作刚好跟第1种情况是相反的,在第1种情况里,是根据phase值来计算start_frame.

在这里,计算phase的式子如下:

qh->phase = urb->start_frame

& (qh->period - 1);

很显然,在这里必须要满足:start_frame =

phase+K*period(k=0,1,2...)只不过start_frmae是已知的,而phase是末知的.

很显然,只需要取start_frame的低K位就可以了.想一想,如果周期起点位置是从0开始的话,那start_frame肯定是低K位对齐的.所以start_frmae的低K位,也就是周期起点的偏移值,即phase.

3: uhci_insert_td_in_frame_list()函数

uhci_insert_td_in_frame_list就是将TD加到相应的frame里面去.它的代码如下:

static inline void uhci_insert_td_in_frame_list(struct

uhci_hcd *uhci,

struct uhci_td *td, unsigned framenum)

{

framenum &= (UHCI_NUMFRAMES - 1);

td->frame = framenum;

if

(uhci->frame_cpu[framenum]) {

struct uhci_td *ftd, *ltd;

ftd = uhci->frame_cpu[framenum];

ltd = list_entry(ftd->fl_list.prev, struct uhci_td,

fl_list);

list_add_tail(&td->fl_list,

&ftd->fl_list);

td->link = ltd->link;

wmb();

ltd->link = LINK_TO_TD(td);

} else

{

td->link =

uhci->frame[framenum];

wmb();

uhci->frame[framenum] = LINK_TO_TD(td);

uhci->frame_cpu[framenum] = td;

}

}

很显然,在第一次添加的时候,

uhci->frame_cpu[framenum]是空的,也就是说流程会进入到else中.在else的操作中,将frame指向TD,再指TD指向QH.然后将uhci->frame_cpu[framenum]指向这个TD.

很显然,当以后要往这个frame添加TD的时候,它是将TD加到这个TD的后面.那实际上,

uhci->frame_cpu[framenum]就是表示,挂在frame[framenum]上的实时TD链表.

用图来表示上述操作过程,如下:

a4c26d1e5885305701be709a3d33442f.png

回忆一下之前讲UHCI调度初始化的时候,从UHCI spec中列出来的那副调度图,是不是很像了?*^_^*

到这里,

uhci_submit_isochronous()已经全部分析完了.流程返回到uhci_urb_enqueue()中.像其它传输一样,流程会转入uhci_activate_qh中.只不过,在uhci_activate_qh()中调用的子函数是link_iso().它的代码如下:

static inline void link_iso(struct uhci_hcd *uhci, struct

uhci_qh *qh)

{

list_add_tail(&qh->node,

&uhci->skel_iso_qh->node);

}

很显然,就是将实时传输的QH加到了skel_iso_qh的链表上.

在ISO传输中,有一点要特别注意,ISO传输中并没有去创建一个用来表示结尾的TD(因为ISO传输是TD相联的,最后一个TD联QH,如果中间有无效TD,UHCI就不会处理后面的QH了),而其它类型的传输都会有一个表示QH结尾的TD.

到这里,四种传输类型全部都分析完了.下面来个小小的总结:

1:对于控制传输和批量传输,它的qh是加在skel_async_qh上的.另外,这两种传输还支持FSBR.对于FSBR,它会借助于skel_term_qh来构成一个循环

2:对于中断传输,它的qh是加在中断间隔对应的skelqh[]的链表上

3:对于实时传输,它的qh是加在skel_iso_qh上.

四:中断处理过程

在上面的传输分析中,都在在结尾的TD上加上一个IOC属性,这个属性位表示,如果该TD如在的frame处理完成之后,就会给CPU上报一个中断.而UHCI驱动可能根据这个中断来判断urb是否传输完成了.闲言少述,进入代码.

UHCI的中断处理程序为usb_hcd_irq().代码如下:

irqreturn_t usb_hcd_irq (int irq, void *__hcd)

{

struct

usb_hcd *hcd = __hcd;

int start = hcd->state;

if

(unlikely(start == HC_STATE_HALT ||

!test_bit(HCD_FLAG_HW_ACCESSIBLE,

&hcd->flags)))

return IRQ_NONE;

if

(hcd->driver->irq (hcd) ==

IRQ_NONE)

return IRQ_NONE;

set_bit(HCD_FLAG_SAW_IRQ,

&hcd->flags);

if

(unlikely(hcd->state == HC_STATE_HALT))

usb_hc_died (hcd);

return

IRQ_HANDLED;

}

从上面的代码可以看到,流程最终会转向driver->irq().对应的接口为uhci_irq().代码如下:

static irqreturn_t uhci_irq(struct usb_hcd *hcd)

{

struct

uhci_hcd *uhci = hcd_to_uhci(hcd);

unsigned short status;

//STS寄存器

status

= inw(uhci->io_addr + USBSTS);

//USBSTS_HCH:UHCI停止运行时,设置此位

//没有中断.

if

(!(status &

~USBSTS_HCH))

return IRQ_NONE;

//R/WC.把值写回去,用来清除其中的erron 位

outw(status, uhci->io_addr +

USBSTS);

//根据不同的错误.打印出不出的错误提示信息

if

(status & ~(USBSTS_USBINT | USBSTS_ERROR |

USBSTS_RD)) {

if (status & USBSTS_HSE)

dev_err(uhci_dev(uhci), "host system error, "

"PCI problems?\n");

if (status & USBSTS_HCPE)

dev_err(uhci_dev(uhci), "host controller process "

"error, something bad happened!\n");

if (status & USBSTS_HCH) {

spin_lock(&uhci->lock);

if (uhci->rh_state >=

UHCI_RH_RUNNING) {

dev_err(uhci_dev(uhci),

"host controller halted, "

"very bad!\n");

if (debug > 1 &&

errbuf) {

uhci_sprint_schedule(uhci,

errbuf, ERRBUF_LEN);

lprintk(errbuf);

}

uhci_hc_died(uhci);

mod_timer(&hcd->rh_timer,

jiffies);

}

spin_unlock(&uhci->lock);

}

}

//如果收到了Resume信号,则重新启用定时器轮询.

if (status & USBSTS_RD)

usb_hcd_poll_rh_status(hcd);

else

{

spin_lock(&uhci->lock);

//扫描调度队列

uhci_scan_schedule(uhci);

spin_unlock(&uhci->lock);

}

return

IRQ_HANDLED;

}

USBSTS寄存器全名叫USB STATUS

REGISTER.即状态寄存器,这个寄存器里会反应出系统的状态和中断状态.另外,这个寄存器是R/WC类型的,也就是说往寄存器中某位写入1,会将其置为0.代码中也利用了这个特性来清除STS中的ERROR位.

还记得之前分析root

hub的中断传输时候,分析到suspend_rh()函数曾说过,如果设备被挂起,会将端口状态轮询定时器停止的,但是开启了Resume中断,如果收到了Resume信号,就会产生一个中断,中断处理函数就再次启用轮询定时器.这也就是对应上面代码的if(status

& USBSTS_RD)部份.

(末完,待续...)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值