OS-lwip

lwip是一个轻型的网络协议栈,基本满足大部分应用,参考ucos的代码框架,lwip目录下arch/ucos-iiucos对于lwip的接口,这个接口是严格按照lwip的文档来做的;其他目录不需要修改任何文件,直接下载lwip代码即可,这样的代码可读性很强,非常的直观;

协议栈portingucos,主要涉及到:

lwip需要的任务接口,信号量接口,消息队列接口,时钟接口,这些wip官方的基于ucosSDK,已经做了,主要功能就是和ucosapi做一个封装;

另外一方面就是lwip的底层硬件接口,也就是网卡驱动接口;

所以需要做的工作,一是lwip的初始化以及起lwip任务,二就是封装底层网卡驱动

 

1.        lwip接口分析

SDK做了工作就是把ucosAPI封装成lwip自己采用的API,下面就是lwipAPI,包括任务,信号量,邮箱(类似消息队列),时钟,这些函数需要重新封装的,用ucosAPI来实现,这些函数几乎和ucos的类似,区别的只是有一些参数的少许区别,arch/ucos-ii/sys_arch.c就是SDK的核心接口,用来实现封装,如果不是ucos,比如nucleus或者rtems等之类的,这个文件也是唯一需要更改的

 

#define sys_init()

#define sys_timeout(m,h,a)

#define sys_untimeout(m,a)

#define sys_sem_new(c) c

#define sys_sem_signal(s)

#define sys_sem_wait(s)

#define sys_sem_free(s)

#define sys_mbox_new() 0

#define sys_mbox_fetch(m,d)

#define sys_mbox_post(m,d)

#define sys_mbox_free(m)

 

#define sys_thread_new(t,a,p)   

这里sys_timeoutsys_untimeout是定时器函数,sys_sem_waitsys_mbox_fetch实现有点技巧的,因为lwip的增加定时函数,这就涉及到等待时间到后的处理,等待时间为0的时候持续等待,如果不为0,时间到后就就绪状态;

在创建lwip task的函数中维护每个一个指针数组,根据任务数目来的;这样每一个任务就维护一个定时事件链表,因为可能一个task中有几个定时事件,定时事件链表中就维护几个

 

struct timeoutlist {

  struct sys_timeouts timeouts;

  INT8U prio;

};

 

static struct timeoutlist timeoutlist[LWIP_MAX_TASKS];

sys_thread_t

sys_thread_new(void (* function)(void *arg), void *arg, int prio)

{

......

    timeoutlist[sys_thread_no].timeouts.next = NULL;

    timeoutlist[sys_thread_no].prio = prio;

 

    ++sys_thread_no; /* next task created will be one lower to this one */

......

}

 

sys_arch_timeouts返回值是对应任务的定时事件链表开始指针,默认创建的时候timeouts->next为空,所以第一个定时事件的timeout挂接后,增加一个定时事件就返回的

当第二个定时事件时执行时就要对定时事件排序了,当第二个定时时间短的话,第二个定时事件直接插入链首,并且next的时间取减去第一个和第二个定时值的时间差;第三个定时事件的时候,按照时间排序插到链表合适位置;这段代码处理算是蛮有技巧的,当然sys_untimeout是删除定时事件,过程刚好和创建相反,从链表中删除定时事件

 

  timeouts = sys_arch_timeouts();

 

  if (timeouts->next == NULL) {

    timeouts->next = timeout;

    return;

  }

 

  if (timeouts->next->time > msecs) {

    timeouts->next->time -= msecs;

    timeout->next = timeouts->next;

    timeouts->next = timeout;

  } else {

    for(t = timeouts->next; t != NULL; t = t->next) {

      timeout->time -= t->time;

      if (t->next == NULL || t->next->time > timeout->time) {

        if (t->next != NULL) {

          t->next->time -= timeout->time;

        }

        timeout->next = t->next;

        t->next = timeout;

        break;

      }

    }

  }

 

sys_mbox_fetch函数的实现也是非常的有技巧,将定时函数和消息队列一起处理,非常有意思的一代代码,参考下面注释

void

sys_mbox_fetch(sys_mbox_t mbox, void **msg)

{

  u32_t time;

  struct sys_timeouts *timeouts;

  struct sys_timeout *tmptimeout;

  sys_timeout_handler h;

  void *arg;

 

 

 again:

  timeouts = sys_arch_timeouts();

 

  if (!timeouts || !timeouts->next) {

    sys_arch_mbox_fetch(mbox, msg, 0);

    /* 没有定时事件最好了,直接无条件的持续等待消息 */

  } else {

    if (timeouts->next->time > 0) {

      time = sys_arch_mbox_fetch(mbox, msg, timeouts->next->time);

        /* 一个定时事件大于0*/

    } else {

      time = SYS_ARCH_TIMEOUT;

        /* 定时事件到,实际就是定时器函数可以跑了 */

    }

 

    if (time == SYS_ARCH_TIMEOUT) {

      /* If time == SYS_ARCH_TIMEOUT, a timeout occured before a message

   could be fetched. We should now call the timeout handler and

   deallocate the memory allocated for the timeout. */

        /**/

      tmptimeout = timeouts->next;

      timeouts->next = tmptimeout->next;

      h = tmptimeout->h;

      arg = tmptimeout->arg;

      memp_free(MEMP_SYS_TIMEOUT, tmptimeout);

      if (h != NULL) {

        LWIP_DEBUGF(SYS_DEBUG, ("smf calling h=%p(%p)/n", (void *)h, (void *)arg));

          h(arg);

        /* 执行定时事件的函数 */

      }

 

      /* We try again to fetch a message from the mbox. */

        /* 运行到这里:或者是定时函数的定时事件完成;或者是sys_arch_mbox_fetch超时没有收到消息,返回开始继续进入等待状态,和OSQPend超时没收到消息一样的处理 */

      goto again;

    } else {

      /* If time != SYS_ARCH_TIMEOUT, a message was received before the timeout

   occured. The time variable is set to the number of

   milliseconds we waited for the message. */

        /* 运行到这里是sys_arch_mbox_fetch在规定时间收到消息了,刷新时间参数 */

      if (time <= timeouts->next->time) {

  timeouts->next->time -= time;

      } else {

  timeouts->next->time = 0;

      }

    }

 

  }

}

 

 

