# MS-RTOS 网络设备驱动模型

本章将介绍 MS-RTOS 网络设备驱动模型。

MS-RTOS 集成了 lwIP 轻量级 TCP/IP 协议栈,所以需要参照 lwIP 的以太网设备驱动规范来编写 MS-RTOS 上的以太网设备驱动。BSP 需要链接 libnet_lwip.a 静态库,即 bspxxx.mkLOCAL_DEPEND_LIB 需要添加 -lnet_lwip。同时,MS-RTOS 也支持 ESP 无线网络连接。

# 1. 网络基础知识

1.1 以太网

网口由CPU、MAC和PHY三部分组成。DMA控制器通常属于CPU的一部分,用虚线放在这里是为了表示DMA控制器可能会参与到网口数据传输中。

drv_net_ethernet

对于上述的三部分,并不一定都是独立的芯片,根据组合形式,可分为下列几种类型:

  1. CPU集成MAC与PHY;
  2. CPU集成MAC,PHY采用独立芯片;
  3. CPU不集成MAC与PHY,MAC与PHY采用集成芯片;

本例中选用方案二做进一步说明,因为CPU总线接口很常见,通常都会做成可以像访问内存一样去访问,没必要拿出来说,而Mac与PHY之间的MII接口则需要多做些说明。下图是采用方案二的网口结构图。虚框表示CPU,MAC集成在CPU中。PHY芯片通过MII接口与CPU上的Mac连接。

drv_net_ethernet_arch

在软件上对网口的操作通常分为下面几步:

  1. 为数据收发分配内存;
  2. 初始化MAC寄存器;
  3. 初始化PHY寄存器(通过MIIM);
  4. 启动收发;

1.2 WIFI (ESP8266)

ESP8266是ai-thinker公司推出的一款无线WIFI模块,专为移动设备,可穿戴电子产品和物联应用设计,可以通过AT指令配置,和单片机上的串口进行通信,利用WIFI进行数据传输。ESP_XXXXXX,后面的数字是MAC地址后几位。ESP8266 可以用来做串口透传,PWM 调控,远程控制开关:控制插座、开关、电器等。

drv_net_esp8266

ESP8266有几种不同的使用方式,适用于不同水平的开发工作者。

  • 使用AT指令进行操作:这是最常见的方式,也是最简单是一种方式。无需编程,使用PC端的串口助手配合简单的指令就可以实现,也可以配合单片机发送指令使用。
  • LUA语言编程:这是一种单独8266编程的方式,可以不依靠单片机和串口调试软件,直接把程序编写到8266内部。
  • Arduino 开发环境编程:这个接触过Arduino的都会比较熟悉。可以直接在Arduino ide的环境下使用Arduino的开发方式进行开发。

ESP8266共有三种工作模式,分别是Station模式【客户端模式】,AP模式【接入点模式】,AP+Station模式【混合模式】。ESP8266出厂默认是第三种模式。其中STA 模式:ESP8266 模块通过路由器连接互联网,手机或电脑通过互联网实现对设备的远程控制。AP 模式:ESP8266 模块作为热点,手机或电脑直接与模块连接,实现局域网无线控制。STA+AP 模式:两种模式的共存模式,即可以通过互联网控制可实现无缝切换,方便操作。

透传即是透明传送,是指传送网络无论传输业务如何,只负责将需要传送的业务传送到目的节点,同时保证传输的质量即可,而不对传输的业务进行处理。在数据的传输过程中,这组数据不发生任何形式的改变,即不竭断,不分组,不编码,不加密,不混淆等等,仿佛传输过程是透明的一样,原封不动地到了最终接收者手里。 透传模式的要求:

  • 透传模式只能在单链接模式下开启;
  • 模块开启服务器模式时,必须开启多链接模式,所以只能作为单链接模式下的客户端。

透传与非透传的区别:开启透传模式,可以连续的发送数据,而非透传模式下,每次发送数据前都需要发送相关的发送数据的AT指令。

# 2. 网络子系统框架

下图描绘了注册网络设备驱动和应用层通过 socket 接口访问网络设备的部分 IO 流程,仅为示意图:

drv_net_msrtos_lwip

在 MS-RTOS 上适配新的网络类型时,需要实现/填充 ms_net_impl_t 结构体,即相当于实例化一个网络对象,然后调用 ms_net_impl_register(&ms_xxx_net_impl) 将该类型网络注册到 MS-RTOS 中。同时,还需要实现/填充 ms_io_driver_t 结构体,该结构体包含对 socket 进行 IO 操作的支持,然后调用 ms_io_driver_register(&ms_xxx_socket_drv)

