lwIP TCP/IP 协议栈笔记之五: 网络接口管理 ethernetif.c & ethernetif.h 详解

目录

1. nefif 结构体

//// 1 ////

//// 2 ////

//// 3 ////

//// 4 ////

//// 5 ////

//// 6 ////

//// 7 ////

//// 8 ////

//// 9 ////

//// 10 ////

//// 11 ////

//// 12 ////

//// 13 ////

//// 14 ////

//// 15 ////

2. netif 使用

3. netif 相关的底层函数

4. ethernetif.c

4.1 ethernetif 数据结构

4.2 ethernetif_init()

4.3 low_level_init()

4.4 low_level_output()


1. nefif 结构体

网络接口(俗称网卡,如以太网接口)是硬件接口,lwIP是软件。如何实现软件与硬件的无缝连接,而且又要兼容不同的硬件接口,lwIP 使用数据结构—— struct netif 来描述一个网卡,但是由于网卡是直接与硬件打交道的,硬件不同则处理基本是不同的,所以必须由用户提供最底层接口函数,LwIP 提供统一的接口,但是底层的实现需要用户自己去完成。如网卡的初始化、网卡的收发数据。

LwIP 中的 ethernetif.c 文件即为底层接口的驱动的模版,用户为自己的网络设备实现驱动时应参照此模块做修改ethernetif.c 文件中的函数通常为与硬件打交道的底层函数,当有数据需要通过网卡接收或者发送数据的时候就会被调用,经过LwIP 协议栈内部进行处理后,从应用层就能得到数据或者可以发送数据。

简单来说,netif 是LwIP 抽象出来的网卡,LwIP 协议栈可以使用多个不同的接口,而ethernetif.c 文件则提供了netif 访问各种不同的网卡,每个网卡有不同的实现方式,用户只需要修改ethernetif.c 文件即可。

在单网卡中,这个netif 结构体只有一个;多个网卡lwIP 会将每个用netif 描述的网卡连接成一个链表(单向链表),该链表就记录每个网卡的netif。屏蔽硬件接口的差异,完成了对不同网卡的抽象,因此了解netif 结构体是移植LwIP 的关键。

/* lwIP网络接口的通用数据结构*/
struct netif {
#if !LWIP_SINGLE_NETIF
  /** 指向 netif  链表中的下一个*/
  struct netif *next;                                                        //// 1 ///
#endif

#if LWIP_IPV4
  /** 网络字节中的IP 地址、子网掩码、默认网关配置 */
  ip_addr_t ip_addr;
  ip_addr_t netmask;
  ip_addr_t gw;                                                              //// 2 ///
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
  /** Array of IPv6 addresses for this netif. */
  ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];
  /** The state of each IPv6 address (Tentative, Preferred, etc).
   * @see ip6_addr.h */
  u8_t ip6_addr_state[LWIP_IPV6_NUM_ADDRESSES];
#if LWIP_IPV6_ADDRESS_LIFETIMES
  /** Remaining valid and preferred lifetime of each IPv6 address, in seconds.
   * For valid lifetimes, the special value of IP6_ADDR_LIFE_STATIC (0)
   * indicates the address is static and has no lifetimes. */
  u32_t ip6_addr_valid_life[LWIP_IPV6_NUM_ADDRESSES];
  u32_t ip6_addr_pref_life[LWIP_IPV6_NUM_ADDRESSES];
#endif /* LWIP_IPV6_ADDRESS_LIFETIMES */
#endif /* LWIP_IPV6 */


  /** 此函数由网络设备驱动程序调用,将数据包传递到TCP/IP 协议栈。
   *  对于以太网物理层,这通常是ethernet_input() */                             //// 3 ///
  netif_input_fn input;

#if LWIP_IPV4

  /** 此函数由IP 层调用,在接口上发送数据包。通常这个功能,
   *  首先解析硬件地址,然后发送数据包。
   *  对于以太网物理层,这通常是etharp_output() */                              //// 4 ///
  netif_output_fn output;

#endif /* LWIP_IPV4 */

  /** 此函数由ethernet_output()调用,当需要在网卡上发送一个数据包时。
   *  底层硬件输出数据函数,一般是调用自定义函数low_level_output.  */
  netif_linkoutput_fn linkoutput;                                            //// 5 ///

