文章

lwIP no-sys示例流程解析

本文将网络接口(network interface)简称为网口

还有文章可供参考:《LwIP应用开发实战指南》

no-sys 无系统示例

中断部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 以太网帧接收中断处理程序
void eth_mac_irq()
{
  /* Service MAC IRQ here */

  // 假设收到了eth_data_count长度的数据
  // 从lwip内的内存池中分配空间来存放该数据
  /* Allocate pbuf from pool (avoid using heap in interrupts) */
  struct pbuf *p = pbuf_alloc(PBUF_RAW, eth_data_count, PBUF_POOL);

  if (p != NULL)
  {
    /* Copy ethernet frame into pbuf */
    pbuf_take(p, eth_data, eth_data_count);

    /* Put in a queue which is processed in main loop */
    // 将该帧置于queue帧队列中
    if (!queue_try_put(&queue, p))
    {
      /* queue is full -> packet loss */
      pbuf_free(p);
    }
  }
}

应用部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
static void
netif_status_callback(struct netif *netif)
{
  printf("netif status changed %s\n", ip4addr_ntoa(netif_ip4_addr(netif)));
}

// 初始化配置
static err_t
netif_init(struct netif *netif)
{
  netif->linkoutput = netif_output;
  netif->output = etharp_output;
  netif->output_ip6 = ethip6_output;
  netif->mtu = ETHERNET_MTU;
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6;
  MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, 100000000);

  SMEMCPY(netif->hwaddr, your_mac_address_goes_here, ETH_HWADDR_LEN);
  netif->hwaddr_len = ETH_HWADDR_LEN;

  return ERR_OK;
}

void main(void)
{
  struct netif netif;
  // lwip初始化
  lwip_init();

  // 绑定网口(网络接口),需要ipv4地址,子网掩码,网关,网口初始化回调函数,上层输入回调函数
  netif_add(&netif, IP4_ADDR_ANY, IP4_ADDR_ANY, IP4_ADDR_ANY, NULL, netif_init, netif_input);
  netif.name[0] = 'e';
  netif.name[1] = '0';
  netif_create_ip6_linklocal_address(&netif, 1);
  // 注册状态改变回调,包括网口link up/down,ip地址变动等都会触发回调
  netif_set_status_callback(&netif, netif_status_callback);
  // 将该网口设置为默认网口
  netif_set_default(&netif);
  // 启用网口
  netif_set_up(&netif);

  /* Start DHCP and HTTPD */
  dhcp_start(&netif);
  // 启动httpd服务
  httpd_init();

  while (1)
  {
    /* Check link state, e.g. via MDIO communication with PHY */
    // 同步一次物理链路状态
    if (link_state_changed())
    {
      if (link_is_up())
      {
        netif_set_link_up(&netif);
      }
      else
      {
        netif_set_link_down(&netif);
      }
    }

    /* Check for received frames, feed them to lwIP */
    // 为临界资源queue上锁
    lock_interrupts();
    // 检查链路层收到的帧队列,并取出一条
    struct pbuf *p = queue_try_get(&queue);
    unlock_interrupts();

    if (p != NULL)
    {
      // 增加链路通信统计计数
      LINK_STATS_INC(link.recv);

      /* Update SNMP stats (only if you use SNMP) */
      MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);
      int unicast = ((p->payload[0] & 0x01) == 0);
      if (unicast)
      {
        MIB2_STATS_NETIF_INC(netif, ifinucastpkts);
      }
      else
      {
        MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);
      }

      if (netif.input(p, &netif) != ERR_OK)
      {
        pbuf_free(p);
      }
    }

    /* Cyclic lwIP timers check */
    // lwIP内部定时器,用于处理内部定时任务
    sys_check_timeouts();

    /* your application goes here */
    // 如果原来的无系统应用是大循环式的,可以直接内嵌到这
  }
}

接收网口提供的帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
err_t
netif_input(struct pbuf *p, struct netif *inp)
{
  LWIP_ASSERT_CORE_LOCKED();

  LWIP_ASSERT("netif_input: invalid pbuf", p != NULL);
  LWIP_ASSERT("netif_input: invalid netif", inp != NULL);

// 如果启用了以太网
#if LWIP_ETHERNET
  // 如果当前网卡支持以太网和以太网ARP帧,
  // 那么其输出就是以太网类型的帧,使用以太网处理程序
  if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
    return ethernet_input(p, inp);
  } else
#endif /* LWIP_ETHERNET */
    // 否则就直接是网络层ip协议报文
    return ip_input(p, inp);
}

以太网中常用的 Ethernet Ⅱ 帧格式:

1
2
3
4
5
6
|  6B   |   6B  |  2B  | 46-1500B | 4B  |
|DST.MAC|SRC.MAC| TYPE |  DATA    | FCS |
                /       \
               /         \
              | 0x0800 IP |
              | 0x0806 ARP|

