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

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 = &eth_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		= &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网络设备

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,
};

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87946.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CAIDCP系列对话:AI 驱动安全

数字时代&#xff0c;AI浪潮翻涌&#xff0c;网络安全攻防战已悄然升级&#xff1a; 某工业控制系统遭AI驱动勒索攻击&#xff1a;攻击者借 AI 精准捕捉异常网络扫描、远程 PowerShell 痕迹&#xff0c;瞬间加密文件索要赎金&#xff1b; 另一边&#xff0c;某大型科技公司用AI…

ARMv8 没开mmu执行memset引起的非对齐访问异常

最近在haps上验证一个新的芯片&#xff0c;记录一下memset访问出错的问题。在没开mmu和cache的情况下&#xff0c;对全局变量指针进行memset清零操作&#xff0c;发现每次都会出现异常。最后发现是没开mmu导致出现了数据非对齐访问导致报错。排查EC区域发现是0x25&#xff0c;产…

基于LiveKit Go 实现腾讯云实时音视频功能

详细的生产部署建议&#xff0c;适用于 LiveKit Go 服务器 Web 客户端 TURN/HTTPS。 1. 服务器准备 推荐使用云服务器&#xff08;如阿里云、腾讯云、AWS、Azure等&#xff09;&#xff0c;公网IP&#xff0c;带宽建议≥10Mbps。系统推荐 Ubuntu 20.04/22.04 或 CentOS 7/8&…

三位一体:Ovis-U1如何以30亿参数重构多模态AI格局?

1. 时代命题&#xff1a;多模态统一模型的破局之战当GPT-4o以万亿级参数构建多模态帝国时&#xff0c;中国AI军团正在书写另一种答案。Ovis-U1用30亿参数证明&#xff1a;参数量并非决定性因素&#xff0c;架构创新与训练策略的化学反应&#xff0c;同样能催生出改变游戏规则的…

图像处理基础:镜像、缩放与矫正

在图像处理中&#xff0c;镜像、缩放和矫正操作是常见的图像变换手段。这些操作可以帮助我们对图像进行调整&#xff0c;以满足不同的需求。本文将详细介绍这三种操作的原理和实现方法&#xff0c;并通过代码示例展示它们的实际应用。一、图片镜像旋转1.1 什么是镜像旋转&#…

「Java案例」猜数游戏

案例实现 猜数字游戏 设计一个三位数的猜数游戏,三位数随机生成。程序提示用户输入一个三位的数字,依照以下的规则决定赢取多少奖金:1) 如果用户输入的数字和随机数字完全一致,输出:“恭喜恭喜!完全猜对了!获得三个赞!”2) 如果用户输入的数字覆盖了随机生成的所有数…

创客匠人解析创始人 IP 内卷:知识变现时代的生存逻辑与破局路径

当知识付费行业进入 “存量竞争” 阶段&#xff0c;创始人 IP 的 “内卷” 已非选择而是必然。创客匠人在服务数万知识创业者的实践中发现&#xff0c;那些实现逆势增长的案例&#xff0c;其核心差异往往在于创始人是否具备 “从幕后走到台前” 的决心与能力 —— 这种内卷并非…

250705-Debian12-sudo apt update加速+配置RDP远程桌面环境+设置FRP服务为开机启动项

A. 实现sudo apt update加速 在 Debian 12 上运行 sudo apt update 很慢的常见原因包括&#xff1a; &#x1f50d; 一、常见原因分析 使用了国外的软件源 默认 Debian 安装源多数是国际服务器&#xff0c;国内访问会非常慢。 DNS 解析慢或失败 软件源地址解析时间长&#xf…

数学视频动画引擎Python库 -- Manim Voiceover 语音服务 Speech Services

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 Manim Voiceover 是一个为 Manim 打造的专注于语音旁白的插件&#xff1a; 直接在 Python 中添加语音旁白&#xff1a; 无需使用视频编辑器&…

C++11 forward_list 从基础到精通:原理、实践与性能优化

