LWIP 裸机移植

 面对lwIP协议栈庞大的代码量和错综复杂的数据结构,除了对作者Adam的顶礼膜拜外,你是否有无从下手的感觉!但反过来想想,Adam大虾辛苦十来年的成果,若是让我等小菜民几周给摸个滚瓜烂熟,这也太伤Adam的面子了。好在今天我们不看lwIp庞大的代码与复杂的数据结构,我们只看如何移植,这么一来,只需重写两个文件即可。

lwIp的作者做了大量的工作以方便像我这种懒人移植该协议栈,基本上只需修改一个配置头文件和改写3个函数即可完成lwIP的移植。要改写的3个函数都位于网络的最底层,它取决于你使用的是何种网络接口芯片,比如非常常见的RTL8201Bl,最近很火的TI的lm3s6000/8000/9000系列片上集成的网络接口模块等等。之所以要改写,是因为勤劳的lwIP作者已经写出来这三个函数的基本雏形,再次感谢Adam。要改写的函数位于lwIP-1.3.0/src/netif/ethernetif.c中,你也可以用自己更合适的网络接口名来代替“ethernetif”。另外还有一个配置头文件,叫做lwipopts.h文件,它要放在工程目录下。这个文件来源于lwIP-1.3.0/src/include/lwip/opt.h头文件,是对整个协议栈的一个配置,比如是否使用TCP/UDP/DHCP协议等等。

先来看看移植要注意的三个函数,第一个函数为ethernerif_init()函数,这个函数先是设置与协议栈有关的底层操作,指定底层接收回调函数等,接着对实际网络接口芯片进行初始化,设置硬件的工作方式,开放中断等。第二个函数为low_level_output函数,主要目的是将要发送的数据包分解成网络接口芯片能识别的书籍结构并将数据发送到链路上。第三个函数为low_level_input()函数,主要接收以太网数据并将数据打包成lwIP能识别的数据结构。

配置头文件lwipopts.h内容较多,按照具体应用进行配置。

最后要说的是lwip协议栈的cc.h文件,该文件中定义了与处理器相关的数据类型,要稍微注意一下。

 

TI Cortex M3串口转以太网例程上层应用的基础是lwIP,版本是V1.3.2 。对于lwIP,陌生的同学可以到网上查查,它是是瑞士的Adam编写的一个开源TCP/IP协议。既然串口转以太网例程的基础是lwIP,那么还是看看lwIp是如何移植到TI的Cortex M3硬件中的吧。此为分割线-------

移植概述可以参看博客的这篇文章,以下基本按照这个格式来看看具体的移植代码。http://blog.csdn.net/zhzht19861011/article/details/6615965

1.cc.h文件

这个文件主要设置lwIP内部使用的数据类型,比如u8_t、u32_t等。lwIP可以移植到32位、16位甚至是8位构架的微控制器,由于移植的硬件平台以及编译器的不同,这些数据类型是要移植者根据自己的硬件和编译器特性来自行设置的。比如int类型变量,在8位和16位控制器中多表示2字节,但在32位微处理器中却表示4个字节,若是连这些基本数据类型都没有设置正确的话,就谈不上移植了。下面看cc.h的源代码:

view plain copy to clipboard print ?
  1. #ifndef __CC_H__
  2. #define __CC_H__

  3. typedefunsignedcharu8_t;//基本数据类型设置
  4. typedefsignedchars8_t;
  5. typedefunsignedshortu16_t;
  6. typedefsignedshorts16_t;
  7. typedefunsignedlongu32_t;
  8. typedefsignedlongs32_t;
  9. typedefu32_t mem_ptr_t;

  10. #ifndef BYTE_ORDER
  11. #define BYTE_ORDER LITTLE_ENDIAN
  12. #endif

  13. #if defined(__arm__) && defined(__ARMCC_VERSION) //以下主要设置不同编译器的结构体数据的对齐,lwIP需要
  14. //
  15. // Setup PACKing macros for KEIL/RVMDK Tools
  16. //
  17. #define PACK_STRUCT_BEGIN __packed
  18. #define PACK_STRUCT_STRUCT
  19. #define PACK_STRUCT_END
  20. #define PACK_STRUCT_FIELD(x) x
  21. #elif defined (__IAR_SYSTEMS_ICC__)
  22. //
  23. // Setup PACKing macros for IAR Tools
  24. //
  25. #define PACK_STRUCT_BEGIN
  26. #define PACK_STRUCT_STRUCT
  27. #define PACK_STRUCT_END
  28. #define PACK_STRUCT_FIELD(x) x
  29. #define PACK_STRUCT_USE_INCLUDES
  30. #else
  31. //
  32. // Setup PACKing macros for GCC Tools
  33. //
  34. #define PACK_STRUCT_BEGIN
  35. #define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
  36. #define PACK_STRUCT_END
  37. #define PACK_STRUCT_FIELD(x) x
  38. #endif

  39. #ifdef DEBUG
  40. externvoid__error__(char*pcFilename, unsignedlongulLine);
  41. #define LWIP_PLATFORM_ASSERT(expr) \
  42. { \
  43. if(!(expr)) \
  44. { \
  45. __error__(__FILE__, __LINE__); \
  46. } \
  47. }
  48. #else
  49. #define LWIP_PLATFORM_ASSERT(expr)
  50. #endif

  51. #endif /* __CC_H__ */