以太网链路层处理程序

View Code ```c /** * @ingroup lwip_nosys * Process received ethernet frames. Using this function instead of directly * calling ip_input and passing ARP frames through etharp in ethernetif_input, * the ARP cache is protected from concurrent access.
* Don't call directly, pass to netif_add() and call netif->input(). * * @param p the received packet, p->payload pointing to the ethernet header * @param netif the network interface on which the packet was received * * @see LWIP_HOOK_UNKNOWN_ETH_PROTOCOL * @see ETHARP_SUPPORT_VLAN * @see LWIP_HOOK_VLAN_CHECK */ err_t ethernet_input(struct pbuf *p, struct netif *netif) { struct eth_hdr *ethhdr; u16_t type; #if LWIP_ARP || ETHARP_SUPPORT_VLAN || LWIP_IPV6 u16_t next_hdr_offset = SIZEOF_ETH_HDR; #endif /* LWIP_ARP || ETHARP_SUPPORT_VLAN */ LWIP_ASSERT_CORE_LOCKED(); // Ethernet Ⅱ协议帧头(SIZEOF_ETH_HDR)为 6+6+2=14 字节 if (p->len <= SIZEOF_ETH_HDR) { /* a packet with only an ethernet header (or less) is not valid for us */ ETHARP_STATS_INC(etharp.proterr); ETHARP_STATS_INC(etharp.drop); MIB2_STATS_NETIF_INC(netif, ifinerrors); goto free_and_return; } /* points to packet payload, which starts with an Ethernet header */ ethhdr = (struct eth_hdr *)p->payload;// 提取帧头 LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("ethernet_input: dest:%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F", src:%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F", type:%"X16_F"\n", (unsigned char)ethhdr->dest.addr[0], (unsigned char)ethhdr->dest.addr[1], (unsigned char)ethhdr->dest.addr[2], (unsigned char)ethhdr->dest.addr[3], (unsigned char)ethhdr->dest.addr[4], (unsigned char)ethhdr->dest.addr[5], (unsigned char)ethhdr->src.addr[0], (unsigned char)ethhdr->src.addr[1], (unsigned char)ethhdr->src.addr[2], (unsigned char)ethhdr->src.addr[3], (unsigned char)ethhdr->src.addr[4], (unsigned char)ethhdr->src.addr[5], lwip_htons(ethhdr->type))); type = ethhdr->type; #if ETHARP_SUPPORT_VLAN if (type == PP_HTONS(ETHTYPE_VLAN)) { struct eth_vlan_hdr *vlan = (struct eth_vlan_hdr *)(((char *)ethhdr) + SIZEOF_ETH_HDR); next_hdr_offset = SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR; if (p->len <= SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR) { /* a packet with only an ethernet/vlan header (or less) is not valid for us */ ETHARP_STATS_INC(etharp.proterr); ETHARP_STATS_INC(etharp.drop); MIB2_STATS_NETIF_INC(netif, ifinerrors); goto free_and_return; } #if defined(LWIP_HOOK_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) /* if not, allow all VLANs */ #ifdef LWIP_HOOK_VLAN_CHECK if (!LWIP_HOOK_VLAN_CHECK(netif, ethhdr, vlan)) { #elif defined(ETHARP_VLAN_CHECK_FN) if (!ETHARP_VLAN_CHECK_FN(ethhdr, vlan)) { #elif defined(ETHARP_VLAN_CHECK) if (VLAN_ID(vlan) != ETHARP_VLAN_CHECK) { #endif /* silently ignore this packet: not for our VLAN */ pbuf_free(p); return ERR_OK; } #endif /* defined(LWIP_HOOK_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) */ type = vlan->tpid; } #endif /* ETHARP_SUPPORT_VLAN */ #if LWIP_ARP_FILTER_NETIF netif = LWIP_ARP_FILTER_NETIF_FN(p, netif, lwip_htons(type)); #endif /* LWIP_ARP_FILTER_NETIF*/ if (p->if_idx == NETIF_NO_INDEX) { p->if_idx = netif_get_index(netif); } // 多播广播识别 if (ethhdr->dest.addr[0] & 1) { /* this might be a multicast or broadcast packet */ if (ethhdr->dest.addr[0] == LL_IP4_MULTICAST_ADDR_0) { #if LWIP_IPV4 if ((ethhdr->dest.addr[1] == LL_IP4_MULTICAST_ADDR_1) && (ethhdr->dest.addr[2] == LL_IP4_MULTICAST_ADDR_2)) { /* mark the pbuf as link-layer multicast */ p->flags |= PBUF_FLAG_LLMCAST; } #endif /* LWIP_IPV4 */ } #if LWIP_IPV6 else if ((ethhdr->dest.addr[0] == LL_IP6_MULTICAST_ADDR_0) && (ethhdr->dest.addr[1] == LL_IP6_MULTICAST_ADDR_1)) { /* mark the pbuf as link-layer multicast */ p->flags |= PBUF_FLAG_LLMCAST; } #endif /* LWIP_IPV6 */ else if (eth_addr_cmp(&ethhdr->dest, &ethbroadcast)) { /* mark the pbuf as link-layer broadcast */ p->flags |= PBUF_FLAG_LLBCAST; } } switch (type) { #if LWIP_IPV4 && LWIP_ARP /* IP packet? */ // Data为IP数据包 case PP_HTONS(ETHTYPE_IP): if (!(netif->flags & NETIF_FLAG_ETHARP)) { goto free_and_return; } /* skip Ethernet header (min. size checked above) */ if (pbuf_remove_header(p, next_hdr_offset)) { LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("ethernet_input: IPv4 packet dropped, too short (%"U16_F"/%"U16_F")\n", p->tot_len, next_hdr_offset)); LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("Can't move over header in packet\n")); goto free_and_return; } else { /* pass to IP layer */ // 作为ipv4处理函数的输入 ip4_input(p, netif); } break; // Data为ARP数据包 case PP_HTONS(ETHTYPE_ARP): if (!(netif->flags & NETIF_FLAG_ETHARP)) { goto free_and_return; } /* skip Ethernet header (min. size checked above) */ if (pbuf_remove_header(p, next_hdr_offset)) { LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("ethernet_input: ARP response packet dropped, too short (%"U16_F"/%"U16_F")\n", p->tot_len, next_hdr_offset)); LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("Can't move over header in packet\n")); ETHARP_STATS_INC(etharp.lenerr); ETHARP_STATS_INC(etharp.drop); goto free_and_return; } else { /* pass p to ARP module */ etharp_input(p, netif); } break; #endif /* LWIP_IPV4 && LWIP_ARP */ #if PPPOE_SUPPORT case PP_HTONS(ETHTYPE_PPPOEDISC): /* PPP Over Ethernet Discovery Stage */ pppoe_disc_input(netif, p); break; case PP_HTONS(ETHTYPE_PPPOE): /* PPP Over Ethernet Session Stage */ pppoe_data_input(netif, p); break; #endif /* PPPOE_SUPPORT */ #if LWIP_IPV6 case PP_HTONS(ETHTYPE_IPV6): /* IPv6 */ /* skip Ethernet header */ if ((p->len < next_hdr_offset) || pbuf_remove_header(p, next_hdr_offset)) { LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("ethernet_input: IPv6 packet dropped, too short (%"U16_F"/%"U16_F")\n", p->tot_len, next_hdr_offset)); goto free_and_return; } else { /* pass to IPv6 layer */ ip6_input(p, netif); } break; #endif /* LWIP_IPV6 */ default: #ifdef LWIP_HOOK_UNKNOWN_ETH_PROTOCOL if (LWIP_HOOK_UNKNOWN_ETH_PROTOCOL(p, netif) == ERR_OK) { break; } #endif ETHARP_STATS_INC(etharp.proterr); ETHARP_STATS_INC(etharp.drop); MIB2_STATS_NETIF_INC(netif, ifinunknownprotos); goto free_and_return; } /* This means the pbuf is freed or consumed, so the caller doesn't have to free it again */ return ERR_OK; free_and_return: pbuf_free(p); return ERR_OK; } ```

