9. 有操作系统移植LwIP

LwIP不仅能在裸机上运行,也能在操作系统环境下运行,而且在操作系统环境下,用户能使用NETCONN API 与Socket API编程,相比RAW API编程会更加简便。操作系统环境下,这意味着多线程环境,一般来说LwIP作为一个独立的处理线程运行,用户程序也独立为一个/多个线程,这样子在操作系统中就相互独立开,并且借助操作系统的IPC通信机制,更好地实现功能的需求。

LwIP在设计之初,设计者无法预测LwIP运行的环境是怎么样的,而且世界上操作系统那么多,根本没法统一,而如果LwIP要运行在操作系统环境中,那么就必须产生依赖,即LwIP需要依赖操作系统自身的通信机制,如信号量、互斥量、消息队列(邮箱)等,所以LwIP设计者在设计的时候就提供一套与操作系统相关的接口,由用户根据操作系统的不同进行移植,这样子就能降低耦合度,让LwIP内核不受其运行的环境影响,因为往往用户并不能完全了解内核的运作,所以只需要用户在移植的时候对LwIP提供的接口根据不同操作系统进行完善即可。

9.1. LwIP中添加操作系统

9.1.1. 拷贝FreeRTOS源码到工程文件夹

我们先把无操作性移植的代码拷贝过来,再往工程中添加操作系统的源码,操作系统的源码可以从我们对外发布的例程中获取,也可以从官网获取,此处以FreeRTOS为例进行移植操作。

首先将FreeRTOS源码拷贝到工程文件夹下,具体见

图 8‑1拷贝FreeRTOS源码到工程文件夹下

图 8‑1拷贝FreeRTOS源码到工程文件夹下

9.1.2. 添加FreeRTOS源码到工程组文件夹

在上一步我们只是将FreeRTOS的源码放到了本地工程目录下,还没有添加到开发环境里面的组文件夹里面,FreeRTOS也就没有移植到我们的工程中去。

接下来我们在开发环境里面新建FreeRTOS/src和FreeRTOS/port两个组文件夹,其中FreeRTOS/src用于存放src文件夹的所有内容,FreeRTOS/port用于存放port\MemMang文件夹与port\RVDS\ARM_CM?文件夹的内容,“?”表示3、4或者7,具体选择哪个得看你使用的是野火哪个型号的STM32开发板,具体见表8‑1。

表8‑1野火i.MX RT开发板型号对应FreeRTOS的接口文件

野火i.MX RT开发板型号

FreeRTOS不同内核的接口文件

RT1021

portRVDSARM_CM7

RT1052PRO

然后我们将工程文件中FreeRTOS的内容添加到工程中去,按照已经新建的分组添加我们的FreeRTOS工程源码。

在FreeRTOS/port分组中添加MemMang文件夹中的文件只需选择其中一个即可,我们选择“heap_4.c”,这是FreeRTOS的一个内存管理源码文件。同时,需要根据自己的开发板型号在FreeRTOS\port\RVDS\ARM_CM?中选择,“?”表示3、4或者7,但是i.MX RT系列全部都是M7内核所以接口文件都为ARM_CM7。

至此我们的FreeRTOS添加到工程中就已经完成,完成的效果具体见 图8_2

图8‑2添加FreeRTOS源码到工程分组中

图8‑2添加FreeRTOS源码到工程分组中

9.1.3. 指定FreeRTOS头文件的路径

FreeRTOS的源码已经添加到开发环境的组文件夹下面,编译的时候需要为这些源文件指定头文件的路径,不然编译会报错。 FreeRTOS的源码里面只有FreeRTOS\include和FreeRTOS\port\RVDS\ARM_CM?这两个文件夹下面有头文件, 只需要将这两个头文件的路径在开发环境里面指定即可。同时我们还将FreeRTOSConfig.h这个头文件拷贝到了工程根目录下的user文件夹下, 所以user的路径也要加到开发环境里面。FreeRTOS头文件的路径添加完成后的效果具体见 图8_3

图8‑3在开发环境中指定FreeRTOS 的头文件的路径

图8‑3在开发环境中指定FreeRTOS 的头文件的路径

至此,FreeRTOS的整体工程基本移植完毕,我们也不需要修改FreeRTOS配置文件,直接使用我们FreeRTOS例程中的头文件即可。

9.1.4. 修改bsp_systick.c

SysTick中断服务函数是一个非常重要的函数,FreeRTOS所有跟时间相关的事情都在里面处理,SysTick就是FreeRTOS的一个心跳时钟, 驱动着FreeRTOS的运行,就像人的心跳一样,假如没有心跳,我们就相当于“死了”,同样的,FreeRTOS没有了心跳, 那么它就会卡死在某个地方,不能进行任务调度,不能运行任何的东西,因此我们需要实现一个FreeRTOS的心跳时钟, FreeRTOS帮我们实现了SysTick的启动的配置:在port.c文件中已经实现vPortSetupTimerInterrupt()函数, 并且FreeRTOS通用的SysTick中断服务函数也实现了:在port.c文件中已经实现xPortSysTickHandler()函数, 所以移植的时候只需要我们在bsp_systick.c文件中实现对应(I.MX RT)平台上的SysTick_Handler()函数即可。 FreeRTOS为开发者考虑得特别多,PendSV_Handler()与SVC_Handler()这两个很重要的函数都帮我们实现了,在port.c文件中已经实现xPortPendSVHandler()与vPortSVCHandler()函数,防止我们自己实现不了,具体实现见 代码清单8_1 加粗部分。

代码清单8‑1 bsp_systick.c文件内容

 #include "./systick/bsp_systick.h"
 /* FreeRTOS头文件 */
 #include "FreeRTOS.h"
 #include "task.h"
 /*===============================中断方式================================*/

 /**********************中断服务函数******************************/

 extern void xPortSysTickHandler(void);

 /**
 * @brief  SysTick中断服务函数
 * @param  无
 * @retval 无
 * @attention
 */
 void SysTick_Handler(void)
 {
 #if (INCLUDE_xTaskGetSchedulerState  == 1 )
     if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
 #endif  /* INCLUDE_xTaskGetSchedulerState */
         xPortSysTickHandler();
 #if (INCLUDE_xTaskGetSchedulerState  == 1 )
     }
 #endif  /* INCLUDE_xTaskGetSchedulerState */
 }

至此,将FreeRTOS添加到LwIP裸机工程中的步骤就基本完成了,编译的时候就基本也不会有错,下面就正式使用这个工程进行LwIP与操作系统的移植,因为还需要根据操作系统的特性修改很多接口文件。

9.2. lwipopts.h文件需要加入的配置

在前面的章节也说了lwipopts.h文件的作用,而此刻在操作系统中移植,我们首先要将添加了操作系统的工程拿过来,把lwipopts.h文件修改一下,该文件最重要的宏定义就是NO_SYS,我们把它定义为0就表示使用操作系统,当然,在使用操作系统的时候我们一般都会使用NETCONN API 与Socket API编程,那么就需要将宏LWIP_NETCONN与LWIP_SOCKET定义为1,表示使能这两种API编程, lwipopts.h简单修改一下即可,然后再添加一下线程运行的一些宏定义,必须修改的部分具体见 代码清单8_2 加粗部分, 而其他宏定义是根据实际情况进行修改即可。