arch/ucos-ii/include/lwipopts.h主要是lwip的功能开关,函数的使能,类似ucosos_cfg.h,默认的即可

 

2.        底层工作

 

底层网卡要工作起来,以及网卡数据接收丢给上层,上层丢数据给网卡,这个接口需要做,主要就是ucos-ii/netif的两个文件来实现的,还好lwip都提供了框架,自己只需要按照接口把函数填进去,下面是lwip提供的接口

 

err_t

ethernetif_init(struct netif *netif)

 

static void

ethernetif_input(struct netif *netif)

 

static err_t

ethernetif_output(struct netif *netif, struct pbuf *p,

      struct ip_addr *ipaddr)

下面是网卡API,需要封装到lwipAPI上,ethernetif_initlow_level_init用做网卡的初始化;中断到来的时候ne2k_isrethernetif_inputlow_level_input)接收包最终丢给三层处理;ethernetif_outputlow_level_output发送包

 

static void

low_level_init(struct netif *netif)

 

static struct pbuf *

low_level_input(struct netif *netif)

 

static err_t

low_level_output(struct netif *netif, struct pbuf *p)

 

/* void NICISR(void) interrupt */

void ne2k_isr(void)

下面是ethernetif_input收到ARPetharp_arp_input或者IPetharp_ip_input后的处理,之后就到lwip协议栈的工作了,API的工作到此为止

  switch (htons(ethhdr->type)) {

    case ETHTYPE_IP:

      etharp_ip_input(netif, p);

      pbuf_header(p, -14);

      netif->input(p, netif);

      break;

     

    case ETHTYPE_ARP:

      etharp_arp_input(netif, ethernetif->ethaddr, p);

      break;

    default:

      pbuf_free(p);

      p = NULL;

      break;

  }

这里驱动采用的SEND COMMAND方式(个人觉得比Remote DMA READ方式要简单)

初始化bnry读指针 = curr写指针 = PSTART

有数据包要读时,向DMA长度寄存器1写入0x0f(奇怪???不明白)

执行SEND COMMAND命令

然后网卡自动做了如下工作:

用当前的bnry读指针指向的地址写入DMA起址寄存器0,1

用以太网包头中的包长度初始化DMA长度寄存器0,1

先读18个字节的包头看看要不要这个包(4B的网卡物理状态信息,14B的以太网头)

0x10端口读数据

可以看到这里处理和u-boot的轮询处理不太一样,是因为这里直接读以太网头出来了,就判断arp还是ip开始丢给协议栈处理了,u-boot那边是拿到整个包,在上层应用中再做处理的

 

3.        lwip应用

lwip协议栈初始化及任务调用,按照lwip的例子代码,起一个task单独给lwip协议栈(当然实际上lwip协议栈内部另外再起了一些任务的),这个任务主要做一些协议栈,以及TCPIP的初始化之后,就添加硬件设备了,如下代码,这样的话,这个网卡就可以工作了;ping 192.168.18.119 -t,一切通信正常

 

    //add loop interface

    loop_netif = mem_malloc(sizeof(struct netif));

    IP4_ADDR(&gw, 127,0,0,1);

    IP4_ADDR(&ipaddr, 127,0,0,1);

    IP4_ADDR(&netmask, 255,0,0,0);

    netif_add(loop_netif, &ipaddr, &netmask, &gw, NULL, loopif_init, tcpip_input);

       

    //add rtl8019 interface

      rtl8019_netif = mem_malloc(sizeof(struct netif));

    IP4_ADDR(&gw, 192,168,10,1);

    IP4_ADDR(&ipaddr, 192,168,10,119);

    IP4_ADDR(&netmask, 255,255,255,0);

    netif_set_default(netif_add(rtl8019_netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input));

接着可以写一个小的socket代码测试协议栈的健壮性

 

4.        lwip协议

 

这张图就是lwip协议栈的整个结构,网络层和传输层就是lwip做的工作,主要做包判断,填数据头,剥数据头,本身工作就是网络接口层的封装以及和底层驱动,当然还有就是黄色部分的mboxos封装

lwip 

5.        小结

编译就搞了好个晚上,这个SDK是基于lwip-1.3.0来实现的,我用的还是老的1.1.0,一些lwipapi参数不匹配了,不过还好,都是一些小问题

中断不建议边沿触发,建议电平触发,奇怪边沿触发经常无法进中断

lwip的定时函数代码加得有参考价值,移植到ucos是个非常不错的功能(ucos2 2.83增加了定时管理os_tmr.c 1)

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值