《5.linux驱动开发--5.3.字符设备驱动高级》5.3.1.-2注册字符设备驱动新接口1 :register_chrdev_region注册主次设备号cdev_init 初始化,cdev_add

5.3.1.注册字符设备驱动新接口1
5.3.1.1、新接口与老接口
(1)老接口:register_chrdev

《5.linux驱动开发--5.3.字符设备驱动高级》5.3.1.-2注册字符设备驱动新接口1 :register_chrdev_region注册主次设备号cdev_init 初始化,cdev_add_第1张图片

《5.linux驱动开发--5.3.字符设备驱动高级》5.3.1.-2注册字符设备驱动新接口1 :register_chrdev_region注册主次设备号cdev_init 初始化,cdev_add_第2张图片

(2)新接口:register_chrdev_region/alloc_chrdev_region + cdev

    1.register_chrdev_region : 注册设备驱动 号

          注册设备号和注册设备驱动 不是一回事

     1.1  . alloc_chrdev_region

           alloc分配内存: 注册设备号自动分配设备号

     2.  cdev: 内核结构体 在 cdev.h

        

struct cdev {
	struct kobject kobj;  
	struct module *owner;  记录 和模块挂钩 
	const struct file_operations *ops;  这个字符驱动结构体 file_operations
	struct list_head list;  链表
	dev_t dev;   内核 设备号: 主设备 + 次设备号
	unsigned int count;   驱动设备计数: 例如 被 open 了多少次
};

const struct file_operations *ops;  这个字符驱动结构体 file_operations

注册的关键  这个字符驱动结构体 file_operations


(2)相关函数:cdev_alloc、cdev_init、cdev_add、cdev_del

cdev_alloc : 创造 内存空间
cdev_init : 初始化:用的时候再说
cdev_add : 注册,向内核 注册 驱动
cdev_del :  注销 驱动


(3)为什么需要新接口
5.3.1.2、cdev介绍
(1)结构体
(2)相关函数:cdev_alloc、cdev_init、cdev_add、cdev_del
5.3.1.3、设备号
(1)主设备号和次设备号:

为什么有主设备号和次设备号??

早期只有 主设备号,主设备号  类似 身份证号,主设备号 就是 数组 的下标!

主设备号255 肯定不够用, 

针对一个设备 需要好几个驱动! 假如 4 颗 LED, 是分开4颗LED写驱动or 还是单独写驱动?


(2)dev_t类型
(3)MKDEV、MAJOR、MINOR三个宏 : 主次设备号 换算

        MKDEV :  算出 主 次 设备号

        MAJOR : 主设备号

        MINOR  : 次设备号
5.3.1.4、编程实践

1. register_chrdev_region  : region 范围

int register_chrdev_region(dev_t from, unsigned count, const char *name)
dev_t from : 起始设备号
unsigned count :注册几个次设备号,次设备号的数量
例如: 我们要注册 主设备号 为 200 ; 次设备号为 0、 1 、 2、 3(4个次设备号)
from MKDEV(200,0)  先用 MKDEV 宏 算出 主设备号 和 次设备号(这个宏 算出主次设备 我不理解) 
count 4 
内核就会知道我们要注册的 主设备号 为200,次设备号起始位0, 有4个次设备号
返回值 : 为1 代表错误
函数使用:
register_chrdev_region(mydev, MYCNT, MYNAME); 


2.cdev_init  初始化  传参两个 结构体 :
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
  一个 cdev 结构体
struct cdev {
	struct kobject kobj;  
	struct module *owner;  记录 和模块挂钩 
	const struct file_operations *ops;  这个字符驱动结构体 file_operations
	struct list_head list;  链表
	dev_t dev;   内核 设备号: 主设备 + 次设备号
	unsigned int count;   驱动设备计数: 例如 被 open 了多少次
};

另一个结构体 file_operations 
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 struct cdev test_device_cdev; /* 定义一个cdev 结构体变量 test_device_cdev ,cdev_init函数的 第一个参数 */
 cdev_init(&test_device_cdev, &test_fops);/* 初始化 */




3.  cdev_add   /* 完成真正的 驱动注册*/

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
传参:
struct cdev *p : cdev 结构体变量
dev_t dev :/*register_chrdev_region 第一个参数 主设备号,这里用来接收 一个主次 设备号*/
unsigned count :  #define MYCNT   1      /* 注册 几个次 设备号 */