RAW API 回调管理

lwIP 开放了一些回调,来让用户能够在其中插入执行部分代码,通过使用注册函数来注册回调。

回调分为两类:

  • 状态回调(status callback)
  • 额外状态回调(ext status callback)

状态回调

当网口状态发生变化时触发回调,相关的状态如下:

  • 设置ip地址
  • 网络接口启用
  • 网络接口关闭
  • ipv6地址状态切换(INVALID, TEMPTATIVE, PREFERRED, DEPRECATED)

回调原型,参数为网口对象:

1
typedef void (*netif_status_callback_fn)(struct netif *netif);

注册函数(每个网口只能注册唯一回调):

1
void netif_set_status_callback(struct netif *netif, netif_status_callback_fn status_callback);

网口状态改变时触发回调,参数只有对应的网口对象,没有触发原因,需要在回调函数中自行判断当前状态。

额外状态回调

额外状态回调是对状态回调的补充,扩展了如下状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * @ingroup netif
 * Extended netif status callback (NSC) reasons flags.
 * May be extended in the future!
 */
typedef u16_t netif_nsc_reason_t;

/* used for initialization only */
#define LWIP_NSC_NONE                     0x0000
/** netif was added. arg: NULL. Called AFTER netif was added. */
#define LWIP_NSC_NETIF_ADDED              0x0001
/** netif was removed. arg: NULL. Called BEFORE netif is removed. */
#define LWIP_NSC_NETIF_REMOVED            0x0002
/** link changed */
#define LWIP_NSC_LINK_CHANGED             0x0004
/** netif administrative status changed.<br>
  * up is called AFTER netif is set up.<br>
  * down is called BEFORE the netif is actually set down. */