#if LWIP_IPV6
  /** This function is called by the IPv6 module when it wants
   *  to send a packet on the interface. This function typically
   *  first resolves the hardware address, then sends the packet.
   *  For ethernet physical layer, this is usually ethip6_output() */
  netif_output_ip6_fn output_ip6;
#endif /* LWIP_IPV6 */

#if LWIP_NETIF_STATUS_CALLBACK
  /** 当netif 状态设置为up 或down 时调用此函数
   */
  netif_status_callback_fn status_callback;                                //// 6 ///
#endif /* LWIP_NETIF_STATUS_CALLBACK */

#if LWIP_NETIF_LINK_CALLBACK
  /** 当netif 状态设置为up 或down 时调用此函数
   */
  netif_status_callback_fn link_callback;                                  //// 7 ///
#endif /* LWIP_NETIF_LINK_CALLBACK */

#if LWIP_NETIF_REMOVE_CALLBACK
  /** 当netif 被删除时调用此函数 */
  netif_status_callback_fn remove_callback;                                //// 8 ///
#endif /* LWIP_NETIF_REMOVE_CALLBACK */

  /** 此字段可由设备驱动程序设置并指向设备的状态信息。
   *  主要是将网卡的某些私有数据传递给上层,用户可以自由发挥,也可以不用 */
  void *state;                                                            //// 9 ///

#ifdef netif_get_client_data
  void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];
#endif

#if LWIP_NETIF_HOSTNAME
  /* 这个netif 的主机名,NULL 也是一个有效值 */
  const char*  hostname;
#endif /* LWIP_NETIF_HOSTNAME */

#if LWIP_CHECKSUM_CTRL_PER_NETIF
  u16_t chksum_flags;
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/

  /** 最大传输单位(以字节为单位),对于以太网一般设为 1500 */
  u16_t mtu;                                                            //// 10 ///

#if LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES
  /** maximum transfer unit (in bytes), updated by RA */
  u16_t mtu6;
#endif /* LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES */

  /** 此网卡的链路层硬件地址 */
  u8_t hwaddr[NETIF_MAX_HWADDR_LEN];                                    //// 11 ///

  /** 硬件地址长度,对于以太网就是 MAC 地址长度,为6 字节 */
  u8_t hwaddr_len;                                                      //// 12 ///

  /** flags (@see @ref netif_flags) */
  /* 网卡状态信息标志位,是很重要的控制字段 
   * 它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。 */
  u8_t flags;                                                           //// 13 ///

  /** descriptive abbreviation */
  /* 字段用于保存每一个网卡的名字。用两个字符的名字来标识网络接
   * 口使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网卡
   * 表示的硬件的种类。比如蓝牙设备( bluetooth)的网卡名字可以是 bt,
   * 而 IEEE 802.11b WLAN 设备的名字就可以是wl,当然设置什么名字用户是可
   * 以自由发挥的,这并不影响用户对网卡的使用。当然,如果两个网卡
   * 具有相同的网络名字,我们就用 num 字段来区分相同类别的不同网 */ 
  char name[2];                                                        //// 14 ///

  /** 用来标示使用同种驱动类型的不同网卡 */
  u8_t num;                                                            //// 15 ///

#if LWIP_IPV6_AUTOCONFIG
  /** is this netif enabled for IPv6 autoconfiguration */
  u8_t ip6_autoconfig_enabled;
#endif /* LWIP_IPV6_AUTOCONFIG */
#if LWIP_IPV6_SEND_ROUTER_SOLICIT
  /** Number of Router Solicitation messages that remain to be sent. */
  u8_t rs_count;
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */

#if MIB2_STATS

  /** 连接类型 (from "snmp_ifType" enum from snmp_mib2.h) */
  u8_t link_type;

  /** (estimate) 连接速度 */
  u32_t link_speed;

  /** 最后一次更改的时间戳 (up/down) */
  u32_t ts;

  /** counters */
  struct stats_mib2_netif_ctrs mib2_counters;
#endif /* MIB2_STATS */

#if LWIP_IPV4 && LWIP_IGMP

  /** 可以调用此函数来添加或删除多播中的条目以太网MAC 的过滤表.*/
  netif_igmp_mac_filter_fn igmp_mac_filter;

#endif /* LWIP_IPV4 && LWIP_IGMP */
#if LWIP_IPV6 && LWIP_IPV6_MLD
  /** This function could be called to add or delete an entry in the IPv6 multicast
      filter table of the ethernet MAC. */
  netif_mld_mac_filter_fn mld_mac_filter;
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
#if LWIP_NETIF_USE_HINTS
  struct netif_hint *hints;
