linux驱动学习笔记---实现中断下半部以及驱动编写规范(七)

中断下半部:

tasklet :
struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    
    void (*func)(unsigned long); //下半部要执行的代码
    unsigned long data; // 传递给func的参数
};
    
    1, 初始化tasklet 
        tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
    2, 在中断上半部,将tasklet加入到内核线程
    
        tasklet_schedule(struct tasklet_struct * t)
    
    
    3, 模块卸载的时候,需要从内核线程中移除tasklet
    
        tasklet_kill(struct tasklet_struct * t)
------------------------------------------------------------------
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;//下半部要执行的代码
};

    
    1, 初始化work
    INIT_WORK(struct work_struct * work, work_func_t func);
    
    work_func_t func为结构体struct work_struct中的函数指针;
    2, 在中断上半部,将work加入到内核线程
    schedule_work(struct work_struct * work);
    
    
    3, 模块卸载的时候,需要从内核线程中移除work
    cancel_work_sync(struct work_struct * work)

 

工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

引用: https://blog.csdn.net/av_geek/article/details/41278801

一个为 struct tasklet_struct *next任务链表,一个为struct list_head entry;内核链表

驱动编写规范

设计一个对象描述所有的全局变量

//设计一个对象类型--描述所有的全局的变量
struct s5pv210_key{
	__u8 have_data; //用于描述是否有数据
	int major ; //记录主设备号
	struct class *cls; //用于创建 类
	struct device *dev; //用来创建设备文件
	wait_queue_head_t wq_head; //用于实现阻塞的等待队列头
	struct key_event event; //用于存放数据的包
	struct work_struct work;//用于实现中断下半部
};

 声明一个对象

//声明一个对象
struct s5pv210_key  *key_dev;

初始化时统一申请空间(这也是好处之一) 

	// 0-为全局设备/数据对象分配空间
	//参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
	key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);

 错误判断

if(key_dev == NULL)
	{
		printk(KERN_ERR"kmalloc error\n");
		return -ENOMEM;
	}

指针错误判断

if(IS_ERR(key_dev->cls))
	{
		ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
		goto err_unregister;
	}

 统一下函数的最后做处理

static int __init key_drv_init(void)
{

	int ret;

	// 0-为全局设备/数据对象分配空间
	//参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
	key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
	if(key_dev == NULL)
	{
		printk(KERN_ERR"kmalloc error\n");
		return -ENOMEM;
	}

	
	// 1, 申请主设备号--动态申请主设备号
	key_dev->major = register_chrdev(0, "key_dev", &key_fops);
	if(key_dev->major < 0)
	{
		printk(KERN_ERR"register_chrdev error\n");
		ret = key_dev->major;
		goto err_free;	
	}


	// 2, 创建设备节点
	key_dev->cls  = class_create(THIS_MODULE, "key_cls");
	if(IS_ERR(key_dev->cls))
	{
		ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
		goto err_unregister;
	}
	

	key_dev->dev  = device_create(key_dev->cls, NULL,  MKDEV(key_dev->major, 0),  NULL, "key0");
	if(IS_ERR(key_dev->dev))
	{
		ret = PTR_ERR(key_dev->dev); 
		goto err_destory_cls;
	}
	// 3,  硬件初始化-- 映射地址或者中断申请
	
	//参数1--中断号码
			//获取中断号码的方法: 1,外部中断IRQ_EINT(x)  
			// 2, 头文件 #include <mach/irqs.h>  #include <plat/irqs.h>
	//参数2--中断的处理方法
	//参数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--中断的描述-自定义字符串
	//参数5--传递个参数2的任意数据
	//返回值0表示正确
	int i;
	int irqno;
	int flags;
	char *name;
	for(i=0; i<ARRAY_SIZE(all_keys); i++)
	{
		name =  all_keys[i].name;
		irqno = all_keys[i].irqno;
		flags = all_keys[i].flags;
		ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
		if(ret != 0)
		{
			printk("request_irq error\n");
			goto err_destroy_dev;
		}
	}
	
	
	// 定义一个等待队列头,并且初始化
	init_waitqueue_head(&key_dev->wq_head);

	//初始化work
	INIT_WORK(&key_dev->work,  work_key_irq);
	
	
	
	
	return 0;

err_destroy_dev:
	device_destroy(key_dev->cls,  MKDEV(key_dev->major, 0));

err_destory_cls:
	class_destroy(key_dev->cls);

err_unregister:
	unregister_chrdev(key_dev->major, "key_dev");

err_free:
	kfree(key_dev);
	return ret;

}

 完整驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>

#include <asm/uaccess.h>

// 设计一个对象--描述按键产生的数据包
struct key_event{
	int code; //什么按键: 左键, 回车键
	int value; // 按键的状态: 按下/抬起 (1/0)
};

//设计一个描述按键的对象: 名字, irqno, gpio, 按键值,触发方式
struct key_desc{
	char *name;
	int irqno;
	int gpio;
	int code;
	int flags;// 触发方式
};