文章目录一、为什么需要 forward_list&#xff1f;二、基础篇&#xff1a;forward_list 的核心特性与接口2.1 数据结构与迭代器2.2 常用接口速览2.3 基础操作示例&#xff1a;从初始化到遍历2.3.1 初始化与遍历2.3.2 插入与删除&#xff1a;before_begin 的关键作用三、进阶篇&…

物联网技术的核心组件与发展趋势(截至2025年)

一、物联网技术的核心组件物联网&#xff08;IoT&#xff09;技术体系由感知层、网络层、平台层、应用层和安全层构成&#xff0c;各层技术协同工作&#xff0c;实现物理世界与数字世界的深度融合。1. 感知层&#xff1a;数据采集与交互传感器技术&#xff1a;类型&#xff1a;…

面试中常见的问题:JavaScript 宏任务与微任务,包教包会

事件循环Event Loop 我们都知道&#xff0c;JavaScript 是一种单线程的编程语言&#xff0c;简单的说就是&#xff1a;js只有一条通道&#xff0c;那么在任务多的情况下&#xff0c;就会出现拥挤的情况&#xff0c;这种情况下就产生了 ‘多线程’ &#xff0c;但是这种“多线程…

【LeetCode102.二叉树的层序遍历】vs.【LeetCode103.二叉树的锯齿形层序遍历】

题目链接 LeetCode102.二叉树的层序遍历&#xff1a;102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09;LeetCode103.二叉树的锯齿形层序遍历&#xff1a;103. 二叉树的锯齿形层序遍历 - 力扣&#xff08;LeetCode&#xff09; 实现思路 定义一个队列&#xff0…

Redis On-CPU Profiling定位瓶颈到可视化火焰图

1 . 前置检查&#xff1a;确认 CPU 真的是瓶颈 在正式打性能“补丁”前&#xff0c;务必跑一遍系统级健康核对表&#xff08;推荐 Brendan Greg 的 USE Method&#xff09;&#xff1a;资源关注指标常用工具CPUUtil/Idle、RunQueuetop、vmstat、sar内存Fault、Swap、Cache Miss…

未来趋势:AI与量子计算对服务器安全的影响

随着技术的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;和量子计算正在深刻改变信息技术的各个领域。特别是在服务器安全领域&#xff0c;这两项技术既带来了新的可能性&#xff0c;也带来了前所未有的挑战。本文将探讨AI和量子计算技术对服务器安全的影响&#xf…

markdown学习笔记(个人向) Part.1

markdown学习笔记&#xff08;个人向&#xff09; Part.1 1. 推荐插件 markdown&#xff1a; 安装支持markdown的插件&#xff1b; markdown-preview-github-styles&#xff1a; 可以将VS Code上默认的markdown预览样式修改成github上常用的形式&#xff0c;很大程度上提高文件…

ZooKeeper 实现分布式锁

1. 分布式锁概述 在分布式系统中&#xff0c;为了保证共享资源在并发访问下的数据一致性&#xff0c;需要引入分布式锁。分布式锁是一种在分布式环境下控制多个进程对共享资源进行互斥访问的机制。它与单机环境下的锁&#xff08;如Java中的synchronized或Lock&#xff09;不同…

Linux线程——基础全解

一、什么是线程&#xff08;Thread&#xff09;&#xff1f;✅ 定义&#xff1a;线程是程序执行的最小单位。即线程&#xff08;Thread&#xff09;是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;是进程中的实际运作单位。一个进程可以并发多…

Java基础--封装+static

目录 什么是封装&#xff1f; 什么是访问限定符&#xff1f; static静态修饰符 用static修饰的类变量或类方法的注意事项&#xff1a; 什么是封装&#xff1f; 封装是面向对象的三大特性之一&#xff0c;指的是将一个类中的实现细节进行隐藏&#xff0c;对外只提供一些开放…

DAY 51 复习日

作业&#xff1a;day43的时候我们安排大家对自己找的数据集用简单cnn训练&#xff0c;现在可以尝试下借助这几天的知识来实现精度的进一步提高import torch import torch.nn as nn import torch.nn.functional as F import torchvision import torchvision.transforms as trans…