学习笔记之字符设备驱动框架

函数接口:
申请设备号

 int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
参数1:主设备号
		设备号(32bit--dev_t)==主设备号(12bit) + 次设备号(20bit)
			主设备号:表示一类设备--camera
			次设备号: 表示一类设备中某一个:前置,后置

		给定到方式有两种:
			1,动态--参数1直接填0
			2,静态--指定一个整数,250
参数2: 描述一个设备信息,可以自定义
		/proc/devices列举出所有到已经注册的设备
参数3: 文件操作对象--提供open, read,write
返回值: 正确返回0,错误返回负数

取消设备号

void unregister_chrdev(unsigned int major, const char * name)
参数1:主设备号
参数2: 描述一个设备信息,可以自定义

3,创建设备节点:
1,手动创建–缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失

	mknod /dev/设备名  类型  主设备号 次设备号
	比如:
		mknod  /dev/chr0  c  250 0


	[root@farsight drv_module]# ls /dev/chr0 -l
	crw-r--r--    1 0        0         250,   0 Jan  1 00:33 /dev/chr0

2,自动创建(通过udev/mdev机制)

	struct class *class_create(owner, name)//创建一个类
		参数1: THIS_MODULE
		参数2: 字符串名字,自定义
		返回一个class指针

创建一个设备文件

	struct device *device_create(struct class * class, struct device * parent, dev_t devt, 
						void * drvdata, const char * fmt,...)
	参数1: class结构体,class_create调用之后到返回值
	参数2:表示父亲,一般直接填NULL
	参数3: 设备号类型 dev_t
			dev_t devt
				#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
				#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
				#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
	参数4:私有数据,一般直接填NULL
	参数5和6:表示可变参数,字符串,表示设备节点名字


	销毁动作:
		void device_destroy(devcls,  MKDEV(dev_major, 0));
		参数1: class结构体,class_create调用之后到返回值
		参数2: 设备号类型 dev_t

		void class_destroy(devcls);
		参数1: class结构体,class_create调用之后到返回值
		int copy_to_user(void __user * to, const void * from, unsigned long n)
将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用
参数1:应用驱动中的一个buffer
参数2:内核空间到一个buffer
参数3:个数
返回值:大于0,表示出错,剩下多少个没有拷贝成功
		等于0,表示正确


int copy_from_user(void * to, const void __user * from, unsigned long n)
将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
参数1:内核驱动中的一个buffer
参数2:应用空间到一个buffer
参数3:个数

映射

void *ioremap(cookie, size)
参数1: 物理地址
参数2: 长度
返回值: 虚拟地址

去映射–解除映射

	void iounmap(void __iomem *addr)
	参数1: 映射之后到虚拟地址

申请中断

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
	参数1: 设备对应的中断号
	参数2: 中断的处理函数
			typedef irqreturn_t (*irq_handler_t)(int, void *);
	参数3:触发方式
			#define IRQF_TRIGGER_NONE	0x00000000  //内部控制器触发中断的时候的标志
			#define IRQF_TRIGGER_RISING	0x00000001 //上升沿
			#define IRQF_TRIGGER_FALLING	0x00000002 //下降沿
			#define IRQF_TRIGGER_HIGH	0x00000004  // 高点平
			#define IRQF_TRIGGER_LOW	0x00000008 //低电平触发
	参数4:中断的描述,自定义,主要是给用户查看的
			/proc/interrupts
	参数5:传递给参数2中函数指针的值
	返回值: 正确为0,错误非0

字符设备驱动模块框架:
1、驱动模块的加载卸载入口及GPL认证
2、在模块加载入口函数中

1、设定一个全局的设备对象
2、申请设备号
3、注册设备节点
4、硬件的初始化–地址映射或者中断申请
5、实现file_operations
代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/sched.h>

struct value_event{
	int code;    //键的类型,home,esc等
	int value;   //键的值,按下还是抬起
};

