5.3.7.自动创建字符设备驱动的设备文件
5.3.7.1、问题描述:
(1)整体流程回顾
(2)使用mknod创建设备文件的缺点
(3)能否自动生成和删除设备文件
5.3.7.2、解决方案:udev是PC机(嵌入式中用的是mdev)
(1)什么是udev?应用层的一个应用程序
(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
(3)应用层启用udev,内核驱动中使用相应接口
(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除
5.3.7.3、内核驱动设备类相关函数
(1)class_create
(2)device_create
5.3.7.4、编程实践
1.module_test.c 驱动程序
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h> //copy_from_user
#include <linux/errno.h> // 错误码
#include <mach/regs-gpio.h>//arch/arm/mach-s5pv210/include/mach/regs-gpio.h 这两个顺序不能放错,c语言基础
#include <mach/gpio-bank.h> //arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>//包含 内核的 memset 、strcmp
#include <linux/io.h> //ioremap
#include <linux/ioport.h> //request_mem_region
#include <linux/cdev.h> // cdev结构体和 新注册接口函数
#include <linux/device.h> //class_create
//#define MYMAJOR 200 /* 定义 register_chrdev 注册设备的 主设备号 : 先 cat /proc/devices 查看 200 有没有被占用 , 新老注册函数 通用*/
#define MYCNT 1 /* 注册 几个次 设备号 ,新注册接口函数 */
#define MYNAME "test_char" /* 定义 register_chrdev 注册设备的 设备名字 新老注册函数 通用 */
/*********静态内存*****************************/
#define GPJ0CON S5PV210_GPJ0CON // FD500240 虚拟地址
#define GPJ0DAT S5PV210_GPJ0DAT // FD500244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
/*********静态内存end*****************************/
/*********动态内存*****************************/
#define GPJ0CON_PA 0xE0200240 // 寄存器真实物理 地址
#define GPJ0DAT_PA 0xE0200244 //
unsigned int *pGPJOCON; // ioremap 返回的 虚拟地址
unsigned int *pGPJODAT;
/*********动态内存end*****************************/
//int mymajor; /* 定义 register_chrdev 注册设备号 (这个是老驱动模块 )*/
char kbuf[100];/* 内核空间的 buf*/
static dev_t mydev; /*register_chrdev_region 第一个参数 主设备号,这里用来接收 一个主次 设备号 , 新注册接口函数*/
//static struct cdev test_device_cdev; /* 定义一个cdev 结构体变量 test_device_cdev ,cdev_init函数的 第一个参数 新注册接口函数 */
static struct cdev *pcdev;/* 我们不 定义一个cdev 结构体变量 test_device_cdev ,而是定义一个 *pcdev 指针 */
/* 不管 static struct cdev结构体占用多大的内存, 我们这个 *pcdev 指针 只占内存的 4 个字节*/
/*********自动创建字符设备驱动的设备文件*****************************/
static struct class *test_class;
/*********自动创建字符设备驱动的设备文件end*****************************/
/* NOTE 自己定义函数指针 test_chrdev_open */
static int test_chrdev_open(struct inode *inode, struct file *file)
{
/* 这个函数中真正应该 放置 打开这个硬件设备的 操作代码 ,我们先 printk 代替一下 */
printk(KERN_INFO "test_chrdev_open module_test.c->test_chrdev_open \n");
/* 在应用app.c 执行open 时,就会执行 LED */
rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
return 0;
} /* test_chrdev_open() */
/* NOTE 自己定义函数指针 test_chrdev_release , release对应的就是 close */
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_release \n");
/* 在应用app.c 执行close 时,就会执行 LED灭 */
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
return 0;
}
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_read \n");
/* 从内核 的 kbuf, 复制到用户的 ubuf */
ret = copy_to_user(ubuf,kbuf,size); /* 成功后 就会拷贝到用户 ubuf */
if(ret) /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
{
printk(KERN_ERR "copy_to_user fail \n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user OK!!! module_test.c->test_chrdev_write\n");
return 0;
}
// 写函数的本质:将应用层 传递过来的数据先 复制到 内核中,然后将之正确的方式写入硬件完成的操作!(数据从应用层到驱动层的复制,)
// 内核有一个 虚拟地址空间,应用层有一个 虚拟地址空间
static ssize_t test_chrdev_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_write\n");
memset(kbuf,0,sizeof(kbuf)); /*清除kbuf */
//使用改函数将: 应用层传过来的 ubuf 中的内容 拷贝到驱动空间中的 一个 kbuf 中
/* 不能用memcpy(kbuf,buf); 因为 2 个 不在一个地址空间中,不能 比较 霍元甲和成龙 谁更厉害 ,不在一个年龄段*/
ret = copy_from_user(kbuf,user_buf,count); /* 成功后 就会 放到 kbuf 中 */
if(ret) /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
{
printk(KERN_ERR "copy_from_user fail \n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user OK!!! module_test.c->test_chrdev_write\n");
/* 真正的 驱动的 数据从 应用层 复制 到 驱动中后,我们就要根据这个数据去写硬件的操作,所以下面就应该操作硬件 */
if(kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5)); //LED亮
}
else if (kbuf[0]=='0')
{
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
}
return 0;
}
//自定义 file_operations 结构体 及其元素填充
/* NOTE 定义 register_chrdev 注册设备的 设备结构体 test_fops */
static const struct file_operations test_fops = {
.owner = THIS_MODULE, /* 所有的驱动 代码这一行不需要动,所有的都是这样,不是函数指针, 惯例直接写即可 */
.open = test_chrdev_open, /* 将来应用 open 打开这个设备时实际 调用的就是这个 .open 函数指针*/
.release = test_chrdev_release, /* release对应的就是 close 函数指针 */
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval; /*register_chrdev_region 的返回值 */
printk(KERN_INFO "chrdev_init helloworld init an zhuang qu dong \n");
// 使用新的cdev接口来注册字符设备驱动
// 新的接口注册字符设备驱动需要2步
// 第1步:注册/分配主次设备号
retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME); /* 自动分配 主次设备号*/
if (retval < 0) {
printk(KERN_ERR "Unable to alloc_chrdev_region %s error \n",MYNAME);
goto flag1;
}
printk(KERN_INFO " alloc_chrdev_region success ok \n");
printk(KERN_INFO "major = %d, minor = %d \n", MAJOR(mydev), MINOR(mydev)); /* MAJOR(mydev), MINOR(mydev)用这两个宏打印 主次设备号*/
#if 0
mydev = MKDEV(MYMAJOR,0);/* MKDEV : 算出 主 次 设备号 ,我们这里有 主设备号200, 次设备号起始为 0 */
retval = register_chrdev_region(mydev, MYCNT, MYNAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s error \n",MYNAME);
return -EINVAL;
}
printk(KERN_INFO " register_chrdev_region success ok \n");
#endif
// 第2步:注册字符设备驱动
pcdev = cdev_alloc(); /* 给 pcdev 分配内存,指针实例化 */
//cdev_init(pcdev, &test_fops);/* 初始化,两个参数都是取地址,说明都是指针,cdev 结构体变量 test_device_cdev和 file_operations 结构体变量 test_fops*/
pcdev->owner = THIS_MODULE; /* THIS_MODULE 这个是test_fops 结构体 的一个 元素 */
pcdev->ops = &test_fops; /* 这两行就是 代替 cdev_init(pcdev, &test_fops); */
retval = cdev_add(pcdev, mydev, MYCNT); /* 完成真正的 驱动注册*/
if (retval) {
printk(KERN_ERR "Unable to cdev_add error error \n");
goto flag2;
}
printk(KERN_INFO " cdev_add success ok \n");
// 第3步:注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
// 给udev,让udev自动创建和删除设备文件
test_class = class_create(THIS_MODULE, "liang_class"); /*THIS_MODULE 是内核提供的 宏,"liang_class" 这是 一个 类 名 */
if (IS_ERR(test_class)) {
printk(KERN_ERR "liang_class:" "Could not register class 'liang_class'\n");
return -EINVAL;
}
// device_create 最后1个参数字符串"test",就是我们将来要在/dev目录下创建的设备文件的名字
// 所以我们这里要的文件名是/dev/test
device_create(test_class, NULL, mydev, NULL, "test");
//使用动态映射 操作 寄存器 第3步:
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON_PA")) /* 向内核申请(报告)需要映射的内存资源。*/
goto flag3;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CDAT_PA")) /* 向内核申请(报告)需要映射的内存资源。*/
goto flag3;
//第4步:
pGPJOCON = ioremap(GPJ0CON_PA, 4);//真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJODAT = ioremap(GPJ0CON_PA+4, 4);//真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址
*pGPJOCON = 0x11111111;
*(pGPJOCON+1) = ((0<<3)|(0<<4)|(0<<5)); //LED亮 //*pGPJODAT = ((0<<3)|(0<<4)|(0<<5)); 等同于 *(pGPJOCON+1) = ((0<<3)|(0<<4)|(0<<5));
return 0;
// 如果第4步才出错跳转到这里来
// 如果第3步才出错跳转到这里来
flag3:
// 真正注销字符设备驱动用cdev_del
cdev_del(pcdev);
// 如果第2步才出错跳转到这里来
flag2:
// 在这里把第1步做成功的东西给注销掉
unregister_chrdev_region(mydev, MYCNT); /* 去注销申请的主次设备号 */
// 如果第1步才出错跳转到这里来
flag1:
return -EINVAL;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit xie zai \n");
// 使用新的接口来注销字符设备驱动
// 注销分2步:
// 第一步真正注销字符设备驱动用cdev_del
cdev_del(pcdev);
// 第二步去注销申请的主次设备号
unregister_chrdev_region(mydev, MYCNT);
//删除 设备类 操作: device_destroy 和 class_destroy
device_destroy(test_class, mydev);
class_destroy(test_class);
/**** 动态内存 释放 ****/
*pGPJODAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
iounmap(pGPJOCON);//解除映射时,传给他一个虚拟地址
iounmap(pGPJODAT);//解除映射时,传给他一个虚拟地址
release_mem_region(GPJ0CON_PA,4);//释放申请的内存 物理内存
release_mem_region(GPJ0DAT_PA,4); //释放申请的内存
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
2. app.c 和 Makefile 无更改
运行结果:
代码大概浏览:
5.3.8.设备类相关代码分析1
5.3.8.1、sys文件系统简介
(1)sys文件系统的设计思想
(2)设备类的概念
(3)/sys/class/xxx/中的文件的作用
5.3.8.2、 class_create 代码 解析
class_create
__class_create
__class_register
kset_register
kobject_uevent
5.3.8.3、 device_create 代码 解析
device_create
device_create_vargs
kobject_set_name_vargs
device_register
device_add
kobject_add
device_create_file
device_create_sys_dev_entry
devtmpfs_create_node
device_add_class_symlinks
device_add_attrs
device_pm_add
kobject_uevent
好难 看不懂了