目录
1.2、cdev介绍(include/linux/cdev.h)
1.5、使用alloc_chrdev_region自动分配设备号
1.7、中途出错的倒影式错误处理方法(函数内使用goto语句做异常处理)
2.2、新接口register_chrdev_region和alloc_chrdev_region分析
1.注册字符设备驱动新接口
1.1、新接口与老接口
(1)老接口:register_chrdev//获取设备号并注册,将设备号和fops绑定
(2)新接口:register_chrdev_region/alloc_chrdev_region + cdev
拆分成设备号的获取+字符设备驱动的注册
register_chrdev_region:指定一个设备号注册,告诉内核这个设备号我要了
alloc_chrdev_region:让内核自动给我分配设备号
新老接口的差异好比取号等餐:
类似KFC,以前是排队等餐
现在是取号登记,然后是等号取餐
1.2、cdev介绍(include/linux/cdev.h)
(1)结构体
(2)相关函数:cdev_alloc、cdev_init、cdev_add、cdev_del
cdev_alloc:给cdev结构体分配内存
cdev_init:初始化
cdev_add:注册驱动
cdev_del:注销驱动,并释放cdev_alloc申请的堆内存
1.3、设备号
(1)主设备号和次设备号
主设备号决定这个字符设备驱动在内核字符设备驱动数组里的下标
为什么需要次设备号?
1.主设备号不够用,当硬件进一步复杂化后,主设备号是不够用的
2.有时候针对同一类设备要写好几个驱动,譬如多颗LED
次设备号决定该设备在这个主设备中的排名
(2)dev_t类型
(3)内核提供MKDEV、MAJOR、MINOR三个宏用于主次设备号和设备号之间的换算
MKDEV:用主设备号和次设备号计算设备号
MAJOR:从设备号中提取出主设备号
MINOR:从设备号中提取出次设备号
1.4、编程实践
(1)使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册
modules_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //struct file_operations
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> //S5PV210_GPJ0CON S5PV210_GPJ0DAT
#include <linux/string.h> //memset
#include <linux/ioport.h> //request_mem_region
#include <asm/io.h> //ioremap
#include <linux/cdev.h> //cdev_init
#define MY_MAJOR 200 //主设备号
#define MY_CNT 1 //设备的个数
#define MY_NAME "test" //主设备的名字
static dev_t mydev; //第一个设备
static struct cdev test_cdev; //cdev结构体
static int mymajor;
static char kbuf[100]; //内核空间的buf
#define GPJ0CON S5PV210_GPJ0CON //GPJ0CON对应的虚拟地址 fd500240
#define GPJ0DAT S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址 fd500244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xE0200240 //GPJ0CON对应的物理地址 0xE0200240
//#define GPJ0DAT_PA 0xE0200244 //GPJ0DAT对应的物理地址 0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;
#define GPJ0_BASE_PA 0xE0200240 //GPJ0类寄存器的基地址
unsigned int *pGPJ0BASE = NULL;
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数真正应该放置打开这个设备的硬件操作代码
printk(KERN_INFO "test_chrdev_open\n");
//rGPJ0CON = 0x11111111;
//使用静态映射方式操作寄存器
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
//*pGPJ0CON = 0x11111111;
//使用动态映射方式操作寄存器(两个寄存器一起映射)
*pGPJ0BASE = 0x11111111;
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success...\n");
return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
//使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
//memcpy(kbuf,ubuf); 不可以,这两个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success...\n");
//使用动态映射方式操作寄存器(两个寄存器一起映射)
if(kbuf[0] == '1')
{
*(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
}
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
if(kbuf[0] == '1')
{
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用静态映射方式操作寄存器
/*
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灭
}
*/
/*
//真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
if(!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, //惯例
.open = test_chrdev_open, //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
.release = test_chrdev_release,
.read = test_chrdev_read,
.write = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
//int ret;
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
/*
//注册驱动
//major传0表示让内核帮我自动分配一个空闲的主设备号
//成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
mymajor = register_chrdev (0, MY_NAME, &test_fops);
if(!mymajor)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
*/
//使用新的cdev接口来注册字符设备驱动
//新的接口注册字符设备驱动需要2步
//1.注册自定义的主次设备号
mydev = MKDEV(MY_MAJOR, 0);
retval = register_chrdev_region(mydev, MY_CNT,
MY_NAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success...\n");
//2.注册字符设备驱动
cdev_init(&test_cdev, &test_fops);
retval = cdev_add(&test_cdev, mydev, MY_CNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
return -EINVAL;
}
printk(KERN_INFO "cdev_add success...\n");
//insmod执行的硬件操作
//rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
//printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
//使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
return -EBUSY;
}
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
//使用动态映射的方式来操作寄存器(两个寄存器一起映射)
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASH")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
/*
//注销驱动
unregister_chrdev(mymajor,MY_NAME);
*/
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
//使用新的cdev接口来注销驱动
//注销分两步
//1.注销字符设备驱动
cdev_del(&test_cdev);
//2.注销申请的主次设备号
unregister_chrdev_region(mydev, MY_CNT);
//解除动态映射(两个寄存器分开独立映射)
/*
//1.iounmap,解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
//2.release_mem_region,释放资源
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
//解除动态映射(两个寄存器一起映射)
//1.iounmap,解除映射
iounmap(pGPJ0BASE);
//2.release_mem_region,释放资源
release_mem_region(GPJ0_BASE_PA, 8);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
测试:
1.5、使用alloc_chrdev_region自动分配设备号
(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须要去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
1.6、得到分配的主设备号和次设备号
(1)使用MAJOR宏和MINOR宏从dev_t(设备号)得到major(主设备号)和minor(次设备号)。
(2)反过来使用MKDEV宏从major(主设备号)和minor(次设备号)得到dev_t(设备号)。
(3)使用这些宏的代码具有很好可移植性
modules_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //struct file_operations
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> //S5PV210_GPJ0CON S5PV210_GPJ0DAT
#include <linux/string.h> //memset
#include <linux/ioport.h> //request_mem_region
#include <asm/io.h> //ioremap
#include <linux/cdev.h> //cdev_init
//#define MY_MAJOR 200 //自定义的主设备号
#define MY_CNT 1 //设备的个数
#define MY_NAME "test" //主设备的名字
static dev_t mydev; //第一个设备
static struct cdev test_cdev; //cdev结构体
//static int mymajor;
static char kbuf[100]; //内核空间的buf
//#define GPJ0CON S5PV210_GPJ0CON //GPJ0CON对应的虚拟地址 fd500240
//#define GPJ0DAT S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址 fd500244
//#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
//#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xE0200240 //GPJ0CON对应的物理地址 0xE0200240
//#define GPJ0DAT_PA 0xE0200244 //GPJ0DAT对应的物理地址 0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;
#define GPJ0_BASE_PA 0xE0200240 //GPJ0类寄存器的基地址
unsigned int *pGPJ0BASE = NULL;
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数真正应该放置打开这个设备的硬件操作代码
printk(KERN_INFO "test_chrdev_open\n");
//rGPJ0CON = 0x11111111;
//使用静态映射方式操作寄存器
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
//*pGPJ0CON = 0x11111111;
//使用动态映射方式操作寄存器(两个寄存器一起映射)
*pGPJ0BASE = 0x11111111;
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success...\n");
return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
//使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
//memcpy(kbuf,ubuf); 不可以,这两个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success...\n");
//使用动态映射方式操作寄存器(两个寄存器一起映射)
if(kbuf[0] == '1')
{
*(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
}
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
if(kbuf[0] == '1')
{
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用静态映射方式操作寄存器
/*
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灭
}
*/
/*
//真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
if(!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, //惯例
.open = test_chrdev_open, //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
.release = test_chrdev_release,
.read = test_chrdev_read,
.write = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
//int ret;
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
/*
//注册驱动
//major传0表示让内核帮我自动分配一个空闲的主设备号
//成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
mymajor = register_chrdev (0, MY_NAME, &test_fops);
if(!mymajor)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
*/
//使用新的cdev接口来注册字符设备驱动
//新的接口注册字符设备驱动需要2步
/*
//1.注册自定义的主次设备号
mydev = MKDEV(MY_MAJOR, 0);
retval = register_chrdev_region(mydev, MY_CNT,
MY_NAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success...\n");
*/
//1.由内核自动分配主次设备号并注册
retval = alloc_chrdev_region(&mydev, 0, MY_CNT,
MY_NAME);
if (retval < 0) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "alloc_chrdev_region success...\n");
printk(KERN_INFO "major = %d, minor = %d\n", MAJOR(mydev), MINOR(mydev));//打印主次设备号
//2.注册字符设备驱动
cdev_init(&test_cdev, &test_fops);
retval = cdev_add(&test_cdev, mydev, MY_CNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
return -EINVAL;
}
printk(KERN_INFO "cdev_add success...\n");
//insmod执行的硬件操作
//rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
//printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
//使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
return -EBUSY;
}
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
//使用动态映射的方式来操作寄存器(两个寄存器一起映射)
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASH")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
/*
//注销驱动
unregister_chrdev(mymajor,MY_NAME);
*/
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
//使用新的cdev接口来注销驱动
//注销分两步
//1.注销字符设备驱动
cdev_del(&test_cdev);
//2.注销申请的主次设备号
unregister_chrdev_region(mydev, MY_CNT);
//解除动态映射(两个寄存器分开独立映射)
/*
//1.iounmap,解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
//2.release_mem_region,释放资源
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
//解除动态映射(两个寄存器一起映射)
//1.iounmap,解除映射
iounmap(pGPJ0BASE);
//2.release_mem_region,释放资源
release_mem_region(GPJ0_BASE_PA, 8);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
测试:
1.7、中途出错的倒影式错误处理方法(函数内使用goto语句做异常处理)
(1)内核中很多函数中包含了很多个操作,这些操作每一步都有可能出错,而且出错后后面的步骤就没有进行下去的必要性了,但前面成功的步骤却都申请资源成功了,所以要做错误处理方法。
//如果第4步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag4:
//把前三步做成功的事情注销掉
release_mem_region(GPJ0_BASE_PA, 8); //释放资源
//如果第3步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag3:
//把前二步做成功的事情注销掉
cdev_del(&test_cdev); //注销字符设备驱动
//如果第2步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag2:
//把前一步做成功的事情注销掉
unregister_chrdev_region(mydev, MY_CNT);//注销主次设备号
//如果第1步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag1:
return -EINVAL;
完整代码:
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //struct file_operations
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> //S5PV210_GPJ0CON S5PV210_GPJ0DAT
#include <linux/string.h> //memset
#include <linux/ioport.h> //request_mem_region
#include <asm/io.h> //ioremap
#include <linux/cdev.h> //cdev_init
//#define MY_MAJOR 200 //自定义的主设备号
#define MY_CNT 1 //设备的个数
#define MY_NAME "test" //主设备的名字
static dev_t mydev; //第一个设备
static struct cdev test_cdev; //cdev结构体
//static int mymajor;
static char kbuf[100]; //内核空间的buf
//#define GPJ0CON S5PV210_GPJ0CON //GPJ0CON对应的虚拟地址 fd500240
//#define GPJ0DAT S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址 fd500244
//#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
//#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xE0200240 //GPJ0CON对应的物理地址 0xE0200240
//#define GPJ0DAT_PA 0xE0200244 //GPJ0DAT对应的物理地址 0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;
#define GPJ0_BASE_PA 0xE0200240 //GPJ0类寄存器的基地址
unsigned int *pGPJ0BASE = NULL;
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数真正应该放置打开这个设备的硬件操作代码
printk(KERN_INFO "test_chrdev_open\n");
//rGPJ0CON = 0x11111111;
//使用静态映射方式操作寄存器
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
//*pGPJ0CON = 0x11111111;
//使用动态映射方式操作寄存器(两个寄存器一起映射)
*pGPJ0BASE = 0x11111111;
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success...\n");
return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
//使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
//memcpy(kbuf,ubuf); 不可以,这两个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success...\n");
//使用动态映射方式操作寄存器(两个寄存器一起映射)
if(kbuf[0] == '1')
{
*(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
}
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
if(kbuf[0] == '1')
{
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用静态映射方式操作寄存器
/*
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灭
}
*/
/*
//真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
if(!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, //惯例
.open = test_chrdev_open, //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
.release = test_chrdev_release,
.read = test_chrdev_read,
.write = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
//int ret;
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
/*
//注册驱动
//major传0表示让内核帮我自动分配一个空闲的主设备号
//成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
mymajor = register_chrdev (0, MY_NAME, &test_fops);
if(!mymajor)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
*/
//使用新的cdev接口来注册字符设备驱动
//新的接口注册字符设备驱动需要2步
/*
//1.注册自定义的主次设备号
mydev = MKDEV(MY_MAJOR, 0);
retval = register_chrdev_region(mydev, MY_CNT,
MY_NAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success...\n");
*/
//1.由内核自动分配主次设备号并注册
retval = alloc_chrdev_region(&mydev, 0, MY_CNT,
MY_NAME);
if (retval < 0) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success...\n");
printk(KERN_INFO "major = %d, minor = %d\n", MAJOR(mydev), MINOR(mydev));//打印主次设备号
//2.注册字符设备驱动
cdev_init(&test_cdev, &test_fops);
retval = cdev_add(&test_cdev, mydev, MY_CNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success...\n");
//insmod执行的硬件操作
//rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
//printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
//使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
return -EBUSY;
}
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
//使用动态映射的方式来操作寄存器(两个寄存器一起映射)
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASH")) {
goto flag3;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
if(pGPJ0BASE == NULL)
{
goto flag4;
}
return 0;//前面执行全部成功
//如果第4步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag4:
//把前三步做成功的事情注销掉
release_mem_region(GPJ0_BASE_PA, 8); //释放资源
//如果第3步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag3:
//把前二步做成功的事情注销掉
cdev_del(&test_cdev); //注销字符设备驱动
//如果第2步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag2:
//把前一步做成功的事情注销掉
unregister_chrdev_region(mydev, MY_CNT);//注销主次设备号
//如果第1步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag1:
return -EINVAL;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
/*
//注销驱动
unregister_chrdev(mymajor,MY_NAME);
*/
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
//使用新的cdev接口来注销驱动
//注销分两步
//1.注销字符设备驱动
cdev_del(&test_cdev);
//2.注销申请的主次设备号
unregister_chrdev_region(mydev, MY_CNT);
//解除动态映射(两个寄存器分开独立映射)
/*
//1.iounmap,解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
//2.release_mem_region,释放资源
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
//解除动态映射(两个寄存器一起映射)
//1.iounmap,解除映射
iounmap(pGPJ0BASE);
//2.release_mem_region,释放资源
release_mem_region(GPJ0_BASE_PA, 8);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
1.8、使用cdev_alloc
(1)cdev_alloc的编程实践
modules_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //struct file_operations
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> //S5PV210_GPJ0CON S5PV210_GPJ0DAT
#include <linux/string.h> //memset
#include <linux/ioport.h> //request_mem_region
#include <asm/io.h> //ioremap
#include <linux/cdev.h> //cdev_init
//#define MY_MAJOR 200 //自定义的主设备号
#define MY_CNT 1 //设备的个数
#define MY_NAME "test" //主设备的名字
static dev_t mydev; //第一个设备
//static struct cdev test_cdev; //cdev结构体
static struct cdev *pcdev;
//static int mymajor;
static char kbuf[100]; //内核空间的buf
//#define GPJ0CON S5PV210_GPJ0CON //GPJ0CON对应的虚拟地址 fd500240
//#define GPJ0DAT S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址 fd500244
//#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
//#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xE0200240 //GPJ0CON对应的物理地址 0xE0200240
//#define GPJ0DAT_PA 0xE0200244 //GPJ0DAT对应的物理地址 0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;
#define GPJ0_BASE_PA 0xE0200240 //GPJ0类寄存器的基地址
unsigned int *pGPJ0BASE = NULL;
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数真正应该放置打开这个设备的硬件操作代码
printk(KERN_INFO "test_chrdev_open\n");
//rGPJ0CON = 0x11111111;
//使用静态映射方式操作寄存器
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
//*pGPJ0CON = 0x11111111;
//使用动态映射方式操作寄存器(两个寄存器一起映射)
*pGPJ0BASE = 0x11111111;
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success...\n");
return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
//使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
//memcpy(kbuf,ubuf); 不可以,这两个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success...\n");
//使用动态映射方式操作寄存器(两个寄存器一起映射)
if(kbuf[0] == '1')
{
*(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
}
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
if(kbuf[0] == '1')
{
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用静态映射方式操作寄存器
/*
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灭
}
*/
/*
//真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
if(!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, //惯例
.open = test_chrdev_open, //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
.release = test_chrdev_release,
.read = test_chrdev_read,
.write = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
//int ret;
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
/*
//注册驱动
//major传0表示让内核帮我自动分配一个空闲的主设备号
//成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
mymajor = register_chrdev (0, MY_NAME, &test_fops);
if(!mymajor)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
*/
//使用新的cdev接口来注册字符设备驱动
//新的接口注册字符设备驱动需要2步
/*
//1.注册自定义的主次设备号
mydev = MKDEV(MY_MAJOR, 0);
retval = register_chrdev_region(mydev, MY_CNT,
MY_NAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success...\n");
*/
//1.由内核自动分配主次设备号并注册
retval = alloc_chrdev_region(&mydev, 0, MY_CNT,
MY_NAME);
if (retval < 0) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success...\n");
printk(KERN_INFO "major = %d, minor = %d\n", MAJOR(mydev), MINOR(mydev));//打印主次设备号
//2.注册字符设备驱动
pcdev = cdev_alloc(); //给pcdev分配内存
cdev_init(pcdev, &test_fops);
retval = cdev_add(pcdev, mydev, MY_CNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success...\n");
//insmod执行的硬件操作
//rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
//printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
//使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
return -EBUSY;
}
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
//使用动态映射的方式来操作寄存器(两个寄存器一起映射)
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASH")) {
goto flag3;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
if(pGPJ0BASE == NULL)
{
goto flag4;
}
return 0;//前面执行全部成功
//如果第4步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag4:
//把前三步做成功的事情注销掉
release_mem_region(GPJ0_BASE_PA, 8); //释放资源
//如果第3步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag3:
//把前二步做成功的事情注销掉
cdev_del(pcdev); //注销字符设备驱动
//如果第2步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag2:
//把前一步做成功的事情注销掉
unregister_chrdev_region(mydev, MY_CNT);//注销主次设备号
//如果第1步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag1:
return -EINVAL;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
/*
//注销驱动
unregister_chrdev(mymajor,MY_NAME);
*/
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
//使用新的cdev接口来注销驱动
//注销分两步
//1.注销字符设备驱动
cdev_del(pcdev);
//2.注销申请的主次设备号
unregister_chrdev_region(mydev, MY_CNT);
//解除动态映射(两个寄存器分开独立映射)
/*
//1.iounmap,解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
//2.release_mem_region,释放资源
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
//解除动态映射(两个寄存器一起映射)
//1.iounmap,解除映射
iounmap(pGPJ0BASE);
//2.release_mem_region,释放资源
release_mem_region(GPJ0_BASE_PA, 8);
}
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)从内存角度体会cdev_alloc用与不用的差别
程序要使用内存,内存的来源:
全局变量 .data段 直到程序结束才释放
局部变量 栈 随时用随时释放,灵活性强
malloc 堆 按需使用
使用cdev_alloc的好处在于按需分配,如果使用全局变量定义一个struct cdev结构体,则这个驱动只要一insmod装装,这个全局变量所占用的内存直至驱动卸载前都会被一直占用。
有申请内存就有释放内存,释放内存由cdev_del实现,内部会判断是否是堆内存然后释放
cdev_del
cdev_unmap
kobj_unmap
kfree
1.9、cdev_init的替代
(1)cdev_init源码分析
(2)不使用cdev_init时的编程
//cdev_init(pcdev, &test_fops);
//有时候cdev_init会被以下两句代码所替代
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;
2.字符设备驱动注册函数分析
2.1、老接口register_chrdev分析
register_chrdev
__register_chrdev //由此可见register_chrdev只能注册主设备号,次设备号默认为0
__register_chrdev_region
cdev_alloc
cdev->owner = fops->owner;
cdev->ops = fops;
cdev_add
2.2、新接口register_chrdev_region和alloc_chrdev_region分析
register_chrdev_region
for //这里可以看出register_chrdev_region的特点是可以一次性申请多个主次设备号
__register_chrdev_region
alloc_chrdev_region
__register_chrdev_region(0,....)
//和上面对比可见__register_chrdev_region这个函数包含两种情况
//第一个参数为0则表示自动分配
//不为0则由我们自己指定
__register_chrdev_region函数内容:
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) {
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
//这就是为什么每次自动分配主设备号时,都是从最大的没被使用的主设备号开始分配的原因
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
i = major_to_index(major);
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
内核中用来存放字符设备驱动的数组是一个指针数组
static struct char_device_struct {
struct char_device_struct *next;//用来做链表的
unsigned int major;//主设备号
unsigned int baseminor;//此设备号
int minorct;//次设备号个数
char name[64];//字符设备驱动的名字,最大64字节
struct cdev *cdev; /* will die *///存放fops结构体等等
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];//CHRDEV_MAJOR_HASH_SIZE = 255
3.自动创建字符设备驱动的设备文件
3.1、问题描述
(1)整体流程回顾
应用层调用open API打开一个设备文件,设备文件中包含主次设备号的信息,根据这个信息找到驱动层的fops结构体,调用其中的.open函数去进行硬件操作
(2)使用mknod创建设备文件的缺点
(3)能否自动生成和删除设备文件
3.2、解决方案:udev(嵌入式中用的是mdev)
(1)什么是udev?应用层的一个应用程序,busybox的一个命令
(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
通过这个协议,udev可以知道内核那边到底安装了什么模块,它的主次设备都是什么,得到这些信息后,udev就可以去自动帮我们创建设备文件。
(3)应用层启用udev,内核驱动中使用相应接口(class_create和device_create)
在/etc/init.d/rcS文件中,有这么两行用于开机启动udev
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
/etc/init.d/rcS文件内容:
(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除
3.3、内核驱动设备类相关函数
(1)class_create
(2)device_create
3.4、编程实践
modules_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //struct file_operations
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> //S5PV210_GPJ0CON S5PV210_GPJ0DAT
#include <linux/string.h> //memset
#include <linux/ioport.h> //request_mem_region
#include <asm/io.h> //ioremap
#include <linux/cdev.h> //cdev_init
#include <linux/device.h> //class_create
//#define MY_MAJOR 200 //自定义的主设备号
#define MY_CNT 1 //设备的个数
#define MY_NAME "test" //主设备的名字
static dev_t mydev; //第一个设备
//static struct cdev test_cdev; //cdev结构体
static struct cdev *pcdev;
static struct class *test_dev_class;//用于自动创建和删除设备文件
//static int mymajor;
static char kbuf[100]; //内核空间的buf
//#define GPJ0CON S5PV210_GPJ0CON //GPJ0CON对应的虚拟地址 fd500240
//#define GPJ0DAT S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址 fd500244
//#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
//#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xE0200240 //GPJ0CON对应的物理地址 0xE0200240
//#define GPJ0DAT_PA 0xE0200244 //GPJ0DAT对应的物理地址 0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;
#define GPJ0_BASE_PA 0xE0200240 //GPJ0类寄存器的基地址
unsigned int *pGPJ0BASE = NULL;
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数真正应该放置打开这个设备的硬件操作代码
printk(KERN_INFO "test_chrdev_open\n");
//rGPJ0CON = 0x11111111;
//使用静态映射方式操作寄存器
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
//*pGPJ0CON = 0x11111111;
//使用动态映射方式操作寄存器(两个寄存器一起映射)
*pGPJ0BASE = 0x11111111;
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success...\n");
return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
//使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
//memcpy(kbuf,ubuf); 不可以,这两个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success...\n");
//使用动态映射方式操作寄存器(两个寄存器一起映射)
if(kbuf[0] == '1')
{
*(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
}
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
if(kbuf[0] == '1')
{
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用静态映射方式操作寄存器
/*
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灭
}
*/
/*
//真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
if(!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, //惯例
.open = test_chrdev_open, //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
.release = test_chrdev_release,
.read = test_chrdev_read,
.write = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
//int ret;
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
/*
//注册驱动
//major传0表示让内核帮我自动分配一个空闲的主设备号
//成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
mymajor = register_chrdev (0, MY_NAME, &test_fops);
if(!mymajor)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
*/
//使用新的cdev接口来注册字符设备驱动
//新的接口注册字符设备驱动需要2步
/*
//1.注册自定义的主次设备号
mydev = MKDEV(MY_MAJOR, 0);
retval = register_chrdev_region(mydev, MY_CNT,
MY_NAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success...\n");
*/
//1.由内核自动分配主次设备号并注册
retval = alloc_chrdev_region(&mydev, 0, MY_CNT,
MY_NAME);
if (retval < 0) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success...\n");
printk(KERN_INFO "major = %d, minor = %d\n", MAJOR(mydev), MINOR(mydev));//打印主次设备号
//2.注册字符设备驱动
pcdev = cdev_alloc(); //给pcdev分配内存
//cdev_init(pcdev, &test_fops);
//有时候cdev_init会被以下两句代码所替代
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;
retval = cdev_add(pcdev, mydev, MY_CNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success...\n");
//注册字符设备驱动完成后,我们发信息给udev,让udev自动创建和删除设备文件
test_dev_class = class_create(THIS_MODULE, "chm_class");
if (IS_ERR(test_dev_class))
return -EINVAL;
//最后一个参数就是将来要在/dev目录下创建的设备文件的名字
//这里创建的是/dev/test
device_create(test_dev_class, NULL, mydev, NULL, "test");
//insmod执行的硬件操作
//rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
//printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
//使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
return -EBUSY;
}
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
//使用动态映射的方式来操作寄存器(两个寄存器一起映射)
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASH")) {
goto flag3;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
if(pGPJ0BASE == NULL)
{
goto flag4;
}
return 0;//前面执行全部成功
//如果第4步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag4:
//把前三步做成功的事情注销掉
release_mem_region(GPJ0_BASE_PA, 8); //释放资源
//如果第3步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag3:
//把前二步做成功的事情注销掉
cdev_del(pcdev); //注销字符设备驱动
//如果第2步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag2:
//把前一步做成功的事情注销掉
unregister_chrdev_region(mydev, MY_CNT);//注销主次设备号
//如果第1步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag1:
return -EINVAL;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
/*
//注销驱动
unregister_chrdev(mymajor,MY_NAME);
*/
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
//使用新的cdev接口来注销驱动
//注销分两步
//1.注销字符设备驱动
cdev_del(pcdev);
//2.注销申请的主次设备号
unregister_chrdev_region(mydev, MY_CNT);
//自动删除设备文件
device_destroy(test_dev_class, mydev);
class_destroy(test_dev_class);
//解除动态映射(两个寄存器分开独立映射)
/*
//1.iounmap,解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
//2.release_mem_region,释放资源
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
//解除动态映射(两个寄存器一起映射)
//1.iounmap,解除映射
iounmap(pGPJ0BASE);
//2.release_mem_region,释放资源
release_mem_region(GPJ0_BASE_PA, 8);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
测试:
4.内核设备类相关函数分析
4.1、sys文件系统简介
(1)sys文件系统的设计思想
将内核中很多的数据结构以文件的方式展现在sys目录下,以便我们在应用层和内核交互
(2)设备类的概念
根据每个设备的功能特性划分成一个类
(3)/sys/class/xxx/中的文件的作用
class_create创建了class目录下的一个文件夹,代表创建了一个设备类
device_create创建了class/xxx目录下的一个文件夹,代表创建了一个设备文件
以之前代码为例:使用class_create创建了chm_class类和test设备文件
在/sys/class/chm_class/test目录下,有很多和/dev/test设备文件的信息
譬如说主次设备号、设备文件名
4.2、设备类相关函数分析
(1)class_create
class_create
__class_create
__class_register
kset_register
kobject_uevent
(2)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
5.静态映射表建立过程分析
5.1、建立映射表的三个关键部分
(1)映射表具体物理地址和虚拟地址的值相关的宏定义
(2)映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
smdkc110_map_io
s5p_init_io
iotable_init
结论:经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
(3)开机时调用映射表建立函数
问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
start_kernel
setup_arch
paging_init
devicemaps_init
if (mdesc->map_io)
mdesc->map_io();
6.动态映射结构体方式操作寄存器
6.1、问题描述
(1)仿效真实驱动中,用结构体封装的方式来进行单次多寄存器的地址映射。
6.2、编程实践
modules_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //struct file_operations
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> //S5PV210_GPJ0CON S5PV210_GPJ0DAT
#include <linux/string.h> //memset
#include <linux/ioport.h> //request_mem_region
#include <asm/io.h> //ioremap
#include <linux/cdev.h> //cdev_init
#include <linux/device.h> //class_create
//#define MY_MAJOR 200 //自定义的主设备号
#define MY_CNT 1 //设备的个数
#define MY_NAME "test" //主设备的名字
static dev_t mydev; //第一个设备
//static struct cdev test_cdev; //cdev结构体
static struct cdev *pcdev;
static struct class *test_dev_class;//用于自动创建和删除设备文件
//static int mymajor;
static char kbuf[100]; //内核空间的buf
//#define GPJ0CON S5PV210_GPJ0CON //GPJ0CON对应的虚拟地址 fd500240
//#define GPJ0DAT S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址 fd500244
//#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
//#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xE0200240 //GPJ0CON对应的物理地址 0xE0200240
//#define GPJ0DAT_PA 0xE0200244 //GPJ0DAT对应的物理地址 0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;
#define GPJ0_BASE_PA 0xE0200240 //GPJ0类寄存器的基地址
//unsigned int *pGPJ0BASE = NULL;
//动态映射结构体方式操作寄存器
typedef struct GPJO_BASE{
volatile unsigned int GPJ0CON;
volatile unsigned int GPJ0DAT;
}gpj0_reg_t;
gpj0_reg_t *pGPJ0BASE = NULL;
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数真正应该放置打开这个设备的硬件操作代码
printk(KERN_INFO "test_chrdev_open\n");
//rGPJ0CON = 0x11111111;
//使用静态映射方式操作寄存器
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
//*pGPJ0CON = 0x11111111;
//使用动态映射方式操作寄存器(两个寄存器一起映射)
//*pGPJ0BASE = 0x11111111;
//动态映射结构体方式操作寄存器
pGPJ0BASE->GPJ0CON = 0x11111111;
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success...\n");
return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
//使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
//memcpy(kbuf,ubuf); 不可以,这两个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success...\n");
//动态映射结构体方式操作寄存器
if(kbuf[0] == '1')
{
pGPJ0BASE->GPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
pGPJ0BASE->GPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
/*
//使用动态映射方式操作寄存器(两个寄存器一起映射)
if(kbuf[0] == '1')
{
*(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
if(kbuf[0] == '1')
{
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用静态映射方式操作寄存器
/*
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灭
}
*/
/*
//真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
if(!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, //惯例
.open = test_chrdev_open, //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
.release = test_chrdev_release,
.read = test_chrdev_read,
.write = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
//int ret;
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
/*
//注册驱动
//major传0表示让内核帮我自动分配一个空闲的主设备号
//成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
mymajor = register_chrdev (0, MY_NAME, &test_fops);
if(!mymajor)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
*/
//使用新的cdev接口来注册字符设备驱动
//新的接口注册字符设备驱动需要2步
/*
//1.注册自定义的主次设备号
mydev = MKDEV(MY_MAJOR, 0);
retval = register_chrdev_region(mydev, MY_CNT,
MY_NAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success...\n");
*/
//1.由内核自动分配主次设备号并注册
retval = alloc_chrdev_region(&mydev, 0, MY_CNT,
MY_NAME);
if (retval < 0) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success...\n");
printk(KERN_INFO "major = %d, minor = %d\n", MAJOR(mydev), MINOR(mydev));//打印主次设备号
//2.注册字符设备驱动
pcdev = cdev_alloc(); //给pcdev分配内存
//cdev_init(pcdev, &test_fops);
//有时候cdev_init会被以下两句代码所替代
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;
retval = cdev_add(pcdev, mydev, MY_CNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success...\n");
//注册字符设备驱动完成后,我们发信息给udev,让udev自动创建和删除设备文件
test_dev_class = class_create(THIS_MODULE, "chm_class");
if (IS_ERR(test_dev_class))
return -EINVAL;
//最后一个参数就是将来要在/dev目录下创建的设备文件的名字
//这里创建的是/dev/test
device_create(test_dev_class, NULL, mydev, NULL, "test");
//insmod执行的硬件操作
//rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
//printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
//使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
return -EBUSY;
}
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
/*
//使用动态映射的方式来操作寄存器(两个寄存器一起映射)
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASE")) {
goto flag3;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
if(pGPJ0BASE == NULL)
{
goto flag4;
}
*/
//动态映射结构体方式操作寄存器
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, sizeof(gpj0_reg_t), "GPJ0_BASE")) {
goto flag3;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, sizeof(gpj0_reg_t));
if(pGPJ0BASE == NULL)
{
goto flag4;
}
return 0;//前面执行全部成功
//如果第4步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag4:
//把前三步做成功的事情注销掉
release_mem_region(GPJ0_BASE_PA, 8); //释放资源
//如果第3步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag3:
//把前二步做成功的事情注销掉
cdev_del(pcdev); //注销字符设备驱动
//如果第2步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag2:
//把前一步做成功的事情注销掉
unregister_chrdev_region(mydev, MY_CNT);//注销主次设备号
//如果第1步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag1:
return -EINVAL;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
/*
//注销驱动
unregister_chrdev(mymajor,MY_NAME);
*/
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
//使用新的cdev接口来注销驱动
//注销分两步
//1.注销字符设备驱动
cdev_del(pcdev);
//2.注销申请的主次设备号
unregister_chrdev_region(mydev, MY_CNT);
//自动删除设备文件
device_destroy(test_dev_class, mydev);
class_destroy(test_dev_class);
//解除动态映射(两个寄存器分开独立映射)
/*
//1.iounmap,解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
//2.release_mem_region,释放资源
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
//解除动态映射(两个寄存器一起映射)
//1.iounmap,解除映射
iounmap(pGPJ0BASE);
//2.release_mem_region,释放资源
release_mem_region(GPJ0_BASE_PA, sizeof(gpj0_reg_t));
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
7.内核提供的读写寄存器接口
7.1、前面访问寄存器的方式
(1)行不行
(2)好不好
这种指针解引用的方式操作寄存器平台依赖性太强、具备的可移植性很差,
使用内核提供的读写宏可移植性会更强。
7.2、内核提供的寄存器读写接口
(1)writel和readl
(2)iowrite32和ioread32
7.3、编程实践
modules_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> //struct file_operations
#include <asm/uaccess.h> //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> //S5PV210_GPJ0CON S5PV210_GPJ0DAT
#include <linux/string.h> //memset
#include <linux/ioport.h> //request_mem_region
#include <asm/io.h> //ioremap
#include <linux/cdev.h> //cdev_init
#include <linux/device.h> //class_create
//#define MY_MAJOR 200 //自定义的主设备号
#define MY_CNT 1 //设备的个数
#define MY_NAME "test" //主设备的名字
static dev_t mydev; //第一个设备
//static struct cdev test_cdev; //cdev结构体
static struct cdev *pcdev;
static struct class *test_dev_class;//用于自动创建和删除设备文件
//static int mymajor;
static char kbuf[100]; //内核空间的buf
//#define GPJ0CON S5PV210_GPJ0CON //GPJ0CON对应的虚拟地址 fd500240
//#define GPJ0DAT S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址 fd500244
//#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
//#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
//#define GPJ0CON_PA 0xE0200240 //GPJ0CON对应的物理地址 0xE0200240
//#define GPJ0DAT_PA 0xE0200244 //GPJ0DAT对应的物理地址 0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;
#define GPJ0_BASE_PA 0xE0200240 //GPJ0类寄存器的基地址
//unsigned int *pGPJ0BASE = NULL;
//动态映射结构体方式操作寄存器
typedef struct GPJO_BASE{
volatile unsigned int GPJ0CON;
volatile unsigned int GPJ0DAT;
}gpj0_reg_t;
gpj0_reg_t *pGPJ0BASE = NULL;
static int test_chrdev_open(struct inode *inode, struct file *file)
{
//这个函数真正应该放置打开这个设备的硬件操作代码
printk(KERN_INFO "test_chrdev_open\n");
//rGPJ0CON = 0x11111111;
//使用静态映射方式操作寄存器
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
//*pGPJ0CON = 0x11111111;
//使用动态映射方式操作寄存器(两个寄存器一起映射)
//*pGPJ0BASE = 0x11111111;
//动态映射结构体方式操作寄存器
//pGPJ0BASE->GPJ0CON = 0x11111111;
//使用内核读写接口宏操作寄存器
wirtel(0x11111111, &pGPJ0BASE->GPJ0CON);
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success...\n");
return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
//使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
//memcpy(kbuf,ubuf); 不可以,这两个不在一个地址空间中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
if(ret > 0)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success...\n");
//动态映射结构体方式操作寄存器
if(kbuf[0] == '1')
{
//pGPJ0BASE->GPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
wirtel(((0<<3) | (0<<4) | (0<<5)), &pGPJ0BASE->GPJ0DAT);
}else if(kbuf[0] == '0')
{
//pGPJ0BASE->GPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
wirtel(((1<<3) | (1<<4) | (1<<5)), &pGPJ0BASE->GPJ0DAT);
}
/*
//使用动态映射方式操作寄存器(两个寄存器一起映射)
if(kbuf[0] == '1')
{
*(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
if(kbuf[0] == '1')
{
*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(kbuf[0] == '0')
{
*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
//使用静态映射方式操作寄存器
/*
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灭
}
*/
/*
//真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
if(!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
}else if(!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}
*/
return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, //惯例
.open = test_chrdev_open, //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
.release = test_chrdev_release,
.read = test_chrdev_read,
.write = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int retval;
//int ret;
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
/*
//注册驱动
//major传0表示让内核帮我自动分配一个空闲的主设备号
//成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
mymajor = register_chrdev (0, MY_NAME, &test_fops);
if(!mymajor)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
*/
//使用新的cdev接口来注册字符设备驱动
//新的接口注册字符设备驱动需要2步
/*
//1.注册自定义的主次设备号
mydev = MKDEV(MY_MAJOR, 0);
retval = register_chrdev_region(mydev, MY_CNT,
MY_NAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success...\n");
*/
//1.由内核自动分配主次设备号并注册
retval = alloc_chrdev_region(&mydev, 0, MY_CNT,
MY_NAME);
if (retval < 0) {
printk(KERN_ERR "Unable to register minors for %s\n",MY_NAME);
goto flag1;
}
printk(KERN_INFO "alloc_chrdev_region success...\n");
printk(KERN_INFO "major = %d, minor = %d\n", MAJOR(mydev), MINOR(mydev));//打印主次设备号
//2.注册字符设备驱动
pcdev = cdev_alloc(); //给pcdev分配内存
//cdev_init(pcdev, &test_fops);
//有时候cdev_init会被以下两句代码所替代
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;
retval = cdev_add(pcdev, mydev, MY_CNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
goto flag2;
}
printk(KERN_INFO "cdev_add success...\n");
//注册字符设备驱动完成后,我们发信息给udev,让udev自动创建和删除设备文件
test_dev_class = class_create(THIS_MODULE, "chm_class");
if (IS_ERR(test_dev_class))
return -EINVAL;
//最后一个参数就是将来要在/dev目录下创建的设备文件的名字
//这里创建的是/dev/test
device_create(test_dev_class, NULL, mydev, NULL, "test");
//insmod执行的硬件操作
//rGPJ0CON = 0x11111111;
//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
//printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
//printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
//使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
return -EBUSY;
}
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
return -EBUSY;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*/
/*
//使用动态映射的方式来操作寄存器(两个寄存器一起映射)
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASE")) {
goto flag3;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
if(pGPJ0BASE == NULL)
{
goto flag4;
}
*/
//动态映射结构体方式操作寄存器
//1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
if (!request_mem_region(GPJ0_BASE_PA, sizeof(gpj0_reg_t), "GPJ0_BASE")) {
goto flag3;
}
//2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
pGPJ0BASE = ioremap(GPJ0_BASE_PA, sizeof(gpj0_reg_t));
if(pGPJ0BASE == NULL)
{
goto flag4;
}
return 0;//前面执行全部成功
//如果第4步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag4:
//把前三步做成功的事情注销掉
release_mem_region(GPJ0_BASE_PA, 8); //释放资源
//如果第3步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag3:
//把前二步做成功的事情注销掉
cdev_del(pcdev); //注销字符设备驱动
//如果第2步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag2:
//把前一步做成功的事情注销掉
unregister_chrdev_region(mydev, MY_CNT);//注销主次设备号
//如果第1步才出错,跳转到这里来,做收尾工作(将之前注册的注销掉,申请的释放掉),然后退出
flag1:
return -EINVAL;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
/*
//注销驱动
unregister_chrdev(mymajor,MY_NAME);
*/
//rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
//使用新的cdev接口来注销驱动
//注销分两步
//1.注销字符设备驱动
cdev_del(pcdev);
//2.注销申请的主次设备号
unregister_chrdev_region(mydev, MY_CNT);
//自动删除设备文件
device_destroy(test_dev_class, mydev);
class_destroy(test_dev_class);
//解除动态映射(两个寄存器分开独立映射)
/*
//1.iounmap,解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
//2.release_mem_region,释放资源
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
*/
//解除动态映射(两个寄存器一起映射)
//1.iounmap,解除映射
iounmap(pGPJ0BASE);
//2.release_mem_region,释放资源
release_mem_region(GPJ0_BASE_PA, sizeof(gpj0_reg_t));
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息