正点原子RK3568学习日志18-一个驱动兼容不同设备
1.一个驱动兼容不同设备
函数原型:
container_of(ptr,type,member)
函数作用:
通过结构体变量中某个成员的首地址获取到整个结构体变量的首地址。
参数含义:
ptr是结构体变量中某个成员的地址。
type是结构体的类型
member是该结构体变量的具体名字
container_of宏的作用是通过结构体内某个成员变量的地址和该变量名,以及结构体类型。找到该结构体变量的地址
struct device_test dev1;//定义设备结构体变量dev1 struct device_test dev2;//定义设备结构体变量dev2//open函数实现,在那个file结构体找 static int cdev_test_open(struct inode *inode, struct file *file) {dev1.minor = 0; //次设备号赋值为0dev2.minor = 1; //次设备号赋值为1//inode->i_rdev 为该 inode 的设备号,使用container_of函数找到结构体变量dev1 dev2的地址 //然后设置私有数据file->private_data = container_of(inode->i_cdev, struct device_test, cdev_test); printk("This is cdev_test_open\r\n");return 0; }if (test_dev->minor == 0) //次设备号为0{if (copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf)) != 0) //从内核空间拷贝数据到用户空间{printk("copy_to_user is error\n");return -1;}}else if (test_dev->minor == 1) //次设备号为1{if (copy_to_user(buf, test_dev->kbuf, size) != 0) //从内核空间拷贝数据到用户空间{printk("copy_to_user is error\n");return -1;}}if (test_dev->minor == 0) //次设备号为0{if (copy_from_user(test_dev->kbuf, buf, size) != 0)//从用户空间拷贝数据到内核空间{printk("copy_from_user is error\n");return -1;}printk("test_dev->kbuf = %s\r\n", test_dev->kbuf);}else if (test_dev->minor == 1) //次设备号为1{if (copy_from_user(test_dev->kbuf, buf, size) != 0)//从用户空间拷贝数据到内核空间{printk("copy_from_user is error\n");return -1;}printk("test_dev->kbuf = %s\r\n", test_dev->kbuf);}
2.实验:11_container_of 一个驱动兼容不同设备实验
container_of.c
#include <linux/init.h> //初始化头文件 #include <linux/module.h> //基本模块文件 #include <linux/kdev_t.h> //包含设备号相关宏和函数 #include <linux/cdev.h> //字符设备结构体头文件cdev #include <linux/fs.h> //注册设备节点的文件结构体 #include <linux/uaccess.h> //用户空间和内核空间数据传输头文件// //设备号dev_t,设备cdev // static dev_t dev_num; //定义32位的变量dev_num,用于存放设备号 // static int major; //定义主设备号变量 // static int minor; //定义次设备号变量 // static struct cdev cdev_test; //定义cdev结构体类型的变量cdev_test// //设备节点 // static struct class *class; //表示要创建的类 // static struct device *device; //表示要创建的设备struct device_test{dev_t dev_num; //设备号int major;int minor;struct cdev cdev_test;struct class *class;struct device *device;char kbuf[32];};struct device_test dev1;//定义设备结构体变量dev1 struct device_test dev2;//定义设备结构体变量dev2//open函数实现,在那个file结构体找 static int cdev_test_open(struct inode *inode, struct file *file) {dev1.minor = 0; //次设备号赋值为0dev2.minor = 1; //次设备号赋值为1//inode->i_rdev 为该 inode 的设备号,使用container_of函数找到结构体变量dev1 dev2的地址 //然后设置私有数据file->private_data = container_of(inode->i_cdev, struct device_test, cdev_test); printk("This is cdev_test_open\r\n");return 0; }//read函数,从设备读取数据 static ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) {struct device_test *test_dev = (struct device_test *)file->private_data; //获取设备结构体变量地址if (test_dev->minor == 0) //次设备号为0{if (copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf)) != 0) //从内核空间拷贝数据到用户空间{printk("copy_to_user is error\n");return -1;}}else if (test_dev->minor == 1) //次设备号为1{if (copy_to_user(buf, test_dev->kbuf, size) != 0) //从内核空间拷贝数据到用户空间{printk("copy_to_user is error\n");return -1;}}return 0; }//write函数,向设备写入数据 static ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) {struct device_test *test_dev = (struct device_test *)file->private_data; //获取设备结构体变量地址if (test_dev->minor == 0) //次设备号为0{if (copy_from_user(test_dev->kbuf, buf, size) != 0)//从用户空间拷贝数据到内核空间{printk("copy_from_user is error\n");return -1;}printk("test_dev->kbuf = %s\r\n", test_dev->kbuf);}else if (test_dev->minor == 1) //次设备号为1{if (copy_from_user(test_dev->kbuf, buf, size) != 0)//从用户空间拷贝数据到内核空间{printk("copy_from_user is error\n");return -1;}printk("test_dev->kbuf = %s\r\n", test_dev->kbuf);}return 0; }static int cdev_test_release (struct inode *inode, struct file *file) {printk("This is cdev_test_release\n");return 0; }//设备操作函数 static struct file_operations cdev_test_fops = { //file_operations结构体.owner = THIS_MODULE, //指向本模块.open = cdev_test_open, //指向cdev_test_open函数.read = cdev_test_read, //指向cdev_test_read函数.write = cdev_test_write, //指向cdev_test_write函数.release = cdev_test_release, //指向cdev_test_release函数 };static int __init chr_fops_init(void) {//注册字符驱动int ret; //1.创建设备号//动态申请设备号,2个设备号ret = alloc_chrdev_region(&dev1.dev_num, 0, 2, "alloc_name"); if(ret < 0) //申请失败{printk("alloc_chrdev_region is error\n");}printk("alloc_chrdev_region is ok\n"); dev1.major = MAJOR(dev1.dev_num); //获取主设备号dev1.minor = MINOR(dev1.dev_num); //获取次设备号printk("major = %d\n", dev1.major); //打印设备号printk("minor = %d\n", dev1.minor); //2.注册字符设备cdev //初始化cdev//使用cdev_init()初始化cdev_test结构体,并且连接到cdev_test_ops结构体cdev_init(&dev1.cdev_test, &cdev_test_fops); dev1.cdev_test.owner = THIS_MODULE; //将owner字段指向本模块,防止模块被卸载//添加cdevcdev_add(&dev1.cdev_test, dev1.dev_num, 1); //注册字符设备到内核//3.创建设备节点//创建类和设备节点dev1.class = class_create(THIS_MODULE, "test1"); //创建类,名字为testdev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test1"); //创建设备,名字为test//第二个设备cdevdev2.major = MAJOR(dev1.dev_num + 1); //获取主设备号dev2.minor = MINOR(dev1.dev_num + 1); //获取次设备号printk("major = %d\r\n", dev2.major); //打印设备号printk("minor = %d\r\n", dev2.minor); cdev_init(&dev2.cdev_test, &cdev_test_fops); dev2.cdev_test.owner = THIS_MODULE; //将owner字段指向本模块,防止模块被卸载//添加cdevcdev_add(&dev2.cdev_test, dev1.dev_num+1, 1); //注册字符设备到内核dev2.class = class_create(THIS_MODULE, "test2"); //创建类,名dev2.device = device_create(dev2.class, NULL, dev1.dev_num+1, NULL, "test2"); //创建设备return 0; } static void __exit chr_fops_exit(void) {unregister_chrdev_region(dev1.dev_num, 1); //释放设备号 unregister_chrdev_region(dev1.dev_num+1, 1); //释放设备号 cdev_del(&dev1.cdev_test); //注销字符设备 cdev_del(&dev2.cdev_test); //注销字符设备 device_destroy(dev1.class, dev1.dev_num); //删除设备节点device_destroy(dev2.class, dev1.dev_num+1); //删除设备节点 class_destroy(dev1.class); //删除类 class_destroy(dev2.class); //删除类 }module_init(chr_fops_init); module_exit(chr_fops_exit); MODULE_LICENSE("GPL v2"); //声明模块许可证 MODULE_AUTHOR("AFANFAN"); //声明模块作者
Makefile
export ARCH=arm64#设置平台架构 export CROSS_COMPILE=/opt/atk-dlrk3568-5_10_sdk-toolchain/bin/aarch64-buildroot-linux-gnu-#交叉编译器前缀 obj-m += container_of.o #此处要和你的驱动源文件同名 KDIR :=/home/alientek/rk3568_linux5.10_sdk/kernel #这里是你的内核目录 PWD ?= $(shell pwd) all:make -C $(KDIR) M=$(PWD) modules #make操作 clean:make -C $(KDIR) M=$(PWD) clean #make clean操作
app.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>int main(int argc, char *argv[]) {int fd1; //设备1文件描述符int fd2; //设备2文件描述符 char buf1[32] = "nihao, /dev/test1"; //写入设备1的数据缓冲区char buf2[32] = "hello, /dev/test2"; //写入设备2的数据缓冲区fd1 = open("dev/test1", O_RDWR); //打开设备文件/dev/test1if(fd1 < 0) //打开失败{printf("open file error\n");return -1;}write(fd1, buf1, sizeof(buf1)); //将buf1缓冲区的数据写入到dev/test设备文件中close(fd1); //关闭设备文件 对取消文件描述符到文件的映射fd2 = open("dev/test2", O_RDWR); //打开设备文件/dev/test2if(fd2 < 0) //打开失败{printf("open file error\n");return -1;}write(fd2, buf2, sizeof(buf2)); //将buf2缓冲区的数据写入到dev/test设备文件中close(fd2); //关闭设备文件 对取消文件描述符到文件的映射return 0; }///opt/atk-dlrk3568-5_10_sdk-toolchain/bin/aarch64-buildroot-linux-gnu-gcc -o app app.c -static
驱动编译成驱动模块ko
使用命令“make”进行驱动的编译,编译完生成 container_of.ko目标文件
编译测试程序app
生成的app文件就是之后放在开发板上运行的可执行文件
/opt/atk-dlrk3568-5_10_sdk-toolchain/bin/aarch64-buildroot-linux-gnu-gcc -o app app.c -static
运行驱动编译成模块insmod
开发板启动之后, insmod container_of.ko
运行测试程序 ./app
3.问题:
1.私有数据的使用场兼容不同设备,
cdev_add(&dev2.cdev_test, dev1.dev_num+1, 1); //注册字符设备到内核
将其次设备号加1,而主设备号保持不变。
dev_num+1 的目的就是为了给驱动程序管理的第二个设备分配一个与第一个设备不同的、唯一的次设备号,从而让内核和用户空间能够准确地区分和访问这两个不同的设备

