前言
rtt 里的 usbhost 驅動有問題這是眾所周知的事情了,很多人在論壇上提問,也出現(xiàn)了各種解決方案。這里做個匯總,同時把我最終的解決方法說一下。
平臺環(huán)境:STM32F429 正點原子阿波羅
usbhost 驅動相關疑問
第一個疑問
https://club.rt-thread.org/ask/question/430499.html
關于這里的延時,好像 stm32 官方的某個手冊或者 usb 規(guī)范里有講,因為這里 https://bbs.21ic.com/icview-106567-1-1.html 也提到了這個 1ms 延時是必需的。
我曾經(jīng)去掉過這個延時,去掉是有嚴重問題的。識別不出 U盤還是小事兒,還可能嚴重的搞壞系統(tǒng),這一點兒下面細講。
第二個疑問
https://club.rt-thread.org/ask/question/425072.html
出現(xiàn)死循環(huán)的原因只有一個,usb 控制器出現(xiàn) nak 并且自己不可恢復。原驅動中相關代碼如下:
if (HAL_HCD_HC_GetState(&stm32_hhcd_fs, pipe->pipe_index) == HC_NAK)
{
RT_DEBUG_LOG(RT_DEBUG_USB, ("nak\n"));
if (pipe->ep.bmAttributes == USB_EP_ATTR_INT)
{
rt_thread_delay((pipe->ep.bInterval * RT_TICK_PER_SECOND / 1000) > 0 ? (pipe->ep.bInterval * RT_TICK_PER_SECOND / 1000) : 1);
}
HAL_HCD_HC_Halt(&stm32_hhcd_fs, pipe->pipe_index);
HAL_HCD_HC_Init(&stm32_hhcd_fs,
pipe->pipe_index,
pipe->ep.bEndpointAddress,
pipe->inst->address,
USB_OTG_SPEED_FULL,
pipe->ep.bmAttributes,
pipe->ep.wMaxPacketSize);
continue;
}
即便這里有初始化操作,但是實際上并不能恢復,也不能 continue 實現(xiàn)重新提交請求。
其它疑問
不識別,枚舉設備失敗,無法掛載。。。
其它各種問題都和 drv_usbh.c 的 `drv_pipe_xfer` 函數(shù)有千絲萬縷的聯(lián)系。
調(diào)試記錄
刪 `drv_pipe_xfer` 中的延時
這個延時應該是硬件的硬性要求,去掉它會很容易 nak,然后進入上面的死循環(huán)里面。我去掉了延時,所以在 nak 死循環(huán)了。接下來去掉 nak 的死循環(huán)。
修改一個 bug
原代碼是這么寫的 `else if (HAL_HCD_HC_GetState(&stm32_hhcd_fs, pipe->pipe_index) == URB_ERROR)` ,`HAL_HCD_HC_GetState` 返回值是 `HCD_HCStateTypeDef` 類型,而 `URB_ERROR` 是 `HCD_URBStateTypeDef` 類型的,明顯調(diào)用的函數(shù)和比較值類型不一樣。
修改后 `else if (HAL_HCD_HC_GetURBState(&stm32_hhcd_fs, pipe->pipe_index) == URB_ERROR)`。
重寫任何可能引起**死循環(huán)**的代碼
死循環(huán)可不是什么好東西,如果有這個可能,就必須改掉它,比如通過計數(shù),重試有限次之后退出嘗試。
nak retry
把 `continue` 改成重試 10 ,感覺把錯誤轉移了,也沒見有多少改善。
nak 退出
去掉 `continue` ,去掉 retry,直接返回錯誤退出 `drv_pipe_xfer` 函數(shù)。那么,問題來了。
首先說明,`drv_pipe_xfer` 函數(shù)被 `rt_usb_hcd_setup_xfer` 和 `rt_usb_hcd_pipe_xfer` 兩個函數(shù)直接調(diào)用,然后被其它幾十個函數(shù)間接調(diào)用。
`drv_pipe_xfer` 函數(shù)的第三個參數(shù) `void *buffer`,用于傳遞輸入輸出數(shù)據(jù)緩存地址指針。輸出數(shù)據(jù)過程沒有多大問題,當它扮演接收數(shù)據(jù)緩存時就存在潛在的隱患。
因為接收數(shù)據(jù)的內(nèi)存緩存多半不是全局的,而是臨時申請的內(nèi)存,或者是從棧上分配的。假如 `drv_pipe_xfer` 函數(shù)“假”失敗返回,而 stm32 的 usb 控制器還在工作。比如上面的 nak,當我直接錯誤返回退出 `drv_pipe_xfer` 函數(shù)后,開始發(fā)現(xiàn)各種內(nèi)存異常修改。比如返回上層調(diào)用函數(shù)的過程中發(fā)現(xiàn)局部變量(棧)莫名變成其它隨機值。從這里我猜測雖然是 nak,但是并不一定表示有什么嚴重的問題。既然 usb 控制器仍然使用傳遞給他的寄存器地址,如果再稍微等待一下是不是變成完成狀態(tài)了?
nak 超時
處理 HC 狀態(tài)和 URB 狀態(tài)前先判斷是否是 nak,如果是 nak 就等待,等待 timeout 個 tick 超時,然后交給下面處理;不是 nak, ok 的可能性很大,直接交給下面處理。
tick = rt_tick_get();
while(HAL_HCD_HC_GetState(&stm32_hhcd_fs, pipe->pipe_index) == HC_NAK)
{
if ((rt_tick_get() - tick) >= timeout)
{
break;
}
else
{
rt_thread_yield();
}
}
這么處理以后,nak 少多了,但是還是有,而且一經(jīng)出現(xiàn)就很難再繼續(xù)。
階段小結
修改了狀態(tài)比較的錯誤,添加上 `drv_pipe_xfer` 中的 1ms 延時,同時添加了 nak 狀態(tài)等待。去掉任何死循環(huán)操作,如果 nak 等待超時后還是 nak 就錯誤退出。到此,U 盤失敗和 sd 卡識別已經(jīng)有明顯的改觀了,大多時候可以走到 df_mount 之前(修改前走到 rt_udisk_run 就很難,rt_udisk_run 和 df_mount 中間有幾個地方都可能出錯終止)。
下面繼續(xù) debug。
上層操作 retry
`drv_pipe_xfer` 里 retry 的操作嘗試過了,經(jīng)過測試才知道,底層的工作原理不允許這么暴力 retry,這樣可能引起 usb 控制器工作異常。
換個思路 retry,把 retry 往上層移動,找最容易出錯誤的函數(shù)調(diào)用路徑中的某個接口,比如 `rt_usbh_storage_read10` 或 `rt_usbh_storage_write10` ,讀操作最多返回錯誤,應用層操作失敗。寫操作失敗意味著可能寫 U 盤的任意節(jié)點出現(xiàn)問題了,這個時候放任不管可能會丟失數(shù)據(jù)的。
于是 `rt_usb_bulk_only_xfer(intf, &cmd, buffer, timeout);` 返回值不是 OK 就重試。這種嘗試好像有效果,但是還是有多次重試后失敗的。
繼續(xù)查找原因
最底層的和比較上層的部分都使用各種方法嘗試過了,問題還是存在。而且,讓人頭疼的是出現(xiàn)問題后 U 盤的文件系統(tǒng)有被損壞的概率。經(jīng)過多次格式化 U 盤后發(fā)現(xiàn)也只有 `rt_usbh_storage_write10` -> `rt_usb_bulk_only_xfer` 出錯概率最大,寫壞 U 盤的操作也出現(xiàn)在這里。
那么,進入 `rt_usb_bulk_only_xfer` 函數(shù)尋找機會。
rt_usb_bulk_only_xfer
這個函數(shù)大概率出錯,肯定有它自己的獨特的操作。 `rt_usbh_storage_read10` 也調(diào)用了這個函數(shù)但是不出錯,由此,我把可疑范圍縮小到
if(cmd->xfer_len != 0)
{
...
size = rt_usb_hcd_pipe_xfer(pipe->inst->hcd, pipe, (void*)buffer,
cmd->xfer_len, timeout);
...
}
`cmd->xfer_len` 比較大的時候,也就是說讀寫數(shù)據(jù)量大的時候,`rt_usb_hcd_pipe_xfer` 函數(shù)執(zhí)行就容易出錯。
為什么呢?數(shù)據(jù)量多少直接影響了出錯概率。其它地方調(diào)用 `drv_pipe_xfer` 能正常工作,這里出錯,能說明硬件配置有問題嗎?
`rt_usb_hcd_pipe_xfer` 里面把大數(shù)據(jù)分包,分成 64 字節(jié)的小包一次次發(fā)送,這個包能改大一點兒嗎?發(fā)大包會有影響嗎?
多處判斷包大小和 wMaxPacketSize 的關系, wMaxPacketSize 能修改大一點嗎?
加延時,還是 retry ?哪種解決方法更高效?
最終方案
調(diào)用 `rt_usb_hcd_pipe_xfer` 之前加個短延時,比如 3ms 的延時。因為其它方法都嘗試過,暫時沒找到能解決問題的。
如果對延時引起的性能降低比較在意,先判斷發(fā)送的包大小有多大,如果一包處理不完,延時一下;如果一包就可以處理完不需要延時。
讀和寫延時也不一樣,寫操作要求延時時間長,讀操作可能不延時也沒問題。
當這里加延時后,手頭的 U 盤和讀卡器識別和讀寫文件都正常了。
因為這里的延時是經(jīng)驗值,有的需要延時時間短,有的需要延時時間長。不確定延時多少怎么辦?可以如下動態(tài)調(diào)整延時時間。
...
if(cmd->xfer_len > pipe->ep.wMaxPacketSize)
{
rt_thread_mdelay(wr_delay);
}
size = rt_usb_hcd_pipe_xfer(pipe->inst->hcd, pipe, (void*)buffer,
cmd->xfer_len, timeout);
if(size != cmd->xfer_len)
{
if (wr_delay < 5)
{
wr_delay += 2;
}
...
}
其它
不知道這個算不算問題,無論從理論上還是實際應用中,讀寫 U 盤的時候是禁止拔掉 U 盤的。
不說寫 U 盤,假設讀 U 盤的時候拔掉 U 盤,原驅動有一定的機率在讀操作過程中清理掉了對應通道的設備。因為,監(jiān)測 usb 端口在一個獨立線程,然后讀寫接口被文件系統(tǒng)調(diào)用,肯定是另外的應用層線程操作的了。兩個不同的線程,一個使用 usb 通道時,另一個清理掉了它!
所以,在 `rt_usbh_hub_port_change` `rt_udisk_read` `rt_udisk_write` 等接口出添加互斥操作避免上述情況出現(xiàn)。這個對讀寫都是有效的。
> 本文所有提到的更改已經(jīng)提交到 gitee ,歡迎大家測試 https://gitee.com/thewon/rt_thread_repo
審核編輯:湯梓紅
-
調(diào)試
+關注
關注
7文章
572瀏覽量
33898 -
RT-Thread
+關注
關注
31文章
1273瀏覽量
39924 -
STM32F429
+關注
關注
0文章
40瀏覽量
10648 -
USBHost
+關注
關注
0文章
2瀏覽量
1681
發(fā)布評論請先 登錄
相關推薦
評論