三、字符设备驱动注册新接口

一、register_chrdev()方式注册字符设备驱动
register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);

但其实这个函数是linux版本2.4之前的注册方式,它的原理是:

(1)确定一个主设备号

(2)构造一个file_operations结构体, 然后放在chrdevs数组中

(3)注册:register_chrdev

然后当读写字符设备的时候,就会根据主设备号从chrdevs数组中取出相应的结构体,并调用相应的处理函数。

它会有个很大的缺点:

每注册个字符设备,还会连续注册0~255个次设备号,使它们绑定在同一个file_operations操作方法结构体上,在大多数情况下,都只用极少的次设备号,所以会浪费很多资源.

二、新的方式注册字符设备驱动

在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用到 __register_chrdev_region 函数 。

同时分为了静态注册(指定设备编号来注册) register_chrdev_region、动态分配(不指定设备编号来注册) register_chrdev_region,以及有连续注册的次设备编号范围区间,避免了register_chrdev()浪费资源的缺点

  1. register_chrdev_region()
/**
 * register_chrdev_region() - 指定设备编号来静态注册一个字符设备*
 * @from: 注册的指定起始设备编号,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0
 * @count: 需要连续注册的次设备编号个数,比如: 起始次设备号为0,count=100,表示0~99的次设备号都要绑定在同一个file_operations操作方法结构体上
 * @name: 字符设备名称
 *
 * return:当返回值小于0,表示注册失败
 */
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
	struct char_device_struct *cd;
	dev_t to = from + count;
	dev_t n, next;
 
	for (n = from; n < to; n = next) {
		next = MKDEV(MAJOR(n)+1, 0);
		if (next > to)
			next = to;
		cd = __register_chrdev_region(MAJOR(n), MINOR(n),
			       next - n, name);
	}
	return 0;
}

register_chrdev_region 则是根据要求的范围进行申请,同时我们需要手动 cdev_init cdev_add 。

  1. alloc_chrdev_region:注册一系列设备号,内核分配设备号
/**
 * alloc_chrdev_region() - 动态分配一个字符设备,注册成功并将分配到的主次设备号放入*dev里
 * @*dev:存放起始设备编号的指针,当注册成功, *dev就会等于分配到的起始设备编号,可以通过MAJOR()和MINNOR()宏来提取主次设备号
 * @baseminor: 次设备号基地址,也就是起始次设备号
 * @count:需要连续注册的次设备编号个数,比如: 起始次设备号(baseminor)为0,baseminor=2,表示0~1的此设备号都要绑定在同一个file_operations操作方法结构体上
 * @name:字符设备名称
 *
 * return:当返回值小于0,表示注册失败
 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
{
	struct char_device_struct *cd;
	cd = __register_chrdev_region(0, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	*dev = MKDEV(cd->major, cd->baseminor);
	return 0;
}
  1. cdev_alloc()

cdev_alloc函数针对于需要空间申请的操作,而cdev_init针对于不需要空间申请的操作;因此如果你定义的是一个指针,那么只需要使用cdev_alloc函数并在其后做一个ops的赋值操作就可以了;如果你定义的是一个结构体而非指针,那么只需要使用cdev_init函数就可以了。

  1. cdev_init()
/*初始化cdev结构体,并将file_operations结构体放入cdev-> ops 里*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops);

其中cdev结构体的成员,如下所示:

struct cdev {
       struct kobject    kobj;              // 内嵌的kobject对象 
       struct module   *owner;              //所属模块
       const struct file_operations  *ops;  //操作方法结构体
       struct list_head  list;        //与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
       dev_t dev;                //起始设备编号,可以通过MAJOR(),MINOR()来提取主次设备号
       unsigned int count;             //连续注册的次设备号个数
};
  1. cdev_add()
/*将cdev结构体添加到系统中,并将dev(注册好的设备编号)放入cdev-> dev里,  count(次设备编号个数)放入cdev->count里*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
  1. cdev_del()
/*将系统中的cdev结构体删除掉*/
void cdev_del(struct cdev *p);
  1. unregister_chrdev_region()
 /*注销字符设备*/
void unregister_chrdev_region(dev_t from, unsigned count);

from: 注销的指定起始设备编号,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0

count:需要连续注销的次设备编号个数,比如: 起始次设备号为0,baseminor=100,表示注销掉0~99的次设备号

  1. 相关的几个宏

MKDEV、MAJOR、MINOR三个宏

使用MAJOR和MINOR宏可以从dev_t得到major和minor,反过来也可以使用MKDEV宏冲major和minor得到dev_t,使用这些宏代码可以使的驱动更具有可移植性。

驱动实例
#include <linux/module.h> // module_init  module_exit
#include <linux/init.h>	  // __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> 
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define MYCNT 1
#define MYNAME "testchar"

#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;  // 该cdev是直接定义的变量,后期使用register_chrdev_region()直接注册驱动。
static struct cdev *pcdev;
static struct class *test_class;

char kbuf[100]; // 内核空间的buf

static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
	printk(KERN_INFO "test_chrdev_open\n");

	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((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));

	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个不在一个地址空间中
	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 (kbuf[0] == '1')
	{
		rGPJ0DAT = ((0 << 3) | (0 << 4) | (0 << 5));
	}
	else if (kbuf[0] == '0')
	{
		rGPJ0DAT = ((1 << 3) | (1 << 4) | (1 << 5));
	}
	return 0;
}

// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
	.owner = THIS_MODULE, // 惯例,直接写即可
	.open = test_chrdev_open,		// 将来应用open打开这个设备时实际调用的
	.release = test_chrdev_release, // 就是这个.open对应的函数
	.write = test_chrdev_write,
	.read = test_chrdev_read,
};

// 模块安装函数
static int __init chrdev_init(void)
{
	int retval;

	printk(KERN_INFO "chrdev_init helloworld init\n");

	// 使用新的cdev接口来注册字符设备驱动
	// 新的接口注册字符设备驱动需要2步

	// 第1步:分配主次设备号
	retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
	if (retval < 0)
	{
		printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
		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);
	pcdev->owner = THIS_MODULE;
	pcdev->ops = &test_fops;

	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");

	// 注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
	// 给udev,让udev自动创建和删除设备文件
	test_class = class_create(THIS_MODULE, "aston_class");
	if (IS_ERR(test_class))
		return -EINVAL;
	// 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
	// 所以我们这里要的文件名是/dev/test
	device_create(test_class, NULL, mydev, NULL, "test111");

	// 使用动态映射的方式来操作寄存器
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		//		return -EINVAL;
		goto flag3;
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
		//		return -EINVAL;
		goto flag3;

	pGPJ0CON = ioremap(GPJ0CON_PA, 4);
	pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);

	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((0 << 3) | (0 << 4) | (0 << 5)); // 亮

	//goto flag0:
	return 0;

	// 如果第4步才出错跳转到这里来
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);

// 如果第3步才出错跳转到这里来
flag3:
	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");

	*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);
*/

	device_destroy(test_class, mydev);
	class_destroy(test_class);

	// 使用新的接口来注销字符设备驱动
	// 注销分2步:
	// 第一步真正注销字符设备驱动用cdev_del
	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");		   // 描述模块的别名信息
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值