一、网络驱动简介

openvela 内置了一套轻量级的 TCP/IP 网络协议栈,并提供了一套网络驱动框架。通过该框架,内置的 TCP/IP 协议栈可以与芯片驱动交互,实现网络数据包的收发。

在网络驱动架构中,openvela 提供了通用的 上半部分实现(UpperHalf),厂商只需实现驱动的 下半部分(LowerHalf),即可完成驱动适配工作,从而赋予 openvela 访问互联网的能力。

二、配置说明

openvela 使用 Kconfig 工具进行功能配置。以下是与网络相关的主要配置选项:

1、网络协议栈配置

根据需求启用所需的网络协议:

/* 网络协议栈配置相关,按需启用所需协议 */
CONFIG_NET
CONFIG_NET_TCP
CONFIG_NET_UDP
CONFIG_NET_ICMP
CONFIG_NET_IGMP
CONFIG_NET_IPv4
CONFIG_NET_IPv6
CONFIG_NET_MLD
CONFIG_NET_ROUTE
CONFIG_NET_ETHERNET

 

2、驱动相关配置

以下选项用于配置网络驱动功能:

/* Driver配置相关 */
CONFIG_NETDEVICES
CONFIG_NETDEV_IOCTL
CONFIG_NETDEV_WIRELESS_HANDLER

 

三、数据收发流程

1、发送流程

 

2、接收流程

 

四、驱动适配接口说明

本节介绍 openvela 网络驱动适配接口的设计与实现,主要基于 nuttx/net/netdev_lowerhalf.h 文件。通过这些接口,开发者可以实现网络设备的驱动适配工作。

1、驱动接口

驱动接口定义

以下是网络设备操作接口的定义,包含设备启动、关闭、数据收发、MAC 地址管理等功能。

struct netdev_ops_s
{
  CODE int (*ifup)(FAR struct netdev_lowerhalf_s *dev);
  CODE int (*ifdown)(FAR struct netdev_lowerhalf_s *dev);

  CODE int (*transmit)(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt);
  CODE FAR netpkt_t (*receive)(FAR struct netdev_lowerhalf_s *dev);

  CODE int (*addmac)(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac);
  CODE int (*rmmac)(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac);
  CODE int (*ioctl)(FAR struct netdev_lowerhalf_s *dev, int cmd, unsigned long arg);

  CODE void (*reclaim)(FAR struct netdev_lowerhalf_s *dev);
}

 

无线网络操作接口

无线网络设备的操作接口定义如下,支持无线网络的连接、断开以及相关参数的读写操作。

struct wireless_ops_s
{
  CODE int (*connect)(FAR struct netdev_lowerhalf_s *dev);
  CODE int (*disconnect)(FAR struct netdev_lowerhalf_s *dev);

  iw_handler_rw essid;
  iw_handler_rw bssid;
  iw_handler_rw passwd;
  iw_handler_rw mode;
  iw_handler_rw auth;
  iw_handler_rw freq;
  iw_handler_rw bitrate;
  iw_handler_rw txpower;
  iw_handler_rw country;
  iw_handler_rw sensitivity;
  iw_handler_rw scan;
  iw_handler_ro range;
};

 

网络设备结构体

网络设备的核心结构体 netdev_lowerhalf_s 定义如下:

struct netdev_lowerhalf_s
{
  FAR const struct netdev_ops_s *ops;
  FAR const struct wireless_ops_s *iw_ops;
  atomic_int quota[NETPKT_TYPENUM]; /* Max # of buffer held by driver */

  ...
};

 

驱动适配 API

以下是网络设备适配的主要 API:

int netdev_lower_register(FAR struct netdev_lowerhalf_s *dev,
                          enum net_lltype_e lltype);
int netdev_lower_unregister(FAR struct netdev_lowerhalf_s *dev);
void netdev_lower_carrier_on(FAR struct netdev_lowerhalf_s *dev);
void netdev_lower_carrier_off(FAR struct netdev_lowerhalf_s *dev);

void netdev_lower_rxready(FAR struct netdev_lowerhalf_s *dev);
void netdev_lower_txdone(FAR struct netdev_lowerhalf_s *dev);

 

网络设备操作说明

