uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
2006-9-1
1 下载LwIP.................................................................................................................................................................
2
5
2 建立一个最基本的工程 .........................................................................................................................................2
3 把LwIP加入工程 .....................................................................................................................................................2
4 编写操作系统模拟层相关代码 .............................................................................................................................3
4.1 操作系统模拟层移植说明――中文翻译.................................................................................................3
4.2 编写操作系统模拟层 .................................................................................................................................6
4.2.1 准备工作――建立文件、定义数据类型及其它.........................................................................6
4.2.2 信号量操作函数 .............................................................................................................................8
4.2.3 邮箱操作函数 ...............................................................................................................................13
4.2.4 实现sys_thread_new()函数.......................................................................................................20
4.2.5 实现sys_arch_timeouts()函数.................................................................................................22
4.2.6 实现临界保护函数 .......................................................................................................................25
4.2.7 扫尾――结束操作系统模拟层的编写.......................................................................................26
LwIP接口――初始设置及网络驱动 ...................................................................................................................28
5.1 准备工作――建立LwIP入口函数文件...................................................................................................28
ilvInitLwIP().....................................................................................................................................29
5.2
ilvSetLwIP().......................................................................................................................................30
5.3
ethernetif_init()――初始化底层界面.............................................................................................35
5.4
ethernetif_init()函数分析.....................................................................................................35
5.4.1
low_level_output()――链路层发送函数...............................................................................36
5.4.2
low_level_init()――网卡初始化函数...................................................................................38
5.4.3
EMACInit()――网卡初始化工作的实际完成者.......................................................................40
5.4.4
ethernetif_input()――实现接收线程...................................................................................47
5.4.5
low_level_input()――得到一整帧数据.................................................................................49
5.4.6
GetInputPacketLen()――获得帧长.........................................................................................50
5.4.7
EMACReadPacket()――复制,从接收缓冲区到pbuf ...............................................................53
5.4.8
EMACSendPacket()――发送一帧资料.......................................................................................55
5.4.9
5.4.10 编译――ethernetif.c及lib_emac.c ......................................................................................56
ping――结束LwIP的移植 ...................................................................................................................................57
6.1 编译、链接整个工程 ...............................................................................................................................57
ping测试...................................................................................................................................................59
6.2
后记...............................................................................................................................................................................62
6
- 1 -
uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
2006-9-1
本文将指导读者一步步完成LwIP在ADS1.2开发环境下的移植工作,包括底层驱动的编写。本文使用的
硬件平台是AT91SAM7X256 + RTL8201BL(PHY),至于软件平台,读者从本文标题即可看出。我们使用 uC/OS-II
作为底层操作系统,而LwIP的移植亦将主要围绕uC/OS-II展开。好了,不再多说,开始吧……
1 下载LwIP
很简单,到LwIP的官方网站即可:http://savannah.nongnu.org/projects/lwip/。如果你不想看看
其 它 内 容( 可 能 对你 会 很 重要), 就 只 是 想 得 到源 码 , 好的 , 直 接到 这 个 地
址 下 载: http://download.savannah.nongnu.org/releases/lwip/。 目 前 官 方 发 布 的 最 新 版 本 是
1.1.1, 找 到 lwip-1.1.1.zip,然后下载、解压缩,第一项工作完成。
2 建立一个最基本的工程
要想完成移植工作,我们必须要有一个包含uC/OS-II的工程才行,这一步我们就是要建立这个工程。 工程
建立完毕后,编译链接没有问题,那么,第二项工作也完成了。关于如何建立一个包含uC/OS-II的
ADS 工程 的问 题 , 不 在本 文 描 述 范围 之 内 , 这里 不 做 讲 述。 随 本 笔 记一 同 发 布 的源 码
文 档 中
LwIPPortingTest_2 档夹下包含了这个最基本工程的源码,读者可以直接使用。我的基本工程建立的
路径是D:\work\LwIPPortingTest,下文将以相对路径进行讲述,不再提供绝对路径。
3 把LwIP加入工程
首先,在\src\活页夹下,建立LwIP活页夹,即:\src\LwIP;然后将下载的LwIP源码文件中api、
core、include、netif文件复制到\src\LwIP\活页夹下,如下图所示:
图3.1
然后,用ADS打开工程文件,按照LwIP源码文件的实际存放路径建立LwIP的工程结构,如下图所示:
- 2 -
uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
2006-9-1
图3.2
这里需要特别说明的是,源码中的IP V6、SLIP及PPP部分我们没有添加进来,主要是考虑我及大多数 读
者的网络还是V4,而SLIP、PPP暂时不在我的考虑范围之内。另外,在移植层面V6也和V4相差不多, 这里
就不再讲解这部分内容了。现在基础工程结构建立完毕,可以把LwIP源码添加进来了。这一步很容 易,
按照文件存放路径,将源码文件添加到相应的工程结构下即可。源码添加完成后的工程参见所附源 码档
的LwIPPortingTest_3活页夹。
4 编写操作系统模拟层相关代码
LwIP的作者为操作系统模拟层提供了较为详细的说明,文件名为sys_arch.txt,在LwIP的doc文 件夹
下。我们的编写工作根据这个说明进行。
4.1 操作系统模拟层移植说明――中文翻译
事先声明,之所以笔者要翻译该文档,主要是笔者在撰写这篇笔记时亦没有通读该文档。笔者先
前 使用的模拟层源码是杨晔大侠的。为了真正弄懂 LwIP,笔者决定自己重新实现 LwIP 的移植,本
笔 记是跟随移植同步进行的,因此,翻译的文档也放在了这篇笔记中,使读者能够真正了解笔者
的移 植历程。另外再说一句,这个文档是为 LwIP 0.6++版编写,笔者搜遍了整个 LwIP 官方网站,
没有 发现比这更新的,笔者只好认为操作系统模拟层在 0.6++之后没有任何改动,如果有谁发现
了更新 的,一定通知笔者,先谢谢了。好的,言归正传,下面就是译文:
- 3 -
uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
2006-9-1
LwIP 0.6++ sys_arch 界面
作者:Adam Dunkels
操作系统模拟层(sys_arch)存在的目的主要是为了方便 LwIP 的移植,它在底层操作系统和
LwIP 之间提供了一个接口。这样,我们在移植 LwIP 到一个新的目标系统时,只需修改这个接口即
可。不过,不依赖底层操作系统的支持也可以实现这个接口。
sys_arch需要为LwIP提供信号量(semaphores)和邮箱(mailboxes)两种进程间通讯方式(IPC)。
如果想获得 LwIP 的完整功能,sys_arch 还必须支持多线程。当然,对于仅需要基本功能的用户来
说,可以不去实现多线程。LwIP 以前的版本还要求 sys_arch 实现定时器调度,不过,从 LwIP0.5
开始,这一需求在更高一层实现。除了上文所述的 sys_arch 源文件需要实现的功能外,LwIP 还要
求用户提供几个头文件,这几个头档包含LwIP使用的宏定义。下文将详细讲述sys_arch及头文
件的实现。
信号量即可以是计数信号量,也可以是二值信号量――LwIP都可以正常工作。邮箱用于消息传
递,用户即可以将其实现为一个队列,允许多条消息投递到这个邮箱,也可以每次只允许投递一个
消息。这两种方式LwIP都可以正常运作。不过,前者更加有效。需要用户特别注意的是――投递到
邮箱中的消息只能是一个指标。
在sys_arch.h檔中,我们指定数据类型“sys_sem_t”表示信号量,“sys_mbox_t”表示邮箱。
至于sys_sem_t和sys_mbox_t如何表示这两种不同类型,LwIP没有任何限制。
以下函数必须在sys_arch中实现:
- void sys_init(void)
初始化sys_arch层。
- sys_sem_t sys_sem_new(u8_t count)
建立并返回一个新的信号量。参数count指定信号量的初始状态。
- void sys_sem_free(sys_sem_t sem)
释放信号量。
- void sys_sem_signal(sys_sem_t sem)
发送一个信号。
- u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
等待指定的信号并阻塞线程。timeout参数为0,线程会一直被阻塞至收到指定的信号;非0,
则线程仅被阻塞至指定的timeout时间(单位为毫秒)。在timeout参数值非0的情况下,返回值为
等 待 指 定的 信 号 所消 耗 的 毫秒 数 。 如果 在 指 定的 时 间 内并 没 有 收到 信 号 ,返 回
值为
SYS_ARCH_TIMEOUT。如果线程不必再等待这个信号(也就是说,已经收到信号),返回值也可以为0。
注意,LwIP实现了一个名称与之相似的函数来调用这个函数,sys_sem_wait(),注意区别。
- sys_mbox_t sys_mbox_new(void)
建立一个空的邮箱。
- void sys_mbox_free(sys_mbox_t mbox)
释放一个邮箱。如果释放时邮箱中还有消息,它表明LwIP中存在一个编程错误,应该通知开发
者(原文如此,这句话很费解。个人理解的意思是:当执行 sys_mbox_free()这个函数时,按道理邮箱中不应该再
存在任何消息,如果用户使用 LwIP 时发现邮箱中还存在消息,说明 LwIP 的开发者存在一个编程错误,不能把邮箱
中的消息全部取出并处理掉。遇到这种情况,用户应该告诉LwIP的作者,纠正这个bug,译注)。
- 4 -
uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
2006-9-1
- void sys_mbox_post(sys_mbox_t mbox, void *msg)
投递消息“msg”到指定的邮箱“mbox”。
- u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
阻 塞线 程 直 至邮 箱 收 到至 少 一 条消 息 。 最长 阻 塞 时间 由 timeout 参数 指定
( 与
sys_arch_sem_wait()函数类似)。msg是一个结果参数,用来保存邮箱中的消息指针(即*msg = ptr),
它的值由这个函数设置。“msg”参数有可能为空,这表明当前这条消息应该被丢弃。
返回值与 sys_arch_sem_wait()函数相同:等待的毫秒数或者 SYS_ARCH_TIMEOUT――如果时间溢出
的话。LwIP实现的函数中,有一个名称与之相似的――sys_mbox_fetch(),注意区分。
- struct sys_timeouts *sys_arch_timeouts(void)
返 回 一 个 指 向 当 前 线 程 使 用 的 sys_timeouts 结 构 的 指 针 。 LwIP 中 , 每 一 个 线 程 都 有 一
个
timeouts链表,这个链表由sys_timeout结构组成,sys_timeouts结构则保存了指向这个链表的指
针。这个函数由LwIP的超时调度程序调用,并且不能返回一个空(NULL)值。
单线程sys_arch实现中,这个函数只需简单返回一个指标即可。这个指标指向保存在sys_arch模
块中的sys_timeouts全局变量。
如果底层操作系统支持多线程并且LwIP中需要这样的功能,那么,下面的函数必须实现:
- sys_thread_t sys_thread_new(void(*thread)(void *arg), void *arg, int prio)
启动一个由函数指针 thread 指定的新线程,arg 将作为参数传递给 thread()函数,prio 指定
这个新线程的优先级。返回值为这个新线程的ID,ID和优先级由底层操作系统决定。
- sys_prot_t sys_arch_protect(void)
这是一个可选函数,它负责完成临界区域保护并返回先前的保护状态。该函数只有在小的临界
区域需要保护时才会被调用。基于 ISR 驱动的嵌入式系统可以通过禁止中断来实现这个函数。基于
任务的系统可以通过互斥量或禁止任务来实现这个函数。该函数应该支持来自于同一个任务或中断
的递归调用。换句话说,当该区域已经被保护,sys_arch_protect()函数依然能被调用。这时,函
数的返回值会通知调用者该区域已经被保护。
如果你的移植正在支持一个操作系统,sys_arch_protect()函数仅仅是一个需要。
- void sys_arch_unprotect(sys_prot_t pval)
该函数同样是一个可选函数。它的功能就是恢复受保护区域的先前保护状态,先前是受到保护
还 是 没 有 受 到 保 护 由 参 数 pval 指 定 。 它 与 sys_arch_protect()函 数 配 套 使 用 , 详 细 信 息 参
看
sys_arch_protect()函数。
该函数的说明是按照译者个人理解的意思翻译,原文讲述不是很清楚,如有错误,欢迎批评指正,译注。
--------------------------------------------------------------------------------------
OS支持的模拟层需要添加的头檔说明
-------------------------------------------------------------------------------------
- cc.h 与硬件平台及编译器相关的环境变量及数据类型声明文件(一些或许应该移到sys_arch.h
文件)。
LwIP使用的数据类型定义――u8_t, s8_t, u16_t,s16_t,u32_t,s32_t,mem_ptr_t。
- 5 -
uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
与编译器相关的LwIP结构体封装宏:
2006-9-1
PACK_STRUCT_FIELD(x)
PACK_STRUCT_STRUCT
PACK_STRUCT_BEGIN
PACK_STRUCT_END
与平台相关的调试输出:
LWIP_PLATFORM_DIAG(X)
LWIP_PLATFORM_ASSERT(x) - 故障,输出一条故障信息并放弃执行。
- 非故障,输出一条提示信息。
“轻便的(lightweight)”同步机制:
SYS_ARCH_DECL_PROTECT(x)
SYS_ARCH_PROTECT(x)
SYS_ARCH_UNPROTECT(x)
- 声明一个保护状态变量。
- 进入保护模式。
- 脱离保护模式。
如果编译器不提供 memset()函数,这个文件必须包含它的定义,或者包含(include)一个定
义它的文件。
这个文件要么包含一个本地系统(system-local)提供的头档――这个档定义了
标 准 的 *nix 错 误 编 码 , 要 么 增 加 一 条 宏 定 义 语 句 : #define LWIP_PROVIDE_ERRNO, 这 将 使 得
lwip/arch.h头文件来定义这些编码。这些编码被用于LwIP的各个部分。
--------------------------------------------------------------------------------------
- perf.h 定义了性能测量使用的宏,由LwIP调用,可以将其定义为一个空的宏。
PERF_START
PERF_STOP(x)
- 开始测量。
- 结束测量并记录结果。
--------------------------------------------------------------------------------------
- sys_arch.h sys_arch.c的头文件。
定义Arch(即整个移植所依赖的操作系统平台,译注)需要的数据类型:sys_sem_t,sys_mbox_t,
sys_thread_t,以及可选类型:sys_prot_t。
sys_mbox_t和sys_sem_t变量的NULL值定义:
SYS_MBOX_NULL
SYS_SEM_NULL
NULL
NULL
4.2 编写操作系统模拟层
4.1节已经明白的讲述了如何实现sys_arch接口,我们按照这个说明完成即可。
4.2.1 准备工作――建立文件、定义数据类型及其它
在ADS工程LwIP组中添加一个新组arch并在这个组下面建立源文件sys_arch.c,实际存 放路
径亦如此组织,如下图所示:
- 6 -
uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
2006-9-1
图4.2.1
然后在LwIP/include组同样建立一个新组arch,在arch组建立新档sys_arch.h及cc.h, 如下图
示:
图4.2.2 檔建立完成,我们先实现数据类型定义,这个实现完全按照移植说明一文进行。首先在
cc.h檔中定义常用的数据类型。这些常用数据类型不仅模拟层接口函数使用,底层协议栈实 现
亦 要 使 用 。 在 cc.h文 件 中 添 加 如 下语 句 ( 参 见 LwIPPortingTest_4活页 夹 下 的 cc.h文 件 ):
typedef unsigned char u8_t;
typedef char
s8_t;
typedef unsigned short u16_t;
s16_t;
typedef short
typedef unsigned int
u32_t;
s32_t;
typedef int
typedef u32_t
mem_ptr_t;
在上面的数据类型定义中,除了mem_ptr_t之外其它类型均很直观,不需解释。至于mem_ptr_t
- 7 -
uC/OS-II 平台下的LwIP 移植笔记――作者:焦海波
2006-9-1
为什么指定为 u32_t,而不是像它的名称所表现的一样将其指定为指针呢?其实原因很简单,
笔者在定义它时首先找到了使用它的相关语句,从这些语句中才确定这样声明。读者可找
到 mem.h 文件看看里面有关 mem_ptr_t 的使用语句就能明白怎么回事。好了,不再多说,让我
们 的准备工作接着进行。在sys_arch.h文件中添加如下语句:
typedef HANDLER sys_sem_t;
其中HANDLER是笔者本人自定义的一个宏,它是为了方便uC/OS-II的使用定义的。读者可以在 所附
源码档\src\uCOS_II\API\os_api.h中找到相关定义:
typedef OS_EVENT*
它实际上就是指向uCOS中OS_EVENT结构的指针。 声明相关数据类型的语句添加完成后,我们再把
HANDLER;
这两个档 sys_arch.h 和 cc.h 添加到
sys_arch.c档中,以使该文件里的相关函数能够使用这些新定义的数据类型:
#include
#include
这里一定要注意顺序,先包含cc.h檔,再包含sys_arch.h檔,因为sys_arch.h檔中有 些语句需要用
到cc.h檔中的类型声明。好了,准备工作已经完成,现在开始编写接口函数。
"/LwIP/include/arch/cc.h"
"/LwIP/include/arch/sys_arch.h"
4.2.2 信号量操作函数
相关函数实现读者也可直接参看sys_arch.c文件。
- sys_new_sem()
//*--------------------------------------------------------------------------
//* 函数名称 : sys_sem_new
//* 功能描述 : 建立并返回一个新的信号量
//* 入口参数 : [in] 指定信号量的初始状态
//* 出口参数 : 返回新的信号量
//*--------------------------------------------------------------------------
sys_sem_t sys_sem_new(u8_t count)
{
return OSAPISemNew(count);
}
这个函数的实现其实很简单,因为uC/OS-II提供了信号量,我们只需直接调用建立信号量
的相关函数就行了。上面的源码中OSAPISemNew是笔者本人为了统一对OS底层函数的调用重新
定义的一个接口函数,这个接口函数负责调用OS底层函数完成相应功能。在后面的模拟层接口
函数实现中,笔者使用了很多这样的 API。这些接口函数都以 OSAPI 作为函数名前缀,其实现
细节请参看\src\uCOS_II\API\os_api.c檔,本文不再赘述。
- sys_sem_signal()
//*--------------------------------------------------------------------------
//* 函数名称 : sys_sem_signal
//* 功能描述 : 发送信号
//* 入口参数 : [in] sem指定要发送的信号
//* 出口参数 : 无
//*--------------------------------------------------------------------------
- 8 -