#endif /* LWIP_NETIF_USE_HINTS */
#if ENABLE_LOOPBACK
  /* List of packets to be queued for ourselves. */
  struct pbuf *loop_first;
  struct pbuf *loop_last;
#if LWIP_LOOPBACK_MAX_PBUFS
  u16_t loop_cnt_current;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#endif /* ENABLE_LOOPBACK */
};
  • //// 1 ////

LwIP 使用链表来管理同一设备的多个网卡。

在netif.c 文件中定义两个全局指针:struct netif *netif_list 和struct netif *netif_default,其中netif_list 就是网卡链表
指针,指向网卡链表的首节点(第一个网卡),后者表示默认情况下(有多网口时)使用哪个网卡。next 字段指向下一个netif 结构体指针,在一个设备中有多个网卡时,才使用该字段。

当 LWIP_SINGLE_NETIF == 0,即配置为单网卡时,netif_list是没有被定义的。

  • //// 2 ////

用于描述网卡的网络地址属性。

ip_addr 字段记录的是网络中的IP 地址,netmask 字段记录的是子网掩码,gw 记录的是网关地址

IP 地址必须与网卡对应,即设备拥有多少个网卡那就必须有多少个IP 地址;

子网掩码可以用来判断某个IP 地址与当前网卡是否处于同一个子网中,IP 在发送数据包的时候会选择与目标IP 地址处于同一子网的网卡来发送;

网关地址在数据包的发送、转发过程非常重要,如果要向不属于同一子网的主机(主机目标IP 地址与网卡不属于同一子网)发送一个数据包,那么LwIP 就会将数据包发送到网关中,网关设备会对该数据包进行正确的转发等。

  • //// 3 ////

input 是一个函数指针,指向一个函数,该函数由网络设备驱动程序调用,将数据包传递到TCP/IP 协议栈(IP 层)。对于以太网物理层,这通常是ethernet_input(),参数为pbuf 和netif 类型,其中pbuf 为接收到的数据包。

  • //// 4 ////

output 也是一个函数指针,指向一个函数,此函数由IP 层调用,在接口上发送数据包。

用户需要编写该函数并使output 指向它,通这个函数的处理步骤是首先解析硬件地址,然后发送数据包。

对于以太网物理层,该函数通常是etharp_output(),参数为pbuf、netif 和ip_addr 类型,其中,ipaddr 代表要将该数据包发送到的地址,但不一定是数据包最终到到达的IP 地址,比如,要发送IP 数据报到一个并不在本网络的主机上,该数据包要被发送到一个路由器上,这里的ipaddr 就是路由器IP 地址

  • //// 5 ////

linkoutput 字段和output 类似,也需要用户自己实现一个函数,但只有两个参数,它是由ARP 模块调用的,一般是自定义函数low_level_output()。当需要在网卡上发送一个数据包时,该函数会被ethernet_output()函数调用。

  • //// 6 ////

当netif 状态设置为up 或down 时,将调用此函数

  • //// 7 ////

当netif 状态设置为up 或down 时,将调用此函数

  • //// 8 ////

当netif 被删除时调用此函数

  • //// 9 ////

此字段可由设备驱动程序设置并指向设备的状态信息。

主要是将网卡的某些私有数据传递给上层,用户可以自由发挥,也可以不用。

  • //// 10 ////

最大传输单位(以字节为单位),对于以太网一般为 1500。

在IP层发送数据的时候,LwIP 会使用该字段决定是否需要对数据包进行分片处理,为什么是在IP 层进行分片处理?因为链路层不提供任何的差错处理机制,如果在网卡中接收的数据包不满足网卡自身的属性,那么网卡可能就会直接丢弃该数据包,也可能在底层进行分包发送,但是这种分包在IP 层看来是不可接受的,因为它打乱了数据的结构,所以只能由IP层进行分片处理

  • //// 11 ////

此网卡的链路层硬件地址

  • //// 12 ////

硬件地址长度,对于以太网就是 MAC 地址长度,为6 字节

  • //// 13 ////

网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。

  • //// 14 ////

