1. 程式人生 > >linux裝置驅動之USB資料傳輸分析(之五)

linux裝置驅動之USB資料傳輸分析(之五)

也許,有人會有這樣的疑問: 對於控制傳輸,它不也是基於toggle的糾錯麼,為什麼它就不需要修改後續的包的toggle值呢? 這是因為,控制傳輸的toggle都是從1開始的,刪除掉當前的urb,也不會對後面的發包造成影響. 之後,處理完之後,將無用的td刪除. 跟蹤一下toggle的修正過程.對應的函式為uhci_fixup_toggles().如下所示: static void uhci_fixup_toggles(struct uhci_qh *qh, int skip_first) {     struct urb_priv *urbp = NULL;     struct uhci_td *td;     unsigned int toggle = qh->initial_toggle;     unsigned int pipe;     /* Fixups for a short transfer start with the second URB in the      * queue (the short URB is the first). */     if (skip_first)         urbp = list_entry(qh->queue.next, struct urb_priv, node);     /* When starting with the first URB, if the QH element pointer is      * still valid then we know the URB's toggles are okay. */     else if (qh_element(qh) != UHCI_PTR_TERM)         toggle = 2;     /* Fix up the toggle for the URBs in the queue.  Normally this      * loop won't run more than once: When an error or short transfer      * occurs, the queue usually gets emptied. */     urbp = list_prepare_entry(urbp, &qh->queue, node);     //從第二個URB開始遍歷qh上的URB     list_for_each_entry_continue(urbp, &qh->queue, node) {         /* If the first TD has the right toggle value, we don't          * need to change any toggles in this URB */          //取掛在urb上的第一個TD         td = list_entry(urbp->td_list.next, struct uhci_td, list);         //如果下一個傳輸的URB的起始TD就是損壞包的toggle         if (toggle > 1 || uhci_toggle(td_token(td)) == toggle) {             //取此次URB傳輸的最後一個td             td = list_entry(urbp->td_list.prev, struct uhci_td,                     list);             //最後一個td取反             toggle = uhci_toggle(td_token(td)) ^ 1;         /* Otherwise all the toggles in the URB have to be switched */         } else {             //如果toggle不相符合,則依次給urbp中的td轉換toggle             list_for_each_entry(td, &urbp->td_list, list) {                 td->token ^= __constant_cpu_to_le32(                             TD_TOKEN_TOGGLE);                 toggle ^= 1;             }         }     }     //將最後的toggle儲存進usb device中     wmb();     pipe = list_entry(qh->queue.next, struct urb_priv, node)->urb->pipe;     usb_settoggle(qh->udev, usb_pipeendpoint(pipe),             usb_pipeout(pipe), toggle);     qh->needs_fixup = 0; } 在呼叫這個函式之前,有這樣的設定: qh->initial_toggle = uhci_toggle(td_token(qh->post_td)) ^ 1; 即將qh->initial_toggle設定成為了發生錯誤的TD的toggle值的相反值.也就是繼錯誤TD之後的資料包的toggle值. 由於呼叫這個函式的第二個引數為1.所以,會從qh的第二個urb開始遍歷.如果URB的起始包的toggle與下一個包的toggle相同,則這個URB的toggle值不需要改變,否則,就需要挨個改變URB中的TD的toggle. 最後,還需要將最後的toggle值儲存到usb_dev->toggle[]中.因為下次發包的時候,還要從這裡面去取相應的toggle值做為當前發包的toggle. 第三個要分析的函式是uhci_giveback_urb().程式碼如下示: static void uhci_giveback_urb(struct uhci_hcd *uhci, struct uhci_qh *qh,         struct urb *urb, int status) __releases(uhci->lock) __acquires(uhci->lock) {     struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;     //urb->actual_length為負,說明傳輸失敗     if (qh->type == USB_ENDPOINT_XFER_CONTROL) {         /* urb->actual_length < 0 means the setup transaction didn't          * complete successfully.  Either it failed or the URB was          * unlinked first.  Regardless, don't confuse people with a          * negative length. */         urb->actual_length = max(urb->actual_length, 0);     }     /* When giving back the first URB in an Isochronous queue,      * reinitialize the QH's iso-related members for the next URB. */      //如果是實時傳輸      //如果要刪除的QH是第第一個實時傳輸的URB,則要修改qh的iso_frame和iso_packet_decket     else if (qh->type == USB_ENDPOINT_XFER_ISOC &&             urbp->node.prev == &qh->queue &&             urbp->node.next != &qh->queue) {         struct urb *nurb = list_entry(urbp->node.next,                 struct urb_priv, node)->urb;         qh->iso_packet_desc = &nurb->iso_frame_desc[0];         qh->iso_frame = nurb->start_frame;     }     /* Take the URB off the QH's queue.  If the queue is now empty,      * this is a perfect time for a toggle fixup. */      //將urbp從QH的連結串列上刪除,如果QH是空的,修正toggle值     list_del_init(&urbp->node);     if (list_empty(&qh->queue) && qh->needs_fixup) {         usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),                 usb_pipeout(urb->pipe), qh->initial_toggle);         qh->needs_fixup = 0;     }     //釋放urbp所佔的所用空間,包括它的TD     uhci_free_urb_priv(uhci, urbp);     //從ep的連結串列上將urb刪除     usb_hcd_unlink_urb_from_ep(uhci_to_hcd(uhci), urb);     spin_unlock(&uhci->lock);     usb_hcd_giveback_urb(uhci_to_hcd(uhci), urb, status);     spin_lock(&uhci->lock);     /* If the queue is now empty, we can unlink the QH and give up its      * reserved bandwidth. */      //如果QH為空,將qh斷開,將QH佔用的頻寬釋放     if (list_empty(&qh->queue)) {         uhci_unlink_qh(uhci, qh);         if (qh->bandwidth_reserved)             uhci_release_bandwidth(uhci, qh);     } } 在控制傳輸的過程中,是將urb->actual_length設為-8的,這樣是為了跳過前面的SETUP過程的資料包.如果 SETUP階段發生了錯誤,那麼urb->actual_length將會是一個負值,所以,先要將urb->actual_length修 正為大於或者等於0的數. 如果要刪除的URB是實時佇列QH的第一個URB.那必須更新qh->iso_packet_desc和qh->iso_frame.使其指向有效的起始位置. 另外,如果QH是空的,然後qh->needs_fixup為1,就會在usb_dev->toggle[]修正一次 toggle.(為什麼要這麼做?這裡先放一下,後面會有分析.)這和我們在上面的分析的uhci_fixup_short_transfer()是不同 的.也不會重複.因為uhci_fixup_short_transfer()處理完了之後,會將qh->needs_fixup值設為0.這個 if判斷不會滿足. 將uhci_result_common()中關於need_fixup的部份列出如下: static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb) {         ......         ...... if (ret < 0) {         /* Note that the queue has stopped and save          * the next toggle value */         qh->element = UHCI_PTR_TERM;         qh->is_stopped = 1;         qh->needs_fixup = (qh->type != USB_ENDPOINT_XFER_CONTROL);         qh->initial_toggle = uhci_toggle(td_token(td)) ^                 (ret == -EREMOTEIO);     } else      /* Short packet received */         ret = uhci_fixup_short_transfer(uhci, qh, urbp);     return ret; } 其中,ret<0是表示傳輸錯誤的情況,而ret=1是表示短包錯誤的情況.而這兩種情況有什麼區別呢? 對於短包錯誤,接收是正常的,這時,接收方會回一個ACK.然後再”倒轉”自己這邊驗證的toggle.因為接收方的驗證toggle改變了,所以,錯誤包之後的所有包,都要符號它的驗證條件. 而對於傳輸錯誤的包,接收方接收錯誤,例如CRC檢測錯誤,這裡給對方回一個ACK之後,不會更新本地驗證的toggle值.因此,這種情況下 的後續包沒有必要更改toggle值.但是對於-EREMOTEIO就不同了,這種情況是發生在設定了URB_SHORT_NOT_OK標誌的情況下,這 種情況下,接收方的接包是正常,所以也是需要”倒轉”toggle. 說到這裡,可能有人又會產生一個疑問,對於短包錯誤的,會修正它後面URB的toggle值,對於傳輸錯誤的,就不需要了麼? 不著急,在後面自然會看到. 返回到uhci_giveback_urb()中,繼續斷開URB的一些關聯以及釋放和它相關結構所佔的空間,另外,還會在usb_hcd_giveback_urb()中呼叫urb->complete()來喚醒等待URB傳輸完成的程序.特別注意到,如果QH中沒有URB了,就需要將QH的頻寬回收了.(!!!要等到QH為空再釋放頻寬麼?為什麼不是釋放URB就釋放它所佔的頻寬?) 其實,跟蹤程式碼可以發現,QH只會在新增第一個URB的時候,才會計算保留頻寬,也就是說,不管QH中添加了多少URB,它的所佔頻寬都是一樣的. 對於中斷傳輸來說,這一點很好理解,QH下面掛著TD,每排程一次QH只會排程一個TD,因此,就算QH下掛了再多的TD,也不會影響頻寬. 而對於等時傳輸來說,它的TD是直接掛在UHCI的排程陣列上,每個TD相連之後再聯QH.因為後面新增實時TD不會計算頻寬.這樣,後面就算提交了再多的等時傳輸也是不會去的判斷(等時+中斷<90%),這樣做是不是欠妥? 現在,就來解釋一下上面提出的問題,即對於傳輸錯誤的toggle修正問題. 我們在前面看到,對於傳輸錯誤的URB,會將它所屬的qh設為: qh->element = UHCI_PTR_TERM;         qh->is_stopped = 1;         qh->needs_fixup = (qh->type != USB_ENDPOINT_XFER_CONTROL);         qh->initial_toggle = uhci_toggle(td_token(td)) ^                 (ret == -EREMOTEIO); 其中,如果是控制傳輸的話,是不需要修正toggle的.在這裡,要特別注意,將qh的element設為了UHCI_PTR_TERM.也就是說,這個QH是一個空的,它下面沒有掛任何的TD.那其它的urb是怎麼繼續得到排程的呢? 在uhci_scan_qh()中,有這樣一段程式碼: static void uhci_scan_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) {         ......         ...... if (!list_empty(&qh->queue)) {         if (qh->needs_fixup)             uhci_fixup_toggles(qh, 0);         /* If the first URB on the queue wants FSBR but its time          * limit has expired, set the next TD to interrupt on          * completion before reactivating the QH. */         urbp = list_entry(qh->queue.next, struct urb_priv, node);         if (urbp->fsbr && qh->wait_expired) {             struct uhci_td *td = list_entry(urbp->td_list.next,                     struct uhci_td, list);             td->status |= __cpu_to_le32(TD_CTRL_IOC);         }         uhci_activate_qh(uhci, qh);     } ...... } 如上程式碼所示,傳輸超時和URB傳輸錯誤的QH都會由它進行處理.如果qh->needs_fixup為了,呼叫uhci_fixup_toggles()修正它的toggle值. 如果是一個需要FSBR的URB,但又傳輸超時,設定下一個TD帶IOC屬性,這樣,在下一次中斷的時候,就又會啟用FSBR了. Uhci_activate_qh()已經很熟悉了,我們在之前已經分析過. 就這樣,QH又會被排程起來了. 不妨思考一下,對於傳輸超時的QH,為什麼要先將它加到skel_unlink_qh.然後再重新加到排程佇列呢?為什麼對於傳輸錯誤的QH,要先將qh-> element設為UHCI_PTR_TERM.然後再加入排程佇列呢? 對於傳輸超時,它先連結在skel_unlink_qh.那,必須要等到下個frame中斷的的時候,才會將qh加回排程佇列.呼叫 Uhci_activate_qh()將其加回排程佇列的時候,是加到排程佇列的末尾.( 為什麼是下一個frame呢?注意程式碼中的QH_FINISHED_UNLINKING()操作) 對於傳輸錯誤的QH,它能在本次中斷加回排程佇列,但也是加到排程佇列的末尾.(因為傳輸錯誤的時候,在uhci_result_common()會將is_stopped設為1) 由此可見: Linux採用,是一種緩時排程的機制,將傳輸錯誤或者是傳輸超時的QH,放到排程佇列的末尾,顯然,這樣的機制對實時傳輸是不合適的,因此,在程式碼中對實現傳輸做了特殊處理. 雖然,在這裡的修正必須要滿足QH不為空的情況,當QH為空的情況,它的修正就是在上面分析的uhci_giveback_urb()中完成的. 第四個要分析的函式uhci_cleanup_queue(). static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh,         struct urb *urb) {     struct urb_priv *urbp = urb->hcpriv;     struct uhci_td *td;     int ret = 1;     /* Isochronous pipes don't use toggles and their TD link pointers      * get adjusted during uhci_urb_dequeue().  But since their queues      * cannot truly be stopped, we have to watch out for dequeues      * occurring after the nominal unlink frame. */      //必須要等它排程完了才能刪除     if (qh->type == USB_ENDPOINT_XFER_ISOC) {         ret = (uhci->frame_number + uhci->is_stopped !=                 qh->unlink_frame);         goto done;     }     /* If the URB isn't first on its queue, adjust the link pointer      * of the last TD in the previous URB.  The toggle doesn't need      * to be saved since this URB can't be executing yet. */      //如果不是QH中的第一個urb     if (qh->queue.next != &urbp->node) {         struct urb_priv *purbp;         struct uhci_td *ptd;         //urb的前一個urb         purbp = list_entry(urbp->node.prev, struct urb_priv, node);         WARN_ON(list_empty(&purbp->td_list));         //URB的前面urb中的最後一個TD         ptd = list_entry(purbp->td_list.prev, struct uhci_td,                 list);         //URB的最後一個TD         td = list_entry(urbp->td_list.prev, struct uhci_td,                 list);         //跳過urb的td項         ptd->link = td->link;         goto done;     }     //後面的處理,對應要刪除的urb是qh上的第一個urb     //因此不要管它的連結情況,不過要更新usb_dev的toggle     /* If the QH element pointer is UHCI_PTR_TERM then then currently      * executing URB has already been unlinked, so this one isn't it. */      //如果qh->element等於UHCI_PTR_TERM.說明QH下面沒有連結TD了     if (qh_element(qh) == UHCI_PTR_TERM)         goto done;     qh->element = UHCI_PTR_TERM;     /* Control pipes don't have to worry about toggles */     //如果是控制傳輸,不需要更新toggle     if (qh->type == USB_ENDPOINT_XFER_CONTROL)         goto done;     /* Save the next toggle value */     //initial_toggle更新為qh中起始td的toggle值.     //因為是要刪除qh中的td.因此,qh之後的td因為要以這個toggle為準值     WARN_ON(list_empty(&urbp->td_list));     td = list_entry(urbp->td_list.next, struct uhci_td, list);     qh->needs_fixup = 1;     qh->initial_toggle = uhci_toggle(td_token(td)); done:     return ret; } 這個函式比較簡單,對照新增的註釋自行閱讀即可. 不過要注意,這個函式也涉及到了toggle修正.在後面也會經過上面分析的toggle修正的流程(傳輸錯誤的QH的toggle修正部份). 疑問
:為什麼不是QH中的第一個URB就不需要修正toggle? 最後要分析的函式是uhci_make_qh_idle(). static void uhci_make_qh_idle(struct uhci_hcd *uhci, struct uhci_qh *qh) {     //如果QH依然是ACTIVE狀態,非法...     WARN_ON(qh->state == QH_STATE_ACTIVE);     //如果要刪除的QH是uhci->next_qh.則更新uhci->next_qh     if (qh == uhci->next_qh)         uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,                 node);     //將QH移到uhci->idle_qh_list連結串列上     list_move(&qh->node, &uhci->idle_qh_list);     //將QH的狀態更改為IDLE     qh->state = QH_STATE_IDLE;     /* Now that the QH is idle, its post_td isn't being used */     //現在這個QH已經沒有什麼用處了,如果還有一個緩衝的TD,要將其釋放     if (qh->post_td) {         uhci_free_td(uhci, qh->post_td);         qh->post_td = NULL;     }     /* If anyone is waiting for a QH to become idle, wake them up */     //如果有程序在程序QH,將他們喚醒...     if (uhci->num_waiting)         wake_up_all(&uhci->waitqh); } 當QH空閒,QH也就可以完全釋放了.有人或許有疑問,這個函式處理過後,QH只是回到了初始狀態,並沒有將QH所佔空間釋放.那他是在什麼時候釋放的呢? 首先,考慮一下,每個端點的傳輸型別都是相同的,因此對應每個端點,它的QH也是同一種類型,因此,以後的資料傳輸就會複用這個QH(參考usb2.0 spec上的埠描述符的bmAttribtes欄位). 那QH要等到usb_hcd_disable_endpoint()的時候才會將其刪除. 經過這樣一個漫長的過程,UHCI的中斷處理終於到此結束了. 五:關於複用的QH 在上面提到了第一次傳輸後的QH會被以後的傳輸複用,這部份的程式碼在前面的情景中都沒有涉及到,現在把它串起來研究一下. 首先在uhci_urb_enqueue()中,有如下程式碼片段 : static int uhci_urb_enqueue(struct usb_hcd *hcd,         struct urb *urb, gfp_t mem_flags) {     ......     ...... if (urb->ep->hcpriv)         qh = urb->ep->hcpriv;     else {         qh = uhci_alloc_qh(uhci, urb->dev, urb->ep);         if (!qh)

相關推薦

linux裝置驅動USB資料傳輸分析

也許,有人會有這樣的疑問: 對於控制傳輸,它不也是基於toggle的糾錯麼,為什麼它就不需要修改後續的包的toggle值呢? 這是因為,控制傳輸的toggle都是從1開始的,刪除掉當前的urb,也不會對後面的發包造成影響. 之後,處理完之後,將無用的td刪除. 跟

Linux下檢視檔案和資料夾大小df&du

df 用法:df [選項]… [檔案]… 顯示每個檔案所在的檔案系統的資訊,預設顯示全部的檔案系統 常用選項 -h, –human-readable 大小顯示為人類易讀形式 (e.g., 1K

資料WEB階段JavaEE三大核心技術過濾器

Filter過濾器 一、Filter 過濾器概述 Filter是JavaEE三大核心技術(Servlet 、 Filter 、 Listener)之一 FIlter作用是攔截對資源的訪問 , 攔截下來後可以控制是否允許通過 , 或者在允許通過前後做

python/pandas資料分析-聚合與分組運算例項

用特定於分組的值填充缺失值 用平均值去填充nan s=pd.Series(np.random.randn(6)) s[::2]=np.nan s 0 NaN 1 -0.1181

做一個合格的程式猿淺析Spring AOP原始碼 分析JdkDynamicAopProxy的invoke方法

 上一節我們已經分析了Proxyfactorybean如何去生成一個目標物件的代理的,這一節我們將淺析一下基於JDK動態代理的核心回撥方法invoke的原始碼: 首先先開啟JdkDynamicAop

PASCAL VOC資料分析分類部分

VOC2007資料集共包含:訓練集(5011幅),測試集(4952幅),共計9963幅圖,共包含20個種類。 資料集的組成架構如下: Annotations —目標真值區域ImageSets —-類別標籤JPEGImages —–影象SegmentationClassSegmentationObje

處理大併發 libevent demo詳細分析對比epoll

libevent預設情況下是單執行緒,每個執行緒有且僅有一個event_base,對應一個struct event_base結構體,以及賦予其上的事件管理器,用來安排託管給它的一系列的事件。 當有一個事件發生的時候,event_base會在合適的時間去呼叫繫結在

普通字元裝置驅動的兩種註冊方式新&舊

普通字元裝置驅動的兩種註冊方式(新&舊) 在核心中,對於一個普通的字元裝置驅動,不難發現有兩種註冊方式: register_chrdev族函式+建立裝置類、檔案的函式:這種方法是2.4版本

PASCAL VOC資料分析檢測部分

Pascal VOC 資料集介紹介紹Pascal VOC資料集:Challenge and tasks, 只介紹Detection與Segmentation相關內容。資料格式  Dataset衡量方式  Evaluationvoc2007, voc2012Challenge

DjangoCBV檢視原始碼分析工作原理

1.首先我們先在urls.py定義CBV的路由匹配。 FBV的路由匹配: 2.然後,在views.py建立一名為MyReg的類: 注意:該類必須繼續View類,且方法名必須與請求方式相同(後面會詳解) 3.回到第一步的路由匹配可以看到MyReg.as_view(),直接呼叫了as_view函式。那

Java入門系列集合HashMap原始碼分析十四

前言 我們知道在Java 8中對於HashMap引入了紅黑樹從而提高操作效能,由於在上一節我們已經通過圖解方式分析了紅黑樹原理,所以在接下來我們將更多精力投入到解析原理而不是演算法本身,HashMap在Java中是使用比較頻繁的鍵值對資料型別,所以我們非常有必要詳細去分析背後的具體實現原理,無論是C#還是J

機器學習numpy和matplotlib學習

今天來學習矩陣的建立和一些基本運算 #!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : SundayCoder-俊勇 # @File : numpy7.py import numpy as np # numpy基

osgEarth的Rex引擎原理分析分頁瓦片載入器在更新遍歷時對請求處理過程

目標:(十四)中的34 osgEarthDrivers/engine_rex/Loader.cpp void PagerLoader::traverse(osg::NodeVisitor& nv) { for(count=0; count < _merges

Linux核心設計與實現》讀書筆記- 程序地址空間(kernel 2.6.32.60)

程序地址空間也就是每個程序所使用的記憶體,核心對程序地址空間的管理,也就是對使用者態程式的記憶體管理。 主要內容: 地址空間(mm_struct) 虛擬記憶體區域(VMA) 地址空間和頁表 1. 地址空間(mm_struct) 地址空間就是每個程序所能訪問的記憶體地址範圍。 這個地址

linux裝置驅動USB主機控制器驅動分析

一:前言 Usb是一個很複雜的系統.在usb2.0規範中,將其定義成了一個分層模型.linux中的程式碼也是按照這個分層模型來設計的.具體的分為 usb裝置,hub和主機控制器三部份.在閱讀程式碼的時候,必須要參考相應的規範.最基本的就是USB2.0的spec.

Linux裝置驅動程式架構分析一個I2C驅動例項

作者:劉昊昱  核心版本:3.10.1 編寫一個I2C裝置驅動程式的工作可分為兩部分,一是定義和註冊I2C裝置,即i2c_client;二是定義和註冊I2C裝置驅動,即i2c_driver。下面我們就以mini2440的I2C裝置at24c08 EEPROM為例,介紹如

Linux裝置驅動程式架構分析I2C架構基於3.10.1核心

作者:劉昊昱  核心版本:3.10.1 I2C體系架構的硬體實體包括兩部分: 硬體I2C Adapter:硬體I2C Adapter表示一個硬體I2C介面卡,也就是I2C控制器。一般是SOC中的一個介面,也可以用GPIO模擬。硬體I2C Adapter主要用來在I2

Linux裝置驅動程式架構分析SD Spec摘要

作者:劉昊昱  本文是對SDSpecifications Part 1 Physical Layer Simplified Specification Version 4.10的摘要記錄,具體資訊可參考該文件。 3、SD Memory Card System Conc

Linux裝置驅動程式架構分析MMC/SD

作者:劉昊昱  核心版本:3.10.1 一、s3cmci_ops分析 在上一篇文章中我們分析了Mini2440 MMC/SD驅動的probe函式s3cmci_probe。在該函式中初始化了struct mmc_host指標變數mmc,其中,設定mmc->ops為s

Linux裝置驅動程式架構分析platform基於3.10.1核心

作者:劉昊昱  核心版本:3.10.1 一、platform bus的註冊 platform bus註冊是通過platform_bus_init函式完成的,該函式定義在drivers/base/platform.c檔案中,其內容如下: 904int __init pl