//设计一个对象类型--描述所有的全局的变量
struct s5pv210_key{
	__u8 have_data; //用于描述是否有数据
	int major ; //记录主设备号
	struct class *cls; //用于创建 类
	struct device *dev; //用来创建设备文件
	wait_queue_head_t wq_head; //用于实现阻塞的等待队列头
	struct key_event event; //用于存放数据的包
	struct work_struct work;//用于实现中断下半部
};

//声明一个对象
struct s5pv210_key  *key_dev;


struct key_desc all_keys[] = {
		[0] = {
			.name = "key1_up_eint0", 
			.irqno = IRQ_EINT(0),
			.gpio = S5PV210_GPH0(0),
			.code = KEY_UP,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		[1] = {
			.name = "key2_down_eint1", 
			.irqno = IRQ_EINT(1),
			.gpio = S5PV210_GPH0(1),
			.code = KEY_DOWN,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		[2] = {
			.name = "key3_left_eint2", 
			.irqno = IRQ_EINT(2),
			.gpio = S5PV210_GPH0(2),
			.code = KEY_LEFT,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		[3] = {
			.name = "key4_right_eint3", 
			.irqno = IRQ_EINT(3),
			.gpio = S5PV210_GPH0(3),
			.code = KEY_LEFT,
			.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

		},
		
};




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

	// 通过文件路径可以得到inode
	// 通过得到次设备号可以区分不同的设备
	//int major = 	MAJOR(filp->f_path.dentry->d_inode->i_rdev);
	int major = imajor(filp->f_path.dentry->d_inode);
	int major2 = imajor(inode);

	int minor = iminor(filp->f_path.dentry->d_inode);
	int minor2 = iminor(inode);

	printk("major = %d, minor = %d\n", major, minor);
	printk("major2 = %d, minor2 = %d\n", major2, minor2);
	
	 memset(&key_dev->event, 0, sizeof(struct key_event));
	key_dev->have_data= 0; //为假--一开始都没有按键按下或者抬起


	
	return 0;
}

ssize_t key_drv_read(struct file *filp, char __user *buf,  size_t count, loff_t *fpos)
{
	
	int ret;
	//区分当前是阻塞还是非阻塞
	if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
	{
		return -EAGAIN;
	}

	// 在资源不可达的时候,进行休眠
	// 参数1---当前驱动中的等待队列头
	// 参数2--休眠的条件: 假的话就休眠,真就不休眠
	wait_event_interruptible(key_dev->wq_head, key_dev->have_data);

	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(struct key_event));
	 key_dev->have_data = 0; //没有数据了,等待下一次数据
	
	return count;
}

int key_drv_close(struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	
	return 0;
}

unsigned int key_drv_poll (struct file *filp, struct poll_table_struct *pts)
{
	//返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN
	
	unsigned int mask = 0;

	// 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠
	//参数1-文件对象--当前file
	//参数2--当前的等待队列头
	//参数3--当前传递过来的第三个参数
	poll_wait(filp, &key_dev->wq_head, pts);

	if(key_dev->have_data)
		mask |= POLLIN;

	return mask;

}

// 4, 实现fops
const struct file_operations key_fops = {
	.owner = THIS_MODULE,
	.open = key_drv_open,
	.read = key_drv_read,
	.poll = key_drv_poll,
	.release =key_drv_close,
};
void work_key_irq(struct work_struct *work)
{
	printk("-----------%s-------\n", __FUNCTION__);
	key_dev->have_data = 1;//表示有数据了
	wake_up_interruptible(&key_dev->wq_head);
}

//中断处理方法
//参数1--当前产生的中断的号码
//参数2--request_irq传递过来的参数
irqreturn_t key_irq_handler(int irqno, void *dev_id)
{
	int *p = (int *)dev_id;
	//printk("-----------%s-------0x%x-----\n", __FUNCTION__, *p);
	//区分当前是哪个按键
	struct key_desc *pdesc = (struct key_desc *)dev_id;

	//区分按下还是抬起
	int value = gpio_get_value(pdesc->gpio);
	if(value)
	{
		//抬起
		printk("<kernel>--%s : release\n", pdesc->name);
		//填充值
		key_dev->event.code = pdesc->code;
		key_dev->event.value = 0;
		
	}else
	{
		//按下
		 printk("<kernel>--%s : pressed\n", pdesc->name);
		key_dev->event.code = pdesc->code;
		key_dev->event.value = 1;
	}

	//在中断上半部,将work加入到内核线程
	schedule_work(&key_dev->work);

	return IRQ_HANDLED;
}
static int __init key_drv_init(void)
{

	int ret;

	// 0-为全局设备/数据对象分配空间
	//参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
	key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
	if(key_dev == NULL)
	{
		printk(KERN_ERR"kmalloc error\n");
		return -ENOMEM;
	}

	
	// 1, 申请主设备号--动态申请主设备号
	key_dev->major = register_chrdev(0, "key_dev", &key_fops);
	if(key_dev->major < 0)
	{
		printk(KERN_ERR"register_chrdev error\n");
		ret = key_dev->major;
		goto err_free;	
	}


	// 2, 创建设备节点
	key_dev->cls  = class_create(THIS_MODULE, "key_cls");
	if(IS_ERR(key_dev->cls))
	{
		ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
		goto err_unregister;
	}
	

	key_dev->dev  = device_create(key_dev->cls, NULL,  MKDEV(key_dev->major, 0),  NULL, "key0");
	if(IS_ERR(key_dev->dev))
	{
		ret = PTR_ERR(key_dev->dev); 
		goto err_destory_cls;
	}
	// 3,  硬件初始化-- 映射地址或者中断申请
	
	//参数1--中断号码
			//获取中断号码的方法: 1,外部中断IRQ_EINT(x)  
			// 2, 头文件 #include <mach/irqs.h>  #include <plat/irqs.h>
	//参数2--中断的处理方法
	//参数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--中断的描述-自定义字符串
	//参数5--传递个参数2的任意数据
	//返回值0表示正确
	int i;
	int irqno;
	int flags;
	char *name;
	for(i=0; i<ARRAY_SIZE(all_keys); i++)
	{
		name =  all_keys[i].name;
		irqno = all_keys[i].irqno;
		flags = all_keys[i].flags;
		ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
		if(ret != 0)
		{
			printk("request_irq error\n");
			goto err_destroy_dev;
		}
	}
	
	
	// 定义一个等待队列头,并且初始化
	init_waitqueue_head(&key_dev->wq_head);

	//初始化work
	INIT_WORK(&key_dev->work,  work_key_irq);
	
	
	
	
	return 0;

err_destroy_dev:
	device_destroy(key_dev->cls,  MKDEV(key_dev->major, 0));

err_destory_cls:
	class_destroy(key_dev->cls);

err_unregister:
	unregister_chrdev(key_dev->major, "key_dev");

err_free:
	kfree(key_dev);
	return ret;

}



static void __exit key_drv_exit(void)
{
	//移除work
	cancel_work_sync(&key_dev->work);

	// 释放中断
	//参数1--中断号码
	//参数5--和request_irq第5个参数保持一致
	int i;
	int irqno;
	for(i=0; i<ARRAY_SIZE(all_keys); i++)
	{
		irqno = all_keys[i].irqno;
		free_irq(irqno, &all_keys[i]);
	}
	
	device_destroy(key_dev->cls,  MKDEV(key_dev->major, 0));
	class_destroy(key_dev->cls);
	unregister_chrdev(key_dev->major, "key_dev");
	kfree(key_dev);
}


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








