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,
};
到這里就可以不用編譯內核,解決報錯問題。