#ifndef __CC_H__ #define __CC_H__typedef unsigned char u8_t; //基本数据类型设置 typedef signed char s8_t;typedef unsigned short u16_t; typedef signed short s16_t;typedef unsigned long u32_t; typedef signed long s32_t;typedef u32_t mem_ptr_t; #ifndef BYTE_ORDER#define BYTE_ORDER LITTLE_ENDIAN#endif #if defined(__arm__) && defined(__ARMCC_VERSION) //以下主要设置不同编译器的结构体数据的对齐,lwIP需要 // // Setup PACKing macros for KEIL/RVMDK Tools // #define PACK_STRUCT_BEGIN __packed #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #elif defined (__IAR_SYSTEMS_ICC__) // // Setup PACKing macros for IAR Tools // #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #define PACK_STRUCT_USE_INCLUDES#else // // Setup PACKing macros for GCC Tools // #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x#endif#ifdef DEBUG extern void __error__(char *pcFilename, unsigned long ulLine); #define LWIP_PLATFORM_ASSERT(expr) \ { \ if(!(expr)) \ { \ __error__(__FILE__, __LINE__); \ } \}#else #define LWIP_PLATFORM_ASSERT(expr)#endif#endif /* __CC_H__ */


2.以太网硬件初始化、与硬件密切相关的数据接收、发送函数

虽然Adam为便于lwIP协议栈的移植做了大量的工作,但因为网卡的多样性和新网卡的不断出现,Adam不可能为每一个网卡都写一个驱动。因此,与网卡硬件相关的代码就留给程序员来编写了。其实Adam在lwIP协议栈中已经写好了一个与硬件密切相关的移植代码框架,它位于lwIP-1.3.2/src/netif/ethernetif.c中。Stellaris串口转以太网移植代码也基本上是参照这个代码框架来编写的。Stellais串口转以太网模块与硬件密切相关的移植代码位于stellarisif.c中。这里面的代码主要是三部分:lwIP协议栈和以太网硬件初始化函数、lwIP协议栈将数据发送到网络接口上的输出函数以及从Stellaris以太网硬件读取数据并送给lwIP协议栈的输入函数。

2.1 lwIP协议栈和以太网硬件初始化

在移植代码stellarisif.c中,对lwIP协议栈和以太网硬件初始化的函数是:

err_t stellarisif_init(structnetif *netif)

这个函数先是设置与协议栈有关的底层操作,指定底层接收回调函数等,接着对实际网络接口芯片进行初始化,设置硬件的工作方式,开放中断等。源代码如下所示:

view plain copy to clipboard print ?
  1. /**
  2. * Should be called at the beginning of the program to set up the
  3. * network interface. It calls the function stellarisif_hwinit() to do the
  4. * actual setup of the hardware.
  5. * 此在程序开始的时候被调用,用来设置网络接口.他调用stellarisif_hwinit()函数
  6. * 来完成以太网硬件的设置.
  7. * This function should be passed as a parameter to netif_add().
  8. * 这个函数作为一个参数传递给netif_add()函数.
  9. * @param netif the lwip network interface structure for this ethernetif
  10. * @return ERR_OK if the loopif is initialized
  11. * ERR_MEM if private data couldn't be allocated
  12. * any other err_t on error
  13. */
  14. err_t
  15. stellarisif_init(structnetif *netif)
  16. {
  17. LWIP_ASSERT("netif != NULL", (netif != NULL));

  18. #if LWIP_NETIF_HOSTNAME
  19. /* Initialize interface hostname */
  20. netif->hostname ="lwip";//初始化接口主机名字
  21. #endif /* LWIP_NETIF_HOSTNAME */

  22. /*
  23. * Initialize the snmp variables and counters inside the struct netif.
  24. * The last argument should be replaced with your link speed, in units
  25. * of bits per second.
  26. */
  27. NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, 1000000);//初始化snmp变量

  28. netif->state = &stellarisif_data;//指向以太网接口的私有数据,包括pbuf数据链和MAC地址
  29. netif->name[0] = IFNAME0;
  30. netif->name[1] = IFNAME1;
  31. /* We directly use etharp_output() here to save a function call.
  32. * You can instead declare your own function an call etharp_output()
  33. * from it if you have to do some checks before sending (e.g. if link
  34. * is available...) */
  35. netif->output = etharp_output;//IP层将一包数据发往网络接口时调用此函数
  36. netif->linkoutput = stellarisif_output;//ARP模块将一包数据发往网络接口时调用此函数

  37. stellarisif_data.ethaddr = (structeth_addr *)&(netif->hwaddr[0]);//初始化MAC地址
  38. stellarisif_data.txq.qread = stellarisif_data.txq.qwrite = 0;//初始化pbuf数据链
  39. stellarisif_data.txq.overflow = 0;

  40. /* initialize the hardware */
  41. stellarisif_hwinit(netif);//初始化Stellaris以太网硬件

  42. returnERR_OK;
  43. }

