一、先想清楚:驱动要干什么?
写驱动代码之前,首先梳理清楚这几个问题:
1.应用层怎么用?
open → read → write → ioctl → close。
2.要和谁交互?
寄存器/内存/硬件/虚拟设备(主要是操作SOC提供的外设相关寄存器)。
3.数据怎么传?
用户态 ↔ 内核态(必须用copy_to/from_user)。
4.需要驱动实现哪些接口?
只需要open/close?还是要 read/write/ioctl?
二、整体设计思路(3步法)
1.定义设备结构体(封装所有信息)
把设备号、cdev、缓冲区、寄存器地址、锁等全部包起来:
struct mydev { dev_t devno; struct cdev cdev; char buf[1024]; void __iomem *reg_base; // 硬件寄存器映射地址 int irq; // 中断号 //可以加:mutex、gpio、寄存器指针等};struct mydev mydev;
2.根据实际需求实现file_operations结构体定义的接口
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);int (*iterate_shared) (struct file *, struct dir_context *);unsignedint(*poll)(struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync); ...}
3.实现init/exit(驱动生命周期)
init必须做3件事:
1)申请设备号
alloc_chrdev_region(&mydev.devno, 0, 1, "mydev");
2)初始化并添加cdev
cdev_init(&mydev.cdev, &fops);
cdev_add(&mydev.cdev, mydev.devno, 1);
3)(可选)自动创建设备节点 /dev/xxx
exit必须做2件事:
1)删除cdev;
2)释放设备号。
三、定义设备结构体需要注意的事
1.参考模板
struct my_dev { dev_t devno; // 设备号 struct cdev cdev; // 字符设备主体 struct class *class; // 自动创建设备节点用 struct device *device; void __iomem *reg_base; // 硬件寄存器映射地址 int irq; // 中断号 char buf[1024]; // 数据缓冲区 loff_t pos; // 当前读写偏移 struct mutex lock; // 互斥锁(并发必加),多个进程同时open/read/write会乱};
2.使用container_of
一定要用container_of从cdev找回自己的设备结构体,这是字符驱动最核心技巧:
staticintmy_open(struct inode *inode, structfile *file){//通过成员找整个结构体 struct my_dev *dev = container_of(inode->i_cdev, struct my_dev, cdev); file->private_data = dev; // 存起来,read/write 直接用 return 0;}
3.支持多设备时,结构体就是 "设备实例"
#define DEV_CNT 2static struct my_dev my_devices[DEV_CNT];
四、测试
1.源码
demo_drv.c
#include<linux/module.h>#include<linux/fs.h>#include<linux/cdev.h>#include<linux/uaccess.h>#include<linux/device.h>#include<linux/mutex.h>#define DEMO_BUF_SIZE 1024/* 设备结构体:封装一个设备的所有资源 */struct demo_dev { dev_t devno; struct cdev cdev; struct class *class; struct device *device; char buf[DEMO_BUF_SIZE]; // 数据缓冲区 loff_t pos; // 读写偏移 struct mutex lock;// 互斥锁,保证并发安全};/*目前支持1个设备(想多设备改成 2、3...)*/#define DEVICE_COUNT 1static struct demo_dev demo_devices[DEVICE_COUNT];//实现open函数staticintdemo_open(struct inode *inode, struct file *filp){ // 从cdev找回自己的 demo_dev 结构体 struct demo_dev *dev = container_of(inode->i_cdev, struct demo_dev, cdev); // 保存起来,read/write/release 直接用 filp->private_data = dev; dev->pos = 0; // 每次打开,偏移归 0 return 0;}//实现 release(close)staticintdemo_release(struct inode *inode, struct file *filp){ // 一般做清理、关闭硬件 return 0;}//实现 read(内核 → 用户)staticssize_tdemo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos){ struct demo_dev *dev = filp->private_data; ssize_t ret; int remaining; mutex_lock(&dev->lock); // ====================== 关键修复 ====================== // 已经读到末尾了 → 返回 0,告诉 cat 结束 if (*ppos >= DEMO_BUF_SIZE) { mutex_unlock(&dev->lock); return 0; } // 计算还剩多少数据可以读 remaining = DEMO_BUF_SIZE - *ppos; if (count > remaining) count = remaining; // 拷贝数据到用户空间 if (copy_to_user(buf, dev->buf + *ppos, count)) { ret = -EFAULT; goto out; } // 更新偏移量!!!(必须加) *ppos += count; ret = count;out: mutex_unlock(&dev->lock); return ret;}//实现 write(用户 → 内核)staticssize_tdemo_write(struct file *filp, constchar __user *buf, size_t count, loff_t *ppos){ struct demo_dev *dev = filp->private_data; ssize_t ret; int remaining; mutex_lock(&dev->lock); if (*ppos >= DEMO_BUF_SIZE) { mutex_unlock(&dev->lock); return 0; } remaining = DEMO_BUF_SIZE - *ppos; if (count > remaining) count = remaining; if (copy_from_user(dev->buf + *ppos, buf, count)) { ret = -EFAULT; goto out; } *ppos += count; ret = count;out: mutex_unlock(&dev->lock); return ret;}//绑定 file_operationsstatic const struct file_operations demo_fops = { .owner = THIS_MODULE, .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write,};//init 函数staticint __init demo_init(void){ int i; int ret;dev_t dev_num; // 先定义临时设备号 struct demo_dev *dev; // 1. 申请设备号 ret = alloc_chrdev_region(&dev_num, 0, DEVICE_COUNT, "demo_driver"); if (ret < 0) return ret; // 2. 遍历初始化所有设备(这里是 1 个) for (i = 0; i < DEVICE_COUNT; i++) { dev = &demo_devices[i]; //现在dev才有效 dev->devno = MKDEV(MAJOR(dev_num), i); //次设备号 i //初始化锁 mutex_init(&dev->lock); //3.初始化 cdev cdev_init(&dev->cdev, &demo_fops); dev->cdev.owner = THIS_MODULE; //4.添加 cdev 到内核 cdev_add(&dev->cdev, dev->devno, 1); //5.创建 class & device,自动生成 /dev/demo_devX dev->class = class_create("demo_class"); dev->device = device_create(dev->class, NULL, dev->devno, NULL, "demo_dev%d", i); } printk("demo driver init success\n"); return 0;}//exit 函数staticvoid __exit demo_exit(void){ int i; struct demo_dev *dev; for (i = 0; i < DEVICE_COUNT; i++) { dev = &demo_devices[i]; // 销毁设备、类 device_destroy(dev->class, dev->devno); class_destroy(dev->class); // 删除 cdev cdev_del(&dev->cdev); } // 释放设备号 unregister_chrdev_region(demo_devices[0].devno, DEVICE_COUNT); printk("demo driver exit\n");}module_init(demo_init);module_exit(demo_exit);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Standard Linux char driver demo");MODULE_AUTHOR("AXUN");
2.Makefile
CC = gcc-12 #指定编译器为gcc-12obj-m += demo_drv.o # 要编译的模块名(对应demo_drv.c)KERNELDIR ?= /lib/modules/$(shell uname -r)/build #内核源码路径PWD := $(shell pwd) #当前目录all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules #编译模块clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean #清理编译产物
3.编译及测试
1)make
2)sudo insmod demo_drv.ko
3)ls /dev/demo_dev0
4)echo "hello" > /dev/demo_dev0
5)cat /dev/demo_dev0
6)sudo rmmod demo_drv