4、LwIP的网络接口netif
1、LwIP结构体netif
网络接口(如以太网接口)是硬件接口,网络接口又可以称之为网卡 , LwIP 是软件,那么怎么让硬件与软件无缝连接起来呢?而且, 网卡又有多种多样,怎么能让 LwIP 使用同样的软件能兼容不同的硬件呢?LwIP 使用一个数据结构——netif 来描述一个网卡, 但是由于网卡是直接与硬件打交道的,硬件不同则处理基本是不同的, 所以必须由用户提供最底层接口函数, LwIP 提供统一的接口,但是底层的实现需要用户自己去完成,比如网卡的初始化, 网卡的收发数据,当 LwIP 底层得到了网络的数据之后,才会传入内核中去处理;同理, LwIP 内核需要发送一个数据包的时候,也需要调用网卡的发送函数,这样子才能把数据从硬件接口到软件内核无缝连接起来。
LwIP 中的 ethernetif.c 文件即为底层接口的驱动的模版,用户为自己的网络设备实现驱动时应参照此模块做修改。 ethernetif.c 文件中的函数通常为与硬件打交道的底层函数,当有数据需要通过网卡接收或者发送数据的时候就会被调用,经过 LwIP 协议栈内部进行处理后,从应用层就能得到数据或者可以发送数据。
简单来说, netif 是 LwIP 抽象出来的网卡, LwIP 协议栈可以使用多个不同的接口,而ethernetif.c 文件则提供了 netif 访问各种不同的网卡,每个网卡有不同的实现方式, 用户只需要修改 ethernetif.c 文件即可。
在单网卡中,这个 netif 结构体只有一个,可能还有人会问,那么一个设备中有多个网卡怎么办,很简单, LwIP 会将每个用 netif 描述的网卡连接成一个链表(单向链表),该链表就记录每个网卡的 netif。 屏蔽硬件接口的差异,完成了对不同网卡的抽象,因此了解netif 结构体是移植 LwIP 的关键。
//netif 数据结构 struct netif { #if !LWIP_SINGLE_NETIF struct netif *next; /* 指向 netif 链表中的下一个 */ (1) #endif /* 网络字节中的 IP 地址、子网掩码、默认网关配置 */ #if LWIP_IPV4 ip_addr_t ip_addr; ip_addr_t netmask; ip_addr_t gw; (2) #endif /* LWIP_IPV4 */ netif_input_fn input; /* 此函数由网络设备驱动程序调用,将数据包传递到 TCP/IP 协议栈。对于以太网物理层,这通常是 ethernet_input()*/ (3) #if LWIP_IPV4 netif_output_fn output; /* 此函数由 IP 层调用,在接口上发送数据包。通常这个功能,首先解析硬件地址,然后发送数据包。对于以太网物理层,这通常是 etharp_output() */ (4) #endif /* LWIP_IPV4 */ netif_linkoutput_fn linkoutput; /* 此函数由 ethernet_output()调用,当需要在网卡上发送一个数据包时。底层硬件输出数据函数,一般是调用自定义函数 low_level_output*/ (5) #if LWIP_NETIF_STATUS_CALLBACK netif_status_callback_fn status_callback; /*当 netif 状态设置为 up 或 down 时调用此函数*/ (6) #endif /* LWIP_NETIF_STATUS_CALLBACK */ #if LWIP_NETIF_LINK_CALLBACK netif_status_callback_fn link_callback; /* 当 netif 链接设置为 up 或 down 时,将调用此函数 */ (7) #endif /* LWIP_NETIF_LINK_CALLBACK */ #if LWIP_NETIF_REMOVE_CALLBACK netif_status_callback_fn remove_callback; /* 当 netif 被删除时调用此函数 */ (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 const char* hostname; /* 这个 netif 的主机名, NULL 也是一个有效值 */ #endif /* LWIP_NETIF_HOSTNAME */ #if LWIP_CHECKSUM_CTRL_PER_NETIF u16_t chksum_flags; #endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/ u16_t mtu; /** 最大传输单位(以字节为单位),对于以太网一般设为 1500 */ (10) u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; /** 此网卡的链路层硬件地址 */ (11) u8_t hwaddr_len; /** 硬件地址长度,对于以太网就是 MAC 地址长度,为 6 字节 */ (12) u8_t flags; /* 网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。 */ (13) /* 字段用于保存每一个网卡的名字。用两个字符的名字来标识网络接口使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网卡表示的硬件的种类。比如蓝牙设备( bluetooth)的网卡名字可以是 bt, 而 IEEE 802.11b WLAN 设备的名字就可以是 wl,当然设置什么名字用户是可以自由发挥的,这并不影响用户对网卡的使用。当然,如果两个网卡具有相同的网络名字,我们就用 num 字段来区分相同类别的不同网卡*/ char name[2]; (14) u8_t num; /* 用来标示使用同种驱动类型的不同网卡 */ (15) #if MIB2_STATS u8_t link_type; /* 连接类型 */ u32_t link_speed; /* 连接速度 */ u32_t ts; /* 最后一次更改的时间戳 */ struct stats_mib2_netif_ctrs mib2_counters; #endif /* MIB2_STATS */ #if LWIP_IPV4 && LWIP_IGMP netif_igmp_mac_filter_fn igmp_mac_filter; /** 可以调用此函数来添加或删除多播中的条目以太网 MAC 的过滤表。 */ #endif /* LWIP_IPV4 && LWIP_IGMP */ #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 结构体指针,在一个设备中有多个网卡时, 才使用该字段。 (2):ip_addr 字段记录的是网络中的 IP 地址, netmask 字段记录的是子网掩码, gw 记录的是网关地址,这些字段是用于描述网卡的网络地址属性。IP 地址必须与网卡对应,即设备拥有多少个网卡那就必须有多少个 IP 地址;子网掩码可以用来判断某个 IP 地址与当前网卡是否处于同一个子网中, IP 在发送数据包的时候会选择与目标 IP 地址处于同一子网的网卡来发送;网关地址在数据包的发送、转发过程非常重要,如果要向不属于同一子网的主机(主机目标 IP 地址与网卡不属于同一子网)发送一个数据包,那么 LwIP 就会将数据包发送到网关中,网关设备会对该数据包进行正确的转发,除此之外,网关还提供很多高级功能,如 DNS, DHCP 等。 (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 字段用于保存每一个网卡的名字。用两个字符的名字来标识网卡使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网卡表示的硬件的种类。比如蓝牙设备(bluetooth)的网卡名字可以是 bt,而 IEEE 802.11b WLAN 设备的名字就可以是 wl,当然设置什么名字用户是可以自由发挥的,这并不影响用户对网卡的使用。当然,如果两个网卡具有相同的网络名字,我们就用 num 字段来区分相同类别的不同网卡。 (15):用来标识使用同种驱动类型的不同网卡。
2、 netif结构体的使用
首先我们需要根据我们的网卡定义一个 netif 结构体变量 struct netif gnetif, 我们首先要把网卡挂载到 netif_list 链表上才能使用,因为 LwIP 是通过链表来管理所有的网卡,所有第一步是通过 netif_add()函数将我们的网卡挂载到 netif_list 链表上, netif_add()函数具体见代码清单 4-2。
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) { LWIP_ASSERT_CORE_LOCKED(); if (ipaddr == NULL) { ipaddr = ip_2_ip4(IP4_ADDR_ANY); } if (netmask == NULL) { netmask = ip_2_ip4(IP4_ADDR_ANY); } if (gw == NULL) { gw = ip_2_ip4(IP4_ADDR_ANY); } /*清空主机 IP 地址、子网掩码、网关等字段信息*/ /* reset new interface configuration state */ ip_addr_set_zero_ip4(&netif->ip_addr); ip_addr_set_zero_ip4(&netif->netmask); ip_addr_set_zero_ip4(&netif->gw); netif->output = netif_null_output_ip4; NETIF_SET_CHECKSUM_CTRL(netif, NETIF_CHECKSUM_ENABLE_ALL); netif->mtu = 0; netif->flags = 0; memset(netif->client_data, 0, sizeof(netif->client_data)); /*根据传递进来的参数填写网卡 state、 input 等字段的相关信息*/ /* remember netif specific state information data */ netif->state = state; netif->num = netif_num; netif->input = input; NETIF_RESET_HINTS(netif); /*调用网卡设置函数 netif_set_addr()设置网卡 IP 地址、子网掩码、网关等信息*/ netif_set_addr(netif, ipaddr, netmask, gw); (3) /*通过传递进来的回调函数 init()进行网卡真正的初始化操作, 所以该函数是由用户实现的, 对于不同网卡就使用不一样的初始化, 而此处是以太网, 则该回调函数一般为 ethernetif_init()*/ /* call user specified initialization function for netif */ if (init(netif) != ERR_OK) { return NULL; } { struct netif *netif2; int num_netifs; do { if (netif->num == 255) { netif->num = 0; } num_netifs = 0; for(netif2 = netif_list; netif2 != NULL; netif2 = netif2->next) { num_netifs++; if (netif2->num == netif->num) { netif->num++; break; } } } while (netif2 != NULL); } if (netif->num == 254) { netif_num = 0; } else { netif_num = (u8_t)(netif->num + 1); /8*初始化网卡成功,则遍历当前设备拥有多少个网卡,并为当前网卡分配唯一标识 num*/ } /将当前网卡插入 netif_list 链表中*/ /* add this netif to the list */ netif->next = netif_list; netif_list = netif; mib2_netif_added(netif); ip4_addr_debug_print(NETIF_DEBUG, ipaddr); ip4_addr_debug_print(NETIF_DEBUG, netmask); ip4_addr_debug_print(NETIF_DEBUG, gw); netif_invoke_ext_callback(netif, LWIP_NSC_NETIF_ADDED, NULL); return netif; }
在使用之前需要进行初始化主机 IP 地址、子网掩码、网关等,并且在调用 netif_add()函数之后会触发 netif_init_fn 的回调函数。
总之一句话,在开始使用 LwIP 协议栈的时候,我们就需要将网卡底层移植完成,才能开始使用,而移植的第一步,就是将网络进行初始化,并且设置该网卡为默认网卡,让LwIP 能通过网卡进行收发数据。
/** * @brief LWIP初始化(LWIP启动的时候使用) * @param 无 * @retval 0,成功 * 1,以太网芯片初始化失败 */ uint8_t lwip_comm_init(void) { uint8_t retry = 0; struct netif *netif_init_flag; /* 调用netif_add()函数时的返回值,用于判断网络初始化是否成功 */ ip_addr_t ip_addr; /* ip地址 */ ip_addr_t network_mask; /* 子网掩码 */ ip_addr_t gateway_ip; /* 默认网关 */ lwip_comm_default_ip_set(); /* 设置默认IP等信息 */ lwip_init(); /* 初始化LWIP内核 */ #if LWIP_DHCP /* 使用动态IP */ ip_addr_set_zero_ip4(&ip_addr); /* 对IP地址、子网掩码及网关清零 */ ip_addr_set_zero_ip4(&network_mask); ip_addr_set_zero_ip4(&gateway_ip); #else /* 使用静态IP */ IP4_ADDR(&ip_addr, g_lwip_comm_struct.local_ip[0], g_lwip_comm_struct.local_ip[1], g_lwip_comm_struct.local_ip[2], g_lwip_comm_struct.local_ip[3]); IP4_ADDR(&network_mask, g_lwip_comm_struct.network_mask[0], g_lwip_comm_struct.network_mask[1], g_lwip_comm_struct.network_mask[2], g_lwip_comm_struct.network_mask[3]); IP4_ADDR(&gateway_ip, g_lwip_comm_struct.gateway_ip[0], g_lwip_comm_struct.gateway_ip[1], g_lwip_comm_struct.gateway_ip[2], g_lwip_comm_struct.gateway_ip[3]); printf("网卡en的mac_addr地址为:................%d.%d.%d.%d.%d.%d\r\n", g_lwip_comm_struct.mac_addr[0], g_lwip_comm_struct.mac_addr[1], g_lwip_comm_struct.mac_addr[2], g_lwip_comm_struct.mac_addr[3], g_lwip_comm_struct.mac_addr[4], g_lwip_comm_struct.mac_addr[5]); printf("静态IP地址........................%d.%d.%d.%d\r\n", g_lwip_comm_struct.local_ip[0], g_lwip_comm_struct.local_ip[1], g_lwip_comm_struct.local_ip[2], g_lwip_comm_struct.local_ip[3]); printf("子网掩码..........................%d.%d.%d.%d\r\n", g_lwip_comm_struct.network_mask[0], g_lwip_comm_struct.network_mask[1], g_lwip_comm_struct.network_mask[2], g_lwip_comm_struct.network_mask[3]); printf("默认网关..........................%d.%d.%d.%d\r\n", g_lwip_comm_struct.gateway_ip[0], g_lwip_comm_struct.gateway_ip[1], g_lwip_comm_struct.gateway_ip[2], g_lwip_comm_struct.gateway_ip[3]); g_lwip_comm_struct.dhcp_status = 0XFF; #endif /* 向网卡列表中添加一个网口 */ netif_init_flag = netif_add(&g_lwip_netif, (const ip_addr_t *)&ip_addr, (const ip_addr_t *)&network_mask, (const ip_addr_t *)&gateway_ip, NULL, ðernetif_init, ðernet_input); if (netif_init_flag == NULL) { return 1; /* 网卡添加失败 */ } else /* 网口添加成功后,设置netif为默认值,并且打开netif网口 */ { netif_set_default(&g_lwip_netif); /* 设置netif为默认网口 */ if (netif_is_link_up(&g_lwip_netif)) { netif_set_up(&g_lwip_netif); /* 打开netif网口 */ } else { netif_set_down(&g_lwip_netif); } lwip_link_status_updated(&g_lwip_netif); /* DHCP链接状态更新函数 */ netif_set_link_callback(&g_lwip_netif, lwip_link_status_updated); } #if LWIP_DHCP /* 如果使用DHCP的话 */ g_lwip_comm_struct.dhcp_status = 0; /* DHCP标记为0 */ #endif return 0; /* 操作OK. */ }
挂载网卡的过程是非常简单的, 如果一个设备当前是还没有网卡的, 当调用 netif_add()函数挂载网卡后,其过程如图 4-1 所示,当设备需要挂载多个网卡的时候,就多次调用netif_add()函数即可,新挂载的网卡会在链表的最前面,具体见图 4-2。


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 *pParams);
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 文件到底是怎么样子的,又是怎么将网卡与 LwIP 内核连接在一起。
首先在 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()函数,具体见下面代码清单:
/** * 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) { 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; (1) 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);(2) return ERR_OK; }
代码清单 (1):通过 netif 的 state 成员变量将 ethernetif 结构传递给上层。
代码清单 (2):调用 low_level_init()函数对网卡进行初始化,而该函数需要我们根据网卡的实际情况进行编写。
4.3 、low_level_init()
该函数主要是根据实际情况对网卡进行一系列的初始化工作,例如:初始化 MAC 地址、 长度,设置最大传输包的大小,设置网卡的属性字段,支持广播、多播、 ARP 等功能,如果是使用操作系统的话,还需要建立接收数据、发送数据的任务以及一些需要的消息队列、信号量等,此处讲解的是裸机底层驱动的编写, low_level_init()源码具体见代码清单4-8。
/** * 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) { netif->hwaddr_len = ETHARP_HWADDR_LEN; /* 设置MAC地址长度,为6个字节 */ /* 初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复 */ netif->hwaddr[0] = g_lwipdev.mac[0]; netif->hwaddr[1] = g_lwipdev.mac[1]; netif->hwaddr[2] = g_lwipdev.mac[2]; netif->hwaddr[3] = g_lwipdev.mac[3]; netif->hwaddr[4] = g_lwipdev.mac[4]; netif->hwaddr[5] = g_lwipdev.mac[5]; netif->mtu=1500; /* 最大允许传输单元,允许该网卡广播和ARP功能 */ /* 网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播 */ /* 使能、 ARP 使能等等重要控制位 */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* 广播 ARP协议 链接检测 */ HAL_ETH_DMATxDescListInit(&g_eth_handler,g_eth_dma_tx_dscr_tab,g_eth_tx_buf,ETH_TXBUFNB); /* 初始化发送描述符 */ HAL_ETH_DMARxDescListInit(&g_eth_handler,g_eth_dma_rx_dscr_tab,g_eth_rx_buf,ETH_RXBUFNB); /* 初始化接收描述符 */ HAL_ETH_Start(&g_eth_handler); /* 开启ETH */ }

浙公网安备 33010602011771号