实习日记--驱动编写

一、系统调用处理函数

open 初始化,read/write 传数据(必须用 copy 函数),release 清理。

在此先定义一个结构体

struct mydev_data {
    char *buffer;           /* 数据缓冲区 */
    int buffer_len;         /* 当前数据长度 */
    int open_count;         /* 打开次数统计 */
};

1、open

open 函数:用户调用 open() 时触发

static int my_open(struct inode *inode, struct file *file)

参数说明
inode设备文件的 inode,可获取次设备号 iminor(inode)
file文件结构体,可保存私有数据 file->private_data
static int my_open(struct inode *inode, struct file *file)
{
    /* 保存私有数据,供 read/write 使用 */
    file->private_data = my_data;
    
    /* 初始化硬件、计数等 */
    my_data->open_count++;
    
    return 0;  /* 必须返回0表示成功 */
}

2、read

read 函数:用户调用 read() 时触发

static ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *off)

参数说明
file文件结构体
buf用户空间的缓冲区指针
len请求读取的字节数
off文件偏移指针
返回值成功:读取的字节数;失败:负数错误码
struct my_device {
    char buffer[256];
    int buffer_len;
};

static ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *off)
{
    struct my_device *dev = file->private_data;
    size_t available;   /* 可读的数据量 */
    size_t to_read;     /* 实际要读的数据量 */
    
    /* 计算从当前位置还有多少数据可读 */
    available = dev->buffer_len - *off;
    
    if (available == 0) {
        return 0;  /* EOF */
    }
    
    /* 用户要的长度和可用长度,取较小的 */
    to_read = (len < available) ? len : available;
    
    /* 拷贝数据到用户空间 */
    if (copy_to_user(buf, dev->buffer + *off, to_read)) {
        printk(KERN_ERR "copy_to_user failed\n");
        return -EFAULT;
    }
    
    /* 更新偏移 */
    *off += to_read;
    
    printk(KERN_INFO "read %zu bytes, offset=%lld\n", to_read, *off);
    
    return to_read;
}

3、write

write 函数:用户调用 write() 时触发

static ssize_t my_write(struct file *file, const char __user *buf, size_t len, loff_t *off)

参数说明
file文件结构体
buf用户空间的缓冲区指针
len请求写入的字节数
off文件偏移指针
返回值成功:写入的字节数;失败:负数错误码
struct my_device {
    char buffer[256];
    int buffer_len;
};

static ssize_t my_write(struct file *file, const char __user *buf, size_t len, loff_t *off)
{
    struct my_device *dev = file->private_data;
    size_t to_write;
    
    /* 限制写入长度,防止溢出 */
    to_write = (len < sizeof(dev->buffer) - 1) ? len : sizeof(dev->buffer) - 1;
    
    if (to_write == 0) {
        return -ENOMEM;
    }
    
    /* 从用户空间拷贝数据 */
    if (copy_from_user(dev->buffer, buf, to_write)) {
        printk(KERN_ERR "copy_from_user failed\n");
        return -EFAULT;
    }
    
    /* 添加字符串结束符 */
    dev->buffer[to_write] = '\0';
    dev->buffer_len = to_write;
    
    /* 写完后重置读偏移 */
    *off = 0;
    
    printk(KERN_INFO "wrote %zu bytes: %s\n", to_write, dev->buffer);
    
    return to_write;
}

4、release

release 函数:用户调用 close() 时触发

static int my_release(struct inode *inode, struct file *file)

5、常用辅助函数

copy_to_user / copy_from_user

// 从内核空间拷贝到用户空间(必须!)
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);

// 从用户空间拷贝到内核空间(必须!)
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);

参数说明
to用户空间的目的地址(__user 修饰)
from内核空间的源地址
n要拷贝的字节数

返回值:0--全部拷贝成功,非零--失败的字节数

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

参数说明
to内核空间的目的地址
from用户空间的源地址(__user 修饰)
n要拷贝的字节数

返回值:0--全部拷贝成功,非零--失败的字节数

6、用户空间测试实例

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd;
    char write_buf[] = "Hello from userspace!";
    char read_buf[256] = {0};
    int ret;
    
    // 打开设备
    fd = open("/dev/mychardev", O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    printf("open success\n");
    
    // 写入数据
    ret = write(fd, write_buf, strlen(write_buf));
    printf("write %d bytes: %s\n", ret, write_buf);
    
    // 读取数据
    ret = read(fd, read_buf, sizeof(read_buf));
    printf("read %d bytes: %s\n", ret, read_buf);
    
    // 关闭设备
    close(fd);
    printf("close success\n");
    
    return 0;
}

二、模块框架函数

函数调用时机作用
module_init()insmod 时模块入口,初始化
module_exit()rmmod 时模块出口,清理
probe设备匹配成功时初始化具体设备
remove设备移除时清理具体设备

1、module_init

在 insmod 加载模块时执行,做全局的一次性初始化

static int __init my_init(void)
{
    int ret;
    
    /* 分配全局内存(如果多个设备共享) */
    g_shared_buffer = kmalloc(1024, GFP_KERNEL);
    if (!g_shared_buffer)
        return -ENOMEM;
    
    /* 注册 platform 驱动 */
    ret = platform_driver_register(&my_driver);
    if (ret) {
        kfree(g_shared_buffer);
        return ret;
    }
    
    /* 注册字符设备(如果不依赖设备树) */
    ret = misc_register(&my_misc);
    if (ret) {
        platform_driver_unregister(&my_driver);
        kfree(g_shared_buffer);
        return ret;
    }
    
    pr_info("init success\n");
    return 0;
}