struct key_descr{
	struct device *dev;
	struct class* cls;
	unsigned int major_dev;
	int irqno;
	void *virt_addr;
	struct value_event event;
	wait_queue_head_t head;
	int wait_state;
};


struct key_descr *key_dev;
#define reg_add_base 0x11000c20
#define KEY_ENTER 28

//中断函数
irqreturn_t irq_handler(int irqno, void *dsvid)
{
	unsigned int value;
	printk("------%s------\n",__FUNCTION__);

	value = readl(key_dev->virt_addr + 4) & (0x1 << 2);

	if(value){
		printk("KEY2 up\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 1;
	}else{
		printk("KEY2 press");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;
	}
	
	return IRQ_HANDLED;
}

//从设备树中获取中断号
int get_irqno_from_node(void)
{
	struct device_node *np;
	int irqno;
	np = of_find_node_by_path("/key_int_node");
	if(np ==NULL){
		printk("find node failed\n");
	}else{
		printk("find node ok\n");
	}

	irqno = irq_of_parse_and_map(np, 0);
	printk("irqno = %d\n",irqno);

	return irqno;
}
int key_drv_open (struct inode *inode, struct file *filp)
{
	printk("------%s------\n",__FUNCTION__);

	return 0;	
}
ssize_t key_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	int ret;
	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret > 0){
		printk("copy_to_user error\n");
		return -EFAULT;
	}
	memset(&key_dev->event, 0, sizeof(key_dev->event));
	
	return count;
}
ssize_t key_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	printk("------%s------\n",__FUNCTION__);
	return 0;
}
int key_drv_close (struct inode *inode, struct file *filp)
{
	printk("------%s------\n",__FUNCTION__);

	return 0;
}


const struct file_operations key_fops = {
	.open = key_drv_open,
	.read = key_drv_read,
	.write = key_drv_write,
	.release = key_drv_close,
};


static int __init key_drv_init(void)
{
	int ret;
	//1、设定一个全局的设备对象
	key_dev = kmalloc(sizeof(struct key_descr), GFP_KERNEL);
	if(key_dev == NULL){
		printk(KERN_ERR "kmalloc error\n");
		return -ENOMEM;
	}
	//2、申请设备号
	key_dev->major_dev = register_chrdev(0, "key3_drv", &key_fops);
	if(key_dev->major_dev < 0){
		printk(KERN_ERR "register_chrdev error\n");
		ret = -ENODEV;
		goto err_0;
	}
	//3、注册设备节点
	key_dev->cls = class_create(THIS_MODULE, "key_cls");
	if(IS_ERR(key_dev->cls)){
		printk(KERN_ERR "class_create error\n");
		ret = PTR_ERR(key_dev->cls);
		goto err_1;
	}
	key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major_dev, 0), NULL, "key2");
	if(IS_ERR(key_dev->dev)){
		printk(KERN_ERR "device_create error\n");
		ret = PTR_ERR(key_dev->dev);
		goto err_2;
	}
	//4、硬件的初始化--地址映射或者中断申请
	key_dev->irqno = get_irqno_from_node();
	ret = request_irq(key_dev->irqno, irq_handler,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"KEY2_EINT10", NULL);
	if(ret != 0){
		printk("requst_irq error\n");
		goto err_3;
	}
	key_dev->virt_addr = ioremap(reg_add_base, 8);
	if(IS_ERR(key_dev->virt_addr)){
		printk("ioremap error\n");
		ret = PTR_ERR(key_dev->virt_addr);
		goto err_4;
	}
	
	return 0;	
err_4:
	free_irq(key_dev->irqno, NULL);
err_3:
	device_destroy(key_dev->cls, MKDEV(key_dev->major_dev, 0));
err_2:
	class_destroy(key_dev->cls);
err_1:
	unregister_chrdev(key_dev->major_dev, "key3_drv");
err_0:
	kfree(key_dev);
	return ret;
}

