eCos系统的lwIP驱动及lpc2xxx网卡驱动bug的解决办法

eCos系统的lwIP驱动存在一个bug,该bug导致用于数据发送同步的信号量计数值不断增长,当超出32位整数所能表示的最大值时将会从0xffffffff回滚到0,这时可能会导致lwIP协议栈核心线程永久挂起。

eCos官网:http://ecos.sourceware.org
eCos中文技术网:http://www.52ecos.net
eCos交流QQ群:144940146。
http://blog.csdn.net/zoomdy/article/details/19675089
mingdu.zheng<at>gmail<dot>com

后果

该bug引起的可能后果包括:

 

  1. 覆盖前一次发送数据,这可能会导致在网络上出现错误的数据包,在TCP/IP中,这不会产生多大影响,因为上层协议会保证数据的完整性和重发机制。
  2. 长时间连续运行后可能导致lwIP的核心线程进入永久等待信号量状态,进入这个状态后,lwIP基本上不再会有任何响应。

 

解决办法

修改io/eth/<version>/src/lwip/eth_drv.c源文件的eth_drv_send函数,将319行的if ((sc->funs->can_send)(sc) < 1)删除。

修改前的代码

#ifdef CYGFUN_LWIP_MODE_SEQUENTIAL
L318:    // Wait until we can send
L319:    if ((sc->funs->can_send)(sc) < 1)
L320:        cyg_semaphore_wait(&sc->sc_arpcom.send_sem);
L321:    if ((sc->funs->can_send)(sc) < 1)
L322:        CYG_FAIL("cannot send packet");
#endif // CYGFUN_LWIP_MODE_SEQUENTIAL

修改代码前信号量计数器在不断增长

修改后的代码

 

#ifdef CYGFUN_LWIP_MODE_SEQUENTIAL
    // Wait until we can send
    cyg_semaphore_wait(&sc->sc_arpcom.send_sem);
    if ((sc->funs->can_send)(sc) < 1)
        CYG_FAIL("cannot send packet");
#endif // CYGFUN_LWIP_MODE_SEQUENTIAL

修改代码后,信号量计数值为1或0

lpc2xxx的硬件bug

LPC2XXX以及LPC176X等MCU的以太网控制器在硬件上存在一个bug,这个bug会导致系统复位后的第一个数据包发送完成后不会产生中断。在没有修复lwIP驱动之前,由于负负得正的神奇作用,这个bug不会导致失败,但是如果打开了eCos系统的断言选项,那么在将发送第二个数据包时引发断言;而在修复了lwIP驱动之后,这个bug反而起作用了,因此必须对lpc2xxx的硬件bug进行修复。如果仅修复lwIP驱动,那么lwIP将在尝试发送第二个数据包时进入永远等待信号量的状态;如果仅修复lpc2xxx,长时间运行后可能会进入永久等待信号量状态。

解决lpc2xxx的bug

lpc2xxx的bug仅在系统复位后第一次发送数据包时存在,因此修改lpc2xxx驱动的lpc2xxx_eth_send函数,如果是第一次发送数据包,那么做一次本来属于lpc2xxx_eth_deliver职责范围的工作——发送信号量。修改devs/eth/arm/lpc2xxx/<version>/src/if_lpc2xxx.c的lpc2xxx_eth_send函数。

修改前的代码

static void
lpc2xxx_eth_send(struct eth_drv_sc *sc,
                 struct eth_drv_sg *sg_list,
                 int sg_len,
                 int total_len,
                 unsigned long key)
{
L994:    cyg_uint32 tx_producer_idx;
L995:    cyg_uint32 tx_consumer_idx;
……
L1032:    priv->cur_tx_key = key;
L1033:    HAL_WRITE_UINT32(EMAC_TX_PROD_IDX, tx_producer_idx);
}

修改后的代码

 

static void
lpc2xxx_eth_send(struct eth_drv_sc *sc,
                 struct eth_drv_sg *sg_list,
                 int sg_len,
                 int total_len,
                 unsigned long key)
{
L994:    static cyg_bool silicon_bug_fixed = false;
L995:    cyg_uint32 tx_producer_idx;
L996:    cyg_uint32 tx_consumer_idx;
……
L1033:    priv->cur_tx_key = key;
L1034:    HAL_WRITE_UINT32(EMAC_TX_PROD_IDX, tx_producer_idx);
L1035:
L1036:    if(silicon_bug_fixed == false)
          {
L1038:        silicon_bug_fixed = true;
L1039:        priv->tx_busy = false;
L1040:        _eth_drv_tx_done(sc, priv->cur_tx_key, 0);
          }
}

 