(1)lwIP 协议栈初始化

BSP 需要调用 ms_lwip_net_init 函数初始化 lwIP 协议栈:

// 初始化 lwIP 协议栈
ms_err_t ms_lwip_net_init(void (*init_done_callback)(ms_ptr_t arg), ms_ptr_t arg);

(2)网络接口初始化

BSP 还需要完成网络接口的初始化及添加:

static void stm32_netif_init(void)
{
    static struct netif gnetif;
    ip4_addr_t ipaddr;
    ip4_addr_t netmask;
    ip4_addr_t gw;

    ip4addr_aton("192.168.1.11", &ipaddr);
    ip4addr_aton("255.255.255.0", &netmask);
    ip4addr_aton("192.168.1.1", &gw);

    netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);

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

    if (netif_is_link_up(&gnetif)) {
        /*
         * When the netif is fully configured this function must be called.
         */
        netif_set_up(&gnetif);
    } else {
        /*
         * When the netif link is down this function must be called
         */
        netif_set_down(&gnetif);
    }
}

(3)LwIP 接收数据的流程

网卡是怎么接收数据的,无需多说,基本就是开发板上eth接收完数据后产生一个中断,然后释放一个信号量通知网卡接收线程去处理这些接收的数据,然后将这些数据封装成消息,投递到tcpip_mbox邮箱中,LwIP内核线程得到这个消息,就对消息进行解析,根据消息中数据包类型进行处理,实际上是调用ethernet_input()函数决定是否递交到IP层,如果是ARP包,内核就不会递交给IP层,而是更新ARP缓存表,对于IP数据包则递交给IP层去处理,这就是一个数据从网卡到内核的过程,具体见下图:

drv_net_data_flow

# 3. 网络设备驱动

STM32 以太网设备驱动示例,仅作为参考:

/* Includes ------------------------------------------------------------------*/
#include "stm32f7xx_hal.h"
#include "lwip/opt.h"
#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include "netif/etharp.h"
#include "stm32_drv_netif.h"
#include <string.h>

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* The time to block waiting for input. */
#define TIME_WAITING_FOR_INPUT                 ( MS_TIMEOUT_FOREVER )
/* Stack size of the interface thread */
#define INTERFACE_THREAD_STACK_SIZE            ( 512 )

/* Define those to better describe your network interface. */
#define IFNAME0 's'
#define IFNAME1 't'

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/*
  @Note: The DMARxDscrTab and DMATxDscrTab must be declared in a device non cacheable memory region
         In this example they are declared in the first 256 Byte of SRAM2 memory, so this
         memory region is configured by MPU as a device memory.

         In this example the ETH buffers are located in the SRAM2 with MPU configured as normal 
         not cacheable memory.   
         
         Please refer to MPU_Config() in main.c file.
 */
#if defined ( __CC_ARM   )
ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB] __attribute__((at(0x2004C000)));/* Ethernet Rx DMA Descriptors */

ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB] __attribute__((at(0x2004C0F0)));/* Ethernet Tx DMA Descriptors */

uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__((at(0x2004C1FE))); /* Ethernet Receive Buffers */

uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __attribute__((at(0x2004E0FF))); /* Ethernet Transmit Buffers */

#elif defined ( __ICCARM__ ) /*!< IAR Compiler */
  #pragma data_alignment=4 

#pragma location=0x2004C000
__no_init ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx DMA Descriptors */

#pragma location=0x2004C080
__no_init ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptors */

#pragma location=0x2004C100
__no_init uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffers */

#pragma location=0x2004D8D0
__no_init uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffers */

#elif defined ( __GNUC__ ) /*!< GNU Compiler */

ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB] __attribute__((section(".RxDescripSection")));/* Ethernet Rx DMA Descriptors */

ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB] __attribute__((section(".TxDescripSection")));/* Ethernet Tx DMA Descriptors */

uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__((section(".RxarraySection"))); /* Ethernet Receive Buffers */

uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __attribute__((section(".TxarraySection"))); /* Ethernet Transmit Buffers */

#endif

/* Semaphore to signal incoming packets */
static ms_handle_t s_xSemaphore = MS_HANDLE_INVALID;

static ms_handle_t s_xThread = MS_HANDLE_INVALID;

/* Global Ethernet handle*/
ETH_HandleTypeDef EthHandle;

/* Private function prototypes -----------------------------------------------*/
static void ethernetif_input( void const * argument );

