本章节,我们就来实现一个完整的设备树+platform驱动。
一、设备树节点准备
在设备树文件中添加我们的设备节点:
c
/ {
mychardev: mychardev@0 {
compatible = "lckfb,mychardev"; // 驱动匹配关键字段
reg = <0x0 0xfec00000 0x0 0x1000>; // 寄存器地址范围
buffer-len = <64>; // 自定义属性:缓冲区大小
dev-id = <0xFFA8>; // 自定义属性:设备ID
status = "okay"; // 设备状态
};
};1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
字段说明:
| 字段 | 说明 |
|---|---|
compatible | 驱动匹配字段,必须和驱动中的 of_match_table 一致 |
reg | 寄存器物理地址(64位格式):基地址 0xfec00000,大小 0x1000(4KB) |
buffer-len | 自定义属性,缓冲区大小 64 字节 |
dev-id | 自定义属性,设备ID 0xFFA8 |
status | 设备状态,"okay" 表示启用 |
关于reg字段的地址范围说明(RK3576是ARM64的SoC):
bash
reg = <地址高32位 地址低32位 大小高32位 大小低32位>;
└─────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
组1 组2 组3 组4
└──────────┬──────────┘ └──────────┬──────────┘
64位地址 64位大小1
2
3
4
5
6
2
3
4
5
6
二、编译内核并烧录
我们根据 Debian12内核编译 的教程,重新编译内核生成 boot.img,并单独烧录内核镜像的方式实现替换设备树。
烧录完成之后,板子开机,进行校验设备树节点:
查看解包后的设备树节点
bash
ls /sys/firmware/devicetree/base/mychardev@01
看到 compatible/reg/buffer-len/dev-id/status字段说明生效!
三、编写Platform驱动
1、创建项目目录
bash
mkdir 12_devicetree_platform1
2、编写驱动
进入 12_devicetree_platform/ 目录,创建 platform_chardev.c 文件,并编写以下代码:
c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
// 设备名和类名
#define DEV_NAME "mychardev"
#define CLASS_NAME "class_mychardev"
// 全局变量
static dev_t dev_num; // 设备号
static struct cdev cdev_test; // 字符设备结构体
static struct class *class_test; // 设备类指针
// ==================== 字符设备操作函数 ====================
static int chrdev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychardev: chrdev_open called\n");
return 0;
}
static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "mychardev: chrdev_read called\n");
return 0;
}
static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "mychardev: chrdev_write called\n");
return size;
}
static int chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychardev: chrdev_release called\n");
return 0;
}
static struct file_operations cdev_fops_test = {
.owner = THIS_MODULE,
. open = chrdev_open,
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_release,
};
// ==================== Platform 驱动函数 ====================
// 1. 定义匹配表
static const struct of_device_id mychardev_of_match[] = {
{ .compatible = "lckfb,mychardev" },
{ }
};
MODULE_DEVICE_TABLE(of, mychardev_of_match);
// 2. probe 函数(设备匹配成功后调用)
static int mychardev_probe(struct platform_device *pdev)
{
int ret;
int major, minor;
struct resource *res;
u32 buffer_len, dev_id;
printk(KERN_INFO "mychardev: probe 被调用!\n");
// ========== 获取设备树资源 ==========
// 获取寄存器地址资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
printk(KERN_ERR "mychardev: 获取寄存器资源失败!\n");
return -ENODEV;
}
printk(KERN_INFO "mychardev: 寄存器基地址=0x%llx, 大小=0x%llx\n",
res->start, resource_size(res));
// 读取自定义属性
if (of_property_read_u32(pdev->dev.of_node, "buffer-len", &buffer_len)) {
printk(KERN_ERR "mychardev: 读取 buffer-len 失败!\n");
return -EINVAL;
}
printk(KERN_INFO "mychardev: buffer-len=%u\n", buffer_len);
if (of_property_read_u32(pdev->dev.of_node, "dev-id", &dev_id)) {
printk(KERN_ERR "mychardev: 读取 dev-id 失败!\n");
return -EINVAL;
}
printk(KERN_INFO "mychardev: dev-id=0x%X\n", dev_id);
// ========== 注册字符设备 ==========
// 申请设备号
ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);
if (ret < 0) {
printk(KERN_ERR "mychardev: alloc_chrdev_region failed\n");
return ret;
}
major = MAJOR(dev_num);
minor = MINOR(dev_num);
printk(KERN_INFO "mychardev: 设备号 major=%d, minor=%d\n", major, minor);
// 初始化并注册 cdev
cdev_init(&cdev_test, &cdev_fops_test);
ret = cdev_add(&cdev_test, dev_num, 1);
if (ret < 0) {
printk(KERN_ERR "mychardev: cdev_add failed\n");
unregister_chrdev_region(dev_num, 1);
return ret;
}
// 创建设备类
class_test = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(class_test)) {
printk(KERN_ERR "mychardev: class_create failed\n");
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(class_test);
}
// 创建设备节点
if (device_create(class_test, &pdev->dev, dev_num, NULL, DEV_NAME) == NULL) {
printk(KERN_ERR "mychardev: device_create failed\n");
class_destroy(class_test);
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
return -ENOMEM;
}
printk(KERN_INFO "mychardev: 驱动加载成功!\n");
return 0;
}
// 3. remove 函数(设备移除时调用)
static void mychardev_remove(struct platform_device *pdev)
{
printk(KERN_INFO "mychardev: remove 被调用!\n");
// 释放字符设备资源
device_destroy(class_test, dev_num);
class_destroy(class_test);
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "mychardev: 驱动卸载成功!\n");
}
// 4. 定义 platform_driver
static struct platform_driver mychardev_driver = {
.probe = mychardev_probe,
.remove_new = mychardev_remove,
.driver = {
.name = "mychardev",
.of_match_table = mychardev_of_match,
},
};
// 5. 注册驱动
module_platform_driver(mychardev_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LCKFB");
MODULE_DESCRIPTION("Platform Char Device Driver");1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
代码整体框架:
3、编写Makefile
在 12_devicetree_platform/ 目录,创建 Makefile 文件,并编写以下代码:
makefile
export ARCH=arm64
# 交叉编译器路径
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# 驱动模块名
obj-m += platform_chardev. o
# 内核源码目录
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ? = $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CROSS_COMPILE:依旧是SDK中的编译器路径前缀。KDIR:依旧是内核源码目录。- 需要将
obj-m +=后面的改为platform_chardev.o。
4、编译
在 12_devicetree_platform/ 目录下使用:
makefile
make1
编译代码,并生成 .ko 文件。
四、测试
1、加载驱动
首先我们需要将编译出来的 platform_chardev.ko 文件复制到开发板中(U盘、TF卡或者SSH都行),并运行加载命令:
bash
sudo insmod platform_chardev.ko1
2、查看设备节点
bash
ls -l /dev/mychardev1
3、测试读写
首先基于 /dev/mychardev 权限:
bash
sudo chmod 666 /dev/mychardev1
开始测试:
bash
# 测试写入
echo "hello" > /dev/mychardev
# 测试读取
cat /dev/mychardev1
2
3
4
5
2
3
4
5
可以看到
open和write函数都已经被正常调用,打印出来的信息。
4、卸载驱动
bash
sudo rmmod platform_chardev1
可以使用
sudo lsmod查看当前已经挂载的模块信息。