轉載於: http://linux.chinaunix.net/techdoc/install/2008/09/18/1033112.shtml
------------------------------------------
本文系本站原創,歡迎轉載!
轉載請注明出處: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的參數.有一系列輔助的宏.如下示:
/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint) \
((PIPE_CONTROL
#define usb_rcvctrlpipe(dev,endpoint) \
((PIPE_CONTROL
#define usb_sndisocpipe(dev,endpoint) \
((PIPE_ISOCHRONOUS
#define usb_rcvisocpipe(dev,endpoint) \
((PIPE_ISOCHRONOUS
#define usb_sndbulkpipe(dev,endpoint) \
((PIPE_BULK
#define usb_rcvbulkpipe(dev,endpoint) \
((PIPE_BULK
#define usb_sndintpipe(dev,endpoint) \
((PIPE_INTERRUPT
#define usb_rcvintpipe(dev,endpoint) \
((PIPE_INTERRUPT
這個宏都是根據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
return -ENODEV;
/* For now, get the endpoint from the pipe. Eventually drivers
* will be required to set urb->ep directly and we will eliminate
* urb->pipe.
*/
//取得要傳輸的端口.對端地址是由方向+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;
/* Lots of sanity checks, so HCDs can rely on clean data
* and don't need to duplicate tests
*/
//取得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);
}
/* Cache the direction for later use */
//根據傳輸方向.置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
return -ENODEV;
//傳送/接收的最大字節.如果這個最大巧若拙字節還要小於0,那就是非法的
max = le16_to_cpu(ep->desc.wMaxPacketSize);
if (max
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;
}
/* periodic transfers limit size per frame/uframe,
* but drivers only control those sizes for ISO.
* while we're checking, initialize return status.
*/
//如果是實時傳輸
if (xfertype == USB_ENDPOINT_XFER_ISOC) {
int n, len;
/* "high bandwidth" mode, 1-3 packets/uframe? */
//如果是高速傳輸.則要修正它的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
return -EINVAL;
//urb->number_of_packets: 實時數據包個數.每個實時數據包對應urb->iso_frame_desc[]中的一項
for (n = 0; n number_of_packets; n++) {
len = urb->iso_frame_desc[n].length;
if (len max)
return -EMSGSIZE;
urb->iso_frame_desc[n].status = -EXDEV;
urb->iso_frame_desc[n].actual_length = 0;
}
}
/* the I/O buffer must be mapped/unmapped, except when length=0 */
//如果要傳輸的緩存區大小小於0.非法
if (urb->transfer_buffer_length
return -EMSGSIZE;
#ifdef DEBUG
/* stuff that drivers shouldn't do, but which shouldn't
* cause problems in HCDs if they get it wrong.
*/
{
unsigned int orig_flags = urb->transfer_flags;
unsigned int allowed;
/* enforce simple/standard policy */
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;
/* FALLTHROUGH */
case USB_ENDPOINT_XFER_CONTROL:
allowed |= URB_NO_FSBR; /* only affects UHCI */
/* FALLTHROUGH */
default: /* all non-iso endpoints */
if (!is_out)
allowed |= URB_SHORT_NOT_OK;
break;
case USB_ENDPOINT_XFER_ISOC:
allowed |= URB_ISO_ASAP;
break;
}
urb->transfer_flags &= allowed;
/* fail if submitter gave bogus flags */
if (urb->transfer_flags != orig_flags) {
err("BOGUS urb flags, %x --> %x",
orig_flags, urb->transfer_flags);
return -EINVAL;
}
}
#endif
/*
* Force periodic transfer intervals to be legal values that are
* a power of two (so HCDs don't need to).
*
* FIXME want bus->{intr,iso}_sched_horizon values here. Each HC
* supports different values... this uses EHCI/UHCI defaults (and
* EHCI can use smaller non-default values).
*/
//關於實時傳輸和中斷傳輸的interval處理
switch (xfertype) {
case USB_ENDPOINT_XFER_ISOC:
case USB_ENDPOINT_XFER_INT:
/* too small? */
//interval不能小於或等於0
if (urb->interval
return -EINVAL;
/* too big? */
switch (dev->speed) {
case USB_SPEED_HIGH: /* units are microframes */
/* NOTE usb handles 2^15 */
if (urb->interval > (1024 * 8))
urb->interval = 1024 * 8;
max = 1024 * 8;
break;
case USB_SPEED_FULL: /* units are frames/msec */
case USB_SPEED_LOW:
if (xfertype == USB_ENDPOINT_XFER_INT) {
if (urb->interval > 255)
return -EINVAL;
/* NOTE ohci only handles up to 32 */
max = 128;
} else {
if (urb->interval > 1024)
urb->interval = 1024;
/* NOTE usb and ohci handle up to 2^15 */
max = 1024;
}
break;
default:
return -EINVAL;
}
/* Round down to a power of 2, no more than max */
urb->interval = min(max, 1 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);
/* increment urb's reference count as part of giving it to the HCD
* (which will control it). HCD guarantees that it either returns
* an error or calls giveback(), but not both.
*/
//增加有關的引用計數,usbmon*系列的函數是編譯選擇的.忽略
usb_get_urb(urb);
atomic_inc(&urb->use_count);
atomic_inc(&urb->dev->urbnum);
usbmon_urb_submit(&hcd->self, urb);
/* NOTE requirements on root-hub callers (usbfs and the hub
* driver, for now): URBs' urb->transfer_buffer must be
* valid and usb_buffer_{sync,unmap}() not be needed, since
* they could clobber root hub response data. Also, control
* URBs must be submitted in process context with interrupts
* enabled.
*/
//對傳輸的緩存區進行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; /* Indicate it's queued */
cmd = (struct usb_ctrlrequest *) urb->setup_packet;
typeReq = (cmd->bRequestType 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) {
/* DEVICE REQUESTS */
/* The root hub's remote wakeup enable bit is implemented using
* driver model wakeup flags. If this system supports wakeup
* through USB, userspace may change the default "allow wakeup"
* policy through sysfs or these calls.
*
* Most root hubs support wakeup from downstream devices, for
* runtime power management (disabling USB clocks and reducing
* VBUS power usage). However, not all of them do so; silicon,
* board, and BIOS bugs here are not uncommon, so these can't
* be treated quite like external hubs.
*
* Likewise, not all root hubs will pass wakeup events upstream,
* to wake up the whole system. So don't assume root hub and
* controller capabilities are identical.
*/
case DeviceRequest | USB_REQ_GET_STATUS:
tbuf [0] = (device_may_wakeup(&hcd->self.root_hub->dev)
| (1
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;
/* FALLTHROUGH */
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
break;
case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
switch (wValue & 0xff00) {
case USB_DT_DEVICE
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
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
n = rh_string (wValue & 0xff, hcd, ubuf, wLength);
if (n
goto error;
urb->actual_length = n;
break;
default:
goto error;
}
break;
case DeviceRequest | USB_REQ_GET_INTERFACE:
tbuf [0] = 0;
len = 1;
/* FALLTHROUGH */
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;
/* INTERFACE REQUESTS (no defined feature/status flags) */
/* ENDPOINT REQUESTS */
case EndpointRequest | USB_REQ_GET_STATUS:
// ENDPOINT_HALT flag
tbuf [0] = 0;
tbuf [1] = 0;
len = 2;
/* FALLTHROUGH */
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
case EndpointOutRequest | USB_REQ_SET_FEATURE:
dev_dbg (hcd->self.controller, "no endpoint features yet\n");
break;
/* CLASS REQUESTS (and errors) */
default:
/* non-generic request */
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:
/* "protocol stall" on 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 = urb->transfer_buffer_length;
urb->actual_length = len;
// always USB_DIR_IN, toward host
memcpy (ubuf, bufp, len);
/* report whether RH hardware supports remote wakeup */
if (patch_wakeup &&
len > offsetof (struct usb_config_descriptor,
bmAttributes))
((struct usb_config_descriptor *)ubuf)->bmAttributes
|= USB_CONFIG_ATT_WAKEUP;
}
/* any errors get returned through the urb completion */
spin_lock_irq(&hcd_root_hub_lock);
//處理完成了,可以將urb從ep->urb_list上脫落了
usb_hcd_unlink_urb_from_ep(hcd, urb);
/* This peculiar use of spinlocks echoes what real HC drivers do.
* Avoiding calls to local_irq_disable/enable makes the code
* RT-friendly.
*/
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;
/* Add this URB to the QH */
urbp->qh = qh;
//將urbp加到qh的queue鏈表中
list_add_tail(&urbp->node, &qh->queue);
/* If the new URB is the first and only one on this QH then either
* the QH is new and idle or else it's unlinked and waiting to
* become idle, so we can activate it right away. But only if the
* queue isn't stopped. */
//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); /* Reclaim unused 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) { /* Normal QH */
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 { /* Skeleton QH */
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;
/* The "pipe" thing contains the destination in bits 8--18 */
//dev_num + end_num
destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;
/* 3 errors, dummy TD remains inactive */
//設置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;
/*
* Build the TD for the control request setup packet
*/
//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 direction is "send", change the packet ID from SETUP (0x2D)
* to OUT (0xE1). Else change it from SETUP to IN (0x69) and
* set Short Packet Detect (SPD) for all data packets.
*
* 0-length transfers always get treated as "send".
*/
//判斷是往哪一個方向的
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;
}
/*
* Build the DATA TDs
*/
//數據傳輸階段
while (len > 0) {
int pktsze = maxsze;
//是后的一個包,確實是短包了,清除掉SPD
if (len
pktsze = len;
status &= ~TD_CTRL_SPD;
}
td = uhci_alloc_td(uhci);
if (!td)
goto nomem;
*plink = LINK_TO_TD(td);
/* Alternate Data0/1 (start with Data1) */
//交替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;
}
/*
* Build the final TD for control status
*/
//狀態階段
td = uhci_alloc_td(uhci);
if (!td)
goto nomem;
*plink = LINK_TO_TD(td);
/* Change direction for the status transaction */
//狀態階段的方向跟前一筆事務是相反的方向
destination ^= (USB_PID_IN ^ USB_PID_OUT);
//限定為DATA1
destination |= TD_TOKEN_TOGGLE; /* End in Data1 */
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;
/*
* Build the new dummy TD and activate the old one
*/
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;
/* Low-speed transfers get a different queue, and won't hog the bus.
* Also, some devices enumerate better without FSBR; the easiest way
* to do that is to put URBs on the low-speed queue while the device
* isn't in the CONFIGURED state. */
//確定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; /* Account for the SETUP packet */
return 0;
nomem:
/* Remove the dummy TD from the td_list so it doesn't get freed */
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));
/* Set the element pointer if it isn't set already.
* This isn't needed for Isochronous queues, but it doesn't hurt. */
//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);
}
/* Treat the queue as if it has just advanced */
qh->wait_expired = 0;
qh->advance_jiffies = jiffies;
if (qh->state == QH_STATE_ACTIVE)
return;
qh->state = QH_STATE_ACTIVE;
/* Move the QH from its old list to the correct spot in the appropriate
* skeleton's list */
//剛開始的時候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
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;
/* Find the predecessor QH for our new one and insert it in the list.
* The list of QHs is expected to be short, so linear search won't
* take too long. */
//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 skel)
break;
}
//在skelqh[9]->node中找到一個合適的位置,將QH插下去
list_add(&qh->node, &pqh->node);
/* Link it into the schedule */
//接QH插入到調度隊列
qh->link = pqh->link;
//這里涉及到物理地址的操作,為了防止編譯器優化,插上內存屏障
wmb();
link_to_new_qh = LINK_TO_QH(qh);
pqh->link = link_to_new_qh;
/* If this is now the first FSBR QH, link the terminating skeleton
* QH to it. */
//如果是第1個fsbr,將skelqh[10]指向這個QH
//skel_term_qh: skelqh[10]
if (pqh->skel 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;
/* The terminating skeleton QH always points back to the first
* FSBR QH. Make the last async QH point to the terminating
* skeleton QH. */
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的操作.得出以下的示意圖:
在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;
/* Remove the link from the last async QH to the terminating
* skeleton QH. */
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;
/* Can't have low-speed bulk transfers */
//如果是低速設備,退出.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
return -EINVAL;
/* The "pipe" thing contains the destination in bits 8--18 */
destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
//取得toggle位
toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe),
usb_pipeout(urb->pipe));
/* 3 errors, dummy TD remains inactive */
//設置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;
/*
* Build the DATA TDs
*/
//批量傳輸的數據階段TD
plink = NULL;
td = qh->dummy_td;
do { /* Allow zero length packets */
int pktsze = maxsze;
//最后的分包了
if (len
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
data);
plink = &td->link;
//設置ACTIVE位,則表示該TD有效
status |= TD_CTRL_ACTIVE;
data += pktsze;
len -= maxsze;
toggle ^= 1;
} while (len > 0);
/*
* URB_ZERO_PACKET means adding a 0-length packet, if direction
* is OUT and the transfer_length was an exact multiple of maxsze,
* hence (len = transfer_length - N * maxsze) == 0
* however, if transfer_length == 0, the zero packet was already
* prepared above.
*/
//判斷是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
data);
plink = &td->link;
toggle ^= 1;
}
/* Set the interrupt-on-completion flag on the last packet.
* A more-or-less typical 4 KB URB (= size of one memory page)
* will require about 3 ms to transfer; that's a little on the
* fast side but not enough to justify delaying an interrupt
* more than 2 or 3 URBs, so we will ignore the URB_NO_INTERRUPT
* flag setting. */
//設置IOC位.表示執行傳輸完后,就會觸發一次中斷
td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);
/*
* Build the new dummy TD and activate the old one
*/
//隊列結束的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:
/* Remove the dummy TD from the td_list so it doesn't get freed */
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
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; /* indicate it's queued */
//uhci_start(),會將uses_newpolling設為了1
if (!hcd->uses_new_polling)
mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
/* If a status change has already occurred, report it ASAP */
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]; /* Any root hubs with > 31 ports? */
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) {
/* try to complete the status urb */
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 port change, ask to be resumed */
if (status)
usb_hcd_resume_root_hub(hcd);
break;
case UHCI_RH_AUTO_STOPPED:
/* if port change, auto start */
//如果舊狀態是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:
/* are any devices attached? */
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:
/* auto-stop if nothing connected for 1 second */
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 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's server management chip requires
* a longer delay. */
//HP主機的特殊處理
if (to_pci_dev(uhci_dev(uhci))->vendor ==
PCI_VENDOR_ID_HP)
wait_for_HP(port_addr);
/* If the port was enabled before, turning
* reset on caused a port enable change.
* Turning reset off causes a port connect
* status change. Clear these changes. */
//清除掉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)) {
/* Port received a wakeup request */
//如果是輪詢檢測的.將其在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);
/* Make sure we see the port again
* after the resuming period is over. */
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);
/* The controller won't actually turn off the RD bit until
* it has had a chance to send a low-speed EOP sequence,
* which is supposed to take 3 bit times (= 2 microseconds).
* Experiments show that some controllers take longer, so
* we'll poll for completion. */
//等待UHCI清降RD位
for (i = 0; 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;
/* Some boards (both VIA and Intel apparently) report bogus
* overcurrent indications, causing massive log spam unless
* we completely ignore them. This doesn't seem to be a problem
* with the chipset so much as with the way it is connected on
* the motherboard; if the overcurrent input is left to float
* then it may constantly register false positives. */
//有的主板會誤報錯誤,使mask去掉這幾位的匹配
if (ignore_oc)
mask &= ~USBPORTSC_OCC;
*buf = 0;
//如果端口發生了改變,則將buf中的相應位置1
for (port = 0; port rh_numports; ++port) {
if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) ||
//或者是端口剛恢復過來
test_bit(port, &uhci->port_c_suspend))
*buf |= (1
}
//如果buf不為空,返回真
return !!*buf;
}
從UHCI的spec中可以看到,對應PORTSC寄存器的RW/C屬性的位,其實都是狀態改變位,判斷這些位是否被置就可以得知端口狀態是否發生了改變.
在這里要注意buf中置位操作,它是*buf |= (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)" : "");
/* If we are auto-stopped then no devices are attached so there's
* no need for wakeup signals. Otherwise we send Global Resume
* for 20 ms.
*/
//如果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;
/* End Global Resume and wait for EOP to be sent */
//再次寫入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);
/* Restart root hub polling */
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 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)" : ""));
/* If we get a suspend request when we're already auto-stopped
* then there's nothing to do.
*/
//如果已經在STOP狀態了,不需要進行任何處理了
if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) {
uhci->rh_state = new_state;
return;
}
/* Enable resume-detect interrupts if they work.
* Then enter Global Suspend mode if _it_ works, still configured.
*/
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);
/* If we're auto-stopping then no devices have been attached
* for a while, so there shouldn't be any active URBs and the
* controller should stop after a few microseconds. Otherwise
* we will give the controller one frame to stop.
*/
//調用這個函數的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;
/* USB 1.1 interrupt transfers only involve one packet per interval.
* Drivers can submit URBs of any length, but longer ones will need
* multiple intervals to complete.
*/
//QH在初始化的時候會將bandwidth_reserved置為0
if (!qh->bandwidth_reserved) {
int exponent;
/* Figure out which power-of-two queue to use */
//計算合適的周期值
for (exponent = 7; exponent >= 0; --exponent) {
if ((1 interval)
break;
}
if (exponent
return -EINVAL;
//qh->period:調度QH的周期
qh->period = 1
//QH所在的frame 項
qh->skel = SKEL_INDEX(exponent);
/* For now, interrupt phase is fixed by the layout
* of the QH lists. */
//檢查帶寬是否足夠
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; /* Can't decrease the period */
//將數據保打成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;
/* Find the optimal phase (unless it is already set) and get
* its load value. */
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
load = uhci_highest_load(uhci, phase, qh->period);
if (load
minimax_load = load;
qh->phase = phase;
}
}
}
/* Maximum allowable periodic bandwidth is 90%, or 900 us per frame */
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
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也可以被其它間隔重用.
疑問:int64,int128都是以0項來表示負載的.即uhci->load[0].如果有這樣的情況,int64的中斷傳輸已經處於飽和狀態.那肯定會有: uhci->load[0]即將要大於900.那這時候,int128的中斷傳輸是加不進來的.盡管uhci->frame[]中,int128所占的frame項處於空閑狀態.
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 period) {
uhci->load += 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 ep%02x-%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; /* Since urb->number_of_packets > 0 */
int i, frame;
unsigned long destination, status;
struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
/* Values must not be too big (could overflow below) */
//間隔時間不能超過1024,數據包個數不能超為1024
if (urb->interval >= UHCI_NUMFRAMES ||
urb->number_of_packets >= UHCI_NUMFRAMES)
return -EFBIG;
/* Check the period and figure out the starting frame number */
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; /* Find the best phase */
//找到一個最合適的phase
i = uhci_check_bandwidth(uhci, qh);
if (i)
return i;
/* Allow a little time to allocate the TDs */
//取得當前的frame
uhci_get_current_frame_number(uhci);
//延遲10ms.讓它有足夠的時候分配內存
frame = uhci->frame_number + 10;
/* Move forward to the first frame having the
* correct phase */
//找到frame下一個周期的起始位置
urb->start_frame = frame + ((qh->phase - frame) &
(qh->period - 1));
} else {
//指定的起始幀不能在掃描幀的前面,掃描幀,也就是當前UHCI調度的幀號
//起始幀不能放到被調度幀的前面.這樣避免要傳輸的ISO數據的前面的幀要晚於后面的幀調//度
i = urb->start_frame - uhci->last_iso_frame;
if (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; /* Can't change the period */
} else {
/* Find the next unused frame */
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) {
/* Skip some frames if necessary to insure
* the start frame is in the future.
*/
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));
}
} /* Otherwise pick up where the last URB leaves off */
urb->start_frame = frame;
}
/* Make sure we won't have to go too far into the future */
//數據不能超長
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 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.length),
urb->transfer_dma +
urb->iso_frame_desc.offset);
}
/* Set the interrupt-on-completion flag on the last packet. */
//最后的一個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.length, urb->iso_frame_desc.offset分別表示該幀的長度和在整個數據中的偏移值.
這樣,ISO對應的TD包就全部在urbp->td_list上面了
/* Add the TDs to the frame 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
load = uhci_highest_load(uhci, phase, qh->period);
if (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的情況.
如下圖示:
首先,先提醒一下,phase之前,不能還有一個周期的長度,也就是說,phase是周期起點,這必須要滿足,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
如上圖中分析的,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;
/* Is there a TD already mapped there? */
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鏈表.
用圖來表示上述操作過程,如下:
回憶一下之前講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);
/* Isochronous QHs aren't linked by the hardware */
}
很顯然,就是將實時傳輸的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;
/*
* Read the interrupt status, and write it back to clear the
* interrupt cause. Contrary to the UHCI specification, the
* "HC Halted" status bit is persistent: it is RO, not R/WC.
*/
//STS寄存器
status = inw(uhci->io_addr + USBSTS);
//USBSTS_HCH:UHCI停止運行時,設置此位
//沒有中斷.
if (!(status & ~USBSTS_HCH)) /* shared interrupt, not mine */
return IRQ_NONE;
//R/WC.把值寫回去,用來清除其中的erron 位
outw(status, uhci->io_addr + USBSTS); /* Clear it */
//根據不同的錯誤.打印出不出的錯誤提示信息
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) {
/* Print the schedule for debugging */
uhci_sprint_schedule(uhci,
errbuf, ERRBUF_LEN);
lprintk(errbuf);
}
uhci_hc_died(uhci);
/* Force a callback in case there are
* pending unlinks */
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)部份.
(末完,待續...)