#define LWIP_NSC_STATUS_CHANGED           0x0008
/** IPv4 address has changed */
#define LWIP_NSC_IPV4_ADDRESS_CHANGED     0x0010
/** IPv4 gateway has changed */
#define LWIP_NSC_IPV4_GATEWAY_CHANGED     0x0020
/** IPv4 netmask has changed */
#define LWIP_NSC_IPV4_NETMASK_CHANGED     0x0040
/** called AFTER IPv4 address/gateway/netmask changes have been applied */
#define LWIP_NSC_IPV4_SETTINGS_CHANGED    0x0080
/** IPv6 address was added */
#define LWIP_NSC_IPV6_SET                 0x0100
/** IPv6 address state has changed */
#define LWIP_NSC_IPV6_ADDR_STATE_CHANGED  0x0200
/** IPv4 settings: valid address set, application may start to communicate */
#define LWIP_NSC_IPV4_ADDR_VALID          0x0400

回调原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * @ingroup netif
 * Function used for extended netif status callbacks
 * Note: When parsing reason argument, keep in mind that more reasons may be added in the future!
 * @param netif netif that is affected by change
 * @param reason change reason
 * @param args depends on reason, see reason description
 */
typedef void (*netif_ext_callback_fn)(struct netif* netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t* args);

// 链表节点 netif_ext_callback_t
typedef struct netif_ext_callback
{
  netif_ext_callback_fn callback_fn;
  struct netif_ext_callback* next;
} netif_ext_callback_t;

// 额外参数 netif_ext_callback_args_t
/** @ingroup netif
 * Argument supplied to netif_ext_callback_fn.
 */
typedef union
{
  /** Args to LWIP_NSC_LINK_CHANGED callback */
  struct link_changed_s
  {
    /** 1: up; 0: down */
    u8_t state;
  } link_changed;
  /** Args to LWIP_NSC_STATUS_CHANGED callback */
  struct status_changed_s
  {
    /** 1: up; 0: down */
    u8_t state;
  } status_changed;
  /** Args to LWIP_NSC_IPV4_ADDRESS_CHANGED|LWIP_NSC_IPV4_GATEWAY_CHANGED|LWIP_NSC_IPV4_NETMASK_CHANGED|LWIP_NSC_IPV4_SETTINGS_CHANGED callback */
  struct ipv4_changed_s
  {
    /** Old IPv4 address */
    const ip_addr_t* old_address;
    const ip_addr_t* old_netmask;
    const ip_addr_t* old_gw;
  } ipv4_changed;
  /** Args to LWIP_NSC_IPV6_SET callback */
  struct ipv6_set_s
  {
    /** Index of changed IPv6 address */
    s8_t addr_index;
    /** Old IPv6 address */
    const ip_addr_t* old_address;
  } ipv6_set;
  /** Args to LWIP_NSC_IPV6_ADDR_STATE_CHANGED callback */
  struct ipv6_addr_state_changed_s
  {
    /** Index of affected IPv6 address */
    s8_t addr_index;
    /** Old IPv6 address state */
    u8_t old_state;
    /** Affected IPv6 address */
    const ip_addr_t* address;
  } ipv6_addr_state_changed;
} netif_ext_callback_args_t;

注册函数:

1
2
void netif_add_ext_callback(netif_ext_callback_t* callback, netif_ext_callback_fn fn);
void netif_remove_ext_callback(netif_ext_callback_t* callback);

lwIP 内部使用链表来管理额外状态回调,其中netif_ext_callback_t结构用来存放回调函数和 next 指针,用以形成链表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * @ingroup netif
 * Add extended netif events listener
 * @param callback pointer to listener structure
 * @param fn callback function
 */
void
netif_add_ext_callback(netif_ext_callback_t *callback, netif_ext_callback_fn fn)
{
  LWIP_ASSERT_CORE_LOCKED();
  LWIP_ASSERT("callback must be != NULL", callback != NULL);
  LWIP_ASSERT("fn must be != NULL", fn != NULL);

  callback->callback_fn = fn;
  // 链表追加
  callback->next        = ext_callback;
  ext_callback          = callback;
}

当发生一个事件时,执行额外状态回调,为回调函数传递参数: 网口对象、触发原因、额外参数。

本文由作者按照 CC BY 4.0 进行授权

© Kai. 保留部分权利。

浙ICP备20006745号-2,本站由 Jekyll 生成,采用 Chirpy 主题。