eCos系统的lwIP驱动存在一个bug,该bug导致用于数据发送同步的信号量计数值不断增长,当超出32位整数所能表示的最大值时将会从0xffffffff回滚到0,这时可能会导致lwIP协议栈核心线程永久挂起。
后果
该bug引起的可能后果包括:
- 覆盖前一次发送数据,这可能会导致在网络上出现错误的数据包,在TCP/IP中,这不会产生多大影响,因为上层协议会保证数据的完整性和重发机制。
- 长时间连续运行后可能导致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,问题是信号量计数值只有在数据发送完成时才会进行累加,而发送数据的线程已经挂起等待信号量,死定了!先进行条件检测,某种条件下进行等待,这是条件变量的用法。
标签:信号量,lwIP,tx,send,网卡,sc,驱动,eth From: https://blog.51cto.com/zoomdy/5872656