RK3568 驱动开发:实现一个最基础的网络设备
- 一、引言
- 二、编写网络设备驱动代码
- 1. 核心数据结构与接口
- 2. 核心功能实现
- 3. 网络命名空间管理
- 4.源代码
- 三、编译与验证
- 1.加载模块
- 2.验证网络
- 四、注意事项
一、引言
RK3568 作为一款高性能 ARM 架构处理器,广泛应用于嵌入式设备中。本文将带领读者从零开始开发一个简单的 RK3568 网络设备驱动,帮助理解 Linux 网络子系统的工作原理和驱动开发流程。
二、编写网络设备驱动代码
1. 核心数据结构与接口
设备操作集 (struct net_device_ops
)
static const struct net_device_ops loopback_ops = {.ndo_start_xmit = loopback_xmit,.ndo_get_stats64 = loopback_get_stats64,
};
ndo_start_xmit:处理数据包发送的回调函数
ndo_get_stats64:获取 64 位统计信息的回调函数
网络命名空间操作 (struct pernet_operations
)
struct pernet_operations __net_initdata loopback_net_ops = {.init = loopback_net_init,.exit = loopback_net_exit,
};
init:每个网络命名空间创建时调用的初始化函数
exit:每个网络命名空间销毁时调用的清理函数
2. 核心功能实现
数据包发送与回环 (loopback_xmit
)
static netdev_tx_t loopback_xmit(struct sk_buff *skb, struct net_device *dev)
{netif_stop_queue(dev);skb->protocol = eth_type_trans(skb, dev);if (netif_rx(skb) == NET_RX_SUCCESS) {bytes += skb->len;packets++;}netif_wake_queue(dev);return NETDEV_TX_OK;
}
工作流程:
暂停设备发送队列
确定数据包的协议类型
通过netif_rx()将数据包重新注入网络栈(模拟回环)
更新统计信息
恢复发送队列
统计信息收集 (loopback_get_stats64
)
static void loopback_get_stats64(struct net_device *dev,struct rtnl_link_stats64 *stats)
{stats->rx_packets = packets;stats->tx_packets = packets;stats->rx_bytes = bytes;stats->tx_bytes = bytes;
}
由于是回环设备,收发数据包和字节数完全相同
直接使用全局变量packets和bytes作为统计数据源
3. 网络命名空间管理
命名空间初始化 (loopback_net_init
)
static __net_init int loopback_net_init(struct net *net)
{dev = alloc_netdev(0, "loopback%d", NET_NAME_UNKNOWN, loopback_setup);register_netdev(dev);net->loopback_dev = dev;return 0;
}
为每个网络命名空间创建一个回环设备
设备命名规则:loopback0, loopback1等
将设备指针保存到网络命名空间的loopback_dev字段
设备配置 (loopback_setup
)
static void loopback_setup(struct net_device *dev)
{dev->mtu = 64 * 1024;dev->type = ARPHRD_LOOPBACK;dev->flags = IFF_LOOPBACK;dev->features = NETIF_F_LOOPBACK;dev->header_ops = ð_header_ops;dev->netdev_ops = &loopback_ops;
}
MTU:设置为 64KB,远大于标准以太网的 1500 字节
设备类型:设置为回环设备 (ARPHRD_LOOPBACK)
设备标志:设置IFF_LOOPBACK标志,表示这是一个回环设备
功能特性:支持NETIF_F_LOOPBACK特性
协议头操作:使用以太网头操作集
4.源代码
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/in.h>#include <linux/uaccess.h>
#include <linux/io.h>#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/ethtool.h>
#include <net/sock.h>
#include <net/checksum.h>
#include <linux/if_ether.h> /* For the statistics structure. */
#include <linux/if_arp.h> /* For ARPHRD_ETHER */
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/percpu.h>
#include <linux/net_tstamp.h>
#include <net/net_namespace.h>
#include <linux/u64_stats_sync.h>extern int eth_header(struct sk_buff *skb, struct net_device *dev,unsigned short type,const void *daddr, const void *saddr, unsigned int len);extern int eth_header_parse(const struct sk_buff *skb, unsigned char *haddr);extern int eth_header_cache(const struct neighbour *neigh, struct hh_cache *hh, __be16 type);extern void eth_header_cache_update(struct hh_cache *hh,const struct net_device *dev,const unsigned char *haddr);const struct header_ops eth_header_ops ____cacheline_aligned = {.create = eth_header,.parse = eth_header_parse,.cache = eth_header_cache,.cache_update = eth_header_cache_update,
};u64 packets;
u64 bytes;/* The higher levels take care of making this non-reentrant (it's* called with bh's disabled).*/
static netdev_tx_t loopback_xmit(struct sk_buff *skb,struct net_device *dev)
{netif_stop_queue(dev);skb->protocol = eth_type_trans(skb, dev);if (netif_rx(skb) == NET_RX_SUCCESS) {printk("loopback_xmit NET_RX_SUCCESS");bytes += skb->len;packets++;} else {printk("loopback_xmit NET_RX_FAILURE");}netif_wake_queue(dev);return NETDEV_TX_OK;
}static void loopback_get_stats64(struct net_device *dev,struct rtnl_link_stats64 *stats)
{stats->rx_packets = packets;stats->tx_packets = packets;stats->rx_bytes = bytes;stats->tx_bytes = bytes;printk("loopback_get_stats64");
}static const struct net_device_ops loopback_ops = {.ndo_start_xmit = loopback_xmit,.ndo_get_stats64 = loopback_get_stats64,
};/* The loopback device is special. There is only one instance* per network namespace.*/
static void loopback_setup(struct net_device *dev)
{dev->mtu = 64 * 1024;dev->type = ARPHRD_LOOPBACK; /* 0x0001*/dev->flags = IFF_LOOPBACK;dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM);dev->features = NETIF_F_LOOPBACK;dev->header_ops = ð_header_ops;dev->netdev_ops = &loopback_ops;printk("loopback_setup");
}/* Setup and register the loopback device. */
static __net_init int loopback_net_init(struct net *net)
{struct net_device *dev;int err;err = -ENOMEM;dev = alloc_netdev(0, "loopback%d", NET_NAME_UNKNOWN, loopback_setup);if (!dev)goto out;err = register_netdev(dev);if (err)goto out_free_netdev;net->loopback_dev = dev;printk("loopback_net_init");return 0;out_free_netdev:free_netdev(dev);
out:if (net_eq(net, &init_net))panic("loopback: Failed to register netdevice: %d\n", err);return err;
}static void __net_exit loopback_net_exit(struct net *net)
{struct net_device *dev = net->loopback_dev;unregister_netdev(dev);printk("netdev_exit");
}/* Registered in net/core/dev.c */
struct pernet_operations __net_initdata loopback_net_ops = {.init = loopback_net_init,.exit = loopback_net_exit,
};/* 模块初始化 */
static int __init veth_init(void)
{return register_pernet_subsys(&loopback_net_ops);
}/* 模块退出 */
static void __exit veth_exit(void)
{unregister_pernet_subsys(&loopback_net_ops);
}module_init(veth_init);
module_exit(veth_exit);MODULE_DESCRIPTION("RK3568 Virtual Network Device Driver");
MODULE_AUTHOR("cmy");
MODULE_LICENSE("GPL");
三、编译与验证
1.加载模块
将编译好的ko文件拷贝到开发板并加载模块
执行ifconfig -a
查看所有网络设备
可以看到我们写的网络设备已经加载进来了,使能loopback0网络设备
ifconfig loopback0 up
2.验证网络
使用socket验证网络是否正常
#define PORT 8888extern "C"
JNIEXPORT jint JNICALL
Java_com_example_led9_MainActivity_startTcpServer(JNIEnv *env, jobject thiz) {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};const char *hello = "Hello from server";// 创建 socket 文件描述符if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {LOGD("socket failed");exit(EXIT_FAILURE);}// 设置 socket 选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {LOGD("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY; // 监听所有可用地址address.sin_port = htons(PORT);// 绑定 socket 到指定地址和端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {LOGD("bind failed");exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 3) < 0) {LOGD("listen");exit(EXIT_FAILURE);}// 接受连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address,(socklen_t*)&addrlen)) < 0) {LOGD("accept");exit(EXIT_FAILURE);}// 读取客户端消息int valread = read(new_socket, buffer, 1024);LOGD("Client: %s\n", buffer);// 发送响应send(new_socket, hello, strlen(hello), 0);LOGD("Hello message sent\n");// 关闭连接close(new_socket);close(server_fd);return 0;
}extern "C"
JNIEXPORT jint JNICALL
Java_com_example_led9_MainActivity_startTcpClient(JNIEnv *env, jobject thiz) {const char *server_ip = "127.0.0.1";int sock = 0;struct sockaddr_in serv_addr;char *hello = "Hello from client";char buffer[1024] = {0};// 创建 socket 文件描述符if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {LOGD("\nSocket creation error\n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 将 IPv4 地址从点分十进制转换为二进制形式if(inet_pton(AF_INET, server_ip, &serv_addr.sin_addr) <= 0) {LOGD("\nInvalid address/ Address not supported\n");return -1;}// 连接到服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {LOGD("\nConnection Failed\n");return -1;}// 发送消息send(sock, hello, strlen(hello), 0);LOGD("Hello message sent\n");// 读取服务器响应int valread = read(sock, buffer, 1024);LOGD("Server: %s\n", buffer);// 关闭连接close(sock);return 0;}
可以看到是有数据收发打印,以及数据包数量统计。
四、注意事项
刚开始编译源码文件时出现Unknown symbol eth_header_ops (err -2)
,
是因为我们没有将源码编译进内核,是以ko模块的形式进行测试的,所以需要自己实现eth_header_ops
里面的函数。
具体操作流程如下:
通过命令查找关键结构体:grep -rw "eth_header_ops"
最终在net/ethernet/eth.c
文件中找到,具体定义如下:
由于这些函数已导出,所以我们的文件中可以直接使用:
extern int eth_header(struct sk_buff *skb, struct net_device *dev,unsigned short type,const void *daddr, const void *saddr, unsigned int len);extern int eth_header_parse(const struct sk_buff *skb, unsigned char *haddr);extern int eth_header_cache(const struct neighbour *neigh, struct hh_cache *hh, __be16 type);extern void eth_header_cache_update(struct hh_cache *hh,const struct net_device *dev,const unsigned char *haddr);const struct header_ops eth_header_ops ____cacheline_aligned = {.create = eth_header,.parse = eth_header_parse,.cache = eth_header_cache,.cache_update = eth_header_cache_update,
};
到这里就可以不用编译内核,解决报错问题。