http://www.rt-thread.org/dokuwiki/doku.php?id=rt-thread_lwip%E8%AF%B4%E6%98%8E
在RT-Thread 1.1.x系列中默认使用lwIP 1.4.0作为TCP/IP协议栈,同时为了保持原有驱动的兼容性,
对lwIP 1.4初始移植做了调整,在驱动编写,初始化顺序上可以完整兼容lwIP 1.3.2的风格。
lwip 1.4 迁移指南
一、lwip,netif架构
+-----------------------+
| driver interface |
| |
| struct eth_device |
| +-eth_rx_ready() |
+---->+-link_up() | |
| | +-link_down() | |
| +------------------|----+
+-------------+ | +------------------|----+ +-------------------+
| driver | | | ethernetif | | | lwip 1.4 |
| | | | V | | |
| isr() --------+ | rx_thread | | tcpip_thread |
| rx() <-------------- netif->input ------------>tcpip_input |
| tx() <-------------- netif->output <----------- enetif_linkoutput |
| | | | | |
+-------------+ +-----------------------+ +-------------------+
lwip 1.4的驱动架构和1.32相比并没有太大的变化,不过细节方面有所改动,各个模块之间功能划分更明确:
1、驱动部分
驱动的输入只有一个入口,就是isr
。
isr接收到各种中断以后做相应的处理,包括接收到rx包,链路层通断
消
息等,然后调用ethernetif.h头文件中的各个函数来通知netif层。
驱动的另一个主要需要实现的部分是rx和tx这两个回调函数,它是提供给ethernetif来进行链路层数据收发使用。
rx函数一般会被netif中的rx线程调用,然后将数据包转发给tcpip层。
tx函数有可能被netif中的tx线程调用(lwip1.32的实现)或者直接被tcpip的enetif_linkoutput调用,来进行链路层
发送数据包使用。
2、ethernetif
ethernetif输入部分包含一个线程,用来接收rx消息,以及其他链路层状态消息,然后反馈给lwip。
输出部分由上层lwip调入netif→output函数,这里有可能作为消息传入tx线程(1.32实现)或者直接送给驱动的tx()。
相关参考 [http://lwip.wikia.com/wiki/Network_interfaces_management Network interfaces management]
3、LwIP1.4
lwip1.4 初始化流程和1.32的流程方面有所区别,具体参见下一节。
二、lwip,netif架构 初始化流程
初始化的过程包括两个独立的部分:
驱动初始化、
lwip和netif的初始化。
例如在mini2440平台上只需要分别执行连个函数即可:
rt_hw_dm9000_init();
lwip_enetif_init();
驱动的初始化可以放在启动第一个进程之前进行,而第二个函数需要在线程上下文中执行。
1、驱动初始化部分
a.创建eth_device的继承结构。
b.初始化eth_device中除了netif以外的所有域(netif结构会在ethernetif初始化结束以后填充)。
c.调用rt_device_register注册结构体。
d.注册中断函数。
完成这几件事情以后可能驱动部分就已经开始工作了,但是这时候lwip部分还完全没有初始化。
所以驱动部分还不能把消息传递给lwip/netif层。在这里因为eth_device的netif指针仍没有填充,
而这个指针是驱动通向netif的桥梁,所以虽然此时驱动在工作,例如可能接收中断已经打开了,
但是所有的包都会被丢弃,而不会传递到lwip去处理。
2、lwip和netif的初始化部分
lwip初始化需要遵循一定的流程,主函数是lwip_enetif_init。
在RTOS的环境下需要调用tcpip_init来初始化多线程的tcpip环境,初始化tcpip的内容就放在这个
函数的参数中tcpip_init_done_callback。之所以需要把这些函数都放在这个callback函数中的缘故
是很多函数都必须在tcpip线程中执行,以避免并发问题,例如netif_set_up之类的函数。
在tcpip_init_done_callback中初始化了ethernetif并注册到lwip中,以上这一部分一般来说应该是应
用相关的,这里只是提供一个公共的参考流程。
在初始化的最后,enetif_init函数中先将netif→linkoutput设置成了设备驱动对应的函数,在这一点
的时候实现了从netif到驱动的连接,然后又设置了dev→netif指针,于是从驱动到netif的连接也完成
了,到此为止lwip初始化完成。 有几点需要注意的:
a.在启动ethernetif的时候如果是启用了DHCP则应该调用dhcp_start,
如果启用AUTOIP则是autoip_start,否则调用netif_set_up。
b.包括像netif_set_up,dhcp_start之类的函数,凡是在tcpip线程以外调用的时候都需要以msg发送
给tcpip线程执行的形式来执行以避免并行问题。例如,可以使用netifapi_dhcp_start替代dhcp_start,
用netifapi_netif_set_up替代netif_set_up。
但是在tcpip线程中不能调用这类函数,否则肯定会进入信号堵塞。
三、从lwip1.32升级
从lwip1.32升级到lwip1.4主要需要修改的地方有下面三个:
1、驱动修改
以dm9000的驱动为例,需要将eth_device_ready函数换成eth_rx_ready,这主要是一个语义上的改动。
同样,驱动还可以通过调用eth_linkup,eth_linkdown之类的函数来通知netif相应的状态消息。
- eth_device_ready(&(dm9000_device.parent));
+ eth_rx_ready(&(dm9000_device.parent));
在驱动初始化函数中,原先的eth_device_init需要删除,然后添加初始化和注册设备驱动函数。
- eth_device_init(&(dm9000_device.parent), "e0");
+ dm9000_device.parent.parent.type = RT_Device_Class_NetIf;
+ rt_device_register(&(dm9000_device.parent.parent), "eth0", RT_DEVICE_FLAG_RDWR);
最后,添加一个函数get_eth_dev,这个函数是在ethernetif最后被lwip初始化的时候获得设备指
针用的。对ethernetif来说必须有一个驱动与之对应,在逻辑上netif并不是驱动的一部分,而是
注册在lwip中的一部分,它必须由lwip来初始化,由lwip来使用。
+struct eth_device * get_eth_dev(void)
+{
+ return (struct eth_device *)&dm9000_device;
+}
2、启动调用
目前的启动调用流程逻辑很清楚,按顺序调用下面两个函数即可。
rt_hw_dm9000_init(); //初始化设备驱动
lwip_enetif_init(); //初始化lwip,并间接初始化ethnetif,最后完成ethernetif和驱动的连接。
而原先的
lwip_sys_init(void);
eth_system_device_init();
rt_hw_dm9000_init();
只有第三个函数保留,但功能上会有变化,其余两个函数不再使用。
四、lwip netconn 函数修改
lwip1.4修改了netconn调用接口,所有函数统一返回错误值,例如: lwip1.32中recv函数是
struct netbuf *
netconn_recv(struct netconn *conn)
到了lwip1.4中改为
err_t
netconn_recv(struct netconn *conn, struct netbuf **new_buf)
原先以返回值得到的netbuf改成通过参数返回,其他函数大同小异,可以此类推。