/** * Should be called at the beginning of the program to set up the * network interface. It calls the function stellarisif_hwinit() to do the * actual setup of the hardware. * 此在程序开始的时候被调用,用来设置网络接口.他调用stellarisif_hwinit()函数 * 来完成以太网硬件的设置. * This function should be passed as a parameter to netif_add(). * 这个函数作为一个参数传递给netif_add()函数. * @param netif the lwip network interface structure for this ethernetif * @return ERR_OK if the loopif is initialized * ERR_MEM if private data couldn't be allocated * any other err_t on error */err_tstellarisif_init(struct netif *netif) { LWIP_ASSERT("netif != NULL", (netif != NULL));#if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ netif->hostname = "lwip"; //初始化接口主机名字#endif /* LWIP_NETIF_HOSTNAME */ /* * Initialize the snmp variables and counters inside the struct netif. * The last argument should be replaced with your link speed, in units * of bits per second. */ NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, 1000000); //初始化snmp变量 netif->state = &stellarisif_data; //指向以太网接口的私有数据,包括pbuf数据链和MAC地址 netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; /* We directly use etharp_output() here to save a function call. * You can instead declare your own function an call etharp_output() * from it if you have to do some checks before sending (e.g. if link * is available...) */ netif->output = etharp_output; //IP层将一包数据发往网络接口时调用此函数 netif->linkoutput = stellarisif_output; //ARP模块将一包数据发往网络接口时调用此函数 stellarisif_data.ethaddr = (struct eth_addr *)&(netif->hwaddr[0]); //初始化MAC地址 stellarisif_data.txq.qread = stellarisif_data.txq.qwrite = 0; //初始化pbuf数据链 stellarisif_data.txq.overflow = 0; /* initialize the hardware */ stellarisif_hwinit(netif); //初始化Stellaris以太网硬件 return ERR_OK; }

1. netif->output = etharp_output;用于将一包数据发送到网络接口,由IP层调用。这个函数最终会调用netif->linkoutput来将数据发送到网络接口。

2. netif->linkoutput = stellarisif_output;用于将一包数据发送到网络接口,有ARP模块调用。程序员需根据自己的硬件平台来编写该函数。后面会讲到该函数。

3.stellarisif_hwinit(netif):初始化以太网硬件,还是有必要看看该函数的,代码如下所示,不再单独解释,可以看注释。

view plain copy to clipboard print ?
  1. /**
  2. * In this function, the hardware should be initialized.
  3. * Called from stellarisif_init().
  4. *
  5. * @param netif the already initialized lwip network interface structure
  6. * for this ethernetif
  7. */
  8. staticvoid
  9. stellarisif_hwinit(structnetif *netif)
  10. {
  11. u32_t temp;
  12. //struct stellarisif *stellarisif = netif->state;

  13. /* 设置以太网硬件MAC地址长度 */
  14. netif->hwaddr_len = ETHARP_HWADDR_LEN;

  15. /* 设置以太网硬件地址 */
  16. EthernetMACAddrGet(ETH_BASE, &(netif->hwaddr[0]));

  17. /* 最大发送单元 */
  18. netif->mtu = 1500;

  19. /* 使能网卡的功能,允许网卡广播、ARP功能和允许硬件链路连接该网卡*/
  20. /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  21. netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

  22. /* Do whatever else is needed to initialize interface. */
  23. /* 禁止所有以太网中断 */
  24. EthernetIntDisable(ETH_BASE, (ETH_INT_PHY | ETH_INT_MDIO | ETH_INT_RXER |
  25. ETH_INT_RXOF | ETH_INT_TX | ETH_INT_TXER | ETH_INT_RX));
  26. temp = EthernetIntStatus(ETH_BASE,false);
  27. EthernetIntClear(ETH_BASE, temp);

  28. /* 初始化以太网控制器 */
  29. EthernetInitExpClk(ETH_BASE, SysCtlClockGet());

  30. /*
  31. * 配置以太网控制器正常运行.
  32. * - 使能 TX 全双工模式
  33. * - 使能 TX 填充
  34. * - 使能 TX CRC 生成
  35. * - 使能 RX 组播接收
  36. */
  37. EthernetConfigSet(ETH_BASE, (ETH_CFG_TX_DPLXEN |ETH_CFG_TX_CRCEN |
  38. ETH_CFG_TX_PADEN | ETH_CFG_RX_AMULEN));

  39. /* 使能以太网控制器的发送器和接收器 */
  40. EthernetEnable(ETH_BASE);

  41. /* 使能以太网中断 */
  42. IntEnable(INT_ETH);

  43. /* 使能以太网TX和RX数据包中断 */
  44. EthernetIntEnable(ETH_BASE, ETH_INT_RX | ETH_INT_TX);
  45. }

/** * In this function, the hardware should be initialized. * Called from stellarisif_init(). * * @param netif the already initialized lwip network interface structure * for this ethernetif */static void stellarisif_hwinit(struct netif *netif){ u32_t temp; //struct stellarisif *stellarisif = netif->state; /* 设置以太网硬件MAC地址长度 */ netif->hwaddr_len = ETHARP_HWADDR_LEN; /* 设置以太网硬件地址 */ EthernetMACAddrGet(ETH_BASE, &(netif->hwaddr[0])); /* 最大发送单元 */ netif->mtu = 1500; /* 使能网卡的功能,允许网卡广播、ARP功能和允许硬件链路连接该网卡*/ /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* Do whatever else is needed to initialize interface. */ /* 禁止所有以太网中断 */ EthernetIntDisable(ETH_BASE, (ETH_INT_PHY | ETH_INT_MDIO | ETH_INT_RXER | ETH_INT_RXOF | ETH_INT_TX | ETH_INT_TXER | ETH_INT_RX)); temp = EthernetIntStatus(ETH_BASE, false); EthernetIntClear(ETH_BASE, temp); /* 初始化以太网控制器 */ EthernetInitExpClk(ETH_BASE, SysCtlClockGet()); /* * 配置以太网控制器正常运行. * - 使能 TX 全双工模式 * - 使能 TX 填充 * - 使能 TX CRC 生成 * - 使能 RX 组播接收 */ EthernetConfigSet(ETH_BASE, (ETH_CFG_TX_DPLXEN |ETH_CFG_TX_CRCEN | ETH_CFG_TX_PADEN | ETH_CFG_RX_AMULEN)); /* 使能以太网控制器的发送器和接收器 */ EthernetEnable(ETH_BASE); /* 使能以太网中断 */ IntEnable(INT_ETH); /* 使能以太网TX和RX数据包中断 */ EthernetIntEnable(ETH_BASE, ETH_INT_RX | ETH_INT_TX); }


2.2 Stellaris以太网硬件底层数据发送函数
上面也已经讲到了,ARP模块发送数据到网络接口会调用netif->linkoutput函数;IP层发送数据到网络接口会调用netif->output函数,但IP层实际的数据发送函数任然是netif->linkoutput,在以太网初始化中,已经为linkoutput指针赋值:netif->linkoutput=stellarisif_output;因此,移植lwIP时程序员需要编写的Stellaris以太网硬件底层数据发送函数正是stellarisif_output()函数。先来看以下它的源码:

view plain copy to clipboard print ?
  1. /**
  2. * This function with either place the packet into the Stellaris transmit fifo,
  3. * or will place the packet in the interface PBUF Queue for subsequent
  4. * transmission when the transmitter becomes idle.
  5. * 这个函数要么将数据包放到Stellaris发送缓存FIFO中,要么把数据包放到PBUF队列,等待
  6. * 发送器变空之后载发送。
  7. *
  8. * @param netif the lwip network interface structure for this ethernetif
  9. * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
  10. * @return ERR_OK if the packet could be sent
  11. * an err_t value if the packet couldn't be sent
  12. *
  13. */
  14. staticerr_t
  15. stellarisif_output(structnetif *netif,structpbuf *p)
  16. {
  17. structstellarisif *stellarisif = netif->state;
  18. SYS_ARCH_DECL_PROTECT(lev);

  19. /**
  20. * This entire function must run within a "critical section" to preserve
  21. * the integrity of the transmit pbuf queue.
  22. *
  23. */
  24. SYS_ARCH_PROTECT(lev);

  25. /**
  26. * Bump the reference count on the pbuf to prevent it from being
  27. * freed till we are done with it.
  28. *
  29. */
  30. pbuf_ref(p);

  31. /**
  32. * If the transmitter is idle, and there is nothing on the queue,
  33. * send the pbuf now.
  34. *
  35. */
  36. if(PBUF_QUEUE_EMPTY(&stellarisif->txq) &&
  37. ((HWREG(ETH_BASE + MAC_O_TR) & MAC_TR_NEWTX) == 0)) {
  38. stellarisif_transmit(netif, p);
  39. }

  40. /* Otherwise place the pbuf on the transmit queue. */
  41. else{
  42. /* Add to transmit packet queue */
  43. if(!enqueue_packet(p, &(stellarisif->txq))) {
  44. /* if no room on the queue, free the pbuf reference and return error. */
  45. pbuf_free(p);
  46. SYS_ARCH_UNPROTECT(lev);
  47. return(ERR_MEM);
  48. }
  49. }

  50. /* Return to prior interrupt state and return. */
  51. SYS_ARCH_UNPROTECT(lev);
  52. returnERR_OK;
  53. }

/** * This function with either place the packet into the Stellaris transmit fifo, * or will place the packet in the interface PBUF Queue for subsequent * transmission when the transmitter becomes idle. * 这个函数要么将数据包放到Stellaris发送缓存FIFO中,要么把数据包放到PBUF队列,等待 * 发送器变空之后载发送。 * * @param netif the lwip network interface structure for this ethernetif * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type) * @return ERR_OK if the packet could be sent * an err_t value if the packet couldn't be sent * */static err_t stellarisif_output(struct netif *netif, struct pbuf *p){ struct stellarisif *stellarisif = netif->state; SYS_ARCH_DECL_PROTECT(lev); /** * This entire function must run within a "critical section" to preserve * the integrity of the transmit pbuf queue. * */ SYS_ARCH_PROTECT(lev); /** * Bump the reference count on the pbuf to prevent it from being * freed till we are done with it. * */ pbuf_ref(p); /** * If the transmitter is idle, and there is nothing on the queue, * send the pbuf now. * */ if(PBUF_QUEUE_EMPTY(&stellarisif->txq) && ((HWREG(ETH_BASE + MAC_O_TR) & MAC_TR_NEWTX) == 0)) { stellarisif_transmit(netif, p); } /* Otherwise place the pbuf on the transmit queue. */ else { /* Add to transmit packet queue */ if(!enqueue_packet(p, &(stellarisif->txq))) { /* if no room on the queue, free the pbuf reference and return error. */ pbuf_free(p); SYS_ARCH_UNPROTECT(lev); return (ERR_MEM); } } /* Return to prior interrupt state and return. */ SYS_ARCH_UNPROTECT(lev); return ERR_OK; }
1. SYS_ARCH_DECL_PROTECT(lev);、SYS_ARCH_PROTECT(lev);、SYS_ARCH_UNPROTECT(lev);由于Stellaris以太网在发送数据的时候必须处在临界区,既不能被中断打扰,因此,上面几个宏是用来关闭和打开总中断的。SYS_ARCH_DECL_PROTECT(lev)用来定义一个32位变量lev,这个变量用来保存当前中断使能信息;SYS_ARCH_PROTECT(lev)用来关闭中断;SYS_ARCH_UNPROTECT(lev)用来打开中断。

2. pbuf_ref(p);将参数结构体pbuf的ref域加一。这个域统计有多少个指针指向这个pbuf,这些指针可能是应用程序的指针、协议栈的指针或者数据链中的pbuf->next指针,只有ref为0时,才可以释放这个pbuf。关于结构体pbuf的详细介绍参见博客:http://blog.csdn.net/zhzht19861011/article/details/6591252

3. stellarisif_transmit(netif, p):发送数据。


2.3 Stellaris以太网硬件底层数据过程
当网络上一包数据到达以太网控制器后,以太网控制器会置为接收中断,在中断服务函数中,调用

stellarisif_receive函数(需程序员根据具体硬件自行编写)来接收一个数据包,再通过

ethernet_input函数分析接收到的数据包的类型,比如类型为0x8000,则为IP帧,会调用ip_input

函数来将收到的这个IP数据包送往lwIP协议栈的高层。Stellaris以太网硬件具体中断函数分析见博客

:http://blog.csdn.net/zhzht19861011/article/details/6221699

你可能感兴趣的:(LWIP 裸机移植)