以下是网络设备操作的 API 说明:

  • ifup():启动网络设备。
  • ifdown():关闭网络设备。
  • transmit():通知驱动可以发送数据包,驱动返回发送结果。
  • receive():从驱动获取接收到的数据包,驱动返回接收结果(报文)。
  • addmac()(可选):向网络设备添加接收组播的 MAC 地址。如果设备不涉及 MAC 地址过滤,可不实现。
  • rmmac()(可选):从网络设备移除接收组播的 MAC 地址。如果设备不涉及 MAC 地址过滤,可不实现。
  • ioctl()(可选):实现其他控制命令,主要用于无线网络相关命令(与 wireless_ops_s 二选一实现即可)。
  • reclaim()(可选):用于资源回收。当发送缓冲区(TX Quota)耗尽时,上层会调用该接口。主要用于辅助设备的轮询模式资源回收。如果设备能及时释放缓冲区并调用 TX Done,则无需实现。

2、NetPKT 接口

NetPKT 是 transmit 和 receive 接口与上层交换网络报文使用的数据结构。本节将介绍 NetPKT 的相关接口及其使用方法。

NetPKT Buffer 结构

 

  • reserved:保留字段,可能被驱动使用。
  • base:Buffer 的起始地址。
  • data:数据的起始地址。
  • free:Buffer 的空闲部分。
  • datalen:当前数据的长度。
  • data end:Buffer 的结束地址。
  • next:指向下一个 Buffer 的指针(用于链式结构)。

Buffer 接口

以下是与 NetPKT Buffer 操作相关的接口定义:

#define NETPKT_BUFLEN 1518 /* 不同产品上配置不同,128 ~ 1600 均有可能 */

enum netpkt_type_e
{
  NETPKT_TX,
  NETPKT_RX,
  NETPKT_TYPENUM
};

FAR netpkt_t *netpkt_alloc(FAR struct netdev_lowerhalf_s *dev,
                           enum netpkt_type_e type);
void netpkt_free(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt,
                 enum netpkt_type_e type);

/* 与pkt buffer互相copy数据,任何场景可用,无需关心内部buffer结构 */
int netpkt_copyin(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt,
                  FAR const uint8_t *src, unsigned int len, int offset);
int netpkt_copyout(FAR struct netdev_lowerhalf_s *dev, FAR uint8_t *dest,
                   FAR const netpkt_t *pkt, unsigned int len, int offset);

/* 直接获取buffer中的地址,直接操作单个连续buffer(用来减少一次copy)时使用 */
FAR uint8_t *netpkt_getdata(FAR struct netdev_lowerhalf_s *dev,
                            FAR netpkt_t *pkt);
FAR uint8_t *netpkt_getbase(FAR netpkt_t *pkt);

/* 当前data长度 */
void netpkt_setdatalen(FAR struct netdev_lowerhalf_s *dev,
                       FAR netpkt_t *pkt, unsigned int len);
unsigned int netpkt_getdatalen(FAR struct netdev_lowerhalf_s *dev,
                               FAR netpkt_t *pkt);

/* 重设data起始点 */
void netpkt_reset_reserved(FAR struct netdev_lowerhalf_s *dev,
                           FAR netpkt_t *pkt, unsigned int len);
/* 判断是否连续(数据在一个还是多个buffer里) */
bool netpkt_is_fragmented(FAR netpkt_t *pkt);

 

TX Buffer 布局

在 openvela 的网络驱动中,TX Buffer(Transmit Buffer,发送缓冲区)用于存储即将发送的数据。以下是 TX Buffer 的布局结构及相关说明。

以下是 TX Buffer 的布局结构:

  • reserved:保留字段,可能被驱动使用。
  • tx data:发送数据的起始地址。
  • base:Buffer 的起始地址。
  • data:数据的起始地址。
  • free:Buffer 的空闲部分。
  • datalen:当前数据的长度。
  • data end:Buffer 的结束地址。

说明:TX_RESERVED = LL_GUARDSIZE - LL_HDRLEN

RX Buffer 布局

在 openvela 的网络驱动中,RX Buffer(Receive Buffer,接收缓冲区)用于存储接收到的数据。以下是 RX Buffer 的布局结构及相关说明。

以下是 RX Buffer 的布局结构:

  • rx head:接收数据的头部信息。
  • rx data:接收数据的起始地址。
  • base:Buffer 的起始地址。
  • data:数据的起始地址。
  • free:Buffer 的空闲部分。
  • datalen:当前数据的长度。
  • data end:Buffer 的结束地址。
  • reserved:保留字段。