name 字段用于保存每一个网卡的名字。用两个字符的名字来标识网卡使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网卡表示的硬件的种类。当然,如果两个网卡具有相同的网络名字,我们就用 num 字段来区分相同类别的不同网卡.

  • //// 15 ////

用来标识使用同种驱动类型的不同网卡。

2. netif 使用

介于netif 其意义,乃是承载网卡的相关信息,且数据的实际操作中,实际都是以指针的形式传递的,因此,要使用netif(网卡),则用于需要根据我们的网卡定义一个netif 结构体变量struct netif g_netif,我们首先要把网卡根据硬件配置相关的信息,然后挂载到netif_list 链表上才能使用,因为LwIP 是通过链表(多网卡)来管理所有的网卡。

struct netif *netif_add(struct netif *netif,
                            const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
                            void *state, netif_init_fn init, netif_input_fn input);

函数 netif_add()函数实现以下功能:

1. 初始化 网卡信息

2. 多网卡情况下,把网卡信息挂载到 netif_list链表;

函数实现流程:

a. 清空主机IP 地址、子网掩码、网关等字段信息

b. 根据传递进来的参数填写网卡state、input 等字段的相关信息

c. 调用网卡设置函数netif_set_addr()设置网卡IP 地址、子网掩码、网关等信息。

d. 通过传递进来的回调函数init()进行网卡真正的初始化操作,所以该函数是由用户实现的,对于不同网卡就使用不一样的初始化,而此处是以太网,则该回调函数一般为ethernetif_init()

e. 初始化网卡成功,则遍历当前设备拥有多少个网卡,并为当前网卡分配唯一标识num

f. 将当前网卡插入netif_list 链表中

lwIP TCP/IP 协议栈笔记之五: 网络接口管理 ethernetif.c & ethernetif.h 详解_第1张图片

总之一句话,在开始使用LwIP 协议栈的时候,我们就需要将网卡底层移植完成,才能开始使用,而移植的第一步,就是将网络进行初始化,并且设置该网卡为默认网卡,让LwIP 能通过网卡进行收发数据.

void lwip_stack_init (void)
{
	ip_addr_t ipaddr;
	ip_addr_t netmask;
	ip_addr_t gw;
	struct netif *p_netif = &g_lwip_netif;
  	
	/* Create tcp_ip stack thread */
	tcpip_init( NULL, NULL ); 

#if LWIP_DHCP
	ipaddr.addr = 0;
	netmask.addr = 0;
	gw.addr = 0;
#else
	/* Static address used */
	IP4_ADDR(&ipaddr,  g_lwipDevNetInfo.ip[0],g_lwipDevNetInfo.ip[1] ,
				  g_lwipDevNetInfo.ip[2] , g_lwipDevNetInfo.ip[3] );
	IP4_ADDR(&netmask, g_lwipDevNetInfo.netmask[0], g_lwipDevNetInfo.netmask[1],
				  g_lwipDevNetInfo.netmask[2], g_lwipDevNetInfo.netmask[3]);
	IP4_ADDR(&gw, g_lwipDevNetInfo.gateway[0], g_lwipDevNetInfo.gateway[1], 
				  g_lwipDevNetInfo.gateway[2], g_lwipDevNetInfo.gateway[3]);
#endif  
  
	/* - netif_add(struct netif *netif, struct ip_addr *ipaddr,
	struct ip_addr *netmask, struct ip_addr *gw,
	void *state, err_t (* init)(struct netif *netif),
	err_t (* input)(struct pbuf *p, struct netif *netif))

	Adds your network interface to the netif_list. Allocate a struct
	netif and pass a pointer to this structure as the first argument.
	Give pointers to cleared ip_addr structures when using DHCP,
	or fill them with sane numbers otherwise. The state pointer may be NULL.

	The init function pointer must point to a initialization function for
	your ethernet netif interface. The following code illustrates it's use.*/
	netif_add(p_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);

	/*  Registers the default network interface.*/
	netif_set_default(p_netif);

	if (netif_is_link_up(p_netif)) {
		netif_set_up(p_netif);
	} else {
		netif_set_down(p_netif)
	}
	
	/* Set the link callback function, this function is called on change of link status*/
	netif_set_link_callback(p_netif, bsp_eth_link_callback);
}

网卡的链表挂载过程,挂载多个网卡,就多次调用 netif_add 函数,新挂载的网卡会在链表的最前面, 如下图:

lwIP TCP/IP 协议栈笔记之五: 网络接口管理 ethernetif.c & ethernetif.h 详解_第2张图片lwIP TCP/IP 协议栈笔记之五: 网络接口管理 ethernetif.c & ethernetif.h 详解_第3张图片

3. netif 相关的底层函数

每个netif 接口都需要一个底层接口文件提供访问硬件的支持,而LwIP 将这种支持做成一个框架供我们参考,如ethernetif.c 文件就是实现为一个框架的形式,我们在移植的时候只需要根据实际的网卡特性完善这里面的函数即可。框架中的函数名、参数等都已经实现,我们只需往里面填充完善即可,当然,网卡的驱动与这些函数名字我们也可以进行修改,只要LwIP 内核能正确识别网卡中的功能即可,为了方便,我们还是使用LwIP 提供的框架进行移植操作,当一个设备使用了多个网卡的时候,那就需要编写多个不同的网卡驱动。与网卡驱动密切相关的函数有三个

static void low_level_init(struct netif *netif);

static err_t low_level_output(struct netif *netif, struct pbuf *p);

static struct pbuf * low_level_input(struct netif *netif);

low_level_init()为网卡初始化函数,它主要完成网卡的复位及参数初始化,根据实际的网卡属性进行配置netif 中与网卡相关的字段,例如网卡的MAC 地址、长度,最大发送单元等

low_level_output()函数为网卡的发送函数,它主要将内核的数据包发送出去,数据包采用pbuf 数据结构进行描述,该数据结构是一个比较复杂的数据结构。

low_level_input()函数为网卡的数据接收函数,该函数会接收一个数据包,为了内核易于对数据包的管理,该函数必须将接收的数据封装成pbuf 的形式.

err_t ethernetif_init(struct netif *netif);

void ethernetif_input( void * pvParameters );

ethernetif_init()函数是在上层管理网卡netif 的到时候会被调用的函数,如使用netif_add()添加网卡的时候,就会调用ethernetif_init()函数对网卡进行初始化,其实该函数的最终调用的初始化函数就是low_level_init()函数,我们目前只有一个网卡,就暂时不用对该函数进行改写,直接使用即可,它内部会将网卡的name、output、linkoutput 等字段进行初始化,这样子就能将内核与网卡无缝连接起来。

lwIP TCP/IP 协议栈笔记之五: 网络接口管理 ethernetif.c & ethernetif.h 详解_第4张图片

ethernetif_input()函数的主要作用就是调用low_level_input()函数从网卡中读取一个数据包,然后解析该数据包的类型是属于ARP 数据包还是IP 数据包,再将包递交给上层。

在无操作系统的时候ethernetif_input()就是一个可以直接使用的函数,已经无需我们自己去修改,内核会周期性处理该接收函数。

在多线程操作系统的时候,我们一般会将其改写成一个线程的形式,可以周期性去调用low_level_input()网卡接收函数;也可以使用中断的形式去处理,当这个线程将在尚未接收到数据包的时候,处于阻塞状态,当收到数据包的时候,中断利用操作系统的IPC 通信机制来唤醒线程去处理接收到的数据包,并将数据包递交上层,这样子的效率会更加高效,事实上我们也是这样子处理的。

4. ethernetif.c

4.1 ethernetif 数据结构

在ethernetif.c 文件的开始时,就定义了一个ethernetif 数据结构

struct ethernetif {
  struct eth_addr *ethaddr;
  /* Add whatever per-interface state that is needed here. */
};

ethernetif 数据结构用来描述底层硬件设备的一些私有信息,如MAC 地址等,该结构体唯一不可或缺的是MAC 地址,它是LwIP 用于相应ARP 查询的核心数据。用户可以对该结构进行添加其他的网卡描述信息,如果没有特殊需要,就不用添加其他成员数据,该数据结构在初始化的时候,会通过netif 的state 成员变量将这些硬件的私有信息传递给上层。

4.2 ethernetif_init()

该函数是直接拿来用即可,如果没有特别的需求,基本不需要怎么修改它,它是LwIP中默认的网卡初始化函数,内部封装了low_level_init()函数。

/* 该函数应作为参数传递给netif_add() */
err_t
ethernetif_init(struct netif *netif)
{
  struct ethernetif *ethernetif;

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

  ethernetif = mem_malloc(sizeof(struct ethernetif));
  if (ethernetif == NULL) {
    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
    return ERR_MEM;
  }

#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.
   */
  MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);

  /* 通过netif 的state 成员变量将ethernetif 结构传递给上层 */  
  netif->state = ethernetif;
  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...) */
