参考
VSCode作为zynq相关编辑器的配置
环境准备
bash
复制代码
# 更新
sudo apt update && sudo apt upgrade -y
# 安装编译工具链
sudo apt install build-essential gcc make bc flex bison libncurses-dev libssl-dev -y
# 安装调试工具
sudo apt install gdb git cscope ctags -y
sudo apt install vim nano -y
#安装内核模块开发依赖
sudo apt install linux-headers-$(uname -r) -y
# 创建工作目录
mkdir -p ~/wsl2_kernel && cd ~/wsl2_kernel
# 克隆微软 WSL2 内核仓库(只克隆最新提交)
git init
git remote add origin https://github.com/microsoft/WSL2-Linux-Kernel.git
git fetch --depth 1 origin linux-msft-6.6.87.2
git checkout -b linux-msft-6.6.87.2 FETCH_HEAD
# 复制配置
cp Microsoft/config-wsl .config
# 准备模块构建环境
make KCONFIG_CONFIG=Microsoft/config-wsl modules_prepare
make menuconfig
# 编译内核 生成符号、vmlinux、Module.symvers
make -j$(nproc)
字符设备测试
目录结构
bash
复制代码
$ ls
Makefile
Readme.md
build.sh
chrdevbase.c
chrdevbaseApp.c
测试
bash
复制代码
$ make app (编译app)
cc -Wall -O2 chrdevbaseApp.c -o chrdevbaseApp
$ ./build.sh (编译驱动)
....
[ 7046.020084] chrdevbase: init success, major=200
$ sudo ./chrdevbaseApp /dev/chrdevbase 1 (测试读驱动里的数据)
read data:我是驱动里的data!
bash
复制代码
#!/usr/bin/env bash
sudo rmmod chrdevbase 2>/dev/null
make -j$(nproc) && sudo insmod chrdevbase.ko
dmesg | tail
Makefile
bash
复制代码
# 使用 WSL2 内核源码路径
KERN_DIR := /home/minglie/wsl2_kernel
# 内核模块名(源文件 chrdevbase.c)
obj-m := chrdevbase.o
# 当前目录
PWD := $(shell pwd)
# 用户态应用
APP := chrdevbaseApp
APP_SRC := chrdevbaseApp.c
# 默认目标:只编译内核模块
all:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
# 编译用户态测试程序
app:
$(CC) -Wall -O2 $(APP_SRC) -o $(APP)
# 清理
clean:
$(MAKE) -C $(KERN_DIR) M=$(PWD) clean
chrdevbase.c 驱动
c
复制代码
#include <linux/module.h> // 模块相关宏:module_init / module_exit / MODULE_LICENSE
#include <linux/kernel.h> // printk / pr_xxx 等内核日志接口
#include <linux/init.h> // __init / __exit 标记
#include <linux/fs.h> // struct file_operations,字符设备接口
#include <linux/uaccess.h> // copy_to_user / copy_from_user(用户态 <-> 内核态)
/*
* 静态指定字符设备主设备号
* 真实工程中通常使用 alloc_chrdev_region 动态分配
*/
#define CHRDEVBASE_MAJOR 200
#define CHRDEVBASE_NAME "chrdevbase"
/*
* 内核缓冲区
* 注意:这是内核态内存,用户态不能直接访问
*/
static char readbuf[100];
static char writebuf[100];
/*
* 内核中准备好的一段数据
* read() 时拷贝给用户
*/
static char kerneldata[] = "我是驱动里的data!";
/*
* open():当用户态调用 open("/dev/xxx") 时触发
* inode : 描述设备节点(主次设备号等)
* filp : 描述一次打开的文件实例
*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
/*
* pr_info 是 printk(KERN_INFO ...) 的封装
* 推荐用于新代码
*/
pr_info("chrdevbase: open\n");
/*
* printk 仍然可用
* 这里纯演示,与 pr_info 功能等价
*/
printk("printk chrdevbase open!\r\n");
return 0; // 返回 0 表示打开成功
}
/*
* read():用户态调用 read(fd, buf, cnt) 时触发
*
* filp : 文件实例
* buf : 用户态缓冲区地址(__user 表示来自用户空间)
* cnt : 用户期望读取的字节数
* offt : 文件偏移指针(此示例未使用)
*/
static ssize_t chrdevbase_read(struct file *filp,
char __user *buf,
size_t cnt,
loff_t *offt)
{
size_t len;
int ret;
/*
* 防止用户请求长度超过内核数据长度
* min(a,b) 是内核宏
*/
len = min(cnt, sizeof(kerneldata));
/*
* 将内核空间数据拷贝到用户空间
* 返回值为"未成功拷贝的字节数"
*/
ret = copy_to_user(buf, kerneldata, len);
if (ret)
return -EFAULT; // 用户地址非法或拷贝失败
pr_info("chrdevbase: read %zu bytes\n", len);
/*
* 返回实际读取的字节数
* read() 的返回值语义
*/
return len;
}
/*
* write():用户态调用 write(fd, buf, cnt) 时触发
*
* buf : 用户态数据地址
* cnt : 用户写入的字节数
*/
static ssize_t chrdevbase_write(struct file *filp,
const char __user *buf,
size_t cnt,
loff_t *offt)
{
size_t len;
int ret;
/*
* 预留 1 字节用于字符串结束符 '\0'
* 防止内核缓冲区溢出
*/
len = min(cnt, sizeof(writebuf) - 1);
/*
* 将用户空间数据拷贝到内核空间
*/
ret = copy_from_user(writebuf, buf, len);
if (ret)
return -EFAULT;
/*
* 手动补字符串结束符
* 否则 printk/pr_info 可能越界读
*/
writebuf[len] = '\0';
pr_info("chrdevbase: write \"%s\"\n", writebuf);
return len; // 返回实际写入的字节数
}
/*
* release():当用户态 close(fd) 时触发
* 与 open() 成对
*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
pr_info("chrdevbase: release\n");
return 0;
}
/*
* 文件操作结构体
* 将 VFS 的系统调用映射到驱动函数
*/
static const struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE, // 防止模块在使用中被卸载
.open = chrdevbase_open, // open()
.read = chrdevbase_read, // read()
.write = chrdevbase_write, // write()
.release = chrdevbase_release, // close()
};
/*
* 模块加载入口
* insmod / modprobe 时执行
*/
static int __init chrdevbase_init(void)
{
int ret;
/*
* 注册字符设备
* 将主设备号 + fops 注册到内核
*/
ret = register_chrdev(CHRDEVBASE_MAJOR,
CHRDEVBASE_NAME,
&chrdevbase_fops);
if (ret < 0) {
pr_err("chrdevbase: register failed\n");
return ret;
}
pr_info("chrdevbase: init success, major=%d\n", CHRDEVBASE_MAJOR);
return 0;
}
/*
* 模块卸载出口
* rmmod 时执行
*/
static void __exit chrdevbase_exit(void)
{
/*
* 注销字符设备
* 必须与 register_chrdev 成对
*/
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
pr_info("chrdevbase: exit\n");
}
/*
* 指定模块入口和出口
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
/*
* 模块元信息
*/
MODULE_LICENSE("GPL"); // 否则内核会标记 tainted
MODULE_AUTHOR("minglie");
MODULE_DESCRIPTION("Simple char device for WSL/Linux 6.x");
chrdevbaseApp.c 应用
c
复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
static char usrdata[] = {"usr data!"};
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
char readbuf[100], writebuf[100];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
retvalue = read(fd, readbuf, 50);
if(retvalue < 0){
printf("read file %s failed!\r\n", filename);
}else{
/* 读取成功,打印出读取成功的数据 */
printf("read data:%s\r\n",readbuf);
}
}
if(atoi(argv[2]) == 2){
/* 向设备驱动写数据 */
memcpy(writebuf, usrdata, sizeof(usrdata));
retvalue = write(fd, writebuf, 50);
if(retvalue < 0){
printf("write file %s failed!\r\n", filename);
}
}
/* 关闭设备 */
retvalue = close(fd);
if(retvalue < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
bash
复制代码
sudo rmmod chrdevbase
sudo insmod chrdevbase.ko
## 手动创建一个字符设备节点,只需一次
sudo mknod /dev/chrdevbase c 200 0
# 不用就删了
sudo rm /dev/chrdevbase
常用命令
| 命令 |
主要功能 |
基本用法 |
关键特点 |
典型场景 |
备注 |
| lsmod |
查看已加载的内核模块 |
lsmod |
显示模块名 / 大小 / 引用计数 |
查看当前内核状态 |
本质读取 /proc/modules |
| insmod |
直接加载模块 |
insmod xxx.ko |
不解析依赖,不搜索路径 |
驱动开发、临时调试 |
依赖缺失会报 Unknown symbol |
| rmmod |
卸载模块 |
rmmod xxx |
需模块未被占用 |
调试、模块替换 |
使用模块名,不带 .ko |
| modprobe |
智能加载/卸载模块 |
modprobe xxx modprobe -r xxx |
自动解析依赖,搜索标准路径 |
系统管理、正式环境 |
依赖 modules.dep |
| dmesg |
查看内核日志 |
dmesg `dmesg |
tail dmesg -w` |
查看 printk 输出 |
驱动调试、错误定位 |
dmesg
| 目的 |
命令 |
说明 |
| 查看全部日志 |
dmesg |
显示所有内核日志 |
| 查看最近日志 |
`dmesg |
tail` |
| 指定最近行数 |
`dmesg |
tail -n 50` |
| 实时查看 |
dmesg -w |
动态跟踪新日志 |
| 清空日志 |
dmesg -C |
清空内核环形缓冲区 |
| 显示可读时间 |
dmesg -T |
转换为人类可读时间 |
| 去除时间戳 |
dmesg -t |
仅显示日志内容 |
| 仅显示错误 |
dmesg -l err |
过滤错误级别 |
| 错误+警告 |
dmesg -l err,warn |
常用排错 |
| 指定设施 |
dmesg -f kern,driver |
仅内核/驱动 |
| 反向显示 |
dmesg -r |
最新日志在最前 |
| 过滤关键字 |
`dmesg |
grep i2c` |
| 忽略大小写 |
`dmesg |
grep -i probe` |
| 多关键字 |
`dmesg |
grep -E "i2c |
| 排除关键字 |
`dmesg |
grep -v debug` |
| 高亮显示 |
`dmesg --color=always |
grep -i --color xxx` |
| 实时过滤 |
`dmesg -w |
grep xxx` |
| 驱动加载 |
`dmesg |
grep -i probe` |
| 模块加载 |
`dmesg |
grep -i module` |
| 崩溃排查 |
`dmesg |
grep -i panic` |
fake_reg (ini文件寄存器模拟)
目录结构
bash
复制代码
$ ls
Makefile
build.sh
fake_reg.c
fake_regApp.c
ReadMe.md
Makefile
bash
复制代码
# 使用 WSL2 内核源码路径
KERN_DIR := /home/minglie/wsl2_kernel
# 内核模块名(源文件 chrdevbase.c)
obj-m := fake_reg.o
# 当前目录
PWD := $(shell pwd)
# 用户态应用
APP := fake_regApp
APP_SRC := fake_regApp.c
# 默认目标:只编译内核模块
all:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
# 编译用户态测试程序
app:
$(CC) -Wall -O2 $(APP_SRC) -o $(APP)
# 清理
clean:
$(MAKE) -C $(KERN_DIR) M=$(PWD) clean
bash
复制代码
#!/usr/bin/env bash
sudo rmmod fake_reg 2>/dev/null
make -j$(nproc) && sudo insmod fake_reg.ko
dmesg | tail
fake_reg.c
c
复制代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/init.h>
#define DEVICE_NAME "fake_reg"
#define REG_NUM 4
#define BUF_SIZE 256
static int major;
static u32 regs[REG_NUM] = {0};
static char ini_path[] = "/tmp/fake_reg.ini";
/* ================== ini 解析 ================== */
static void load_regs_from_ini(void)
{
struct file *filp;
loff_t pos = 0;
char *kbuf;
ssize_t len;
char *p, *line;
kbuf = kzalloc(BUF_SIZE, GFP_KERNEL);
if (!kbuf)
return;
filp = filp_open(ini_path, O_RDONLY, 0);
if (IS_ERR(filp)) {
pr_info("fake_reg: no ini file, using defaults\n");
goto out;
}
len = kernel_read(filp, kbuf, BUF_SIZE - 1, &pos);
filp_close(filp, NULL);
if (len <= 0)
goto out;
p = kbuf;
while ((line = strsep(&p, "\n")) != NULL) {
int idx;
u32 val;
if (sscanf(line, "REG%d=%x", &idx, &val) == 2) {
if (idx >= 0 && idx < REG_NUM) {
regs[idx] = val;
}
}
}
out:
kfree(kbuf);
}
/* ================== file ops ================== */
static ssize_t fake_reg_read(struct file *f, char __user *buf,
size_t len, loff_t *off)
{
char kbuf[128];
int n;
n = snprintf(kbuf, sizeof(kbuf),
"REG0=0x%08x\nREG1=0x%08x\nREG2=0x%08x\nREG3=0x%08x\n",
regs[0], regs[1], regs[2], regs[3]);
return simple_read_from_buffer(buf, len, off, kbuf, n);
}
static ssize_t fake_reg_write(struct file *f, const char __user *buf,
size_t len, loff_t *off)
{
char kbuf[64];
int idx;
u32 val;
if (len >= sizeof(kbuf))
return -EINVAL;
if (copy_from_user(kbuf, buf, len))
return -EFAULT;
kbuf[len] = '\0';
if (sscanf(kbuf, "REG%d=%x", &idx, &val) == 2) {
if (idx >= 0 && idx < REG_NUM) {
regs[idx] = val;
pr_info("fake_reg: REG%d <= 0x%x\n", idx, val);
}
}
return len;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.read = fake_reg_read,
.write = fake_reg_write,
};
/* ================== init / exit ================== */
static int __init fake_reg_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0)
return major;
load_regs_from_ini();
pr_info("fake_reg loaded, major=%d\n", major);
pr_info("mknod /dev/fake_reg c %d 0\n", major);
return 0;
}
static void __exit fake_reg_exit(void)
{
unregister_chrdev(major, DEVICE_NAME);
pr_info("fake_reg unloaded\n");
}
module_init(fake_reg_init);
module_exit(fake_reg_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("minglie");
MODULE_DESCRIPTION("Fake register device backed by ini file");
fake_regApp.c
4个寄存器写入4,5, 6, 7
c
复制代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define DEVICE_PATH "/dev/fake_reg"
int main()
{
int fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
// 要写入的寄存器值
unsigned int vals[4] = {5, 6, 7, 8};
char buf[32];
for (int i = 0; i < 4; i++) {
int n = snprintf(buf, sizeof(buf), "REG%d=%u", i, vals[i]);
if (write(fd, buf, n) != n) {
perror("write");
close(fd);
return 1;
}
}
// 读取寄存器内容
char readbuf[128];
int n = read(fd, readbuf, sizeof(readbuf) - 1);
if (n > 0) {
readbuf[n] = '\0';
printf("Fake registers:\n%s", readbuf);
} else {
perror("read");
}
close(fd);
return 0;
}
bash
复制代码
### 手动创建一个字符设备节点,只需一次
dmesg | tail
sudo mknod /dev/fake_reg c 240 0
sudo chmod 666 /dev/fake_reg
### 查看寄存器
$ cat /dev/fake_reg
REG0=0x00000005
REG1=0x00000006
REG2=0x00000007
REG3=0x00000008
fake_reg (内核线程测试)
fake_reg.c
4个寄存器每秒 加1
c
复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/device.h>
#define DEVICE_NAME "fake_reg"
static int major;
static struct class *fake_reg_class;
static struct task_struct *reg_thread;
// 4 个寄存器
static u32 regs[4] = {0};
static DEFINE_MUTEX(regs_mutex);
// ----------------- 文件操作 -----------------
static ssize_t fake_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{
ssize_t ret = 0;
mutex_lock(®s_mutex);
if(len < 4*sizeof(u32)) {
ret = -EINVAL;
goto out;
}
if(copy_to_user(buf, regs, 4*sizeof(u32))) {
ret = -EFAULT;
goto out;
}
ret = 4*sizeof(u32);
out:
mutex_unlock(®s_mutex);
return ret;
}
static ssize_t fake_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
ssize_t ret = 0;
mutex_lock(®s_mutex);
if(len < 4*sizeof(u32)) {
ret = -EINVAL;
goto out;
}
if(copy_from_user(regs, buf, 4*sizeof(u32))) {
ret = -EFAULT;
goto out;
}
ret = 4*sizeof(u32);
out:
mutex_unlock(®s_mutex);
return ret;
}
static long fake_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return -ENOTTY; // 暂不支持自定义 ioctl
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = fake_read,
.write = fake_write,
.unlocked_ioctl = fake_ioctl,
};
// ----------------- 内核线程 -----------------
int reg_thread_fn(void *data)
{
while(!kthread_should_stop()) {
mutex_lock(®s_mutex);
for(int i=0;i<4;i++)
regs[i]++;
mutex_unlock(®s_mutex);
ssleep(1); // 每秒自增
}
return 0;
}
// ----------------- 初始化和退出 -----------------
static int __init fake_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &fops);
if(major < 0)
return major;
fake_reg_class = class_create(DEVICE_NAME);
if(IS_ERR(fake_reg_class)) {
unregister_chrdev(major, DEVICE_NAME);
return PTR_ERR(fake_reg_class);
}
device_create(fake_reg_class, NULL, MKDEV(major,0), NULL, DEVICE_NAME);
reg_thread = kthread_run(reg_thread_fn, NULL, "reg_thread");
printk(KERN_INFO "fake_reg loaded, major=%d\n", major);
return 0;
}
static void __exit fake_exit(void)
{
if(reg_thread)
kthread_stop(reg_thread);
device_destroy(fake_reg_class, MKDEV(major,0));
class_destroy(fake_reg_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_INFO "fake_reg unloaded\n");
}
module_init(fake_init);
module_exit(fake_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("minglie");
MODULE_DESCRIPTION("Fake 4-register driver with kernel thread and mutex");
fake_regApp.c
先配置4个寄存器,然后不断轮询读
c
复制代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
int main()
{
int fd = open("/dev/fake_reg", O_RDWR);
if(fd < 0) {
perror("open");
return -1;
}
uint32_t regs[4] = {5,6,7,8};
if(write(fd, regs, sizeof(regs)) != sizeof(regs)) {
perror("write");
return -1;
}
printf("Initial registers set.\n");
while(1) {
if(read(fd, regs, sizeof(regs)) != sizeof(regs)) {
perror("read");
break;
}
printf("Registers: REG0=%u REG1=%u REG2=%u REG3=%u\n",
regs[0], regs[1], regs[2], regs[3]);
sleep(1);
}
close(fd);
return 0;
}
bash
复制代码
# 测试
$ ./fake_regApp
Initial registers set.
Registers: REG0=5 REG1=6 REG2=7 REG3=8
Registers: REG0=6 REG1=7 REG2=8 REG3=9
...
设备模型与 Sysfs 层次对照表
| 层次 |
作用 |
显示路径 |
注册/创建函数 |
用户态访问 |
典型用途 |
| /sys/devices |
内核真实设备对象,反映总线层次和设备父子关系 |
/sys/devices/...(platform、PCI、I2C 等) |
platform_device_register_simple() / device_register() |
可以访问,但路径深、不固定 |
驱动绑定、管理硬件资源(IRQ、DMA)、调试总线关系 |
| /sys/class |
功能分类抽象,为用户提供稳定、快捷访问 |
/sys/class/<class>/<device> |
class_create() + device_create() |
路径短、固定、用户态友好 |
用户访问设备接口,控制 GPIO、LED、PWM、块设备等 |
| Sysfs 属性文件 |
暴露设备寄存器、状态或控制接口 |
/sys/class/<class>/<device>/<attr> 或 /sys/devices/.../<attr> |
device_create_file() |
用户态通过 cat / echo / 文件操作访问 |
控制寄存器、读写状态、用户态调试接口 |
| /dev |
字符或块设备文件,提供标准文件接口 |
/dev/<device> |
alloc_chrdev_region() + cdev_add() |
用户态直接 open/read/write/ioctl |
用户态程序读写设备、驱动接口、IO 访问 |
fake_reg (注册到/dev/fake_reg)
fake_reg.c
c
复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "fake_reg"
// 4 个寄存器
static u32 regs[4] = {0};
static DEFINE_MUTEX(regs_mutex);
// 字符设备
static dev_t fake_dev; // 主次设备号
static struct cdev fake_cdev;
// ----------------- 字符设备操作 -----------------
static int fake_open(struct inode *inode, struct file *file)
{
return 0;
}
static int fake_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t fake_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
char tmp[32];
int len;
mutex_lock(®s_mutex);
len = snprintf(tmp, sizeof(tmp), "%u\n", regs[0]);
mutex_unlock(®s_mutex);
if (*ppos >= len)
return 0;
if (count > len - *ppos)
count = len - *ppos;
if (copy_to_user(buf, tmp + *ppos, count))
return -EFAULT;
*ppos += count;
return count;
}
static ssize_t fake_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
char tmp[32];
unsigned long val;
if (count >= sizeof(tmp))
return -EINVAL;
if (copy_from_user(tmp, buf, count))
return -EFAULT;
tmp[count] = 0;
if (kstrtoul(tmp, 10, &val))
return -EINVAL;
mutex_lock(®s_mutex);
regs[0] = (u32)val;
mutex_unlock(®s_mutex);
return count;
}
static const struct file_operations fake_fops = {
.owner = THIS_MODULE,
.open = fake_open,
.release = fake_release,
.read = fake_read,
.write = fake_write,
};
// ----------------- 初始化和退出 -----------------
static int __init fake_init(void)
{
int ret;
// 1. 分配主次设备号
ret = alloc_chrdev_region(&fake_dev, 0, 1, DEVICE_NAME);
if (ret)
return ret;
// 2. 初始化 cdev 并注册
cdev_init(&fake_cdev, &fake_fops);
fake_cdev.owner = THIS_MODULE;
ret = cdev_add(&fake_cdev, fake_dev, 1);
if (ret) {
unregister_chrdev_region(fake_dev, 1);
return ret;
}
printk(KERN_INFO "fake_reg loaded: /dev/%s (major=%d, minor=%d)\n",
DEVICE_NAME, MAJOR(fake_dev), MINOR(fake_dev));
return 0;
}
static void __exit fake_exit(void)
{
cdev_del(&fake_cdev);
unregister_chrdev_region(fake_dev, 1);
printk(KERN_INFO "fake_reg unloaded\n");
}
module_init(fake_init);
module_exit(fake_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("minglie");
MODULE_DESCRIPTION("Fake 4-register driver with /dev only");
bash
复制代码
$ make
$ sudo rmmod fake_reg
$ sudo insmod fake_reg.ko
$ dmesg | tail
[1] fake_reg loaded: /dev/fake_reg (major=240, minor=0)
$ sudo mknod /dev/fake_reg c 240 0
$ cd /dev
$ cat fake_reg
0
$ echo 123 | sudo tee fake_reg
123
$ cat fake_reg
123
fake_reg (reg0注册到/sys/class属性)
fake_reg.c
c
复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/device.h>
#define DEVICE_NAME "fake_reg"
// 4 个寄存器
static u32 regs[4] = {0};
static DEFINE_MUTEX(regs_mutex);
static struct class *fake_reg_class;
static struct device *fake_reg_device;
// ----------------- sysfs 属性 -----------------
static ssize_t reg0_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
mutex_lock(®s_mutex);
ret = sprintf(buf, "%u\n", regs[0]);
mutex_unlock(®s_mutex);
return ret;
}
static ssize_t reg0_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long val;
if (kstrtoul(buf, 10, &val))
return -EINVAL;
mutex_lock(®s_mutex);
regs[0] = (u32)val;
mutex_unlock(®s_mutex);
return count;
}
static DEVICE_ATTR(reg0, 0644, reg0_show, reg0_store);
// ----------------- 初始化和退出 -----------------
static int __init fake_init(void)
{
int ret;
fake_reg_class = class_create(DEVICE_NAME);
if (IS_ERR(fake_reg_class))
return PTR_ERR(fake_reg_class);
fake_reg_device = device_create(fake_reg_class, NULL, 0, NULL, DEVICE_NAME);
if (IS_ERR(fake_reg_device)) {
class_destroy(fake_reg_class);
return PTR_ERR(fake_reg_device);
}
ret = device_create_file(fake_reg_device, &dev_attr_reg0);
if (ret) {
device_destroy(fake_reg_class, 0);
class_destroy(fake_reg_class);
return ret;
}
printk(KERN_INFO "fake_reg loaded, reg0 available at /sys/class/%s/%s/reg0\n",
DEVICE_NAME, DEVICE_NAME);
return 0;
}
static void __exit fake_exit(void)
{
device_remove_file(fake_reg_device, &dev_attr_reg0);
device_destroy(fake_reg_class, 0);
class_destroy(fake_reg_class);
printk(KERN_INFO "fake_reg unloaded\n");
}
module_init(fake_init);
module_exit(fake_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("minglie");
MODULE_DESCRIPTION("Fake 4-register driver with reg0 as sysfs attribute");
bash
复制代码
## 测试
$ make
$ sudo rmmod fake_reg
$ sudo insmod fake_reg.ko
$ dmesg | tail
[ 20] reg0 available at /sys/class/fake_reg/fake_reg/reg0
$ cd /sys/class/fake_reg/fake_reg
$ ls
power reg0 subsystem uevent
$ cat reg0
0
$ echo 123 | sudo tee /sys/class/fake_reg/fake_reg/reg0
123
$ cat reg0
123
fake_reg.c
c
复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#define DEVICE_NAME "fake_reg"
// 4 个寄存器
static u32 regs[4] = {0};
static DEFINE_MUTEX(regs_mutex);
static struct class *fake_reg_class;
static struct device *fake_reg_device;
static struct platform_device *fake_pdev;
// ----------------- sysfs 属性 -----------------
static ssize_t reg0_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
mutex_lock(®s_mutex);
ret = sprintf(buf, "%u\n", regs[0]);
mutex_unlock(®s_mutex);
return ret;
}
static ssize_t reg0_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long val;
if (kstrtoul(buf, 10, &val))
return -EINVAL;
mutex_lock(®s_mutex);
regs[0] = (u32)val;
mutex_unlock(®s_mutex);
return count;
}
static DEVICE_ATTR(reg0, 0644, reg0_show, reg0_store);
// ----------------- 初始化和退出 -----------------
static int __init fake_init(void)
{
int ret;
// 1. 注册 platform device -> /sys/devices/platform/fake_reg
fake_pdev = platform_device_register_simple(DEVICE_NAME, -1, NULL, 0);
if (IS_ERR(fake_pdev))
return PTR_ERR(fake_pdev);
// 2. 可选:创建 class 供用户态方便访问 /sys/class/fake_reg
fake_reg_class = class_create(DEVICE_NAME);
if (IS_ERR(fake_reg_class)) {
platform_device_unregister(fake_pdev);
return PTR_ERR(fake_reg_class);
}
//创建 /sys/class/<class>/<device>
fake_reg_device = device_create(fake_reg_class, &fake_pdev->dev, 0, NULL, DEVICE_NAME);
if (IS_ERR(fake_reg_device)) {
class_destroy(fake_reg_class);
platform_device_unregister(fake_pdev);
return PTR_ERR(fake_reg_device);
}
// 3. 添加 sysfs 属性 reg0
ret = device_create_file(fake_reg_device, &dev_attr_reg0);
if (ret) {
device_destroy(fake_reg_class, 0);
class_destroy(fake_reg_class);
platform_device_unregister(fake_pdev);
return ret;
}
printk(KERN_INFO "fake_reg loaded, reg0 available at /sys/devices/platform/%s/reg0\n",
DEVICE_NAME);
return 0;
}
static void __exit fake_exit(void)
{
device_remove_file(fake_reg_device, &dev_attr_reg0);
device_destroy(fake_reg_class, 0);
class_destroy(fake_reg_class);
platform_device_unregister(fake_pdev);
printk(KERN_INFO "fake_reg unloaded\n");
}
module_init(fake_init);
module_exit(fake_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("minglie");
MODULE_DESCRIPTION("Fake 4-register driver with reg0 as sysfs attribute");
bash
复制代码
## 测试
$ make
$ sudo rmmod fake_reg
$ sudo insmod fake_reg.ko
$ dmesg | tail
$ cd /sys/devices/platform/fake_reg/fake_reg/fake_reg
$ ls
device power reg0 subsystem uevent
$ cat reg0
0
$ sudo echo 123 > reg0
123
$ cat reg0
123
设备树API
| 函数名 |
中文说明 |
| of_find_node_by_path |
按设备树绝对路径查找节点 |
| of_find_node_by_name |
按节点名查找节点 |
| of_find_node_by_type |
按节点 type 字段查找 |
| of_find_compatible_node |
按 compatible 字符串查找节点 |
| of_find_matching_node |
按 of_device_id 表匹配节点 |
| of_find_node_opts_by_path |
按路径查找节点并解析选项 |
| for_each_compatible_node |
遍历所有匹配 compatible 的节点 |
| for_each_node_by_type |
遍历指定 type 的节点 |
| of_property_read_string |
读取字符串属性 |
| of_property_read_string_index |
读取字符串数组中的指定项 |
| of_property_match_string |
在字符串数组中匹配字符串 |
| of_property_read_u8 |
读取 u8 数值属性 |
| of_property_read_u16 |
读取 u16 数值属性 |
| of_property_read_u32 |
读取 u32 数值属性 |
| of_property_read_u64 |
读取 u64 数值属性 |
| of_property_read_u8_array |
读取 u8 数组属性 |
| of_property_read_u16_array |
读取 u16 数组属性 |
| of_property_read_u32_array |
读取 u32 数组属性 |
| of_property_read_u64_array |
读取 u64 数组属性 |
| of_property_read_bool |
判断布尔属性是否存在 |
| of_find_property |
查找属性是否存在 |
| of_get_property |
获取属性原始数据指针 |
| of_address_to_resource |
将 reg 转换为 resource 结构 |
| of_iomap |
将 reg 描述的地址映射为虚拟地址 |
| of_translate_address |
设备树地址翻译(父总线) |
| of_get_address |
获取 reg 中的地址信息 |
| of_dma_get_range |
获取 DMA 地址映射范围 |
| of_irq_get |
获取指定索引的中断号 |
| of_irq_get_byname |
按名称获取中断号 |
| of_irq_parse_and_map |
解析并映射中断 |
| irq_of_parse_and_map |
将设备树中断映射为 Linux IRQ |
| of_get_gpio |
获取 GPIO 编号 |
| of_get_named_gpio |
按属性名获取 GPIO |
| of_get_named_gpio_flags |
获取 GPIO 及其标志 |
| of_gpio_count |
统计 GPIO 数量 |
| of_clk_get |
获取设备树中的时钟 |
| of_clk_get_by_name |
按名称获取时钟 |
| of_clk_add_provider |
注册时钟提供者 |
| of_clk_del_provider |
注销时钟提供者 |
| of_reset_control_get |
获取复位控制器 |
| of_reset_control_array_get |
获取多个复位控制器 |
| of_get_regulator |
获取电源调节器 |
| of_regulator_match |
匹配 regulator 节点 |
| of_get_parent |
获取父节点 |
| of_get_next_child |
获取下一个子节点 |
| for_each_child_of_node |
遍历所有子节点 |
| of_get_child_by_name |
按名称获取子节点 |
| of_parse_phandle |
解析 phandle 引用 |
| of_parse_phandle_with_args |
解析带参数的 phandle |
| of_count_phandle_with_args |
统计 phandle 数量 |
| of_match_node |
按匹配表匹配节点 |
| of_device_is_compatible |
判断节点是否兼容 |
| of_node_name_eq |
判断节点名是否相等 |
| of_modalias_node |
生成 modalias 字符串 |
| of_node_put |
释放设备节点引用 |