static void __exit key_drv_exit(void)
{
	printk("------%s------\n",__FUNCTION__);
	iounmap(key_dev->virt_addr);
	free_irq(key_dev->irqno, NULL);
	device_destroy(key_dev->cls, MKDEV(key_dev->major_dev, 0));
	class_destroy(key_dev->cls);
	unregister_chrdev(key_dev->major_dev, "key3_drv");
	kfree(key_dev);
}


module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");

在此代码中,虽然可以实现功能,但可以发现cpu占有率非常高,因为程序在进程上下文不断运行,在用户态和内核态不断切换占用cpu,所以不可取,需要用到等待队列。在没有数据可读的时候进行休眠,有数据的时候唤醒。所以代码如下:在入口函数初始化等待队列,读函数判断,中断唤醒。

static int __init key_drv_init(void)
{
	int ret;
	//1、设定一个全局的设备对象
	key_dev = kmalloc(sizeof(struct key_descr), GFP_KERNEL);
	if(key_dev == NULL){
		printk(KERN_ERR "kmalloc error\n");
		return -ENOMEM;
	}
	//2、申请设备号
	key_dev->major_dev = register_chrdev(0, "key3_drv", &key_fops);
	if(key_dev->major_dev < 0){
		printk(KERN_ERR "register_chrdev error\n");
		ret = -ENODEV;
		goto err_0;
	}
	//3、注册设备节点
	key_dev->cls = class_create(THIS_MODULE, "key_cls");
	if(IS_ERR(key_dev->cls)){
		printk(KERN_ERR "class_create error\n");
		ret = PTR_ERR(key_dev->cls);
		goto err_1;
	}
	key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major_dev, 0), NULL, "key2");
	if(IS_ERR(key_dev->dev)){
		printk(KERN_ERR "device_create error\n");
		ret = PTR_ERR(key_dev->dev);
		goto err_2;
	}
	//4、硬件的初始化--地址映射或者中断申请
	key_dev->irqno = get_irqno_from_node();
	ret = request_irq(key_dev->irqno, irq_handler,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"KEY2_EINT10", NULL);
	if(ret != 0){
		printk("requst_irq error\n");
		goto err_3;
	}
	key_dev->virt_addr = ioremap(reg_add_base, 8);
	if(IS_ERR(key_dev->virt_addr)){
		printk("ioremap error\n");
		ret = PTR_ERR(key_dev->virt_addr);
		goto err_4;
	}
	//初始化等待队列
	init_waitqueue_head(&key_dev->head);
	key_dev->wait_state = 0;
	
	return 0;	
err_4:
	free_irq(key_dev->irqno, NULL);
err_3:
	device_destroy(key_dev->cls, MKDEV(key_dev->major_dev, 0));
err_2:
	class_destroy(key_dev->cls);
err_1:
	unregister_chrdev(key_dev->major_dev, "key3_drv");
err_0:
	kfree(key_dev);
	return ret;
}

ssize_t key_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	//如果当前是非阻塞模式且没有数据则返回一个出错码
	if(filp->f_flags & O_NONBLOCK && !key_dev->wait_state)
		return -EAGAIN;
	//在没有数据的时候,进行休眠
	wait_event_interruptible(key_dev->head , key_dev->wait_state);

	int ret;
	ret = copy_to_user(buf, &key_dev->event, count);
	if(ret > 0){
		printk("copy_to_user error\n");
		return -EFAULT;
	}
	memset(&key_dev->event, 0, sizeof(key_dev->event));
	
	return count;
}
	irqreturn_t irq_handler(int irqno, void *dsvid)
{
	unsigned int value;
	printk("------%s------\n",__FUNCTION__);

	value = readl(key_dev->virt_addr + 4) & (0x1 << 2);

	if(value){
		printk("KEY2 up\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 1;
	}else{
		printk("KEY2 press");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;
	}
	//有数据时将进程/等待队列唤醒同时将标志位置位为1
	wake_up_interruptible(&key_dev->head);
	key_dev->wait_state = 1;
	
	return IRQ_HANDLED;
}

可以发现几乎没有占用CPU

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值