3、其他接口

  • wdog:

  • work_queue: 内核利用独立线程实现的异步执行机制,与 Linux 的工作队列类似,可用于任务的异步处理。例如:

    • 网络数据包的收发处理
    • 中断的下半部处理
  • ninfo,nwarn,nerr: 用于打印不同等级的日志(Log),便于调试网络模块。 打开网络模块的日志功能需要启用以下配置选项:

    CONFIG_DEBUG_NET
    CONFIG_DEBUG_NET_ERROR
    CONFIG_DEBUG_WARN
    CONFIG_DEBUG_NET_WARN
    CONFIG_DEBUG_INFO
    CONFIG_DEBUG_NET_INFO

     

五、驱动实现

本节介绍 Openvela 驱动实现的关键部分,包括驱动数据结构、网络数据包发送和接收的实现方法。示例代码基于 arch/sim/src/sim/sim_netdriver.c。

1、驱动数据结构

以下是驱动数据结构的定义和初始化方法:

struct <chip>_priv_s
{
  /* This holds the information visible to the Vela network */
  struct netdev_lowerhalf_s dev;

  ...
};

static const struct netdev_ops_s g_ops =
{
  .ifup     = <chip>_ifup,
  .ifdown   = <chip>_ifdown,
  .transmit = <chip>_transmit,
  .receive  = <chip>_receive,
  .addmac   = <chip>_addmac,
  .rmmac    = <chip>_rmmac,
  .ioctl    = <chip>_ioctl,
  .reclaim  = <chip>_reclaim
};

/* Wi-Fi 驱动注册函数可以按照如下方式实现,<chip>指芯片名称
 * netdev_lower_register() 为vela提供的网络设备接口,用于注册网络设备驱动
 */

int <chip>_netdev_init(FAR struct <chip>_priv_s *priv)
{
    FAR struct netdev_lowerhalf_s *dev = &priv->dev;

    dev->ops = &g_ops;

    /* 当前驱动最多可以同时持有的buffer数
     * 发送:每当上层transmit交给驱动一个pkt时,相当于驱动持有了一个pkt,会减少tx配额。
     *   tx配额用完时,上层不会再调用transmit,驱动通过netpkt_free释放pkt后配额恢复,transmit继续
     * 接收:每当驱动通过netpkt_alloc获取pkt时,相当于驱动持有了一个pkt,会减少rx配额。
     *   rx配额用完后,netpkt_alloc会失败,通过receive接口向上层提交pkt即可恢复配额
     *   (可以通过调用netdev_lower_rxready触发receive)
     * 如果驱动对每个包都单独处理(使用后立即释放),可以设置为1
     */
    dev->quota[NETPKT_TX] = 1;
    dev->quota[NETPKT_RX] = 1;

    return netdev_lower_register(dev, NET_LL_ETHERNET);
}

 

2、网络数据包发送

数据发送流程

  1. 上半部分(Upper Half):调用 transmit 接口发送数据包。
  2. 下半部分(Lower Half):驱动处理数据包并完成发送。
  3. 发送完成通知:通过 txdone 通知上层发送完成。

 

 

数据发送代码示例

struct <chip>_txhead_s; /* 假定硬件在数据前需要一些头部 */

static int <chip>_transmit(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt)
{
  FAR struct <chip>_priv_s *priv = (FAR struct <chip>_priv_s *)dev;
  unsigned int len = netpkt_getdatalen(dev, pkt);
  
  if (netpkt_is_fragmented(pkt))
    {
      /* 内存不连续,需要用Copy出来的方式,直接向devbuf中copy L2数据 */
      uint8_t devbuf[1600];
      netpkt_copyout(dev, devbuf + sizeof(struct <chip>_txhead_s), pkt, len, 0);

      /* Transmit */
    }
  else
    {
      /* 内存连续情况下也可以直接使用buffer(可选,可以一直用上面的分支copy) */
      FAR uint8_t *databuf = netpkt_getdata(dev, pkt);
      FAR uint8_t *devbuf  = databuf - sizeof(struct <chip>_txhead_s);

      /* 检查是否越界 或 不符合硬件对齐要求(取决于网卡硬件要求,可省略) */
      if (devbuf < netpkt_getbase(pkt) || check_align(devbuf) != OK)
        {
          /* Fail or fallback to copyout */
        }

      /* Transmit */
    }

  return OK;
}

static void <chip>_txdone_interrupt(FAR struct <chip>_priv_s *priv)
{
  FAR struct netdev_lowerhalf_s *dev = &priv->dev;
  
  /* 驱动进行一些处理(如果需要) */
  
  /* 释放buffer并通知上层 */
  netpkt_free(dev, pkt, NETPKT_TX);
  netdev_lower_txdone(dev);
}

 