代码清单 8‑2 lwipopts.h文件

 #ifndef __LWIPOPTS_H__
 #define __LWIPOPTS_H__
 #define USE_RTOS 1
 #define FSL_RTOS_FREE_RTOS 1
 #if USE_RTOS

 /**
 * SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
 * critical regions during buffer allocation, deallocation and memory
 * allocation and deallocation.
 */
 #define SYS_LIGHTWEIGHT_PROT 1

 /**
 * NO_SYS==0: Use RTOS
 */
 #define NO_SYS 0
 /**
 * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
 */
 #define LWIP_NETCONN 1
 /**
 * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
 */
 #define LWIP_SOCKET 1

 /**
 * LWIP_SO_RCVTIMEO==1: Enable receive timeout for sockets/netconns and
 * SO_RCVTIMEO processing.
 */
 #define LWIP_SO_RCVTIMEO 1

 #else
 /**
 * NO_SYS==1: Bare metal lwIP
 */
 #define NO_SYS 1
 /**
 * LWIP_NETCONN==0: Disable Netconn API (require to use api_lib.c)
 */
 #define LWIP_NETCONN 0
 /**
 * LWIP_SOCKET==0: Disable Socket API (require to use sockets.c)
 */
 #define LWIP_SOCKET 0

 #endif
 /* ---------- Memory options ---------- */
 /**
 * MEM_ALIGNMENT: should be set to the alignment of the CPU
 *    4 byte alignment -> #define MEM_ALIGNMENT 4
 *    2 byte alignment -> #define MEM_ALIGNMENT 2
 */
 #ifndef MEM_ALIGNMENT
 #define MEM_ALIGNMENT 4
 #endif

 /**
 * MEM_SIZE: the size of the heap memory. If the application will send
 * a lot of data that needs to be copied, this should be set high.
 */
 #ifndef MEM_SIZE
 #define MEM_SIZE (22 * 1024)
 #endif

 /* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
 sends a lot of data out of ROM (or other static memory), this
 should be set high. */
 #ifndef MEMP_NUM_PBUF
 #define MEMP_NUM_PBUF 15
 #endif
 /* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
 per active UDP "connection". */
 #ifndef MEMP_NUM_UDP_PCB
 #define MEMP_NUM_UDP_PCB 6
 #endif
 /* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
 connections. */
 #ifndef MEMP_NUM_TCP_PCB
 #define MEMP_NUM_TCP_PCB 10
 #endif
 /* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
 connections. */
 #ifndef MEMP_NUM_TCP_PCB_LISTEN
 #define MEMP_NUM_TCP_PCB_LISTEN 6
 #endif
 /* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
 segments. */
 #ifndef MEMP_NUM_TCP_SEG
 #define MEMP_NUM_TCP_SEG 22
 #endif
 /* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
 timeouts. */
 #ifndef MEMP_NUM_SYS_TIMEOUT
 #define MEMP_NUM_SYS_TIMEOUT 10
 #endif

 /* ---------- Pbuf options ---------- */
 /* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
 #ifndef PBUF_POOL_SIZE
 #define PBUF_POOL_SIZE 9
 #endif

 /* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
 /* Default value is defined in lwip\src\include\lwip\opt.h as
 * LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)*/

 /* ---------- TCP options ---------- */
 #ifndef LWIP_TCP
 #define LWIP_TCP 1
 #endif

 #ifndef TCP_TTL
 #define TCP_TTL 255
 #endif

 /* Controls if TCP should queue segments that arrive out of
     order. Define to 0 if your device is low on memory. */
 #ifndef TCP_QUEUE_OOSEQ
 #define TCP_QUEUE_OOSEQ 0
 #endif

 /* TCP Maximum segment size. */
 #ifndef TCP_MSS
 #define TCP_MSS (1500 - 40) /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */
 #endif

 /* TCP sender buffer space (bytes). */
 #ifndef TCP_SND_BUF
 #define TCP_SND_BUF (6 * TCP_MSS) // 2
 #endif

 /* TCP sender buffer space (pbufs). This must be at least = 2 *
 TCP_SND_BUF/TCP_MSS for things to work. */
 #ifndef TCP_SND_QUEUELEN
 #define TCP_SND_QUEUELEN (3 * TCP_SND_BUF) / TCP_MSS // 6
 #endif

 /* TCP receive window. */
 #ifndef TCP_WND
 #define TCP_WND (2 * TCP_MSS)
 #endif

 /* Enable backlog*/
 #ifndef TCP_LISTEN_BACKLOG
 #define TCP_LISTEN_BACKLOG 1
 #endif

 /* ---------- Network Interfaces options ---------- */
 /* Support netif api (in netifapi.c). */
 #ifndef LWIP_NETIF_API
 #define LWIP_NETIF_API 1
 #endif

 /* ---------- ICMP options ---------- */
 #ifndef LWIP_ICMP
 #define LWIP_ICMP 1
 #endif

 /* ---------- RAW options ---------- */
 #if !defined LWIP_RAW
 #define LWIP_RAW 1
 #endif

 /* ---------- DHCP options ---------- */
 /* Define LWIP_DHCP to 1 if you want DHCP configuration of
 interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
 turning this on does currently not work. */
 #ifndef LWIP_DHCP
 #define LWIP_DHCP 1
 #endif

 /* ---------- UDP options ---------- */
 #ifndef LWIP_UDP
 #define LWIP_UDP 1
 #endif
 #ifndef UDP_TTL
 #define UDP_TTL 255
 #endif

 /* ---------- Statistics options ---------- */
 #ifndef LWIP_STATS
 #define LWIP_STATS 0
 #endif
 #ifndef LWIP_PROVIDE_ERRNO
 #define LWIP_PROVIDE_ERRNO 1
 #endif

 /*
 --------------------------------------
 ---------- Checksum options ----------
 --------------------------------------
 */

 /*
 Some MCU allow computing and verifying the IP, UDP, TCP and ICMP checksums by hardware:
 - To use this feature let the following define uncommented.
 - To disable it and process by CPU comment the  the checksum.
 */
 //#define CHECKSUM_BY_HARDWARE

 #ifdef CHECKSUM_BY_HARDWARE
 /* CHECKSUM_GEN_IP==0: Generate checksums by hardware for outgoing IP packets.*/
 #define CHECKSUM_GEN_IP 0
 /* CHECKSUM_GEN_UDP==0: Generate checksums by hardware for outgoing UDP packets.*/
 #define CHECKSUM_GEN_UDP 0
 /* CHECKSUM_GEN_TCP==0: Generate checksums by hardware for outgoing TCP packets.*/
 #define CHECKSUM_GEN_TCP 0
 /* CHECKSUM_CHECK_IP==0: Check checksums by hardware for incoming IP packets.*/
 #define CHECKSUM_CHECK_IP 0
 /* CHECKSUM_CHECK_UDP==0: Check checksums by hardware for incoming UDP packets.*/
 #define CHECKSUM_CHECK_UDP 0
 /* CHECKSUM_CHECK_TCP==0: Check checksums by hardware for incoming TCP packets.*/
 #define CHECKSUM_CHECK_TCP 0
 #else
 /* CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.*/
 #define CHECKSUM_GEN_IP 1
 /* CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.*/
 #define CHECKSUM_GEN_UDP 1
 /* CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.*/
 #define CHECKSUM_GEN_TCP 1
 /* CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.*/
 #define CHECKSUM_CHECK_IP 1
 /* CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.*/
 #define CHECKSUM_CHECK_UDP 1
 /* CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.*/
 #define CHECKSUM_CHECK_TCP 1
 #endif

 /**
 * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread.
 * The stack size value itself is platform-dependent, but is passed to
 * sys_thread_new() when the thread is created.
 */
 #ifndef DEFAULT_THREAD_STACKSIZE
 #define DEFAULT_THREAD_STACKSIZE 3000
 #endif

 /**
 * DEFAULT_THREAD_PRIO: The priority assigned to any other lwIP thread.
 * The priority value itself is platform-dependent, but is passed to
 * sys_thread_new() when the thread is created.
 */
 #ifndef DEFAULT_THREAD_PRIO
 #define DEFAULT_THREAD_PRIO 3
 #endif

 /*
     ------------------------------------
     ---------- Debugging options ----------
     ------------------------------------
 */

 #define LWIP_DEBUG

 #ifdef LWIP_DEBUG
 #define U8_F "c"
 #define S8_F "c"
 #define X8_F "02x"
 #define U16_F "u"
 #define S16_F "d"
 #define X16_F "x"
 #define U32_F "u"
 #define S32_F "d"
 #define X32_F "x"
 #define SZT_F "u"
 #endif

 #define TCPIP_MBOX_SIZE 32
 #define TCPIP_THREAD_STACKSIZE 1024
 #define TCPIP_THREAD_PRIO 8

 /**
 * DEFAULT_RAW_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
 * NETCONN_RAW. The queue size value itself is platform-dependent, but is passed
 * to sys_mbox_new() when the recvmbox is created.
 */
 #define DEFAULT_RAW_RECVMBOX_SIZE 12

 /**
 * DEFAULT_UDP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
 * NETCONN_UDP. The queue size value itself is platform-dependent, but is passed
 * to sys_mbox_new() when the recvmbox is created.
 */
 #define DEFAULT_UDP_RECVMBOX_SIZE 12

 /**
 * DEFAULT_TCP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
 * NETCONN_TCP. The queue size value itself is platform-dependent, but is passed
 * to sys_mbox_new() when the recvmbox is created.
 */
 #define DEFAULT_TCP_RECVMBOX_SIZE 12

 /**
 * DEFAULT_ACCEPTMBOX_SIZE: The mailbox size for the incoming connections.
 * The queue size value itself is platform-dependent, but is passed to
 * sys_mbox_new() when the acceptmbox is created.
 */
 #define DEFAULT_ACCEPTMBOX_SIZE 12

 #if (LWIP_DNS || LWIP_IGMP || LWIP_IPV6) && !defined(LWIP_RAND)
 /* When using IGMP or IPv6, LWIP_RAND() needs to be defined to a random-function returning an u32_t random value*/
 #include "lwip/arch.h"
 u32_t lwip_rand(void);
 #define LWIP_RAND() lwip_rand()
 #endif

 #endif /* __LWIPOPTS_H__ */

 /*****END OF FILE****/