/* Private functions ---------------------------------------------------------*/
/*******************************************************************************
                       Ethernet MSP Routines
*******************************************************************************/
/**
  * @brief  Initializes the ETH MSP.
  * @param  heth: ETH handle
  * @retval None
  */
void HAL_ETH_MspInit(ETH_HandleTypeDef *heth)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  
  /* Enable GPIOs clocks */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();

/* Ethernet pins configuration ************************************************/
  /*
        RMII_REF_CLK ----------------------> PA1
        RMII_MDIO -------------------------> PA2
        RMII_MDC --------------------------> PC1
        RMII_MII_CRS_DV -------------------> PA7
        RMII_MII_RXD0 ---------------------> PC4
        RMII_MII_RXD1 ---------------------> PC5
        RMII_MII_RXER ---------------------> PG2
        RMII_MII_TX_EN --------------------> PG11
        RMII_MII_TXD0 ---------------------> PG13
        RMII_MII_TXD1 ---------------------> PG14
  */

  /* Configure PA1, PA2 and PA7 */
  GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStructure.Pull = GPIO_NOPULL; 
  GPIO_InitStructure.Alternate = GPIO_AF11_ETH;
  GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
  
  /* Configure PC1, PC4 and PC5 */
  GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);

  /* Configure PG2, PG11, PG13 and PG14 */
  GPIO_InitStructure.Pin =  GPIO_PIN_2 | GPIO_PIN_11 | GPIO_PIN_13 | GPIO_PIN_14;
  HAL_GPIO_Init(GPIOG, &GPIO_InitStructure);
  
  /* Enable the Ethernet global Interrupt */
  HAL_NVIC_SetPriority(ETH_IRQn, 0x7, 0);
  HAL_NVIC_EnableIRQ(ETH_IRQn);
  
  /* Enable ETHERNET clock  */
  __HAL_RCC_ETH_CLK_ENABLE();
}

/**
  * @brief  This function handles Ethernet interrupt request.
  * @param  None
  * @retval None
  */
void ETH_IRQHandler(void)
{
  (void)ms_int_enter();

  HAL_ETH_IRQHandler(&EthHandle);

  (void)ms_int_exit();
}

/**
  * @brief  Ethernet Rx Transfer completed callback
  * @param  heth: ETH handle
  * @retval None
  */
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
  ms_semb_post(s_xSemaphore);
}

/*******************************************************************************
                       LL Driver Interface ( LwIP stack --> ETH) 
*******************************************************************************/
/**
  * @brief In this function, the hardware should be initialized.
  * Called from ethernetif_init().
  *
  * @param netif the already initialized lwip network interface structure
  *        for this ethernetif
  */
