【RK3568 驱动开发:实现一个最基础的网络设备】

RK3568 驱动开发:实现一个最基础的网络设备

一、引言

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 = &eth_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		= &eth_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,
};

到这里就可以不用编译内核,解决报错问题。

相关推荐
没有了遇见14 分钟前
Android 渐变色实现总结
android
行止61 小时前
OpenStack云平台管理
linux·openstack
岁岁岁平安1 小时前
CentOS-7-x86_64解决:使用NAT模式无法ping通www.baidu.com或无法ping 8.8.8.8问题。
linux·运维·centos·centos-7
运维小贺2 小时前
各服务器厂商调整BIOS睿频教程
linux·运维·服务器·性能优化
特种加菲猫2 小时前
指尖上的魔法:优雅高效的Linux命令手册
linux·笔记
★Orange★2 小时前
Linux Kernel kfifo 实现和巧妙设计
linux·运维·算法
bemyrunningdog3 小时前
Mock数据
linux·运维·ubuntu
雨白3 小时前
Jetpack系列(四):精通WorkManager,让后台任务不再失控
android·android jetpack
是阿建吖!3 小时前
【Linux | 网络】网络编程套接字
linux·网络
退役小学生呀3 小时前
十、K8s集群资源合理化分配
linux·云原生·容器·kubernetes·k8s