Linux:驱动之字符设备注册新接口(未完)

  • 驱动之字符设备注册新接口

目前尚不是最终版本,还望有心人自己学习的时候,把自己整合的知识点相关的答案也好问题也好,或者实践过程中的一些操作截图,再或者其他的一些想要分享材料发给笔者邮箱:uestc_ganlin@163.com,我们一起完善这篇博客!笔者写这篇博客的时候已经工作第四个年头了,目前是在整理之前有过的学习资料,仅作为笔记,供同志们参考!短时间内可能不会去全部完善。

 

  • 新接口与老接口

老接口:register_chrdev;

新接口:register_chrdev_region/alloc_chrdev_region + cdev;

为什么需要新接口?

cdev介绍:结构体?相关函数:cdev_alloc、cdev_init、cdev_add、cdev_del?

设备号:主设备号和次设备号?dev_t类型?MKDEV、MAJOR、MINOR三个宏?

编程实践?使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册?

完整的相关代码见下述文件(其他文件等同于最后一段完整代码中的新述文件):

module_test.c

// 为了module_init,module_exit相关的,加入下面头文件
#include <linux/module.h>
// 为了__init,__exit相关的,加入下面头文件
#include <linux/init.h>		
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <mach/gpio-bank.h>		
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>

#define MYMAJOR		250
#define MYCNT		1
#define MYNAME		"test_chrdev"
 
// #define GPJ0CON		S5PV210_GPJ0CON
// #define GPJ0DAT		S5PV210_GPJ0DAT
 
// #define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
// #define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)
 
#define GPJ0CON_PA	0xe0200240
#define GPJ0DAT_PA 	0xe0200244

unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;

int mymajor;
static dev_t mydev;
static struct cdev test_cdev;
 
// 内核空间的buf
char kbuf[100];	
 
static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来意思意思。
	printk(KERN_INFO "test_chrdev_open\n");
	
	// rGPJ0CON = 0x11111111;
	*pGPJ0CON = 0x11111111;
	// 三个灯亮
	//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		
	
	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));
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	
	return 0;
}
 
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);
	if (ret)
	{
		printk(KERN_ERR "copy_to_user fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_to_user success..\n");
	
	
	return 0;
}
 
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
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);不行,因为2个buf不在一个地址空间中
	ret = copy_from_user(kbuf, ubuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_from_user fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_from_user success..\n");
 
	/*
	// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
	// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
	if (!strcmp(kbuf, "on"))
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (!strcmp(kbuf, "off"))
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	*/
	
	if (kbuf[0] == '1')
	{
		// rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
		*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));	
	}
	else if (kbuf[0] == '0')
	{
		// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
		*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	}
	
	return 0;
}
 
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
	// 惯例,直接写即可
	.owner		= THIS_MODULE,		
	// 将来应用open打开这个设备时实际调用的
	// 就是这个.open对应的函数
	.open		= test_chrdev_open,			
	.release	= test_chrdev_release,		
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};
 
// 模块安装函数
static int __init chrdev_init(void)
{	
	int retval;
	
	printk(KERN_INFO "chrdev_init helloworld init\n");
	
	/*
	// 在module_init宏调用的函数中去注册字符设备驱动
	// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
	// 内核如果成功分配就会返回分配的主设备号;如果分配失败会返回负数
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
	*/
	
	// 使用新的cdev接口来注册字符设备驱动
	// 新的接口注册字符设备驱动需要2步
	
	// 第1步:注册/分配主次设备号
	mydev = MKDEV(MYMAJOR, 0);
	retval = register_chrdev_region(mydev, MYCNT, MYNAME);
	if (retval) {
		printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev_region success\n");
	
	// 第2步:注册字符设备驱动
	cdev_init(&test_cdev, &test_fops);
	retval = cdev_add(&test_cdev, mydev, MYCNT);
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add\n");
		return -EINVAL;
	}
	printk(KERN_INFO "cdev_add success\n");
	
	// 使用动态映射的方式来操作寄存器
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		return -EINVAL;
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT"))
		return -EINVAL;
	
	pGPJ0CON = ioremap(GPJ0CON_PA, 4);
	pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
	
	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		
	
	/*
	// 模块安装命令insmod时执行的硬件操作
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	printk(KERN_INFO "GPJ0CON = %p.\n", GPJ0CON);
	printk(KERN_INFO "GPJ0DAT = %p.\n", GPJ0DAT);
	*/
 
	return 0;
}
 
// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	
 	// 解除映射
	iounmap(pGPJ0CON);
	iounmap(pGPJ0DAT);
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);
	
	/*
	// 在module_exit宏调用的函数中去注销字符设备驱动
	unregister_chrdev(mymajor, MYNAME);
	*/
	
	// 使用新的接口来注销字符设备驱动
	// 注销分2步:
	// 第一步真正注销字符设备驱动用cdev_del
	cdev_del(&test_cdev);
	// 第二步去注销申请的主次设备号
	unregister_chrdev_region(mydev, MYCNT);
}
 
module_init(chrdev_init);
module_exit(chrdev_exit);
 
// MODULE_xxx这种宏作用是用来添加模块描述信息
// 描述模块的许可证
MODULE_LICENSE("GPL");
// 描述模块的作者				
MODULE_AUTHOR("aston");	
// 描述模块的介绍信息			
MODULE_DESCRIPTION("module test");
// 描述模块的别名信息	
MODULE_ALIAS("alias xxx");	

使用alloc_chrdev_region自动分配设备号:

使用register_chrdev_region需要事先知道要使用的主、次设备号是什么,一般可以先cat /proc/devices去查看设备号的使用情况,自行选则没有使用的设备号进行使用;

更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以进行自动分配了;

自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。

得到分配的主设备号和次设备号:

使用MAJOR宏和MINOR宏从dev_t得到major和minor;

反过来使用MKDEV宏从major和minor得到dev_t;

使用这些宏的代码具有可移植性。

中途出错的倒影式错误处理方法:

内核中很多函数中包含了多个操作,这些操作每一步都有可能出错,出错后后面的步骤就没有进行下去的必要了。

完整的相关代码见下述文件(其他文件等同于最后一段完整代码中的新述文件):

module_test.c

// 为了module_init,module_exit相关的,加入下面头文件
#include <linux/module.h>
// 为了__init,__exit相关的,加入下面头文件
#include <linux/init.h>		
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <mach/gpio-bank.h>		
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>

// #define MYMAJOR		250
#define MYCNT		1
#define MYNAME		"test_chrdev"
 
// #define GPJ0CON		S5PV210_GPJ0CON
// #define GPJ0DAT		S5PV210_GPJ0DAT
 
// #define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
// #define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)
 
#define GPJ0CON_PA	0xe0200240
#define GPJ0DAT_PA 	0xe0200244

unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;

// int mymajor;
static dev_t mydev;
static struct cdev test_cdev;
 
// 内核空间的buf
char kbuf[100];	
 
static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来意思意思。
	printk(KERN_INFO "test_chrdev_open\n");
	
	// rGPJ0CON = 0x11111111;
	*pGPJ0CON = 0x11111111;
	// 三个灯亮
	//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		
	
	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));
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	
	return 0;
}
 
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);
	if (ret)
	{
		printk(KERN_ERR "copy_to_user fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_to_user success..\n");
	
	
	return 0;
}
 
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
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);不行,因为2个buf不在一个地址空间中
	memset(kbuf, 0, sizeof(kbuf));
	ret = copy_from_user(kbuf, ubuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_from_user fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_from_user success..\n");
 
	/*
	// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
	// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
	if (!strcmp(kbuf, "on"))
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (!strcmp(kbuf, "off"))
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	*/
	
	if (kbuf[0] == '1')
	{
		// rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
		*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));	
	}
	else if (kbuf[0] == '0')
	{
		// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
		*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	}
	
	return 0;
}
 
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
	// 惯例,直接写即可
	.owner		= THIS_MODULE,		
	// 将来应用open打开这个设备时实际调用的
	// 就是这个.open对应的函数
	.open		= test_chrdev_open,			
	.release	= test_chrdev_release,		
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};
 
// 模块安装函数
static int __init chrdev_init(void)
{	
	int retval;
	
	printk(KERN_INFO "chrdev_init helloworld init\n");
	
	/*
	// 在module_init宏调用的函数中去注册字符设备驱动
	// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
	// 内核如果成功分配就会返回分配的主设备号;如果分配失败会返回负数
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
	*/
	
	// 使用新的cdev接口来注册字符设备驱动
	// 新的接口注册字符设备驱动需要2步
	
	
	// 第1步:注册/分配主次设备号
	// mydev = MKDEV(MYMAJOR, 0);
	// retval = register_chrdev_region(mydev, MYCNT, MYNAME);
	retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
	if (retval) {
		// printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
		// return -EINVAL;
		printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
		goto flag1;
	}
	// printk(KERN_INFO "register_chrdev_region success\n");
	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, MYCNT);
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add\n");
		goto flag2;
	}
	printk(KERN_INFO "cdev_add success\n");
	
	// 使用动态映射的方式来操作寄存器
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		// return -EINVAL;
		goto flag3;
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT"))
		// return -EINVAL;
		goto flag3;
	
	pGPJ0CON = ioremap(GPJ0CON_PA, 4);
	pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
	
	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		
	
	/*
	// 模块安装命令insmod时执行的硬件操作
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	printk(KERN_INFO "GPJ0CON = %p.\n", GPJ0CON);
	printk(KERN_INFO "GPJ0DAT = %p.\n", GPJ0DAT);
	*/
 
	// goto flag0:
	return 0;
	