static void low_level_init(struct netif *netif)
{
  uint8_t macaddress[6]= { MAC_ADDR0, MAC_ADDR1, MAC_ADDR2, MAC_ADDR3, MAC_ADDR4, MAC_ADDR5 };
  
  EthHandle.Instance = ETH;  
  EthHandle.Init.MACAddr = macaddress;
  EthHandle.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
  EthHandle.Init.Speed = ETH_SPEED_100M;
  EthHandle.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
  EthHandle.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
  EthHandle.Init.RxMode = ETH_RXINTERRUPT_MODE;
  EthHandle.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
  EthHandle.Init.PhyAddress = LAN8742A_PHY_ADDRESS;
  
  /* configure ethernet peripheral (GPIOs, clocks, MAC, DMA) */
  if (HAL_ETH_Init(&EthHandle) == HAL_OK)
  {
    /* Set netif link flag */
    netif->flags |= NETIF_FLAG_LINK_UP;
  }
  
  /* Initialize Tx Descriptors list: Chain Mode */
  HAL_ETH_DMATxDescListInit(&EthHandle, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
     
  /* Initialize Rx Descriptors list: Chain Mode  */
  HAL_ETH_DMARxDescListInit(&EthHandle, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
  
  /* set netif MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set netif MAC hardware address */
  netif->hwaddr[0] =  MAC_ADDR0;
  netif->hwaddr[1] =  MAC_ADDR1;
  netif->hwaddr[2] =  MAC_ADDR2;
  netif->hwaddr[3] =  MAC_ADDR3;
  netif->hwaddr[4] =  MAC_ADDR4;
  netif->hwaddr[5] =  MAC_ADDR5;

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

  /* Accept broadcast address and ARP traffic */
  netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;

  /* create a binary semaphore used for informing ethernetif of frame reception */
  ms_semb_create("netif_semb", MS_FALSE, MS_WAIT_TYPE_PRIO, &s_xSemaphore);

  /* create the task that handles the ETH_MAC */
  ms_thread_create("t_netif", (ms_thread_entry_t)ethernetif_input, netif,
                   INTERFACE_THREAD_STACK_SIZE, TCPIP_THREAD_PRIO, TCPIP_THREAD_TIME_SLICE,
                   MS_THREAD_OPT_SUPER, &s_xThread);

  /* Enable MAC and DMA transmission and reception */
  HAL_ETH_Start(&EthHandle);
}


/**
  * @brief This function should do the actual transmission of the packet. The packet is
  * contained in the pbuf that is passed to the function. This pbuf
  * might be chained.
  *
  * @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
  *
  * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
  *       strange results. You might consider waiting for space in the DMA queue
  *       to become available since the stack doesn't retry to send a packet
  *       dropped because of memory failure (except for the TCP timers).
  */
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  err_t errval;
  struct pbuf *q;
  uint8_t *buffer = (uint8_t *)(EthHandle.TxDesc->Buffer1Addr);
  __IO ETH_DMADescTypeDef *DmaTxDesc;
  uint32_t framelength = 0;
  uint32_t bufferoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t payloadoffset = 0;

  DmaTxDesc = EthHandle.TxDesc;
  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) != (uint32_t)RESET)
    {
      errval = ERR_USE;
      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( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_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) != (uint32_t)RESET)
      {
        errval = ERR_USE;
        goto error;
      }
      
      buffer = (uint8_t *)(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( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
    bufferoffset = bufferoffset + byteslefttocopy;
    framelength = framelength + byteslefttocopy;
  }
 
  /* Prepare transmit descriptors to give to DMA */ 
  HAL_ETH_TransmitFrame(&EthHandle, framelength);
  
  errval = ERR_OK;
  
error:
  
  /* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
  if ((EthHandle.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
  {
    /* Clear TUS ETHERNET DMA flag */
    EthHandle.Instance->DMASR = ETH_DMASR_TUS;
    
    /* Resume DMA transmission*/
    EthHandle.Instance->DMATPDR = 0;
  }
  return errval;
}

/**
  * @brief Should allocate a pbuf and transfer the bytes of the incoming
  * packet from the interface into the pbuf.
  *
  * @param netif the lwip network interface structure for this ethernetif
  * @return a pbuf filled with the received packet (including MAC header)
  *         NULL on memory error
  */
static struct pbuf * low_level_input(struct netif *netif)
{
  struct pbuf *p = NULL, *q = NULL;
  uint16_t len = 0;
  uint8_t *buffer;
  __IO ETH_DMADescTypeDef *dmarxdesc;
  uint32_t bufferoffset = 0;
  uint32_t payloadoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t i=0;
  
  /* get received frame */
  if(HAL_ETH_GetReceivedFrame_IT(&EthHandle) != HAL_OK)
    return NULL;
  
  /* Obtain the size of the packet and put it into the "len" variable. */
  len = EthHandle.RxFrameInfos.length;
  buffer = (uint8_t *)EthHandle.RxFrameInfos.buffer;
  
  if (len > 0)
  {
    /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  }
  
  if (p != NULL)
  {
    dmarxdesc = EthHandle.RxFrameInfos.FSRxDesc;
    bufferoffset = 0;
    
    for(q = p; q != NULL; q = q->next)
    {
      byteslefttocopy = q->len;
      payloadoffset = 0;
      
      /* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size */
      while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
      {
        /* Copy data to pbuf */
        memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
        
        /* Point to next descriptor */
        dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
        buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
        
        byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }
      
      /* Copy remaining data in pbuf */
      memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
      bufferoffset = bufferoffset + byteslefttocopy;
    }
  }
    
  /* Release descriptors to DMA */
  /* Point to first descriptor */
  dmarxdesc = EthHandle.RxFrameInfos.FSRxDesc;
  /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
  for (i=0; i< EthHandle.RxFrameInfos.SegCount; i++)
  {  
    dmarxdesc->Status |= ETH_DMARXDESC_OWN;
    dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
  }
    
  /* Clear Segment_Count */
  EthHandle.RxFrameInfos.SegCount =0;
  