2、module_exit

在 rmmod 卸载模块时执行,清理 init 做的工作。

static void __exit my_exit(void)
{
    /* 1. 注销注册过的资源(与 init 顺序相反) */
    misc_deregister(&my_misc);
    platform_driver_unregister(&my_driver);
    
    /* 2. 释放分配的内存 */
    kfree(g_shared_buffer);
    
    pr_info("module unloaded\n");
}

3、probe

在 设备树匹配成功时调用,初始化具体的一个设备

static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct my_device *priv;
    struct resource *res;
    int ret;
    
    /* ========== 1. 分配私有数据结构 ========== */
    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    
    /* 保存私有数据,remove 时能用 */
    platform_set_drvdata(pdev, priv);
    priv->pdev = pdev;
    
    /* ========== 2. 获取寄存器地址(ioremap) ========== */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(dev, "missing memory resource\n");
        return -ENXIO;
    }
    priv->regs = devm_ioremap(dev, res->start, resource_size(res));
    if (!priv->regs) {
        dev_err(dev, "ioremap failed\n");
        return -ENOMEM;
    }
    
    /* ========== 3. 获取中断号并注册中断 ========== */
    priv->irq = platform_get_irq(pdev, 0);
    if (priv->irq < 0)
        return priv->irq;  /* 中断是必须的 */
    
    ret = devm_request_irq(dev, priv->irq, my_isr, IRQF_TRIGGER_RISING,
                           "my_device", priv);
    if (ret) {
        dev_err(dev, "request_irq failed\n");
        return ret;
    }
    
    /* ========== 4. 获取 GPIO ========== */
    priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
    if (IS_ERR(priv->reset_gpio)) {
        dev_err(dev, "failed to get reset gpio\n");
        return PTR_ERR(priv->reset_gpio);
    }
    
    /* ========== 5. 从设备树读取属性 ========== */
    if (device_property_read_u32(dev, "my-value", &priv->value))
        priv->value = 0;  /* 默认值 */
    
    /* ========== 6. 初始化硬件 ========== */
    /* 复位设备 */
    gpiod_set_value(priv->reset_gpio, 1);
    msleep(10);
    gpiod_set_value(priv->reset_gpio, 0);
    msleep(50);
    
    /* 写寄存器初始化 */
    writel(0x01, priv->regs + REG_CTRL);
    
    /* ========== 7. 创建字符设备或注册到子系统 ========== */
    ret = misc_register(&priv->miscdev);
    if (ret) {
        dev_err(dev, "misc_register failed\n");
        return ret;
    }
    
    /* ========== 8. 创建 sysfs 文件(可选) ========== */
    priv->sysfs_node = sysfs_create_group(&dev->kobj, &my_attr_group);
    
    dev_info(dev, "probe success\n");
    return 0;
}

4、remove

 在 设备移除或模块卸载时调用,清理 probe 做的工作

static int my_remove(struct platform_device *pdev)
{
    struct my_device *priv = platform_get_drvdata(pdev);
    struct device *dev = &pdev->dev;
    
    /* ========== 1. 注销注册过的资源(与 probe 顺序相反) ========== */
    /* 先删除 sysfs */
    sysfs_remove_group(&dev->kobj, &my_attr_group);
    
    /* 再注销 misc 设备 */
    misc_deregister(&priv->miscdev);
    
    /* ========== 2. 停止硬件 ========== */
    /* 关闭中断、停止 DMA、禁用设备等 */
    writel(0x00, priv->regs + REG_CTRL);  /* 禁用设备 */
    writel(0x00, priv->regs + REG_INTEN); /* 屏蔽中断 */
    
    /* ========== 3. 释放 GPIO(devm_ 会自动释放,但手动复位一下) ========== */
    gpiod_set_value(priv->reset_gpio, 1);  /* 复位设备,让给其它驱动 */
    
    /* ========== 4. devm_ 分配的资源会自动释放,不用手动 free ========== */
    
    dev_info(dev, "remove success\n");
    return 0;
}
函数必须写可选写
init注册驱动(platform_driver_register分配全局内存、注册全局字符设备
exit注销驱动(platform_driver_unregister释放全局内存
probe分配私有数据、获取资源、初始化硬件、注册到子系统创建 sysfs、注册中断
remove注销子系统注册、停止硬件删除 sysfs、释放私有数据(devm 自动)

三、如何匹配设备树

1. 定义设备树匹配表

static const struct of_device_id of_gpio_leds_match[] = {
    { .compatible = "gpio-leds", },  // 匹配 compatible 属性为 "gpio-leds"
    {},                               // 空哨兵,表示表结束
};

2. 导出设备表

MODULE_DEVICE_TABLE(of, of_gpio_leds_match);

3.定义platform_driver 结构体

static struct platform_driver gpio_led_driver = {
    .probe      = gpio_led_probe,
    .shutdown   = gpio_led_shutdown,
    .driver     = {
        .name   = "leds-gpio",
        .of_match_table = of_gpio_leds_match,  // 👈 关键!
    },
};

4.匹配流程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值