#if LWIP_IPV4
  netif->output = etharp_output;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
  netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
  netif->linkoutput = low_level_output;

  ethernetif->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]);

  /* initialize the hardware */
  low_level_init(netif);

  return ERR_OK;
}

4.3 low_level_init()

该函数主要是根据实际情况对网卡进行一系列的初始化工作,例如:初始化MAC 地址、长度,设置最大传输包的大小,设置网卡的属性字段,支持广播、多播、ARP 等功能,如果是使用操作系统的话,还需要建立接收数据、发送数据的任务以及一些需要的消息队列、信号量等,此处讲解的是裸机底层驱动的编写.

static void low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
  int i; 
#endif
  // 初始化 bsp_eth 
  bsp_eth_init(); 

  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  netif->hwaddr[0] =  g_lwipDevNetInfo.mac[0];
  netif->hwaddr[1] =  g_lwipDevNetInfo.mac[1];
  netif->hwaddr[2] =  g_lwipDevNetInfo.mac[2];
  netif->hwaddr[3] =  g_lwipDevNetInfo.mac[3];
  netif->hwaddr[4] =  g_lwipDevNetInfo.mac[4];
  netif->hwaddr[5] =  g_lwipDevNetInfo.mac[5];
  
  /* initialize MAC address in ethernet MAC */ 
  ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); 

  /* maximum transfer unit */
  netif->mtu = 1500;

  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;

  /* Initialize Tx Descriptors list: Chain Mode */
  ETH_DMATxDescChainInit(DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
  /* Initialize Rx Descriptors list: Chain Mode  */
  ETH_DMARxDescChainInit(DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

#ifdef CHECKSUM_BY_HARDWARE
  /* Enable the TCP, UDP and ICMP checksum insertion for the Tx frames */
  for(i=0; i

4.4 low_level_output()

该功能应该实际传输数据包。 数据包包含在传递给函数的pbuf中。函数在ethernetif_init()关联。此处讲解的是裸机底层驱动的编写.

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  err_t errval;
  struct pbuf *q;
  u8 *buffer =  (u8 *)(DMATxDescToSet->Buffer1Addr);
  __IO ETH_DMADESCTypeDef *DmaTxDesc;
  uint16_t framelength = 0;
  uint32_t bufferoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t payloadoffset = 0;

  DmaTxDesc = DMATxDescToSet;
  bufferoffset = 0;

  /* copy frame from pbufs to driver buffers */
  for(q = p; q != NULL; q = q->next)
    {
      /* Is this buffer available? If not, goto error */
      if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
      {
        errval = ERR_BUF;
        goto error;
      }

      /* Get bytes in current lwIP buffer */
      byteslefttocopy = q->len;
      payloadoffset = 0;

      /* Check if the length of data to copy is bigger than Tx buffer size*/
      while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
      {
        /* Copy data to Tx buffer*/
        memcpy( (u8_t*)((u8_t*)buffer + bufferoffset), (u8_t*)((u8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );

        /* Point to next descriptor */
        DmaTxDesc = (ETH_DMADESCTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);

        /* Check if the buffer is available */
        if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
        {
          errval = ERR_USE;
          goto error;
        }

        buffer = (u8 *)(DmaTxDesc->Buffer1Addr);

        byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
        framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }

      /* Copy the remaining bytes */
      memcpy( (u8_t*)((u8_t*)buffer + bufferoffset), (u8_t*)((u8_t*)q->payload + payloadoffset), byteslefttocopy );
      bufferoffset = bufferoffset + byteslefttocopy;
      framelength = framelength + byteslefttocopy;
    }
  
  /* Note: padding and CRC for transmitted frame 
     are automatically inserted by DMA */

  /* Prepare transmit descriptors to give to DMA*/ 
  ETH_Prepare_Transmit_Descriptors(framelength);

  errval = ERR_OK;

error:
  
  /* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
  if ((ETH->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
  {
    /* Clear TUS ETHERNET DMA flag */
    ETH->DMASR = ETH_DMASR_TUS;

    /* Resume DMA transmission*/
    ETH->DMATPDR = 0;
  }
  return errval;
}

 

你可能感兴趣的:(嵌入式开发,LWIP,TCP/IP)