usbhost驱动相关疑问与调试记录

发布者:星尘之泪最新更新时间:2024-01-08 来源: elecfans关键字:usbhost  驱动  调试记录 手机看文章 扫描二维码
随时随地手机看文章

前言

rtt 里的 usbhost 驱动有问题这是众所周知的事情了,很多人在论坛上提问,也出现了各种解决方案。这里做个汇总,同时把我最终的解决方法说一下。


平台环境:STM32F429 正点原子阿波罗



usbhost 驱动相关疑问

第一个疑问

https://club.rt-thread.org/ask/question/430499.html


关于这里的延时,好像 stm32 官方的某个手册或者 usb 规范里有讲,因为这里 https://bbs.21ic.com/icview-106567-1-1.html 也提到了这个 1ms 延时是必需的。


我曾经去掉过这个延时,去掉是有严重问题的。识别不出 U盘还是小事儿,还可能严重的搞坏系统,这一点儿下面细讲。


第二个疑问

https://club.rt-thread.org/ask/question/425072.html


出现死循环的原因只有一个,usb 控制器出现 nak 并且自己不可恢复。原驱动中相关代码如下:


if (HAL_HCD_HC_GetState(&stm32_hhcd_fs, pipe->pipe_index) == HC_NAK)

{

   RT_DEBUG_LOG(RT_DEBUG_USB, ("nakn"));

   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 实现重新提交请求。


其它疑问

不识别,枚举设备失败,无法挂载。。。

其它各种问题都和 drv_usbh.c 的 `drv_pipe_xfer` 函数有千丝万缕的联系。


调试记录

删 `drv_pipe_xfer` 中的延时

这个延时应该是硬件的硬性要求,去掉它会很容易 nak,然后进入上面的死循环里面。我去掉了延时,所以在 nak 死循环了。接下来去掉 nak 的死循环。


修改一个 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` 类型的,明显调用的函数和比较值类型不一样。

修改后 `else if (HAL_HCD_HC_GetURBState(&stm32_hhcd_fs, pipe->pipe_index) == URB_ERROR)`。


重写任何可能引起**死循环**的代码

死循环可不是什么好东西,如果有这个可能,就必须改掉它,比如通过计数,重试有限次之后退出尝试。


nak retry

把 `continue` 改成重试 10 ,感觉把错误转移了,也没见有多少改善。


nak 退出

去掉 `continue` ,去掉 retry,直接返回错误退出 `drv_pipe_xfer` 函数。那么,问题来了。


首先说明,`drv_pipe_xfer` 函数被 `rt_usb_hcd_setup_xfer` 和 `rt_usb_hcd_pipe_xfer` 两个函数直接调用,然后被其它几十个函数间接调用。

`drv_pipe_xfer` 函数的第三个参数 `void *buffer`,用于传递输入输出数据缓存地址指针。输出数据过程没有多大问题,当它扮演接收数据缓存时就存在潜在的隐患。


因为接收数据的内存缓存多半不是全局的,而是临时申请的内存,或者是从栈上分配的。假如 `drv_pipe_xfer` 函数“假”失败返回,而 stm32 的 usb 控制器还在工作。比如上面的 nak,当我直接错误返回退出 `drv_pipe_xfer` 函数后,开始发现各种内存异常修改。比如返回上层调用函数的过程中发现局部变量(栈)莫名变成其它随机值。从这里我猜测虽然是 nak,但是并不一定表示有什么严重的问题。既然 usb 控制器仍然使用传递给他的寄存器地址,如果再稍微等待一下是不是变成完成状态了?


nak 超时

处理 HC 状态和 URB 状态前先判断是否是 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 少多了,但是还是有,而且一经出现就很难再继续。


阶段小结

修改了状态比较的错误,添加上 `drv_pipe_xfer` 中的 1ms 延时,同时添加了 nak 状态等待。去掉任何死循环操作,如果 nak 等待超时后还是 nak 就错误退出。到此,U 盘失败和 sd 卡识别已经有明显的改观了,大多时候可以走到 df_mount 之前(修改前走到 rt_udisk_run 就很难,rt_udisk_run 和 df_mount 中间有几个地方都可能出错终止)。


下面继续 debug。


上层操作 retry

`drv_pipe_xfer` 里 retry 的操作尝试过了,经过测试才知道,底层的工作原理不允许这么暴力 retry,这样可能引起 usb 控制器工作异常。

换个思路 retry,把 retry 往上层移动,找最容易出错误的函数调用路径中的某个接口,比如 `rt_usbh_storage_read10` 或 `rt_usbh_storage_write10` ,读操作最多返回错误,应用层操作失败。写操作失败意味着可能写 U 盘的任意节点出现问题了,这个时候放任不管可能会丢失数据的。

于是 `rt_usb_bulk_only_xfer(intf, &cmd, buffer, timeout);` 返回值不是 OK 就重试。这种尝试好像有效果,但是还是有多次重试后失败的。


继续查找原因

最底层的和比较上层的部分都使用各种方法尝试过了,问题还是存在。而且,让人头疼的是出现问题后 U 盘的文件系统有被损坏的概率。经过多次格式化 U 盘后发现也只有 `rt_usbh_storage_write10` -> `rt_usb_bulk_only_xfer` 出错概率最大,写坏 U 盘的操作也出现在这里。

那么,进入 `rt_usb_bulk_only_xfer` 函数寻找机会。


rt_usb_bulk_only_xfer

这个函数大概率出错,肯定有它自己的独特的操作。 `rt_usbh_storage_read10` 也调用了这个函数但是不出错,由此,我把可疑范围缩小到


   if(cmd->xfer_len != 0)

   {

       ...

       size = rt_usb_hcd_pipe_xfer(pipe->inst->hcd, pipe, (void*)buffer,

           cmd->xfer_len, timeout);

       ...

   }

`cmd->xfer_len` 比较大的时候,也就是说读写数据量大的时候,`rt_usb_hcd_pipe_xfer` 函数执行就容易出错。


为什么呢?数据量多少直接影响了出错概率。其它地方调用 `drv_pipe_xfer` 能正常工作,这里出错,能说明硬件配置有问题吗?


`rt_usb_hcd_pipe_xfer` 里面把大数据分包,分成 64 字节的小包一次次发送,这个包能改大一点儿吗?发大包会有影响吗?


多处判断包大小和 wMaxPacketSize 的关系, wMaxPacketSize 能修改大一点吗?


加延时,还是 retry ?哪种解决方法更高效?


最终方案

调用 `rt_usb_hcd_pipe_xfer` 之前加个短延时,比如 3ms 的延时。因为其它方法都尝试过,暂时没找到能解决问题的。

如果对延时引起的性能降低比较在意,先判断发送的包大小有多大,如果一包处理不完,延时一下;如果一包就可以处理完不需要延时。

读和写延时也不一样,写操作要求延时时间长,读操作可能不延时也没问题。

当这里加延时后,手头的 U 盘和读卡器识别和读写文件都正常了。

因为这里的延时是经验值,有的需要延时时间短,有的需要延时时间长。不确定延时多少怎么办?可以如下动态调整延时时间。


   ...

   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 盘,原驱动有一定的机率在读操作过程中清理掉了对应通道的设备。因为,监测 usb 端口在一个独立线程,然后读写接口被文件系统调用,肯定是另外的应用层线程操作的了。两个不同的线程,一个使用 usb 通道时,另一个清理掉了它!

所以,在 `rt_usbh_hub_port_change` `rt_udisk_read` `rt_udisk_write` 等接口出添加互斥操作避免上述情况出现。这个对读写都是有效的。


> 本文所有提到的更改已经提交到 gitee ,欢迎大家测试 https://gitee.com/thewon/rt_thread_repo


关键字:usbhost  驱动  调试记录 引用地址:usbhost驱动相关疑问与调试记录

上一篇:CANOpen系列教程06_ 结合代码理解CAN底层收发数据
下一篇:STM32的中断及是如何使用

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved