函数介绍
在ethernetif.c 文件中,已经有5个函数的框架,包括函数名、函数参数、函数内容等。我们要做的就是实现 ethernetif.c 中相关函数的编写。
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)
void ethernetif_input(void *pParams)
err_t ethernetif_init(struct netif *netif)
low_level_init() 为网卡的初始化函数,它主要用来完成网卡复位及参数初始化,比如网卡MAC地址长度等;
low_level_output() 为网卡数据包的发送函数,这个函数的工作是将内核数据结构pbuf描述的数据包发送出去;
low_level_input() 为网卡数据包接收函数,为了能让协议栈内核能过准确的处理数据,该函数必须将收到的数据封装为pbuf的形式;
ethernetif_input() 的主要作用是调用网卡数据包接收函数 low_level_input 从网卡处读取一个数据包,然后解析该数据包的类型(ARP包或者IP包),最后将该数据包递交给相应的上层。
ethernetif_init() 是上层在管理网络接口结构netif时会调用的函数。该函数主要完成netif结构中某些字段的初始化,并最终调用 low_level_init 完成网卡的初始化。
low_level_init()伪代码:
static void low_level_init(struct netif *netif)
{
status = Bsp_Eth_Init(); //以太网初始化
if(status == OK)
{
/* Set netif link flag */
netif->flags |= NETIF_FLAG_LINK_UP;
}
Set_MACAddr(); //设置MAC地址
//创建 ethernetif_input 任务
sys_thread_new("ETHIN",
ethernetif_input, /* 任务入口函数 */
netif, /* 任务入口函数参数 */
NETIF_IN_TASK_STACK_SIZE,/* 任务栈大小 */
NETIF_IN_TASK_PRIORITY); /* 任务的优先级 */
}
low_level_output() 实现比较复杂,要求移植者对网卡操作及协议栈数据包结构pbuf有详细的了解。
在每个pbuf中,payload字段指向当前pbuf的数据区,len字段表示数据区中数据的长度,tot_len字段表示当前pbuf及后续所有pbuf区的数据总长度。
当使用网卡发送数据时,需要发送链表p上所有pbuf中的数据:
伪代码:
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
static uint8_t buffer[LWIP_FRAMER_MAXSIZE];
for(q = p; q != NULL; q = q->next)
{
//循环将p中的数据拷贝到buffer中
}
//调用以太网发送函数将数据发送
}
low_level_input() 从网卡中读取数据,并将数据组装在pbuf中供内核取用
伪代码:
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
static uint8_t buffer[LWIP_FRAMER_MAXSIZE];
len = ETH_GetReceivedFrame(buffer); //获取一个以太网数据包
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); //为p分配空间
memcpy(p->payload, buffer, len); //将以太网卡中的数据拷贝到p的数据域
return p; //返回p
}
ethernetif_init() 函数为网卡的初始化函数,主要初始化 *netif 网卡。
err_t ethernetif_init(struct netif *netif)
{
struct ethernetif *ethernetif;
ethernetif = mem_malloc(sizeof(struct ethernetif));
netif->state = ethernetif;
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1; //网卡名字
netif->output = etharp_output; //暂不清楚,往后学习
netif->linkoutput = low_level_output; //注册底层发送函数
low_level_init(netif); //调用网卡底层初始化函数
ethernetif->ethaddr = (struct eth_addr *) &(netif->hwaddr[0]); //MAC地址
return ERR_OK;
}
发送过程
由 netif->linkoutput = low_level_output 可知,如果要发送网卡数据,在上层应用肯定将调用 netif->linkoutput() 函数,通过阅读源码可知,在 ethernet_output() 函数中调用 netif->linkoutput()。
ethernet_output()函数原型如下:
err_t
ethernet_output(struct netif * netif, struct pbuf * p,
const struct eth_addr * src, const struct eth_addr * dst,
u16_t eth_type)
{
struct eth_hdr *ethhdr;
u16_t eth_type_be = lwip_htons(eth_type);
{
if (pbuf_add_header(p, SIZEOF_ETH_HDR) != 0) {
goto pbuf_header_failed;
}
}
ethhdr = (struct eth_hdr *)p->payload;
ethhdr->type = eth_type_be;
SMEMCPY(ðhdr->dest, dst, ETH_HWADDR_LEN);
SMEMCPY(ðhdr->src, src, ETH_HWADDR_LEN);
return netif->linkoutput(netif, p); //将调用底层发送函数将数据发送出
pbuf_header_failed:
return ERR_BUF;
}
到这里,数据发送过程我们大致也清楚了,无非就是当有数据需要发送时,上层应用通过一系列的调用,最后调用:ethernet_output -> netif->linkoutput(netif, p) -> low_level_output() -> 调用最底层网卡数据发送。
接收过程
以野火f429为例,其接收方式为通过中断接收,当网卡来了一个数据帧之后,将触发以太网中断:在中断中就干了一件事情,发布 s_xSemaphore 信号量 通知 ethernetif_input() 线程去接收数据帧。
简化伪代码:
void ETH_IRQHandler(void)
{
xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );
}
之后, ethernetif_input() 线程获取到信号量,开始执行。
void ethernetif_input(void *pParams)
{
while(1)
{
if(xSemaphoreTake( s_xSemaphore, portMAX_DELAY ) == pdTRUE)
{
TRY_GET_NEXT_FRAGMENT:
p = low_level_input(netif); //调用底层数据帧获取函数获取网卡数据
if(p != NULL)
{
if (netif->input(p, netif) != ERR_OK) //递交网卡数据到上层
{
pbuf_free(p);
p = NULL;
}
else
{
xSemaphoreTake( s_xSemaphore, 0); //防止网卡中还有剩余数据
goto TRY_GET_NEXT_FRAGMENT;
}
}
}
}
}
也就是说,我们网卡将数据递交到上层应用是通过 netif->input(p, netif) 实现的,那么 netif->input(p, netif)函数原型又是什么?
通过继续深扒发现:
在 netif_add() 函数中,注册了 netif->input
netif_add() 函数原型
netif_add(struct netif *netif,
#if LWIP_IPV4
const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
#endif /* LWIP_IPV4 */
void *state, netif_init_fn init, netif_input_fn input)
{
netif->state = state;
netif->num = netif_num;
netif->input = input; //这里注册 input
netif_set_addr(netif, ipaddr, netmask, gw); //设置网络IP
if (init(netif) != ERR_OK) { //调用初始化函数
return NULL;
}
}
而在 void TCPIP_Init(void) 函数中,调用 netif_add();
void TCPIP_Init(void)
{
tcpip_init(NULL, NULL);
IP4_ADDR(&ipaddr,IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
IP4_ADDR(&netmask,NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3);
IP4_ADDR(&gw,GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
}
到这里,我们就可以看出来,将网卡数据投递到上层应用是用 tcpip_input() 实现的。
err_t
tcpip_input(struct pbuf *p, struct netif *inp)
{
#if LWIP_ETHERNET
if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
return tcpip_inpkt(p, inp, ethernet_input);
} else
#endif /* LWIP_ETHERNET */
return tcpip_inpkt(p, inp, ip_input);
}
而在 tcpip_input() 中,则是调用 tcpip_inpkt() 将数据递交到上层中。
err_t
tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
{
msg->type = TCPIP_MSG_INPKT;
msg->msg.inp.p = p;
msg->msg.inp.netif = inp;
msg->msg.inp.input_fn = input_fn;
if (sys_mbox_trypost(&tcpip_mbox, msg) != ERR_OK) {
memp_free(MEMP_TCPIP_MSG_INPKT, msg);
return ERR_MEM;
}
return ERR_OK;
}
在这儿我们可以看到,通过发布 tcpip_mbox 消息队列达到线程之间的同步。而哪个线程等到 tcpip_mbox 消息队列呢?那就是内核线程。
static void
tcpip_thread(void *arg)
{
struct tcpip_msg *msg;
LWIP_UNUSED_ARG(arg);
LWIP_MARK_TCPIP_THREAD();
LOCK_TCPIP_CORE();
if (tcpip_init_done != NULL) {
tcpip_init_done(tcpip_init_done_arg);
}
while (1) { /* MAIN Loop */
LWIP_TCPIP_THREAD_ALIVE();
/* wait for a message, timeouts are processed while waiting */
TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg); //等待消息队列
if (msg == NULL) {
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
LWIP_ASSERT("tcpip_thread: invalid message", 0);
continue;
}
tcpip_thread_handle_msg(msg);
}
}
整个数据的接收过程就是 :
ETH_IRQHandler() >> ethernetif_input() >> netif->input(p, netif) >> tcpip_input() >> tcpip_inpkt() >> tcpip_thread()
这样,就实现了我们从网卡接收到的数据,最终流入内核线程中。
而在内核中,又调用 ip_input() 函数,这样,数据就进入了 IP层(网络层)中。