9.3. sys_arch.c/h文件的编写

操作系统环境下, LwIP移植的核心就是编写与操作系统相关的接口文件sys_arch.c和sys_arch.h,这两个文件可以自己创建、可以从contrib包中获取也可以从NXP的SDK中的中间件获取,为了方便我们选择从NXP 的SDK中获取。路径分别为“野火【i.MX RT1052开发板】光盘资料\5-NXP官方资料\2-NXP官方SDKMIMXRT1052xxxxB\middleware\lwip\port”与“野火【i.MX RT1052开发板】光盘资料\5-NXP官方资料\2-NXP官方SDKMIMXRT1052xxxxB\middleware\lwip\port\arch”,用户在移植的时候必须根据操作系统的功能为协议栈提供相应的接口,如邮箱(因为本次移植以FreeRTOS为例子,FreeRTOS中没有邮箱这种概念,但是可以使用消息队列替代,为了迎合LwIP中的命名,下文统一采用邮箱表示)、信号量、互斥量等,这些IPC通信机制是保证内核与上层API接口通信的基本保障,也是内核实现管理的继承,同时在sys.h文件中声明了用户需要实现的所有函数框架,这些函数具体见表格 8‑2。

表格 8‑2需要用户实现的函数

名称

属性

功能

所在文件

sys_sem_t

数据类型

指针类型,指向系统信号量

sys_arch.h

sys_mutex_t

数据类型

指针类型,指向系统互斥量

sys_arch.h

sys_mbox_t

数据类型

指针类型,指向系统邮箱

sys_arch.h

sys_thread_t

数据类型

指针类型,指向系统任务

sys_arch.h

SYS_MBOX_NULL

宏定义

系统邮箱的空值

sys_arch.h

SYS_SEM_NULL

宏定义

系统信号量的空值

sys_arch.h

SYS_MRTEX_NULL

宏定义

系统互斥量的空值

sys_arch.h

sys_now

函数

内核时钟

sys_arch.c

sys_init

函数

初始化系统

sys_arch.c

sys_arch_protect

函数

进入临界段

sys_arch.c

sys_arch_unprotect

函数

退出临界段

sys_arch.c

sys_sem_new

函数

创建一个信号量

sys_arch.c

sys_sem_free

函数

删除一个信号量

sys_arch.c

sys_sem_valid

函数

判断信号量是否有效

sys_arch.c

sys_sem_set_invalid

函数

将信号量设置无效状态

sys_arch.c

sys_arch_sem_wait

函数

等待一个信号量

sys_arch.c

sys_sem_signal

函数

释放一个信号量

sys_arch.c

sys_mutex_new

函数

创建一个互斥量

sys_arch.c

sys_mutex_free

函数

删除一个互斥量

sys_arch.c

sys_mutex_set_invalid

函数

设置互斥量为无效状态

sys_arch.c

sys_mutex_lock

函数

获取一个互斥量

sys_arch.c

sys_mutex_unlock

函数

释放一个互斥量

sys_arch.c

sys_mbox_new

函数

创建一个邮箱

sys_arch.c

sys_mbox_free

函数

删除一个邮箱

sys_arch.c

sys_mbox_valid

函数

判断邮箱是否有效

sys_arch.c

sys_mbox_set_invalid

函数

设置邮箱为无效状态

sys_arch.c

sys_mbox_post

函数

向邮箱发送消息,一直阻塞

sys_arch.c

sys_mbox_trypost

函数

向邮箱发送消息,非阻塞

sys_arch.c

sys_mbox_trypost_fromisr

函数

在中断中向邮箱发送消息

sys_arch.c

sys_arch_mbox_fetch

函数

从邮箱中获取消息,阻塞

sys_arch.c

sys_arch_mbox_tryfetch

函数

从邮箱中获取消息,非阻塞

sys_arch.c

sys_thread_new

函数

创建一个线程

sys_arch.c

看到那么多函数,是不是头都大了,其实这些函数的实现都是很简单的,首先讲解一下邮箱函数的实现。在LwIP中,用户代码与协议栈内部之间是通过邮箱进行数据的交互的,邮箱本质上就是一个指向数据的指针,API将指针传递给内核,内核通过这个指针访问数据,然后去处理,反之内核将数据传递给用户代码也是通过邮箱将一个指针进行传递。