// 如果第4步才出错跳转到这里来	
flag4:
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);

// 如果第3步才出错跳转到这里来
flag3:
	cdev_del(&test_cdev);

// 如果第2步才出错跳转到这里来
flag2:
	// 在这里把第1步做成功的东西给注销掉
	unregister_chrdev_region(mydev, MYCNT);
	
// 如果第1步才出错跳转到这里来
flag1:	
	return -EINVAL;
// flag0:	
	// return 0;
}
 
// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	
 	// 解除映射
	iounmap(pGPJ0CON);
	iounmap(pGPJ0DAT);
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);
	
	/*
	// 在module_exit宏调用的函数中去注销字符设备驱动
	unregister_chrdev(mymajor, MYNAME);
	*/
	
	// 使用新的接口来注销字符设备驱动
	// 注销分2步:
	// 第一步真正注销字符设备驱动用cdev_del
	cdev_del(&test_cdev);
	// 第二步去注销申请的主次设备号
	unregister_chrdev_region(mydev, MYCNT);
}
 
module_init(chrdev_init);
module_exit(chrdev_exit);
 
// MODULE_xxx这种宏作用是用来添加模块描述信息
// 描述模块的许可证
MODULE_LICENSE("GPL");
// 描述模块的作者				
MODULE_AUTHOR("aston");	
// 描述模块的介绍信息			
MODULE_DESCRIPTION("module test");
// 描述模块的别名信息	
MODULE_ALIAS("alias xxx");	

使用cdev_alloc:

cdev_alloc的编程实践;

从内存角度体会cdev_alloc用与不用的差别;

这就是非面向对象的语言和面向对象的代码?

cdev_init的替代:

cdev_init源码分析;

不使用cdev_init时的编程;

为什么讲这个?

完整的相关代码见下述文件:

module_test.c

// 为了module_init,module_exit相关的,加入下面头文件
#include <linux/module.h>
// 为了__init,__exit相关的,加入下面头文件
#include <linux/init.h>		
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <mach/gpio-bank.h>		
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>

// #define MYMAJOR		250
#define MYCNT		1
#define MYNAME		"test_chrdev"
 
// #define GPJ0CON		S5PV210_GPJ0CON
// #define GPJ0DAT		S5PV210_GPJ0DAT
 
// #define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
// #define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)
 
#define GPJ0CON_PA	0xe0200240
#define GPJ0DAT_PA 	0xe0200244

unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;

// int mymajor;
static dev_t mydev;
// static struct cdev test_cdev;
static struct cdev *pcdev;
 
// 内核空间的buf
char kbuf[100];	
 
static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来意思意思。
	printk(KERN_INFO "test_chrdev_open\n");
	
	// rGPJ0CON = 0x11111111;
	*pGPJ0CON = 0x11111111;
	// 三个灯亮
	//rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		
	
	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));
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	
	return 0;
}
 
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);
	if (ret)
	{
		printk(KERN_ERR "copy_to_user fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_to_user success..\n");
	
	
	return 0;
}
 
// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
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);不行,因为2个buf不在一个地址空间中
	memset(kbuf, 0, sizeof(kbuf));
	ret = copy_from_user(kbuf, ubuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_from_user fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_from_user success..\n");
 
	/*
	// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
	// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
	if (!strcmp(kbuf, "on"))
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	else if (!strcmp(kbuf, "off"))
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	*/
	
	if (kbuf[0] == '1')
	{
		// rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
		*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));	
	}
	else if (kbuf[0] == '0')
	{
		// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
		*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	}
	
	return 0;
}
 
// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
	// 惯例,直接写即可
	.owner		= THIS_MODULE,		
	// 将来应用open打开这个设备时实际调用的
	// 就是这个.open对应的函数
	.open		= test_chrdev_open,			
	.release	= test_chrdev_release,		
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};
 