  /* When Rx Buffer unavailable flag is set: clear it and resume reception */
  if ((EthHandle.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)  
  {
    /* Clear RBUS ETHERNET DMA flag */
    EthHandle.Instance->DMASR = ETH_DMASR_RBUS;
    /* Resume DMA reception */
    EthHandle.Instance->DMARPDR = 0;
  }
  return p;
}

/**
  * @brief This function is the ethernetif_input task, it is processed when a packet 
  * is ready to be read from the interface. It uses the function low_level_input() 
  * that should handle the actual reception of bytes from the network
  * interface. Then the type of the received packet is determined and
  * the appropriate input function is called.
  *
  * @param netif the lwip network interface structure for this ethernetif
  */
void ethernetif_input( void const * argument )
{
  struct pbuf *p;
  struct netif *netif = (struct netif *) argument;
  
  for( ;; )
  {
    if (ms_semb_wait( s_xSemaphore, TIME_WAITING_FOR_INPUT) == MS_ERR_NONE)
    {
      do
      {
        p = low_level_input( netif );
        if (p != NULL)
        {
          if (netif->input( p, netif) != ERR_OK )
          {
            pbuf_free(p);
          }
        }
      }while(p!=NULL);
    }
  }
}

/**
  * @brief Should be called at the beginning of the program to set up the
  * network interface. It calls the function low_level_init() to do the
  * actual setup of the hardware.
  *
  * This function should be passed as a parameter to 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_t ethernetif_init(struct netif *netif)
{
  LWIP_ASSERT("netif != NULL", (netif != NULL));

#if LWIP_NETIF_HOSTNAME
  /* Initialize interface hostname */
  netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */

  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;
  netif->linkoutput = low_level_output;

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

  return ERR_OK;
}

# 4. 网络应用程序

创建 socket 套接字,获取网卡 IP 和 MAC 地址等信息:

#include <ms_rtos.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>

int show_mac_addr(int sockfd)
{
    int ret;
    struct ifreq ifreq;

    if (sockfd < 0) {
        return  (-1);
    }

    bzero(&ifreq, sizeof(struct ifreq));

    /*
     * Get MAC address
     */
    ret = ioctl(sockfd, SIOCGIFHWADDR, &ifreq);
    if (ret < 0) {
        return  (-1);
    }

    ms_printf("MAC addr: %02x:%02x:%02x:%02x:%02x:%02x\n",
              (ms_uint8_t)ifreq.ifr_hwaddr.sa_data[0],
              (ms_uint8_t)ifreq.ifr_hwaddr.sa_data[1],
              (ms_uint8_t)ifreq.ifr_hwaddr.sa_data[2],
              (ms_uint8_t)ifreq.ifr_hwaddr.sa_data[3],
              (ms_uint8_t)ifreq.ifr_hwaddr.sa_data[4],
              (ms_uint8_t)ifreq.ifr_hwaddr.sa_data[5]);

    return  (0);
}

int show_ip_addr(int sockfd)
{
    int ret;
    struct ifreq ifreq;
    struct sockaddr_in *psockaddrin;
    char ip[sizeof("255.255.255.255")];

    if (sockfd < 0) {
        return  (-1);
    }

    bzero(&ifreq, sizeof(struct ifreq));
    psockaddrin = (struct sockaddr_in *)&(ifreq.ifr_addr);

    /*
     * Get IP address
     */
    ret = ioctl(sockfd, SIOCGIFADDR, &ifreq);
    if (ret < 0) {
        return  (-1);
    }

    inet_ntoa_r(psockaddrin->sin_addr, ip, sizeof(ip));
    ms_printf("IP addr: %s\n", ip);

    return  (0);
}

int main (int argc, char **argv)
{
    int ret;
    int sockfd;

    sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sockfd < 0) {
        ms_printf("Failed to create socket, errno = %d!\n", errno);
        return  (-1);
    }

    ret = show_mac_addr(sockfd);
    if (ret < 0) {
        ms_printf("Failed to show MAC address!\n");
        close(sockfd);
        return  (-1);
    }

    ret = show_ip_addr(sockfd);
    if (ret < 0) {
        ms_printf("Failed to show IP address!\n");
        close(sockfd);
        return  (-1);
    }

    close(sockfd);

    return  (0);
}

# 附录(Appendix)

1. Reference

https://www.cnblogs.com/jason-lu/p/3198424.html

https://www.cnblogs.com/jason-lu/articles/3196096.html

https://www.cnblogs.com/jason-lu/p/3195473.html

https://blog.csdn.net/xh870189248/article/details/77985541

https://www.cnblogs.com/kerwincui/p/12872828.html

https://blog.csdn.net/flyleaf91/article/details/52325542

2. FAQ

(暂无)