NetDev组件
Net 组件的主要内容有 4 个方面,分别是 sal ,AT,lwip 与 netdev。
使用 RT-Thread上网的联网设备,品类繁多,方式各异,但其实追踪到依赖的基础 net 能力其实也没有多复杂。实际上大部分的功能依赖的基础能力,只有两组而已,即 AT ,Lwip;而到用户,大部分就只用到 Sal 这一个概念。
netdev介绍
netdev(network interface device),即网络接口设备,又称网卡。每一个用于网络连接的设备都可以注册成网卡,为了适配更多的种类的网卡,避免系统中对单一网卡的依赖,RT-Thread 系统提供了 netdev 组件用于网卡管理和控制。
功能
- 抽象网卡概念,每个网络连接设备可注册唯一网卡;
- 提供多种网络连接信息查询,方便用户实时获取当前网卡网络状态;
- 建立网卡列表和默认网卡,可用于网络连接的切换;
- 提供多种网卡操作接口(设置 IP、DNS 服务器地址,设置网卡状态等);
- 统一管理网卡调试命令(ping、ifconfig、netstat、dns 等命令);
结构体
struct netdev
{
rt_slist_t list;
char name[RT_NAME_MAX]; /* network interface device name */
ip_addr_t ip_addr; /* IP address */
ip_addr_t netmask; /* subnet mask */
ip_addr_t gw; /* gateway */
#if NETDEV_IPV6
ip_addr_t ip6_addr[NETDEV_IPV6_NUM_ADDRESSES]; /* array of IPv6 addresses */
#endif /* NETDEV_IPV6 */
ip_addr_t dns_servers[NETDEV_DNS_SERVERS_NUM]; /* DNS server */
uint8_t hwaddr_len; /* hardware address length */
uint8_t hwaddr[NETDEV_HWADDR_MAX_LEN]; /* hardware address */
uint16_t flags; /* network interface device status flag */
uint16_t mtu; /* maximum transfer unit (in bytes) */
const struct netdev_ops *ops; /* network interface device operations */
netdev_callback_fn status_callback; /* network interface device flags change callback */
netdev_callback_fn addr_callback; /* network interface device address information change callback */
#ifdef RT_USING_SAL
void *sal_user_data; /* user-specific data for SAL */ // to sal pfs and ops
#endif /* RT_USING_SAL */
void *user_data; /* user-specific data */ // to netif(在netdev_register(...)当中赋值的)
};
网卡OPS
struct netdev_ops
{
/* set network interface device hardware status operations */
int (*set_up)(struct netdev *netdev);
int (*set_down)(struct netdev *netdev);
/* set network interface device address information operations */
int (*set_addr_info)(struct netdev *netdev, ip_addr_t *ip_addr, ip_addr_t *netmask, ip_addr_t *gw);
int (*set_dns_server)(struct netdev *netdev, uint8_t dns_num, ip_addr_t *dns_server);
int (*set_dhcp)(struct netdev *netdev, rt_bool_t is_enabled);
#ifdef RT_USING_FINSH
/* set network interface device common network interface device operations */
int (*ping)(struct netdev *netdev, const char *host, size_t data_len, uint32_t timeout, struct netdev_ping_resp *ping_resp);
void (*netstat)(struct netdev *netdev);
#endif
/* set default network interface device in current network stack*/
int (*set_default)(struct netdev *netdev);
};
/* sal network database name resolving */
struct sal_netdb_ops
{
struct hostent* (*gethostbyname) (const char *name);
int (*gethostbyname_r)(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
int (*getaddrinfo) (const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
void (*freeaddrinfo) (struct addrinfo *ai);
};
netdev状态
/* whether the network interface device is 'up' (set by the network interface driver or application) */
#define NETDEV_FLAG_UP 0x01U //--------->up/down
/* if set, the network interface device has broadcast capability, only supported in the 'lwIP' stack */
#define NETDEV_FLAG_BROADCAST 0x02U
/* if set, the network interface device has an active link (set by the network interface driver) */
#define NETDEV_FLAG_LINK_UP 0x04U //--------->link_up/link_down
/* if set, the network interface device is an ethernet device using ARP, only supported in the 'lwIP' stack */
#define NETDEV_FLAG_ETHARP 0x08U
/* if set, the network interface device is an ethernet device, only supported in the 'lwIP' stack */
#define NETDEV_FLAG_ETHERNET 0x10U
/* if set, the network interface device has IGMP capability, only supported in the 'lwIP' stack */
#define NETDEV_FLAG_IGMP 0x20U
/* if set, the network interface device has MLD6 capability, only supported in the 'lwIP' stack */
#define NETDEV_FLAG_MLD6 0x40U
/* if set, the network interface device connected to internet successfully (set by the network interface driver) */
#define NETDEV_FLAG_INTERNET_UP 0x80U //--------->internet_up/internet_down
/* if set, the network interface device has DHCP capability (set by the network interface device driver or application) */
#define NETDEV_FLAG_DHCP 0x100U
API
#include <arpa/inet.h> /* 包含 ip_addr_t 等地址相关的头文件 */
#include <netdev.h> /* 包含全部的 netdev 相关操作接口函数 */
int netdev_register(struct netdev *netdev, const char *name, void *user_data);
int netdev_unregister(struct netdev *netdev);
struct netdev *netdev_get_first_by_flags(uint16_t flags);
struct netdev *netdev_get_by_family(int family);
struct netdev *netdev_get_by_ipaddr(ip_addr_t *ip_addr);
struct netdev *netdev_get_by_name(const char *name); // 网卡名称唯一
void netdev_set_default(struct netdev *netdev);
int netdev_set_up(struct netdev *netdev);
int netdev_set_down(struct netdev *netdev);
int netdev_dhcp_enabled(struct netdev *netdev, rt_bool_t is_enabled);
/* 设置网卡 IP 地址 */
int netdev_set_ipaddr(struct netdev *netdev, const ip_addr_t *ipaddr); // dhcp关闭状态下使用
/* 设置网卡网关地址 */
int netdev_set_gw(struct netdev *netdev, const ip_addr_t *gw); // dhcp关闭状态下使用
/* 设置网卡子网掩码地址 */
int netdev_set_netmask(struct netdev *netdev, const ip_addr_t *netmask); // dhcp关闭状态下使用
int netdev_set_dns_server(struct netdev *netdev, uint8_t dns_num, const ip_addr_t *dns_server); // dhcp关闭状态下使用
typedef void (*netdev_callback_fn )(struct netdev *netdev, enum netdev_cb_type type);
void netdev_set_status_callback(struct netdev *netdev, netdev_callback_fn status_callback); // 网卡可以设置回调用函数,状态改变时调用 TODO
void netdev_set_addr_callback(struct netdev *netdev, netdev_callback_fn addr_callback) // 地址改变时调用
#define netdev_is_up(netdev)
#define netdev_is_link_up(netdev)
#define netdev_is_internet_up(netdev)
#define netdev_is_dhcp_enable(netdev)
状态设置调用路径
up/down 状态以及 dhcp_enable/dhcp_disable 状态可以通过 netdev 组件提供的接口设置,可以在应用层控制。其他状态是由网卡底层驱动或者 netdev 组件根据当前网卡网络连接情况自动设置
状态 | 含义 | 设置 |
---|---|---|
up | 网卡开启 | 底层: eth_device_init(…)/lwip_netdev_set_up(struct netdev *netif) -> netif_set_up(struct netif *netif)-> netdev_low_level_set_status(struct netdev *netdev, rt_bool_t is_up); 上层:netdev->ops->set_up( ); |
down | 网卡禁用 | eth_device_deinit(…)/lwip_netdev_set_down(struct netdev *netif)-> netif_set_down(struct netif *netif)-> netdev_low_level_set_status(struct netdev *netdev, rt_bool_t is_up) |
link_up | 链路有效 | eth_device_linkchange(struct eth_device* dev, rt_bool_t up)-> netif_set_link_up(struct netif *netif)-> netdev_low_level_set_link_status(struct netdev *netdev, rt_bool_t is_up) |
link_down | 链路无效 | eth_device_linkchange(struct eth_device* dev, rt_bool_t up)-> netif_set_link_down(struct netif *netif )-> netdev_low_level_set_link_status(struct netdev *netdev, rt_bool_t is_up) |
internet_up | 连接到因特网 | check_netdev_internet_up_work(struct rt_work *work, void *work_data)-> netdev_low_level_set_internet_status(struct netdev *netdev, rt_bool_t is_up); |
internet_down | 未连接到因特网 | netdev_low_level_set_…(…)-> sal_check_netdev_internet_up(…)-> check_netdev_internet_up_work(struct rt_work *work, void *work_data)-> netdev_low_level_set_internet_status(struct netdev *netdev, rt_bool_t is_up); |
dhcp_enable | 开启 DHCP | … |
dhcp_disable | 关闭 DHCP | … |
Lwip 设备eth_device及网卡netif 和rt-thread网卡netdev的状态同步,通过上面的函数间的调用关系,如果使用lwip的网卡来是适配我们的协议栈,底层调用关系已经设置好了。
文件结构
├─ netdev // netdev 组件
│ ├─ include
│ │ ├─ arpa // 引用了netdev_ipaddr.h
│ │ │ └─ inet.h
│ │ ├─ netdev.h // netdev 结构体定义及相关操作声明,netdev状态定义
│ │ └─ netdev_ipaddr.h // 一些IP 地址相关的操作
│ ├─ Kconfig
│ ├─ SConscript
│ └─ src
│ ├─ netdev.c
│ └─ netdev_ipaddr.c
Lwip 介绍
Lwip网卡注册调用关系
xxx_hw_init();--->
rt_err_t eth_device_init(struct eth_device * dev, const char *name); --->
rt_err_t eth_device_init_with_flag(struct eth_device *dev, const char *name, rt_uint16_t flags);--->
netifapi_netif_add(...);--->
tcpip_api_call(tcpip_api_call_fn fn, struct tcpip_api_call_data *call);---> // TODO:why call this func?? 猜测是为了线程安全。
static err_t eth_netif_device_init(struct netif *netif); --->
static int netdev_add(struct netif *lwip_netif); --->
int netdev_register(struct netdev *netdev, const char *name, void *user_data); ---> // struct netif 作为user data在netdev中进行链接
网卡设备继承关系
RT-Thread 的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VfzGzvi8-1667636788512)(25a40859735f5684b1fe6a76ebc98593.png)]
struct rt_object --->
struct rt_device --->
struct eth_device --->
struct eth_device_xxx // device type: RT_Device_Class_NetIf
Lwip 函数调用流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8MRfYM3s-1667636788513)(8e47ad50f996d7368e2788de6d98d5ef.png)]
网络接口层:
lwip 的ETH 层可以单独分两个线程进行数据的发送和接收,连线程和tcpip线程间通过邮箱通信。是否开这两个线程和邮箱通过 LWIP_NO_TX_THREAD/LWIP_NO_RX_THREAD 宏进行控制(开线程和不开的区别在哪?)。
收发线程和邮箱的初始化放在lwip初始化的时候, lwip_system_init()
时完成。
rx:硬件收到数据后,产生一个对应的中断,调用eth_device_ready(...)
通过邮箱通知eth_rx_thread去读取数据并处理存放进 struct pbuf 中,再调用网卡netif入口函数ethernet_input(...)
进行处理。
tx:发送线程,从邮箱中接收到数据后,调用底层提供的接口,把数据写到FIFO中,等待发送完成的反馈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LuAujJV9-1667636788513)(255d43f7d1e8d0470c90cc427f53f3c2.png)]
网卡信息的配置
目前认为有两种方式获取Ip地址,一种是DHCP分配,一种是调用netdev提供的接口手动设置。
DHCP 动态分配
根据rt-thread的代码,当设置了DHCP自动分配IP地址的时候,无法手动设置IP地址;但是DHCP申请到了IP地址是如何给netdev的呢?
DHCP打开的时候,手动进行IP地址的设置会失败。
dhcp_bind(struct netif *netif)->
netif_set_addr(...)->
DHCP可以在程序运行中打开/关闭,列一下有哪些时候会进行DHCP的打来关闭
dhcp_start(struct netif *netif); // DHCP 开 初始化时:eth_netif_device_init(struct netif *netif) ->dhcp_start();
程序运行中:通过netdev提供的接口lwip_netdev_set_dhcp(...)
dhcp_stop(struct netif *netif); // DHCP 关 删除eth_device时:eth_device_deinit(struct eth_device *dev)
程序运行时同dhcp_start(...)
dhcp 静态分配
当在配置文件中关闭通过DHCP分配IP地址后,rt_thread会给网卡分配一个静态IP地址,换个说法就是,当打开dhcp 时,rt_thread会把网卡的初始地址设成0,否则初始地址为一个局部IP地址。
手动设置
eth_device&netdev&netif
eth_device 网络接口设备
struct eth_device
{
/* inherit from rt_device */
struct rt_device parent;
/* network interface for lwip */
struct netif *netif; // to netif
rt_uint16_t flags;
rt_uint8_t link_changed;
rt_uint8_t link_status;
rt_uint8_t rx_notice;
/* eth device interface */
struct pbuf* (*eth_rx)(rt_device_t dev); // send data
rt_err_t (*eth_tx)(rt_device_t dev, struct pbuf* p); // recv data
};
netdev 是对 lwip 中 netif 的继承。因为 netdev 从 netif 中取出了一些字节用于关键信息的填充。
需求
需要在rt_thread和DR之间搭一个数据传输通道,那么需要在rt_thread中注册一个网卡;----需要写一个虚拟网卡驱动;
需要给rt_thread暴露一个ip地址;—有两种实现方式,一种是通过DHCP和DR的dhcp_service获取一个地址,这种方式需要我们先把想要分配的地址写到DR的dhcp_service中;另外一种方式是,关闭dhcp功能的情况下,通过调用netdev提供的接口把IP地址等设置好。(不使用dhcp似乎是更好的选择)