// 模块安装函数
static int __init chrdev_init(void)
{	
	int retval;
	
	printk(KERN_INFO "chrdev_init helloworld init\n");
	
	/*
	// 在module_init宏调用的函数中去注册字符设备驱动
	// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
	// 内核如果成功分配就会返回分配的主设备号;如果分配失败会返回负数
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
	*/
	
	// 使用新的cdev接口来注册字符设备驱动
	// 新的接口注册字符设备驱动需要2步
	
	
	// 第1步:注册/分配主次设备号
	// mydev = MKDEV(MYMAJOR, 0);
	// retval = register_chrdev_region(mydev, MYCNT, MYNAME);
	retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
	if (retval) {
		// printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
		// return -EINVAL;
		printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
		goto flag1;
	}
	// printk(KERN_INFO "register_chrdev_region success\n");
	printk(KERN_INFO "alloc_chrdev_region success\n");
	printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
	
	// 第2步:注册字符设备驱动
	// 给pcdev分配内存,指针实例化
	pcdev = cdev_alloc();
	// cdev_init(&test_cdev, &test_fops);		
	// cdev_init(pcdev, &test_fops);
	pcdev->owner = THIS_MODULE;
	pcdev->ops = &test_fops;
	// retval = cdev_add(&test_cdev, mydev, MYCNT);
	retval = cdev_add(pcdev, mydev, MYCNT);
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add\n");
		goto flag2;
	}
	printk(KERN_INFO "cdev_add success\n");
	
	// 使用动态映射的方式来操作寄存器
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		// return -EINVAL;
		goto flag3;
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT"))
		// return -EINVAL;
		goto flag3;
	
	pGPJ0CON = ioremap(GPJ0CON_PA, 4);
	pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
	
	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		
	
	/*
	// 模块安装命令insmod时执行的硬件操作
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	printk(KERN_INFO "GPJ0CON = %p.\n", GPJ0CON);
	printk(KERN_INFO "GPJ0DAT = %p.\n", GPJ0DAT);
	*/
 
	// goto flag0:
	return 0;
	
// 如果第4步才出错跳转到这里来	
flag4:
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);

// 如果第3步才出错跳转到这里来
flag3:
	// cdev_del(&test_cdev);
	cdev_del(pcdev);

// 如果第2步才出错跳转到这里来
flag2:
	// 在这里把第1步做成功的东西给注销掉
	unregister_chrdev_region(mydev, MYCNT);
	
// 如果第1步才出错跳转到这里来
flag1:	
	return -EINVAL;
// flag0:	
	// return 0;
}
 
// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	// rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	
	
 	// 解除映射
	iounmap(pGPJ0CON);
	iounmap(pGPJ0DAT);
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);
	
	/*
	// 在module_exit宏调用的函数中去注销字符设备驱动
	unregister_chrdev(mymajor, MYNAME);
	*/
	
	// 使用新的接口来注销字符设备驱动
	// 注销分2步:
	// 第一步真正注销字符设备驱动用cdev_del
	// cdev_del(&test_cdev);
	cdev_del(pcdev);
	// 第二步去注销申请的主次设备号
	unregister_chrdev_region(mydev, MYCNT);
}
 
module_init(chrdev_init);
module_exit(chrdev_exit);
 
// MODULE_xxx这种宏作用是用来添加模块描述信息
// 描述模块的许可证
MODULE_LICENSE("GPL");
// 描述模块的作者				
MODULE_AUTHOR("aston");	
// 描述模块的介绍信息			
MODULE_DESCRIPTION("module test");
// 描述模块的别名信息	
MODULE_ALIAS("alias xxx");	

Makefile

# ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
# KERN_VER = $(shell uname -r)
# KERN_DIR = /lib/modules/$(KERN_VER)/build	

# 开发板的linux内核的源码树目录
KERN_DIR = /root/qt/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
#	arm-none-linux-gnueabi-gcc app.c -o app
	
cp:
	cp *.ko /root/removal/rootfs/root/driver_test
#	cp app /root/removal/rootfs/root/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define FILE	"/dev/test_chrdev"

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);
	
	// 读写文件
	while (1)
	{
		memset(buf, 0 , sizeof(buf));
		printf("请输入 on | off \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(1);
				write(fd, "0", 1);
				sleep(1);
			}
		}	
		else if (!strcmp(buf, "quit"))
		{
			break;
		}
	}
	
	// 关闭文件
	close(fd);
	
	return 0;
}

源码分析?为了加深功力可以去看相关内核源码,这里仅以分析出来的主要函数调用关系作为简单记录:

老接口分析 :

register_chrdev
    __register_chrdev
        __register_chrdev_region
        cdev_alloc
        cdev_add

新接口分析:

register_chrdev_region
    __register_chrdev_region

alloc_chrdev_region
    __register_chrdev_region

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值