本篇文章以以太网网卡驱动为例,分析SDK中的TCP echo回传源码,主要是对netif
的初始化进行分析,从而深入地理解LwIP中netif
的作用。
#define
后的代码,有些没有用的代码也去掉首先来看一下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
结构体。所以我们就从这个结构体出发来一探究竟:
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
函数中进行初始化的,现在来看看这个函数:
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
函数。
和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);}
}
本文主要介绍了LwIP中netif网卡的初始化代码流程,如果想深入了解LwIP协议中的以太网,最好还是要先理解以太网协议,比如前面phyksz8081网卡是怎么根据参考手册进行配置的、用户设置的哪些参数需要设置到芯片的ENET寄存器中。这个如果后续有时间,我会专门写一个博客进行介绍。
本文中一些变量的作用也不太明显,光看名字看不出来是干什么的,也没有在我们分析的过程中再出现过,我们也不可能一个个刨根问底地进行分析。但是在后续代码分析过程中遇到了这些变量,回过头来看的时候,我们便会恍然大悟,或者再回来补充这些。