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 協議棧的時候,我們就需要將網卡底層移植完成,才能開始使用,而移植的第一步,就是將網絡進行初始化,並且設置該網卡爲默認網卡,讓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, &ethernetif_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 函數,新掛載的網卡會在鏈表的最前面, 如下圖:

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 等字段進行初始化,這樣子就能將內核與網卡無縫連接起來。

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<ETH_TXBUFNB; i++)
    {
      ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
    }
#endif

   /* Note: TCP, UDP, ICMP checksum checking for received frame are enabled in DMA config */

  /* Enable MAC and DMA transmission and reception */
  ETH_Start();

}

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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章