3、网络数据包接收

本节介绍网络数据包接收的实现流程,包括中断处理和数据包接收的具体实现。

 

 

struct <chip>_rxhead_s;

/* 中断处理 */
static void <chip>_rxready_interrupt(FAR struct <chip>_priv_s *priv)
{
  FAR struct netdev_lowerhalf_s *dev = &priv->dev;
  netdev_lower_rxready(dev);
}

/* 数据包接收 */
static FAR netpkt_t *<chip>_receive(FAR struct netdev_lowerhalf_s *dev)
{
  /* 也可以提前申请pkt,提前收完数据再调用rxready并通过receive返回 */
  FAR netpkt_t *pkt = netpkt_alloc(dev, NETPKT_RX);

  if (pkt)
    {
#if NETPKT_BUFLEN < 15xx
      uint8_t devbuf[1600];

      /* 从src进行copy的方式,len对应L2的完整数据长度 */
      len = receive_data_into(devbuf);
      netpkt_copyin(dev, pkt, devbuf + sizeof(struct <chip>_rxhead_s), len, 0);
#else
      /* 直接写入pkt对应buffer的方式,len对应L2的完整数据长度 */
      len = receive_data_into(netpkt_getbase(pkt));
      netpkt_resetreserved(&priv->dev, pkt, sizeof(struct <chip>_rxhead_s));
      netpkt_setdatalen(&priv->dev, pkt, len);
#endif
    }

  return pkt;
}

 

4、WAPI 命令对接

WAPI(Wireless Application Protocol Interface) 命令依赖驱动的 ioctl() 接口实现。上层通过 WAPI 命令设置或获取 Wi-Fi 参数,控制 Wi-Fi 的行为。常用功能已被抽象为接口,在初始化时将其设置到 dev->iw_ops 即可。

WAPI 接口定义

typedef int (*iw_handler_rw)(FAR struct netdev_lowerhalf_s *dev,
                             FAR struct iwreq *iwr, bool set);
typedef int (*iw_handler_ro)(FAR struct netdev_lowerhalf_s *dev,
                             FAR struct iwreq *iwr);

struct wireless_ops_s
{
  /* Connect / disconnect operation, should exist if essid or bssid exists */

  int (*connect)(FAR struct netdev_lowerhalf_s *dev);
  int (*disconnect)(FAR struct netdev_lowerhalf_s *dev);

  /* The following attributes need both set and get. */

  iw_handler_rw essid;
  iw_handler_rw bssid;
  iw_handler_rw passwd;
  iw_handler_rw mode;
  iw_handler_rw auth;
  iw_handler_rw freq;
  iw_handler_rw bitrate;
  iw_handler_rw txpower;
  iw_handler_rw country;
  iw_handler_rw sensitivity;

  /* Scan operation: start scan (set=1) / get scan result (set=0). */
  iw_handler_rw scan;

  /* Get-only attributes. */
  iw_handler_ro range;
};

说明:

  • 驱动需要完成列表中所有 WAPI 命令的适配,完成上述接口后,WAPI 命令即可用于验证。

WAPI 命令列表

序号 Item Usage Results
1 Show info wapi show <ifname> 打印当前 <ifname> 对应网卡的相关信息。
2 Scan wapi scan <ifname> 打印扫描到的 AP 信息。
3 Scan SSID wapi scan <ifname> <essid> 打印指定 ESSID 的扫描信息。
4 Set channel or frequency wapi freq <ifname> <frequency/channel> <index/flag> 在多个相同 SSID 但频道(Channel)不同的场景下,指定频道。
5 Set ESSID wapi essid <ifname> <essid> <index/flag> 设置 ESSID 并完成配网操作。
Flag 说明:
0:断开连接
1:连接
2:设置 ESSID,但暂不连接,待设置 BSSID 后连接。
6 Set PSK wapi psk <ifname> <passphrase> <index/flag> 设置 AP 密码及加密类型(开放网络无需此命令)。
Flag 说明:
1:WEP
2:TKIP
3:CCMP
7 Disconnect wapi disconnect <ifname> 断开当前无线连接(STA/AP 模式),断开后网络通信将中断。
8 Set mode (STA/AP) wapi mode <ifname> <index/mode> 设置无线网络的工作模式。
Mode 说明:
2:STA 模式
3:AP 模式
9 Set BSSID wapi ap <ifname> <``MAC`` address> 指定 BSSID(基本服务集标识符)连接,防止路由 AP 修改 ESSID。
10 Save config to wapi.conf wapi save_config <ifname> 保存当前联网信息到 /data/wapi.conf 文件。
11 Reconnect from wapi.conf wapi reconnect <ifname> 从 /data/wapi.conf 加载配置并重新联网。
12 Set Country Code wapi country <ifname> <country code> 设置国家码。
13 Sensitivity(RSSI) wapi sense <ifname> 获取当前连接的信号强度(RSSI,接收信号强度指示)。