返回值 : 为1 代表错误

函数使用:
cdev_add(&test_device_cdev, mydev, MYCNT);  /* 完成真正的 驱动注册*/


(1)使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册

1. module_test.c 添加新的  注册 和  注销 驱动 的新的函数,我把老的函数 删了!

#include 		// module_init  module_exit
#include 			// __init   __exit
#include 
#include       //copy_from_user
#include   // 错误码
#include //arch/arm/mach-s5pv210/include/mach/regs-gpio.h  这两个顺序不能放错,c语言基础
#include  //arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include //包含 内核的 memset  、strcmp
#include   //ioremap
#include   //request_mem_region
#include  // cdev结构体和 新注册接口函数


#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 struct cdev test_device_cdev; /* 定义一个cdev 结构体变量 test_device_cdev ,cdev_init函数的 第一个参数   新注册接口函数 */
static dev_t mydev; /*register_chrdev_region 第一个参数 主设备号,这里用来接收 一个主次 设备号 ,  新注册接口函数*/

/* 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步:注册/分配主次设备号
	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");
	// 第2步:注册字符设备驱动
	cdev_init(&test_device_cdev, &test_fops);/* 初始化,两个参数都是取地址,说明都是指针,cdev 结构体变量 test_device_cdev和 file_operations 结构体变量 test_fops*/
	retval = cdev_add(&test_device_cdev, mydev, MYCNT);  /* 完成真正的 驱动注册*/
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add error error \n");
		return -EINVAL;
	}
	printk(KERN_INFO " cdev_add success ok \n");
	

	//使用动态映射 操作 寄存器
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON_PA")) /* 向内核申请(报告)需要映射的内存资源。*/
		return -EINVAL;
		
		
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CDAT_PA")) /* 向内核申请(报告)需要映射的内存资源。*/
		return -EINVAL;
		
	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;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit xie zai \n");

	// 使用新的接口来注销字符设备驱动
	// 注销分2步:
	// 第一步真正注销字符设备驱动用cdev_del
	cdev_del(&test_device_cdev);
	// 第二步去注销申请的主次设备号
	unregister_chrdev_region(mydev, MYCNT);
	
	
	

/**** 动态内存 释放   ****/
	*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 无更改

#include 
#include 
#include 
#include 
#include 

#define FILE  "/dev/test"   // 刚才mknod创建的设备文件 名,必须保持一致

char buf[100];

int main(void)
{
	int fd = -1;
	int i= 0;
	
	fd = open(FILE, O_RDWR);
	if(fd < 0)
	{
		printf("open %s error \n",FILE);
		return -1;
	}
	printf("open %s success..\n",FILE);
#if 0
	//璇诲啓鏂囦欢
	write(fd, "1",1);//亮
	sleep(2);
	write(fd, "0",1);//灭
	sleep(2);
	write(fd, "1",1);
	sleep(2);
#endif
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		printf("请输入 on|off | flash | quit \n");
		scanf("%s",buf);
		if (!strcmp(buf,"on"))
		{
			write(fd, "1",1);//亮
		}
		else if (!strcmp(buf,"off"))	
		{
			write(fd, "0",1);//灭
		}	
		else if (!strcmp(buf,"flash"))	
		{	
			for(i=0;i<3;i++)
			{
				write(fd, "1",1);//亮
				sleep(2);
				write(fd, "0",1);//灭
				sleep(2);
			}
			
		}
		else if (!strcmp(buf,"quit"))	
		{
			break;
		}
	}

	//鍏抽棴鏂囦欢
	close(fd);
	
	return 0;
}

运行结果:

《5.linux驱动开发--5.3.字符设备驱动高级》5.3.1.-2注册字符设备驱动新接口1 :register_chrdev_region注册主次设备号cdev_init 初始化,cdev_add_第3张图片

 《5.linux驱动开发--5.3.字符设备驱动高级》5.3.1.-2注册字符设备驱动新接口1 :register_chrdev_region注册主次设备号cdev_init 初始化,cdev_add_第4张图片

 《5.linux驱动开发--5.3.字符设备驱动高级》5.3.1.-2注册字符设备驱动新接口1 :register_chrdev_region注册主次设备号cdev_init 初始化,cdev_add_第5张图片

 

你可能感兴趣的:(朱老师,5linux驱动开发,驱动开发)