本篇文章以以太网网卡驱动为例,分析SDK中的TCP echo回传源码,主要是对netif
的初始化进行分析,从而深入地理解LwIP中netif
的作用。
- 开发板:NXP Kinetis
- 操作系统:FreeRTOS,LwIP版本:2.2.0
- 文章中分析的代码均把没有用到的宏定义相关的代码去掉了,为了方便阅读,代码都是预编译完替换了
#define
后的代码,有些没有用的代码也去掉
1 TCP_ECHO程序
首先来看一下TCP回显的代码,大概流程就是初始化网卡netif
,然后创建一个任务接收TCP数据并回显:
const mdio_operations_t enet_ops = {
.mdioInit = ENET_MDIO_Init,
.mdioWrite = ENET_MDIO_Write,
.mdioRead = ENET_MDIO_Read,
.mdioWriteExt = NULL,
.mdioReadExt = NULL
};
const phy_operations_t phyksz8081_ops = {
.phyInit = PHY_KSZ8081_Init,
.phyWrite = PHY_KSZ8081_Write,
.phyRead = PHY_KSZ8081_Read,
.getAutoNegoStatus = PHY_KSZ8081_GetAutoNegotiationStatus,
.getLinkStatus = PHY_KSZ8081_GetLinkStatus,
.getLinkSpeedDuplex = PHY_KSZ8081_GetLinkSpeedDuplex,
.setLinkSpeedDuplex = PHY_KSZ8081_SetLinkSpeedDuplex,
.enableLoopback = PHY_KSZ8081_EnableLoopback
};
static mdio_handle_t mdioHandle = {.ops = &enet_ops};
static phy_handle_t phyHandle = {
/* 开发板以太网0的物理地址 */
.phyAddr = 0x00,
/* MDIO(Management Data Input/Output)操作结构体 */
.mdioHandle = &mdioHandle,
/* 以太网收发器phyksz8081的驱动 */
.ops = &phyksz8081_ops
};
/* 初始化函数 */
static void stack_init(void *arg)
{
static struct netif netif;
ip4_addr_t netif_ipaddr, netif_netmask, netif_gw;
ethernetif_config_t enet_config = {
.phyHandle = &phyHandle,
.macAddress = {0x02, 0x12, 0x13, 0x10, 0x15, 0x11},
};
mdioHandle.resource.csrClock_Hz = CLOCK_GetFreq(kCLOCK_CoreSysClk);
/* 设置IP地址,子网掩码和网关 */
IP4_ADDR(&netif_ipaddr, 192, 168, 0, 102);
IP4_ADDR(&netif_netmask, 255, 255, 255, 0);
IP4_ADDR(&netif_gw, 192, 168, 0, 100);
tcpip_init(NULL, NULL);
netifapi_netif_add(&netif, &netif_ipaddr, &netif_netmask, &netif_gw, &enet_config, ethernetif0_init,
tcpip_input);
netifapi_netif_set_default(&netif);
netifapi_netif_set_up(&netif);
/* 创建TCP_ECHO任务进行回显 */
tcpecho_init();
vTaskDelete(NULL);
}
其中tcpip_init
在我的另一篇博客tcpip_init和tcpip_thread函数分析中我简单地分析过。
可以看到上面主要是传入一些参数,然后初始化netif
结构体。所以我们就从这个结构体出发来一探究竟:
- 这里不对IPV6做分析,故在下面代码中将与IPV6相关的结构体成员的去掉以易于查看
2 netif结构体
struct netif {
/* 如果程序中有多个网卡,如用PPP,以太网等连接,则不同的网卡netif用该链表连接 */
struct netif *next;
/* IPV4地址配置(网络字节序):IP,子网掩码,网关 */
ip_addr_t ip_addr;
ip_addr_t netmask;
ip_addr_t gw;
/* 由网络设备驱动调用该函数在TCP/IP协议栈上传递一组数据包 */
netif_input_fn input;
/* 由IP模块调用来解析硬件地址再发送一组数据包,对于以太网的物理层来说,该函数为etharp_output */
netif_output_fn output;
/* 当想要发送一组数据包时,在ethernet_output中调用,它输出链路层中的原始pbuf */
netif_linkoutput_fn linkoutput;
/* 当链路层网卡状态改变时(连接/断开)时调用 */
netif_status_callback_fn status_callback;
/* 当链路层网卡发起连接或断开时调用 */
netif_status_callback_fn link_callback;
/* 当一个netif网卡结构体移除时调用 */
netif_status_callback_fn remove_callback;
/* 供用户传递数据使用 */
void *state;
/* 存放一些客户端的数据,如DHCP客户端结构体 */
void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];
/* netif的主机名 */
const char* hostname;
/* 每个netif都可以使能/失能校验和的生成和校验 */
u16_t chksum_flags;
/* 最大传输单元(单位:bytes) */
u16_t mtu;
/* 链路层的硬件地址 */
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
/* 硬件地址的长度 */
u8_t hwaddr_len;
/* 网卡状态信息标志位,包括网卡功能使能、广播使能、 ARP使能等标志位 */
u8_t flags;
/** 该netif的名字 */
char name[2];
/* 用来标示使用同种驱动类型的不同网卡的数量 */
u8_t num;
/* 该函数用来添加或删除以太网MAC层组播的过滤表中的条目 */
netif_igmp_mac_filter_fn igmp_mac_filter;
/* ACD模块:有关自动IP获取 */
struct acd *acd_list;
/* VLAN PCP相关 */
struct netif_hint *hints;
/* 环回相关:略 */
};
部分不好理解的参数具体在代码中用到了我们再来研究。我们注意到网卡接口netif
是在netifapi_netif_add
函数中进行初始化的,现在来看看这个函数:
3 netif_add函数详解
struct netifapi_msg {
struct tcpip_api_call_data call;
struct netif *netif;
union {
struct {
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;
} add;
struct {
netifapi_void_fn voidfunc;
netifapi_errt_fn errtfunc;
} common;
struct {
char *name;
u8_t index;
} ifs;
} msg;
};
err_t netifapi_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)
{
err_t err;
struct netifapi_msg msg;
msg.netif = netif;
msg.msg.add.ipaddr = ipaddr;
msg.msg.add.netmask = netmask;
msg.msg.add.gw = gw;
msg.msg.add.state = state;
msg.msg.add.init = init;
msg.msg.add.input = input;
/* msg.call为结构体第一个元素的地址,也就是结构体的地址 */
err = tcpip_api_call(netifapi_do_netif_add, &msg.call);
return err;
}
而tcpip_api_call
实际上就是执行netifapi_do_netif_add
函数,然后在执行前获得互斥锁,执行后释放互斥锁,这样可以让用户在自己的代码中实现LwIP的一些操作。
err_t tcpip_api_call(tcpip_api_call_fn fn, struct tcpip_api_call_data *call)
{
err_t err;
sys_lock_tcpip_core();
err = fn(call);
sys_unlock_tcpip_core();
return err;
}
现在来看看netifapi_do_netif_add
函数:
static err_t netifapi_do_netif_add(struct tcpip_api_call_data *m)
{
struct netifapi_msg *msg = (struct netifapi_msg *)(void *)m;
if (!netif_add( msg->netif,
msg->msg.add.ipaddr,
msg->msg.add.netmask,
msg->msg.add.gw,
msg->msg.add.state,
msg->msg.add.init,
msg->msg.add.input))
{
return ERR_IF;
}
return ERR_OK;
}
所以到头来就是调用了netif_add
函数,由于例程中有操作系统所以需要考虑互斥,现在来看看netif_add
:
struct netif* netif_add(struct netif *netif,const ip4_addr_t *ipaddr, const ip4_addr_ *netmask,
const ip4_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input)
{
sys_check_core_locking();
/* 如果IP、子网掩码、网关为0的话,赋一个默认值,地址用32位数表示 */
if (ipaddr == 0) {
ipaddr = ((&ip_addr_any));
}
if (netmask == 0) {
netmask = ((&ip_addr_any));
}
if (gw == 0) {
gw = ((&ip_addr_any));
}
/* 将netif结构体中的IP、子网掩码、网关清零 */
((&netif->ip_addr)->addr = 0);
((&netif->netmask)->addr = 0);
((&netif->gw)->addr = 0);
/* 设置默认output函数为netif_null_output_ip4,该函数中没有内容 */
netif->output = netif_null_output_ip4;
/* 设置结构体的初始值 */
netif->mtu = 0;
netif->flags = 0;
memset(netif->client_data, 0, sizeof(netif->client_data));
/* 用户传的自定义传输,这里为enet_config */
netif->state = state;
/* 记录netif网卡的数量 */
netif->num = netif_num;
/* 网卡向TCP协议栈发送数据的函数,这里为tcpip_input */
netif->input = input;
/* 设置 */
netif_set_addr(netif, ipaddr, netmask, gw); //(1)
/* 调用前面传入的init参数(函数),即ethernetif0_init */
if (init(netif) != ERR_OK) { //(2)
return 0;
}
/* 前面netif->num已经赋值为netif_num,这里遍历整个netif_list寻找一个唯一的num给当前的netif */
struct netif *netif2;
do {
if (netif->num == 255) {
netif->num = 0;
}
for (netif2 = netif_list; netif2 != NULL; netif2 = netif2->next) {
if (netif2->num == netif->num) {
netif->num++;
break;
}
}
} while (netif2 != NULL);
/* netif_num用来记录上次分配的netif->num+1,方便下次分配 */
if (netif->num == 254) {
netif_num = 0;
} else {
netif_num = (u8_t)(netif->num + 1);
}
/* 将当前netif结构体加入netif_list链表中 */
netif->next = netif_list;
netif_list = netif;
return netif;
}
(1)netif_set_addr
void netif_set_addr(struct netif *netif, const ip4_addr_t *ipaddr,
const ip4_addr_t *netmask, const ip4_addr_t *gw)
{
ip_addr_t *old_nm = 0;
ip_addr_t *old_gw = 0;
ip_addr_t old_addr;
int remove;
sys_check_core_locking();
/* 再判断IP、子网掩码和网关是否为0,是的话设置为ip_addr_any:该部分代码略 */
/* 如果没有设置IP地址或者设置为ip_addr_any,则remove为真 */
remove = ((ipaddr) == 0 || ((*(ipaddr)).addr == ((u32_t)0x00000000UL)));
if (remove) {
/* 检查IP和之前netif中设置的是否一样,若不一样,则保存之前的IP到old_addr
* 然后调用tcp_netif_ip_addr_changed修改tcp_active_pcbs和tcp_bound_pcbs
* 两个TCP链表,最后再判断如何在listen之前的地址,改为listen新设置的地址
*/
netif_do_set_ipaddr(netif, ipaddr, &old_addr);
}
/* 设置子网掩码:仅仅修改netif结构体中的netmask项 */
netif_do_set_netmask(netif, netmask, old_nm));
/* 设置网关:仅仅修改netif结构体中的gateway项 */
netif_do_set_gw(netif, gw, old_gw);
/* 如果前面没有设置IP,则最后设置,这样做的原因:移除地址前必须先修改子网掩码和网关
* 以保证tcp RST段可以正确地发送,可以在最前面设置是因为remove表示该netif没有建立连接
*/
if (!remove) {
netif_do_set_ipaddr(netif, ipaddr, &old_addr);
}
}
(2)ethernetif0_init
err_t ethernetif0_init(struct netif *netif)
{
static struct ethernetif ethernetif_0;
__attribute__((aligned((16U)))) static enet_rx_bd_struct_t rxBuffDescrip_0[5];
__attribute__((aligned((16U)))) static enet_tx_bd_struct_t txBuffDescrip_0[3];
__attribute__((aligned((16U)))) static rx_buffer_t rxDataBuff_0[5*2];
__attribute__((aligned((16U)))) static tx_buffer_t txDataBuff_0[3];
ethernetif_0.RxBuffDescrip = &(rxBuffDescrip_0[0]);
ethernetif_0.TxBuffDescrip = &(txBuffDescrip_0[0]);
ethernetif_0.RxDataBuff = &(rxDataBuff_0[0]);
ethernetif_0.TxDataBuff = &(txDataBuff_0[0]);
return ethernetif_init(netif, ðernetif_0, ethernetif_get_enet_base(0U), (ethernetif_config_t *)netif->state);
}
可以看到ethernetif_0
就是声明了几个数组填充到ethernetif
结构体中,然后调用ethernetif_init
来初始化以太网:
err_t ethernetif_init(struct netif *netif,
struct ethernetif *ethernetif,
void *enetBase,
const ethernetif_config_t *ethernetifConfig)
{
/* netif->state赋值为ethernetif0_init()中声明的ethernetif_0结构体 */
netif->state = ethernetif;
netif->name[0] = 'e';
netif->name[1] = 'n';
/* output函数为解析硬件地址并发送数据包,以太网的output函数是etharp_output */
netif->output = etharp_output;
/* 以太网输出链路层中的原始pbuf的函数为ethernetif_linkoutput */
netif->linkoutput = ethernetif_linkoutput;
/* 设置ethernetif->base函数参数中的enetBase,即芯片中以太网的物理地址 */
*ethernetif_enet_ptr(ethernetif) = enetBase;
/* 设置MAC硬件地址长度 */
netif->hwaddr_len = 6;
/* ethernetifConfig即netif_add函数传的用户变量state,实际上是enet_config */
memcpy(netif->hwaddr, ethernetifConfig->macAddress, 6U);
/* 设置以太网MTU */
netif->mtu = 1500;
/* 设置以太网Braodcast、ARP和LinkUp的Flag */
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
/* 以太网初始化函数 */
ethernetif_enet_init(netif, ethernetif, ethernetifConfig);
return ERR_OK;
}
可以看到ethernetif_init
就是设置了一些参数,最后调用ethernetif_enet_init
来初始化,最后我们来看看这个函数做了什么事
void ethernetif_enet_init(struct netif *netif,
struct ethernetif *ethernetif,
const ethernetif_config_t *ethernetifConfig)
{
enet_config_t config;
uint32_t sysClock;
enet_buffer_config_t buffCfg[1U];
phy_speed_t speed;
phy_duplex_t duplex;
int i;
/* 接收buffer描述符号码 */
buffCfg[0].rxBdNumber = 5;
/* 发送buffer描述符号码 */
buffCfg[0].txBdNumber = 3;
/* 接收buffer的对齐字节数 */
buffCfg[0].rxBuffSizeAlign = sizeof(rx_buffer_t);
/* 发送buffer的对齐字节数 */
buffCfg[0].txBuffSizeAlign = sizeof(tx_buffer_t);
/* 接收buffer描述符的起始地址(ethernetif0_init中声明的数组地址) */
buffCfg[0].rxBdStartAddrAlign = &(ethernetif->RxBuffDescrip[0]);
/* 发送buffer描述符的起始地址(ethernetif0_init中声明的数组地址) */
buffCfg[0].txBdStartAddrAlign = &(ethernetif->TxBuffDescrip[0]);
/* 接收buffer的起始地址,NULL表示该buffer由回调函数分配 */
buffCfg[0].rxBufferAlign = NULL;
/* 发送buffer的起始地址(ethernetif0_init中声明的数组地址) */
buffCfg[0].txBufferAlign = &(ethernetif->TxDataBuff[0][0]);
/* 发送帧信息的起始地址 */
buffCfg[0].txFrameInfo = NULL;
/* 接收buffer的cache维护 */
buffCfg[0].rxMaintainEnable = 1;
/* 发送buffer的cache维护 */
buffCfg[0].txMaintainEnable = 1;
/* csrClock_Hz在前面stack_init()函数中初始化 */
sysClock = ethernetifConfig->phyHandle->mdioHandle->resource.csrClock_Hz;
/* 获取默认配置结构体:MII mode,全双工,100Mbps等 */
ENET_GetDefaultConfig(&config);
/* 仅使用一个ring */
config.ringNum = 1U;
/* 接收buffer的分配函数:事先分配好ENET_RXBUFF_NUM个数组作为接收buffer */
config.rxBuffAlloc = ethernetif_rx_alloc;
/* 接收buffer的释放函数 */
config.rxBuffFree = ethernetif_rx_free;
/* netif结构体作为用户参数 */
config.userData = netif;
/* 调用之前填入的硬件上的以太网收发器phyksz8081的ops中的初始化函数PHY_KSZ8081_Init对网卡进行初始化
* 然后调用PHY_KSZ8081_GetAutoNegotiationStatus和PHY_KSZ8081_GetLinkStatus判断是否初始化成功
* 若初始化成功则调用PHY_KSZ8081_GetLinkSpeedDuplex获得交互后实际设置的网卡速度和全/半双工
*/
ethernetif_phy_init(ethernetif, ethernetifConfig, &speed, &duplex);
/* 将获取的网卡速度和全/半双工状态记录到config结构体中 */
config.miiSpeed = (enet_mii_speed_t)speed;
config.miiDuplex = (enet_mii_duplex_t)duplex;
uint32_t instance;
/* 硬件上ENET的基地址 */
static ENET_Type *const enetBases[] = { ((ENET_Type *)(0x400C0000u)) };
/* 以太网硬件发送中断IRQ */
static const IRQn_Type enetTxIrqId[] = { ENET_Transmit_IRQn };
/* 以太网硬件接收中断IRQ */
static const IRQn_Type enetRxIrqId[] = { ENET_Receive_IRQn };
/* 创建一个事件标志位来处理多个发送请求 */
ethernetif->enetTransmitAccessEvent = xEventGroupCreate();
ethernetif->txFlag = 0x1;
/* 打开TX/RX Frame中断、TX Buffer中断和Late Collision(在本该发生collision的时间窗口结束后产生collision)中断 */
config.interrupt |=
kENET_RxFrameInterrupt | kENET_TxFrameInterrupt | kENET_TxBufferInterrupt | kENET_LateCollisionInterrupt;
/* 回调函数:处理以太网数据的输入和发送时enetTransmitAccessEvent事件位的管理 */
config.callback = ethernet_callback;
/* 芯片支持一个/多个以太网,遍历所有以太网的物理地址,若和当前初始化的以太网地址匹配,则设置发送/接收IRQ的优先级 */
for (instance = 0; instance < (sizeof(enetBases) / sizeof((enetBases)[0])); instance++)
{
if (enetBases[instance] == ethernetif->base)
{
__NVIC_SetPriority(enetRxIrqId[instance], (6U));
__NVIC_SetPriority(enetTxIrqId[instance], (6U));
break;
}
}
/* 前面提到的ENET_RXBUFF_NUM个分配好的接收buffer需要初始化 */
for (i = 0; i < ENET_RXBUFF_NUM; i++)
{
/* 设置释放函数 */
ethernetif->RxPbufs[i].p.custom_free_function = ethernetif_rx_release;
/* 原始数据buffer */
ethernetif->RxPbufs[i].buffer = &(ethernetif->RxDataBuff[i][0]);
/* 标记是否使用 */
ethernetif->RxPbufs[i].buffer_used = 0;
/* 网卡netif结构体 */
ethernetif->RxPbufs[i].netif = netif;
}
/* 使能ENET时钟并复位ENET,最后调用ENET_Up函数,完成如下工作:
* 1.检查前面的发送/接收buffer描述符的合法性;
* 2.根据配置设置MAC控制器相关寄存器;
* 3.保存buffCfg的部分到ethernetif->handle,再将该handle保存到变量s_ENETHandle供后续使用,最后设置中断处理函数
*/
ENET_Init(ethernetif->base, ðernetif->handle, &config, &buffCfg[0], netif->hwaddr, sysClock);
/* 设置ENET->RDAR寄存器以开启以太网的数据读取 */
ENET_ActiveRead(ethernetif->base);
}
这里的初始化还是比较复杂的,但无非就是根据用户的配置对ENET的寄存器进行相应的配置,然后将一些软件上的配置,如buffer,保存在本地供后续以太网收发时使用这些变量。如果要深入了解ENET的寄存器,建议详细阅读ENET_Up
函数。
4 netif_set_default和netif_setup
和netif_add
一样,netifapi_netif_set_default
和netifapi_netif_set_up
最终将调用netif_set_default
和netif_setup
。最后来看看这两个函数:
void netif_set_default(struct netif *netif)
{
netif_default = netif;
}
顾名思义,netif_set_default
就是设置默认网卡,LwIP支持多个网卡同时使用,比如一个用以太网上网,一个用4G PPP拨号上网,但LwIP要怎么区分当前使用哪个网卡呢?就是通过netif_default
。
void netif_set_up(struct netif *netif)
{
if(!(netif->flags & NETIF_FLAG_UP)){
/* 设置该netif的状态为UP */
netif_set_flags(netif, NETIF_FLAG_UP);
/* netif状态的变化在一些特定情况下,还需要通知ARP/IGMP/MLD/RS层
* 这里会调用etharp_gratuitous来发送一个ARP请求包来请求IP地址,然后设置到netif结构体
* /
netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV4 | NETIF_REPORT_TYPE_IPV6);
}
}
5 总结
本文主要介绍了LwIP中netif网卡的初始化代码流程,如果想深入了解LwIP协议中的以太网,最好还是要先理解以太网协议,比如前面phyksz8081网卡是怎么根据参考手册进行配置的、用户设置的哪些参数需要设置到芯片的ENET寄存器中。这个如果后续有时间,我会专门写一个博客进行介绍。
本文中一些变量的作用也不太明显,光看名字看不出来是干什么的,也没有在我们分析的过程中再出现过,我们也不可能一个个刨根问底地进行分析。但是在后续代码分析过程中遇到了这些变量,回过头来看的时候,我们便会恍然大悟,或者再回来补充这些。