WAPI 命令使用示例

以下是 WAPI 命令的常见使用场景:

AP 模式

wapi disconnect wlan0
ifup wlan0
wapi mode wlan0 3
wapi psk wlan0 <psk> 3
wapi essid wlan0 <ssid> 1
dhcpd wlan0 &

STA 模式

  1. 通过 ESSID 连接:

    ifup wlan0
    wapi mode wlan0 2
    wapi psk wlan0 <psk> 3
    wapi freq wlan0 1 1
    wapi essid wlan0 <ssid> 1
    renew wlan0

     

  2. 通过 BSSID 连接:

    ifup wlan0
    wapi mode wlan0 2
    wapi psk wlan0 <psk> 3
    wapi freq wlan0 1 1
    wapi ap wlan0 <bssid>
    renew wlan0

     

配置保存与加载

wapi save_config wlan0   # 保存当前配置到 wapi.conf  
wapi reconnect wlan0     # 从 wapi.conf 加载配置并重新联网

5、如何实现双网卡(AP/STA)

在 openvela 的 Wi-Fi 框架中,支持 AP(Access Point,接入点) 和 STA(Station,站点) 模式的共存。通过在驱动初始化阶段枚举两个网卡实例(如 wlan0 和 wlan1),可以分别固定为 STA 模式和 AP 模式。

双网卡模式的功能说明

  1. 双重功能支持: 模组既可以连接到环境中的无线热点(STA 模式),又可以允许外部无线终端接入(AP 模式)。
  2. DHCP 功能支持:
    • 在 wlan0 接口上支持 DHCP Client 功能,从外部无线热点动态获取 IP 地址。
    • 在 wlan1 接口上支持 DHCP Server 功能,为外部无线终端动态分配 IP 地址。
  3. 接口独立性: STA 和 AP 两个网络接口独立工作,互不干扰:
    • 一个接口的 UP/DOWN 状态发生变化时,另一个接口不受影响。
    • 一个接口上有 Wi-Fi 连接建立或释放时,另一个接口上的 Wi-Fi 连接不受影响。

驱动实现注意事项

  1. 弃用方法:
    • WAPI_ESSID_DELAY_ON 方式已经弃用。
  2. 新的连接逻辑:
    • 设置 AP 的 MAC 地址时,如果没有设置过 ESSID(扩展服务集标识符),则不会触发任何动作。
    • 当设置 ESSID 时,会触发连接操作。
    • 如果之前已经设置过 ESSID,再设置 AP 的 MAC 地址时,也会触发新的连接。

参考实现代码

以下是 Linux 中相关实现的参考代码链接:

六、测试工具

openvela 提供了多个网络测试工具,用于驱动移植和网络吞吐量(Throughput)调试。以下是相关工具的说明和用法。

1、ping (Packet Internet Groper)

功能说明

  • ping 的作用:用于测试网络连通性和响应时间。
  • 配置启用:通过配置 CONFIG_NETUTILS_PING 选项可以启用 ping 功能。

使用方法

Usage: ping [-c <count>] [-i <interval>] [-W <timeout>] [-s <size>] <hostname>
       ping -h

Where:
  <hostname> is either an IPv4 address or the name of the remote host
   that is requested the ICMPv4 ECHO reply.
  -c <count> determines the number of pings.  Default 10.
  -i <interval> is the default delay between pings (milliseconds).
    Default 1000.
  -W <timeout> is the timeout for wait response (milliseconds).
    Default 1000.
  -s <size> specifies the number of data bytes to be sent.  Default 56.
  -h shows this text and exits.

> r //此命令表示每个包携带1400字节数据以100毫秒的间隔给 www.xiaomi.com 发送50次,等待response的超时时间为200毫秒

2、iperf2/3

有关 iperf2 和 iperf3 的详细使用说明,请参考以下文档:

3、tcpdump

有关 tcpdump 的详细使用说明,请参考以下文档:

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