在操作系统环境下,LwIP会作为一个线程运行,线程的名字叫tcpip_thread,在初始化LwIP的时候,内核就会自动创建这个线程,并且在线程运行的时候阻塞在邮箱上,等待数据进行处理,这个邮箱数据的来源可能在底层网卡接收到的数据或者上层应用程序的数据,总之,tcpip_thread线程在获取到邮箱中的数据时候,就会退出阻塞态,去处理数据,在处理完毕数据后又进入阻塞态中等待数据的到来,如此反复。

信号量与互斥量的实现为内核提供同步与互斥的机制,比如当用户想要发送一个数据的时候,就会调用上层API接口,API接口就会去先发送一个数据给内核去处理,然后尝试获取一个信号量,因为此时是没有信号量的,所以就会阻塞用户线程;内核在知道用户想要发送数据后,就会调用对应的网卡去发送数据,当数据发送完成后就释放一个信号量告知用户线程发送完成,这样子用户线程就得以继续执行。

所以这些函数的接口都必须由用户实现,下面具体看看这些函数的实现,具体见 代码清单8_3

代码清单 8‑3 sys_arch.c文件内容

 /* ------------------- System architecture includ------------------- */
 #include "arch/sys_arch.h"

 /* ------------------------ lwIP includes ------------------------- */
 #include "lwip/opt.h"

 #include "lwip/debug.h"
 #include "lwip/def.h"
 #include "lwip/sys.h"
 #include "lwip/mem.h"
 #include "lwip/stats.h"

 #include "lwip/tcpip.h"
 #include "lwip/init.h"
 #include "lwip/netif.h"
 #include "lwip/sio.h"
 #include "enet_ethernetif.h"
 #include "enet_ethernetif_priv.h"

 #include "lwip/netifapi.h"

 #if NO_SYS
 #include "lwip/init.h"
 #endif

 #ifndef errno
 int errno = 0;
 #endif

 /*
 * Prints an assertion messages and aborts execution.
 */
 void sys_assert( char *pcMessage )
 {
 //FSL:only needed for debugging
 #ifdef LWIP_DEBUG
     PRINTF(pcMessage);
     PRINTF("\n\r");
 #endif
 #if !NO_SYS
     portENTER_CRITICAL();
 #endif
     for (;;) {
     }
 }

 /******************************************************************
 * Generates a pseudo-random number.
 * NOTE: Contrubuted by the FNET project.
 *******************************************************************/
 static  u32_t _rand_value;
 u32_t lwip_rand(void)
 {
     _rand_value = _rand_value * 1103515245u + 12345u;
     return ((u32_t)(_rand_value>>16u) % (32767u + 1u));
 }

 #if !NO_SYS
 /*---------------------------------------------------------------------*
 * Routine:  sys_mbox_new
 *---------------------------------------------------------------------*
 * Description:
 *      Creates a new mailbox
 * Inputs:
 *      int size                -- Size of elements in the mailbox
 * Outputs:
 *      sys_mbox_t              -- Handle to new mailbox
 *---------------------------------------------------------------------*/
 err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
 {
     err_t xReturn = ERR_MEM;
     *pxMailBox = xQueueCreate( iSize, sizeof( void * ) );
     if ( *pxMailBox != NULL ) {
         xReturn = ERR_OK;
         SYS_STATS_INC_USED( mbox );
     }
     return xReturn;
 }

 /*---------------------------------------------------------------*
 * Routine:  sys_mbox_free
 *---------------------------------------------------------------*
 * Description:
 *      Deallocates a mailbox. If there are messages still present in the
 *      mailbox when the mailbox is deallocated, it is an indication of a
 *      programming error in lwIP and the developer should be notified.
 * Inputs:
 *      sys_mbox_t mbox         -- Handle of mailbox
 * Outputs:
 *      sys_mbox_t              -- Handle to new mailbox
 *------------------------------------------------------------------*/
 void sys_mbox_free( sys_mbox_t *pxMailBox )
 {
     unsigned long ulMessagesWaiting;

     ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox );
     configASSERT( ( ulMessagesWaiting == 0 ) );

 #if SYS_STATS
     {
         if ( ulMessagesWaiting != 0UL ) {
             SYS_STATS_INC( mbox.err );
         }

         SYS_STATS_DEC( mbox.used );
     }
 #endif /* SYS_STATS */

     vQueueDelete( *pxMailBox );
 }

 /*---------------------------------------------------------------------*
 * Routine:  sys_mbox_post
 *---------------------------------------------------------------------*
 * Description:
 *      Post the "msg" to the mailbox.
 * Inputs:
 *      sys_mbox_t mbox         -- Handle of mailbox
 *      void *data              -- Pointer to data to post
 *-------------------------------------------------------------------*/
 void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost )
 {
     while ( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE );
 }

 /*--------------------------------------------------------------------*
 * Routine:  sys_mbox_trypost
 *--------------------------------------------------------------------*
 * Description:
 *      Try to post the "msg" to the mailbox.  Returns immediately with
 *      error if cannot.
 * Inputs:
 *      sys_mbox_t mbox         -- Handle of mailbox
 *      void *msg               -- Pointer to data to post
 * Outputs:
 *      err_t                   -- ERR_OK if message posted, else ERR_MEM
 *                                  if not.
 *-------------------------------------------------------------------*/
 err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost )
 {
     portBASE_TYPE taskToWake = pdFALSE;
 #ifdef __CA7_REV
     if (SystemGetIRQNestingLevel())
 #else
     if (__get_IPSR())
 #endif
     {
         if (pdTRUE == xQueueSendToBackFromISR(*pxMailBox, &pxMessageToPost, &taskToWake)) {
             if (taskToWake == pdTRUE) {
                 portYIELD_FROM_ISR(taskToWake);
             }
             return ERR_OK;
         } else {
             /* The queue was already full. */
             SYS_STATS_INC( mbox.err );
             return ERR_MEM;
         }
     } else {
         if (pdTRUE == xQueueSendToBack(*pxMailBox, &pxMessageToPost, 0) ) {
             return ERR_OK;
         } else {
             /* The queue was already full. */
             SYS_STATS_INC( mbox.err );
             return ERR_MEM;
         }
     }
 }

 /*------------------------------------------------------------------*
 * Routine:  sys_mbox_trypost_fromisr
 *------------------------------------------------------------------*
 * Description:
 *      Try to post the "msg" to the mailbox.  Returns immediately with
 *      error if cannot. To be be used from ISR.
 * Inputs:
 *      sys_mbox_t mbox         -- Handle of mailbox
 *      void *msg               -- Pointer to data to post
 * Outputs:
 *      err_t                   -- ERR_OK if message posted, else ERR_MEM
 *                                  if not.
 *-------------------------------------------------------------------*/
 err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg)
 {
     return sys_mbox_trypost(mbox, msg);
 }

 /*-------------------------------------------------------------------*
 * Routine:  sys_arch_mbox_fetch
 *-------------------------------------------------------------------*
 * Description:
 *      Blocks the thread until a message arrives in the mailbox, but does
 *      not block the thread longer than "timeout" milliseconds (similar to
 *      the sys_arch_sem_wait() function). The "msg" argument is a result
 *      parameter that is set by the function (i.e., by doing "*msg =
 *      ptr"). The "msg" parameter maybe NULL to indicate that the message
 *      should be dropped.
 *
 *      The return values are the same as for the sys_arch_sem_wait() function:
 *      Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a
 *      timeout.
 *
 *      Note that a function with a similar name, sys_mbox_fetch(), is
 *      implemented by lwIP.
 * Inputs:
 *      sys_mbox_t mbox         -- Handle of mailbox
 *      void **msg              -- Pointer to pointer to msg received
 *      u32_t timeout           -- Number of milliseconds until timeout
 * Outputs:
 *      u32_t                   -- SYS_ARCH_TIMEOUT if timeout, else number
 *                                  of milliseconds until received.
 *-------------------------------------------------------------------*/
 u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut )
 {
     void *pvDummy;
     TickType_t xStartTime, xEndTime, xElapsed;
     unsigned long ulReturn;

     xStartTime = xTaskGetTickCount();

     if ( NULL == ppvBuffer ) {
         ppvBuffer = &pvDummy;
     }

     if ( ulTimeOut != 0UL ) {
         if ( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_PERIOD_MS ) )
     {
             xEndTime = xTaskGetTickCount();
             xElapsed = ( xEndTime - xStartTime ) * portTICK_PERIOD_MS;

             ulReturn = xElapsed;
         } else {
             /* Timed out. */
             *ppvBuffer = NULL;
             ulReturn = SYS_ARCH_TIMEOUT;
         }
     } else {
         while ( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) );
         xEndTime = xTaskGetTickCount();
         xElapsed = ( xEndTime - xStartTime ) * portTICK_PERIOD_MS;

         if ( xElapsed == 0UL ) {
             xElapsed = 1UL;
         }
         ulReturn = xElapsed;
     }
     return ulReturn;
 }

 /*--------------------------------------------------------------------*
 * Routine:  sys_arch_mbox_tryfetch
 *--------------------------------------------------------------------*
 * Description:
 *      Similar to sys_arch_mbox_fetch, but if message is not ready
 *      immediately, we'll return with SYS_MBOX_EMPTY.  On success, 0 is
 *      returned.
 * Inputs:
 *      sys_mbox_t mbox         -- Handle of mailbox
 *      void **msg              -- Pointer to pointer to msg received
 * Outputs:
 *      u32_t                   -- SYS_MBOX_EMPTY if no messages.  Otherwise,
 *                                  return ERR_OK.
 *------------------------------------------------------------------*/
 u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer )
 {
     void *pvDummy;
     unsigned long ulReturn;

     if ( ppvBuffer== NULL ) {
         ppvBuffer = &pvDummy;
     }

     if ( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL ) ) {
         ulReturn = ERR_OK;
     } else {
         ulReturn = SYS_MBOX_EMPTY;
     }

     return ulReturn;
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_sem_new
 *---------------------------------------------------------------------------*
 * Description:
 *      Creates and returns a new semaphore. If the value of "ucCount" argument
 *      is 0, maximum count of semaphore is 1 and the initial state is 0.
 *      Otherwise the "ucCount" argument specifies both initial state and
 *      maximum count of the semaphore.
 * Inputs:
 *      sys_mbox_t mbox         -- Handle of mailbox
 *      u8_t ucCount            -- Initial ucCount of semaphore
 * Outputs:
 *      sys_sem_t               -- Created semaphore or 0 if could not create.
 *---------------------------------------------------------------------------*/
 err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount )
 {
     err_t xReturn = ERR_MEM;

     if ( ucCount > 1U ) {
         *pxSemaphore = xSemaphoreCreateCounting( ucCount, ucCount );
     } else {
         vSemaphoreCreateBinary( ( *pxSemaphore ) );
     }

     if ( *pxSemaphore != NULL ) {
         if ( ucCount == 0U ) {
             xSemaphoreTake( *pxSemaphore, 1UL );
         }

         xReturn = ERR_OK;
         SYS_STATS_INC_USED( sem );
     } else {
         SYS_STATS_INC( sem.err );
     }

     return xReturn;
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_arch_sem_wait
 *---------------------------------------------------------------------------*
 * Description:
 *      Blocks the thread while waiting for the semaphore to be
 *      signaled. If the "timeout" argument is non-zero, the thread should
 *      only be blocked for the specified time (measured in
 *      milliseconds).
 *
 *      If the timeout argument is non-zero, the return value is the number of
 *      milliseconds spent waiting for the semaphore to be signaled. If the
 *      semaphore wasn't signaled within the specified time, the return value is
 *      SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore
 *      (i.e., it was already signaled), the function may return zero.
 *
 *      Notice that lwIP implements a function with a similar name,
 *      sys_sem_wait(), that uses the sys_arch_sem_wait() function.
 * Inputs:
 *      sys_sem_t sem           -- Semaphore to wait on
 *      u32_t timeout           -- Number of milliseconds until timeout
 * Outputs:
 *      u32_t                   -- Time elapsed or SYS_ARCH_TIMEOUT.
 *---------------------------------------------------------------------------*/
 u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout )
 {
     TickType_t xStartTime, xEndTime, xElapsed;
     unsigned long ulReturn;

     xStartTime = xTaskGetTickCount();

     if ( ulTimeout != 0UL ) {
         if ( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_PERIOD_MS ) == pdTRUE ) {
             xEndTime = xTaskGetTickCount();
             xElapsed = (xEndTime - xStartTime) * portTICK_PERIOD_MS;
             ulReturn = xElapsed;
         } else {
             ulReturn = SYS_ARCH_TIMEOUT;
         }
     } else {
         while ( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE );
         xEndTime = xTaskGetTickCount();
         xElapsed = ( xEndTime - xStartTime ) * portTICK_PERIOD_MS;

         if ( xElapsed == 0UL ) {
             xElapsed = 1UL;
         }

         ulReturn = xElapsed;
     }

     return ulReturn;
 }

 /** Create a new mutex
 * @param mutex pointer to the mutex to create
 * @return a new mutex */
 err_t sys_mutex_new( sys_mutex_t *pxMutex )
 {
     err_t xReturn = ERR_MEM;

     *pxMutex = xSemaphoreCreateMutex();

     if ( *pxMutex != NULL ) {
         xReturn = ERR_OK;
         SYS_STATS_INC_USED( mutex );
     } else {
         SYS_STATS_INC( mutex.err );
     }

     return xReturn;
 }

 /** Lock a mutex
 * @param mutex the mutex to lock */
 void sys_mutex_lock( sys_mutex_t *pxMutex )
 {
     while ( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS );
 }

 /** Unlock a mutex
 * @param mutex the mutex to unlock */
 void sys_mutex_unlock(sys_mutex_t *pxMutex )
 {
     xSemaphoreGive( *pxMutex );
 }

 /** Delete a semaphore
 * @param mutex the mutex to delete */
 void sys_mutex_free( sys_mutex_t *pxMutex )
 {
     SYS_STATS_DEC( mutex.used );
     vQueueDelete( *pxMutex );
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_sem_signal
 *---------------------------------------------------------------------------*
 * Description:
 *      Signals (releases) a semaphore
 * Inputs:
 *      sys_sem_t sem           -- Semaphore to signal
 *---------------------------------------------------------------------------*/
 void sys_sem_signal( sys_sem_t *pxSemaphore )
 {
     xSemaphoreGive( *pxSemaphore );
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_sem_free
 *---------------------------------------------------------------------------*
 * Description:
 *      Deallocates a semaphore
 * Inputs:
 *      sys_sem_t sem           -- Semaphore to free
 *---------------------------------------------------------------------------*/
 void sys_sem_free( sys_sem_t *pxSemaphore )
 {
     SYS_STATS_DEC(sem.used);
     vQueueDelete( *pxSemaphore );
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_init
 *---------------------------------------------------------------------------*
 * Description:
 *      Initialize sys arch
 *---------------------------------------------------------------------------*/
 void sys_init(void)
 {
 }

 u32_t sys_now(void)
 {
     return xTaskGetTickCount() * portTICK_PERIOD_MS;
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_thread_new
 *---------------------------------------------------------------------------*
 * Description:
 *      Starts a new thread with priority "prio" that will begin its
 *      execution in the function "thread()". The "arg" argument will be
 *      passed as an argument to the thread() function. The id of the new
 *      thread is returned. Both the id and the priority are system
 *      dependent.
 * Inputs:
 *      char *name              -- Name of thread
 *      void (* thread)(void *arg) -- Pointer to function to run.
 *      void *arg               -- Argument passed into function
 *      int stacksize           -- Required stack amount in bytes
 *      int prio                -- Thread priority
 * Outputs:
 *      sys_thread_t            -- Pointer to per-thread timeouts.
 *---------------------------------------------------------------------------*/
 sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void
 *pvArg, int iStackSize, int iPriority )
 {
     TaskHandle_t xCreatedTask;
     portBASE_TYPE xResult;
     sys_thread_t xReturn;

     xResult = xTaskCreate( pxThread, pcName, iStackSize, pvArg, iPriority, &xCreatedTask );

     if ( xResult == pdPASS ) {
         xReturn = xCreatedTask;
     } else {
         xReturn = NULL;
     }

     return xReturn;
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_arch_protect
 *---------------------------------------------------------------------------*
 * Description:
 *      This optional function does a "fast" critical region protection and
 *      returns the previous protection level. This function is only called
 *      during very short critical regions. An embedded system which supports
 *      ISR-based drivers might want to implement this function by disabling
 *      interrupts. Task-based systems might want to implement this by using
 *      a mutex or disabling tasking. This function should support recursive
 *      calls from the same task or interrupt. In other words,
 *      sys_arch_protect() could be called while already protected. In
 *      that case the return value indicates that it is already protected.
 *
 *      sys_arch_protect() is only required if your port is supporting an
 *      operating system.
 * Outputs:
 *      sys_prot_t              -- Previous protection level (not used here)
 *---------------------------------------------------------------------------*/
 sys_prot_t sys_arch_protect( void )
 {
     sys_prot_t result = 0;

 #ifdef __CA7_REV
     if (SystemGetIRQNestingLevel())
 #else
     if (__get_IPSR())
 #endif
     {
         result = portSET_INTERRUPT_MASK_FROM_ISR();
     } else {
         portENTER_CRITICAL();
     }
     return result;
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_arch_unprotect
 *---------------------------------------------------------------------------*
 * Description:
 *      This optional function does a "fast" set of critical region
 *      protection to the value specified by pval. See the documentation for
 *      sys_arch_protect() for more information. This function is only
 *      required if your port is supporting an operating system.
 * Inputs:
 *      sys_prot_t              -- Previous protection level (not used here)
 *---------------------------------------------------------------------------*/
 void sys_arch_unprotect( sys_prot_t xValue )
 {
 #ifdef __CA7_REV
     if (SystemGetIRQNestingLevel())
 #else
     if (__get_IPSR())
 #endif
     {
         portCLEAR_INTERRUPT_MASK_FROM_ISR(xValue);
     } else {
         portEXIT_CRITICAL();
     }
 }

 #else /* Bare-metal */

 static volatile uint32_t time_now = 0;

 void time_isr(void)
 {
 #ifdef __CA7_REV
     SystemClearSystickFlag();
 #endif
     time_now++;
 }

 void time_init(void)
 {
 #ifdef __CA7_REV
     /* special for i.mx6ul */
     SystemSetupSystick(1000U, (void *)time_isr, 0U);
     SystemClearSystickFlag();
 #else
     /* Set SysTick period to 1 ms and enable its interrupts */
     SysTick_Config(USEC_TO_COUNT(1000U, sourceClock));
 #endif
 }

 /*
 This optional function returns the current time in milliseconds (don't care
 for wraparound, this is only used for time diffs).
 Not implementing this function means you cannot use some modules (e.g. TCP
 timestamps, internal timeouts for NO_SYS==1).
 */

 u32_t sys_now(void)
 {
     return (u32_t)time_now;
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_arch_protect
 *---------------------------------------------------------------------------*
 * Description:
 *      This optional function does a "fast" critical region protection and
 *      returns the previous protection level. This function is only called
 *      during very short critical regions. An embedded system which supports
 *      ISR-based drivers might want to implement this function by disabling
 *      interrupts. Task-based systems might want to implement this by using
 *      a mutex or disabling tasking. This function should support recursive
 *      calls from the same task or interrupt. In other words,
 *      sys_arch_protect() could be called while already protected. In
 *      that case the return value indicates that it is already protected.
 *
 *      sys_arch_protect() is only required if your port is supporting an
 *      operating system.
 * Outputs:
 *      sys_prot_t              -- Previous protection level (not used here)
 *---------------------------------------------------------------------------*/
 sys_prot_t sys_arch_protect( void )
 {
     sys_prot_t result;

     result = (sys_prot_t)DisableGlobalIRQ();

     return result;
 }

 /*---------------------------------------------------------------------------*
 * Routine:  sys_arch_unprotect
 *---------------------------------------------------------------------------*
 * Description:
 *      This optional function does a "fast" set of critical region
 *      protection to the value specified by pval. See the documentation for
 *      sys_arch_protect() for more information. This function is only
 *      required if your port is supporting an operating system.
 * Inputs:
 *      sys_prot_t              -- Previous protection level (not used here)
 *---------------------------------------------------------------------------*/
 void sys_arch_unprotect( sys_prot_t xValue )
 {
     EnableGlobalIRQ((uint32_t)xValue);
 }

 #endif /*NO_SYS*/
 struct netif gnetif;
 ip4_addr_t ipaddr;
 ip4_addr_t netmask;
 ip4_addr_t gw;
 void TCPIP_Init(void)
 {
     ethernetif_config_t fsl_enet_config0 = {
         .phyAddress = EXAMPLE_PHY_ADDRESS,
         .clockName  = EXAMPLE_CLOCK_NAME,
         .macAddress = configMAC_ADDR,
     };
     tcpip_init(NULL, NULL);

     /* IP addresses initialization */
     /* USER CODE BEGIN 0 */
 #if LWIP_DHCP
     ip_addr_set_zero_ip4(&ipaddr);
     ip_addr_set_zero_ip4(&netmask);
     ip_addr_set_zero_ip4(&gw);
 #else
     IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
     IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
     IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
 #endif /* USE_DHCP */

     netifapi_netif_add(&gnetif, &ipaddr, &netmask, &gw, &fsl_enet_config0,
                     ethernetif0_init, tcpip_input);
     netifapi_netif_set_default(&gnetif);
     netifapi_netif_set_up(&gnetif);

 #if LWIP_DHCP         //若使用了DHCP
     int err;
     /*  Creates a new DHCP client for this interface on the first call.
     Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at
     the predefined regular intervals after starting the client.
     You can peek in the netif->dhcp struct for the actual DHCP status.*/

     PRINTF("本例程将使用DHCP动态分配IP地址,如果不需要则在lwipopts.h中将LWIP_DHCP定义为0\n\n");

     err = dhcp_start(&gnetif);      //开启dhcp
     if (err == ERR_OK)
         PRINTF("lwip dhcp init success...\n\n");
     else
         PRINTF("lwip dhcp init fail...\n\n");
     while (ip_addr_cmp(&(gnetif.ip_addr),&ipaddr)) { //等待dhcp分配的ip有效
         vTaskDelay(1);
     }
 #endif
     PRINTF("本地IP地址是:%d.%d.%d.%d\n\n",  \
         ((gnetif.ip_addr.addr)&0x000000ff),       \
         (((gnetif.ip_addr.addr)&0x0000ff00)>>8),  \
         (((gnetif.ip_addr.addr)&0x00ff0000)>>16), \
         ((gnetif.ip_addr.addr)&0xff000000)>>24);
 }

 /*-------------------------------------------------------------------------*
 * End of File:  sys_arch.c
 *-------------------------------------------------------------------------*/

这些函数都是对操作系统的IPC通信机制进行简单的封装,在这里用户只需要稍微注意一下sys_arch_sem_wait()函数与sys_arch_mbox_fetch()函数,因为LwIP中使用的时间是以毫秒(ms)为单位的,而操作系统中则以时钟节拍(tick)为单位,那么在返回等待信号量或者邮箱所使用的时间就是要转换成ms,而操作系统并未提供等待这些信息的时间,那么我们可以使用一个折中的方法,在获取的时候开始记录时间戳,在获取结束后再次记录一次时间戳,两次时间戳相减就得到等待的时间,但是需要将这些时间(tick)转换为毫秒,这种做法当然是不精确的,但是对LwIP来说影响不大。

9.4. 网卡底层的编写

在这里网卡的收发数据就是单纯的收发数据,ethernetif_input()函数就是处理接收网卡数据的函数,当数据包准备好被读取时,应该调用此函数。ethernetif_input()函数在ethernet_callback()回调函数中被调用。网卡接收函具体见 代码清单8_4

代码清单 8‑4网卡接收线程ethernetif_input()

 void ethernetif_input(struct netif *netif)
 {
     struct pbuf *p;

     LWIP_ASSERT("netif != NULL", (netif != NULL));

     /* move received packet into a new pbuf */
     while ((p = ethernetif_linkinput(netif)) != NULL) {
         /* pass all packets to ethernet_input, which decides what packets it supports */
         if (netif->input(p, netif) != ERR_OK) {
             LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
             pbuf_free(p);
             p = NULL;
         }
     }
 }

网卡初始化是ethernetif_enet_init()函数具体见 代码清单8_5

代码清单 8‑5网卡初始化函数 ethernetif_enet_init ()

 /**
 * ENET驱动程序初始化
 */
 void ethernetif_enet_init(struct netif *netif, struct ethernetif *ethernetif,
                         const ethernetif_config_t *ethernetifConfig)
 {
     enet_config_t config;
     uint32_t sysClock;
     enet_buffer_config_t buffCfg[ENET_RING_NUM];

     /* 配置buff. */
     buffCfg[0].rxBdNumber = ENET_RXBD_NUM;/* 接收缓冲区描述符号。 */
     buffCfg[0].txBdNumber = ENET_TXBD_NUM;/* 传输缓冲区描述符号. */
     buffCfg[0].rxBuffSizeAlign = sizeof(rx_buffer_t);/*对齐接收数据缓冲区大小. */
     buffCfg[0].txBuffSizeAlign = sizeof(tx_buffer_t);/* 对齐的传输数据缓冲区大小. */
     buffCfg[0].rxBdStartAddrAlign = &(ethernetif->RxBuffDescrip[0]); /* 对齐的接收缓冲区描述符起始地址. */

     buffCfg[0].txBdStartAddrAlign = &(ethernetif->TxBuffDescrip[0]); /* 对齐的发送缓冲区描述符起始地址. */

     buffCfg[0].rxBufferAlign = &(ethernetif->RxDataBuff[0][0]); /* 接收数据缓冲区起始地址*/
     buffCfg[0].txBufferAlign = &(ethernetif->TxDataBuff[0][0]); /* 传输数据缓冲区起始地址. */
     /*获取以太网时钟*/
     sysClock = CLOCK_GetFreq(ethernetifConfig->clockName);
     /*以太网默认配置*/
     ENET_GetDefaultConfig(&config);
     config.ringNum = ENET_RING_NUM;
     /*初始化phy*/
     ethernetif_phy_init(ethernetif, ethernetifConfig, &config);

 #if USE_RTOS && defined(FSL_RTOS_FREE_RTOS)
     uint32_t instance;
     static ENET_Type *const enetBases[] = ENET_BASE_PTRS;
     static const IRQn_Type enetTxIrqId[] = ENET_Transmit_IRQS;
     /*! @brief 指向enet的指针接收每个实例的IRQ编号. */
     static const IRQn_Type enetRxIrqId[] = ENET_Receive_IRQS;
 #if defined(ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) && ENET_ENHANCEDBUFFERDESCRIPTOR_MODE
     /*! @brief 指向每个实例的enet时间戳IRQ编号的指针. */
     static const IRQn_Type enetTsIrqId[] = ENET_1588_Timer_IRQS;
 #endif /* ENET_ENHANCEDBUFFERDESCRIPTOR_MODE */

     /* 为传输忙释放触发器创建事件. */
     ethernetif->enetTransmitAccessEvent = xEventGroupCreate();
     ethernetif->txFlag = 0x1;

     config.interrupt |= kENET_RxFrameInterrupt | kENET_TxFrameInterrupt | kENET_TxBufferInterrupt;

     for (instance = 0; instance < ARRAY_SIZE(enetBases); instance++) {
         if (enetBases[instance] == ethernetif->base) {
 #ifdef __CA7_REV
             GIC_SetPriority(enetRxIrqId[instance], ENET_PRIORITY);
             GIC_SetPriority(enetTxIrqId[instance], ENET_PRIORITY);
 #if defined(ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) && ENET_ENHANCEDBUFFERDESCRIPTOR_MODE
             GIC_SetPriority(enetTsIrqId[instance], ENET_1588_PRIORITY);
 #endif /* ENET_ENHANCEDBUFFERDESCRIPTOR_MODE */
 #else
             NVIC_SetPriority(enetRxIrqId[instance], ENET_PRIORITY);
             NVIC_SetPriority(enetTxIrqId[instance], ENET_PRIORITY);
 #if defined(ENET_ENHANCEDBUFFERDESCRIPTOR_MODE) && ENET_ENHANCEDBUFFERDESCRIPTOR_MODE
             NVIC_SetPriority(enetTsIrqId[instance], ENET_1588_PRIORITY);
 #endif /* ENET_ENHANCEDBUFFERDESCRIPTOR_MODE */
 #endif /* __CA7_REV */
             break;
         }
     }

     LWIP_ASSERT("Input Ethernet base error!", (instance != ARRAY_SIZE(enetBases)));
 #endif /* USE_RTOS */

     /*初始化ENET模块.*/
     ENET_Init(ethernetif->base, &ethernetif->handle, &config, &buffCfg[0], netif->hwaddr, sysClock);

 #if USE_RTOS && defined(FSL_RTOS_FREE_RTOS)
     /*设置回调函数*/
     ENET_SetCallback(&ethernetif->handle, ethernet_callback, netif);
 #endif
     ENET_ActiveRead(ethernetif->base);
 }

9.5. 协议栈初始化

经过上面的移植,我们的底层与操作系统接口都基本移植完毕,想要让LwIP在操作系统中能跑起来,还需要最后一步,将协议栈进行初始化,前面我们也说了,内核在操作系统中是作为一个线程独立存在的,在初始化的时候,我们不仅要挂载网卡,也要创建tcpip_thread()线程,当然,这个线程LwIP会在初始的时候自动创建,而挂载网卡的内容与无操作系统是一样的,就无需过多修改,协议栈初始化的源码具体见 代码清单8_6

代码清单 8‑6 协议栈初始化

 #define DEST_IP_ADDR0                192
 #define DEST_IP_ADDR1               168
 #define DEST_IP_ADDR2                 0
 #define DEST_IP_ADDR3               83

 #define DEST_PORT                   5001

 #define UDP_SERVER_PORT             5002   /* define the UDP local connection port */
 #define UDP_CLIENT_PORT             5002   /* define the UDP remote connection port */

 #define LOCAL_PORT                 5001

 /*Static IP ADDRESS: IP_ADDR0.IP_ADDR1.IP_ADDR2.IP_ADDR3 */
 #define IP_ADDR0                    192
 #define IP_ADDR1                    168
 #define IP_ADDR2                      0
 #define IP_ADDR3                    122

 /*NETMASK*/
 #define NETMASK_ADDR0               255
 #define NETMASK_ADDR1               255
 #define NETMASK_ADDR2               255
 #define NETMASK_ADDR3                 0

 /*Gateway Address*/
 #define GW_ADDR0                    192
 #define GW_ADDR1                    168
 #define GW_ADDR2                      0
 #define GW_ADDR3                      1

 /* MAC address configuration. */
 #define configMAC_ADDR                     \
     {                                      \
         0x02, 0x12, 0x13, 0x10, 0x15, 0x11 \
     }

 /* Address of PHY interface. */
 #define EXAMPLE_PHY_ADDRESS 0x00U

 /* System clock name. */
 #define EXAMPLE_CLOCK_NAME kCLOCK_CoreSysClk

 struct netif gnetif;
 ip4_addr_t ipaddr;
 ip4_addr_t netmask;
 ip4_addr_t gw;
 void TCPIP_Init(void)
 {
     ethernetif_config_t fsl_enet_config0 = {
         .phyAddress = EXAMPLE_PHY_ADDRESS,
         .clockName  = EXAMPLE_CLOCK_NAME,
         .macAddress = configMAC_ADDR,
     };
     tcpip_init(NULL, NULL);

     /* IP addresses initialization */
     /* USER CODE BEGIN 0 */
 #if LWIP_DHCP
     ip_addr_set_zero_ip4(&ipaddr);
     ip_addr_set_zero_ip4(&netmask);
     ip_addr_set_zero_ip4(&gw);
 #else
     IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
     IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
     IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
 #endif /* USE_DHCP */

     netifapi_netif_add(&gnetif, &ipaddr, &netmask, &gw, &fsl_enet_config0,
                         ethernetif0_init, tcpip_input);
     netifapi_netif_set_default(&gnetif);
     netifapi_netif_set_up(&gnetif);

 #if LWIP_DHCP         //若使用了DHCP
     int err;
     /*  Creates a new DHCP client for this interface on the first call.
     Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at
     the predefined regular intervals after starting the client.
     You can peek in the netif->dhcp struct for the actual DHCP status.*/

     PRINTF("本例程将使用DHCP动态分配IP地址,如果不需要则在lwipopts.h中将LWIP_DHCP定义为0\n\n");

     err = dhcp_start(&gnetif);      //开启dhcp
     if (err == ERR_OK)
         PRINTF("lwip dhcp init success...\n\n");
     else
         PRINTF("lwip dhcp init fail...\n\n");
     while (ip_addr_cmp(&(gnetif.ip_addr),&ipaddr)) { //等待dhcp分配的ip有效
         vTaskDelay(1);
     }
 #endif
     PRINTF("本地IP地址是:%d.%d.%d.%d\n\n",  \
             ((gnetif.ip_addr.addr)&0x000000ff),       \
             (((gnetif.ip_addr.addr)&0x0000ff00)>>8),  \
             (((gnetif.ip_addr.addr)&0x00ff0000)>>16), \
             ((gnetif.ip_addr.addr)&0xff000000)>>24);
 }

在tcpip_init()函数中,LwIP会调用lwip_init()将内核进行初始化,并且创建一个tcpip_mbox邮箱, 邮箱的大小是TCPIP_MBOX_SIZE,用于接收从底层或者上层传递过来的消息,并且最重要的是创建一个tcpip_thread线程, 这就是LwIP在操作系统中作为一个独立的线程运行,所有处理的数据都要这个线程去处理,这个线程我们会在后续讲解。 lwip_init()函数的源码具体见 代码清单8_7

代码清单 8‑7 lwip_init()源码

 void
 tcpip_init(tcpip_init_done_fn initfunc, void *arg)
 {
     lwip_init();

     tcpip_init_done = initfunc;
     tcpip_init_done_arg = arg;
     if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK)
     {
         LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
     }
 #if LWIP_TCPIP_CORE_LOCKING
     if (sys_mutex_new(&lock_tcpip_core) != ERR_OK)
     {
         LWIP_ASSERT("failed to create lock_tcpip_core", 0);
     }
 #endif /* LWIP_TCPIP_CORE_LOCKING */

     sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL,
                 TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
 }

对于 代码清单8_6 中netif_add()函数,与裸机移植的时候有一点细微的差别,它最后一个参数就是tcpip_input, 是一个函数,而不是原来的ethernet_input(),因为tcpip_input()函数会将网卡收到的数据包打包成为一个消息, 发送到tcpip_mbox邮箱中,传递给tcpip_thread线程去处理,不过本质上也是调用ethernet_input()函数去递交这个数据包, 只不过是绕了一大圈,因此,想要LwIP能正常运行,消息机制是不可或缺的,关于这个运作机制我们在后文详细讲解。

9.6. 移植后使用ping测试基本响应

在移植完操作系统与LwIP,我们也应该用电脑去ping一下开发版,看看是否能ping通, 如果通了就表明我们的移植是正常的,那么我们才能继续去用LwIP完成更高级的应用,ping结果具体见 图8_4

图 8‑4ping响应结果

图 8‑4ping响应结果