 应用层app代码


#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>

#include <linux/input.h>

// 设计一个对象--描述按键产生的数据包
struct key_event{
	int code; //什么按键: 左键, 回车键
	int value; // 按键的状态: 按下/抬起 (1/0)
};


int main(int argc, char *argv[])
{

	int on;
	int ret;
	char kbd_buf[128];
	struct key_event data;
	
	//直接将驱动模块当做文件来操作
	int fd = open("/dev/key0", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}

	struct pollfd pfd[2];

	pfd[0].fd = fd; //自己写的按键设备
	pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR)

	pfd[1].fd = 0; //监控标准输入
	pfd[1].events = POLLIN;

	while(1)
	{
		//参数1--你需要监控的文件描述符的集合
		//参数2--监控的文件的个数
		//参数3--监控的时间--毫秒为单位,如果是负数,永久监控
		ret = poll(pfd, 2,  -1);
		if(ret < 0)
		{
			perror("poll");
			exit(1);
		}
		if(ret > 0)
		{

			//判断是哪个有数据
			if(pfd[1].revents & POLLIN)
			{
				//表示键盘是输入
				ret = read(0, kbd_buf, 128);
				//fgets(kbd_buf, 128,  stdin);
				kbd_buf[ret] = '\0';
				printf("kbd_buf = %s\n", kbd_buf);
				
			}
			if(pfd[0].revents & POLLIN)
			{
				//获取数据--不会阻塞
				ret  = read(pfd[0].fd, &data, sizeof(struct key_event));
				//解析包
				switch(data.code)
				{
					case KEY_UP:
						if(data.value)
						{
							printf("<app>---KEY_UP pressed\n");
						}else
						{
							printf("<app>---KEY_UP release\n");	
						}
						break;
					case KEY_DOWN:
						if(data.value)
						{
							printf("<app>---KEY_DOWN pressed\n");
						}else
						{
							printf("<app>---KEY_DOWN release\n");	
						}
						break;
					case KEY_LEFT:
						if(data.value)
						{
							printf("<app>---KEY_LEFT pressed\n");
						}else
						{
							printf("<app>---KEY_LEFT release\n");	
						}
						break;
					case KEY_RIGHT:
						if(data.value)
						{
							printf("<app>---KEY_RIGHT pressed\n");
						}else
						{
							printf("<app>---KEY_RIGHT release\n");	
						}
						break;

				}
			
			}
		}

	}	

	
	

	close(fd);


}

猜你喜欢

转载自blog.csdn.net/weixin_42471952/article/details/81609141