wsl环境下驱动开发的例子

参考

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!

build.sh 编译并覆盖旧驱动

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

Readme.md

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

build.sh

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

ReadMe.md

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(&regs_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(&regs_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(&regs_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(&regs_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(&regs_mutex);
        for(int i=0;i<4;i++)
            regs[i]++;
        mutex_unlock(&regs_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;
}

Readme.md

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(&regs_mutex);
    len = snprintf(tmp, sizeof(tmp), "%u\n", regs[0]);
    mutex_unlock(&regs_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(&regs_mutex);
    regs[0] = (u32)val;
    mutex_unlock(&regs_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");

Readme.md

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(&regs_mutex);
    ret = sprintf(buf, "%u\n", regs[0]);
    mutex_unlock(&regs_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(&regs_mutex);
    regs[0] = (u32)val;
    mutex_unlock(&regs_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");

Readme.md

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 (reg0注册到/sys/devices/platform属性)

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(&regs_mutex);
    ret = sprintf(buf, "%u\n", regs[0]);
    mutex_unlock(&regs_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(&regs_mutex);
    regs[0] = (u32)val;
    mutex_unlock(&regs_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");

Readme.md

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 释放设备节点引用
相关推荐
前进的程序员3 小时前
驱动开发中Linux系统裁剪、开发、调试步骤
linux·驱动开发·设备树
dump linux3 小时前
Linux 显示服务器与合成器架构详解
linux·驱动开发·3d
Aaron158813 小时前
基于RFSOC的数字射频存储技术应用分析
c语言·人工智能·驱动开发·算法·fpga开发·硬件工程·信号处理
嵌入式-老费17 小时前
Linux camera驱动开发(串行和解串)
驱动开发
代码游侠18 小时前
学习笔记——Linux字符设备驱动开发
linux·arm开发·驱动开发·单片机·嵌入式硬件·学习·算法
wxmtwfx1 天前
Linux内核GPIO工具概述
linux·驱动开发·gpio
松涛和鸣1 天前
69、Linux字符设备驱动实战
linux·服务器·网络·arm开发·数据库·驱动开发
TangDuoduo00051 天前
【Linux下LED基础设备驱动】
linux·驱动开发
夜星辰20232 天前
RV1126 音频驱动开发:I2S + ES8311编解码实战
驱动开发·音频驱动·es8311