在 OHCI(Open Host Controller Interface)架構下,判斷 USB 資料傳輸是否完成,必須透過中斷處理程序來實現。當 USB 主機設定好 HcControl 與 HcCommandStatus 暫存器啟動傳輸後,控制器(如 AM9200)會自動執行資料傳輸,並定期檢查 HcDoneHead 暫存器的內容,將其轉移至 HCCA 的 DoneHead,隨後產生中斷,觸發中斷服務程式(ISR)。
在中斷處理程序中,首要任務是讀取 HcInterruptStatus 暫存器,檢查 WDH(Writeback Done Head) 位元是否為 1,以確認是否有傳輸描述子(TD)處理完畢。其他中斷狀態位通常不需理會。一旦發現 TD 完成,還需進一步判斷該 TD 是否為當前傳輸命令的最後一個 TD;若是,則標記命令執行結束,通知上層程式進行後續處理。
/**
* OHCI 中斷處理程序
*/
void AT91F_UHP_Handler(void)
{
unsigned int status;
unsigned char idx;
unsigned char cc;
// 取得 HcInterruptStatus 暫存器內容
status = ohciGetIntrStatus();
// 檢查 WDH 位,判斷是否有 TD 傳輸完畢
if ((status & OHCI_HC_INTR_WDH) != 0)
{
// 依據目前執行的命令類型,確定 TD 數量
switch (usbCmdState.cmdType)
{
case USB_CMD_TYPE_BULK_WRITE:
case USB_CMD_TYPE_BULK_READ:
idx = 3;
break;
case USB_CMD_TYPE_CTRL_READ:
idx = 2;
break;
case USB_CMD_TYPE_CTRL_WRITE:
idx = 1;
break;
default:
usbCmdState.state = USB_CMD_OVER;
ohciClearIntrStatus();
return;
}
// 取得目前完成之 TD 的 Complete Code 值
cc = getTdCC(ohciGetHccaDoneHead());
// 判斷目前完成的 TD 是否為命令的最後一個 TD
if (ohciGetHccaDoneHead() == usbGetLastTdAddr(idx))
{
usbCmdState.cmdResult = cc;
usbCmdState.state = USB_CMD_OVER;
}
else
{
// 目前 TD 非最後一個,但執行失敗,不會繼續處理 TD 列表
if (cc)
{
usbCmdState.cmdResult = cc;
usbCmdState.state = USB_CMD_OVER;
}
else
{
// 目前 TD 非最後一個,等待繼續處理
usbCmdState.state++;
}
}
}
// 清除 HcInterruptStatus 暫存器,以便產生新的中斷
ohciClearIntrStatus();
}
早期嘗試直接輪詢 ohciGetIntrStatus() 判斷傳輸完成,但實測無法正確取得 TD 處理完畢的資訊。經調試發現,因端點描述子(ED)可掛載多個 TD,AM9200 處理速度關係,可能產生一個或多個中斷。因此,在 ISR 中必須判斷「目前結束的 TD 是否為該命令的最後一個 TD」,才能確保整個 ED 佇列真正處理完畢。
誠然,這種「檢查最後一個 TD」的做法屬於權宜之計。原本期望控制器提供專門暫存器,在 ED 佇列處理完畢時主動通知,但翻閱規格書未果,只能採用此法。若讀者有更優雅的解法,歡迎交流指教。
本文出自 “rainman” 博客,請務必保留此出處:https://lancelot.blog.51cto.com/393579/261181