RK3568 驱动开发:实现一个最基础的网络设备
- 一、引言
- 二、编写网络设备驱动代码
-
- [1. 核心数据结构与接口](#1. 核心数据结构与接口)
- [2. 核心功能实现](#2. 核心功能实现)
- [3. 网络命名空间管理](#3. 网络命名空间管理)
- 4.源代码
- 三、编译与验证
- 四、注意事项
一、引言
RK3568 作为一款高性能 ARM 架构处理器,广泛应用于嵌入式设备中。本文将带领读者从零开始开发一个简单的 RK3568 网络设备驱动,帮助理解 Linux 网络子系统的工作原理和驱动开发流程。
二、编写网络设备驱动代码
1. 核心数据结构与接口
设备操作集 (struct net_device_ops
)
c
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
)
c
struct pernet_operations __net_initdata loopback_net_ops = {
.init = loopback_net_init,
.exit = loopback_net_exit,
};
init:每个网络命名空间创建时调用的初始化函数
exit:每个网络命名空间销毁时调用的清理函数
2. 核心功能实现
数据包发送与回环 (loopback_xmit
)
c
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
)
c
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
)
c
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
)
c
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.源代码
c
#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网络设备
bash
ifconfig loopback0 up

2.验证网络
使用socket验证网络是否正常
cpp
#define PORT 8888
extern "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
文件中找到,具体定义如下:
由于这些函数已导出,所以我们的文件中可以直接使用:
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,
};
到这里就可以不用编译内核,解决报错问题。