bug产生原因

eCos系统中的lwIP包含4部分代码,分别为lwIP协议栈核心(位于net/lwip_tcpip/<version>/src/core和net/lwip_tcpip/<version>/src/api)、lwIP协议栈与操作系统相关的部分(位于net/lwip_tcpip/<version>/src/ecos)、独立于硬件的驱动部分(位于io/eth/<version>/src/lwip)、硬件相关的驱动部分(位于devs/eth目录下,与具体使用的网卡控制器有关,这里以lpc2xxx的网卡控制器为例)。

lwIP有2种工作模式,一种为单线程模式,这种模式下所有协议栈核心代码及应用层代码必须在一个线程内;另一种为多线程模式,这种模式下协议栈核心将启动2个服务线程,应用层代码可以分布在不同的线程中。多线程模式更具有实用性,因此这里仅考虑多线程模式,单线程模式仅适用于网络功能简单且资源十分紧张的场合。

这里描述的bug存在于多线程模式下的独立于硬件的驱动程序中。在多线程模式下,eCos的lwIP驱动假设每次仅发送一个数据包,仅在前一个数据包发送完成后才能发送下一个数据包,这通过一个信号量来同步。lwIP协议栈核心线程调用eth_drv_send函数发送数据包并消费掉信号量。lwIP协议栈的另一个线程则处理以太网控制器的中断事件,当检测到数据发送完成时调用eth_drv_tx_done,eth_drv_tx_done则生产信号量。这是一个典型的生产者消费者模型,因此使用信号量是没有问题的,正常情况下该信号量的计数值将为0或1,不会出现其它数值,信号量初始化时计数值被初始化为1。问题在于生产者eth_drv_tx_done生产信号量是无条件的,也就是每次检测到数据包发送完成,都会生产信号量,而消费者eth_drv_send消费信号量是有条件的,仅在当前正有数据在发送时才会消费信号量,如果没有数据正在发送那么不会消费信号量,这导致了信号量的生产和消费是不平衡的,生产的数量会多于消费的数量。这种不平衡导致的结果是信号量的计数值不再是0或1,而是一个递增的数值。

修改前的代码

#ifdef CYGFUN_LWIP_MODE_SEQUENTIAL
L318:    // Wait until we can send
L319:    if ((sc->funs->can_send)(sc) < 1)
L320:        cyg_semaphore_wait(&sc->sc_arpcom.send_sem);
L321:    if ((sc->funs->can_send)(sc) < 1)
L322:        CYG_FAIL("cannot send packet");
#endif // CYGFUN_LWIP_MODE_SEQUENTIAL

这最终导致一些问题,比较常见的情况是当sc->sc_arpcom.send_sem信号量的计数值大于1时且当前正在发送数据包且未发送完成的情况下又要调用eth_drv_send函数发送数据,L319判断控制器是否可发送数据,这时控制器正在发送数据,因此返回结果是不可以发送数据,也就是if条件成立,执行L320的信号量等待语句。当sc->sc_arpcom.send_sem信号量的计数值大于等于1时,信号量等待语句消费掉一个信号量然后立即返回,本来是需要等数据发送完成后再返回的,现在的情况是立即返回了。导致的结果是在有数据正在的发送的情况下,又硬塞进来一个发送请求,这可能会破坏正在发送的数据包完整性,最终导致发送了错误的包。从L321和L322可以看得出来,如果eCos系统的断言可用,那么将会触发L322的断言。

比较极端的情况下将导致调用eth_drv_send的lwIP核心线程处于永远挂起状态。信号量计数值的范围是有限制的,超过这个范围将会引起数值溢出归零,eCos信号量的计数值为32位无符号整数,有效范围为0~0xFFFFFFFF。假设执行到L319时信号量计数值已经被累加到0xFFFFFFFF,如果这时产生了数据发送完成中断,然后系统切换到lwIP的另一个线程,该线程将调用eth_drv_tx_done函数,eth_drv_tx_done函数调用信号量的post函数,post函数对信号量计数值进行累加,0xFFFFFFFF加1的结果是为0!再切回lwIP核心线程时,信号量计数值已经变成了0,因此L320的信号量等待将会挂起直到信号量计数值大于0,问题是信号量计数值只有在数据发送完成时才会进行累加,而发送数据的线程已经挂起等待信号量,死定了!先进行条件检测,某种条件下进行等待,这是条件变量的用法。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值