回顾——华清中断编程210509

回顾——华清中断编程210509

一,中断号
(一)中断号–就是一个号码,需要通过一定的方式去获取到
在3.14.0内核中,从设备树中获取
(二)获取中断号的方法:
1,宏定义:
IRQ_EINT(号码)
2,设备树文件中
arch/arm/boot/dts/exynos4412-fs4412.dts
硬件连接:
key-----gpx1_2-----EINT10
设备树文件:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
gpx1:gpx1{
gpio-controller;
#gpio-cells = <2>;
interruput-controller;
interrput-parent = <&gic>;
interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
#interrupt-cells = <2>;
};
在编程过程中,需要定义自己的节点–描述当前设备的中断号
arch/arm/boot/dts/exynos4412-fs4412.dts
key_int_nodee{
compatible = “test_key”;
interrupt-parent = <&gpx1>;
interrupts = <2 4>;
}
编译设备树文件:
make dtbs
更新dtbs文件:
cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
二,在驱动中去通过代码获取到中断号,并且申请中断(实现中断处理方法)

(一)获取中断号码:
//自定义函数
int get_irqno_from_node(void)
{
	//获取到设备树中的节点(系统函数)
	struct device_node *np = of_find_node_by_path(*/key_int_node");
	if(np){
		printk("find node ok\n");
	}else{
		printk("find node failed\n");	
	}
	//通过节点去获取到中断号码(系统函数)
	int irqno = irq_of_parse_and_map(np , 0);
	printk("irqno = %d\n", irqno);
	return irqno;
}
(二)申请中断
int request_irq(unsigned int irq, irq_handler_t hander, unsigned long fiags, 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_TPIGGER_HIGH 0x00000004//高电平
#define IRQF_TRIGGER_LOW 0x0000008//低电平触发
参数4:中断的描述,自定义,主要是给用户查看的
/proc/interrupts
参数5:传递给参数2中函数指针的值
返回值:正确为0,错误非0

参数2的赋值:
irqreturn_t key_irq_handler(int irqno, void *devid)
{
	return IRQ_HANDLED;
}
释放中断:
void free_irq(unsigned int irq, void *dev_id);
参数1:设备对应的中断号
参数2:与request_irq中第5个参数保持一致
(三)实现字符设备驱动的框架
//1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
//2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
//3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDET(key_dev->dev_major, 0), NULL, "key0");

(四)驱动中将硬件所产生的数据传递给用户
1,硬件如何获取数据
key:按下和抬起:1/0
读取key对应的gpio的状态,可以判断按下还是抬起
读取key对应gpio的寄存器–数据寄存器
//读取数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
2,驱动如何传递给用户
在中断处理中填充数据:
key_dev->event.code = KEY_ENTER;
key_dev->event.value = o;
在xxx_read中将数据传递给用户
ret = copy_to_user(buf, &key_dev->event, count);
3,用户如何拿到–编写应用程序

while(1)
{
	read(fd, &event, sizeof(struct key_event));
	if(event.code == KEY_ENTER)
	{
		if(event.value)
		{
			printf("APP__ key enter pressed\n");
		}else{
			printf("APP__ key enter up\n");
		}
	}
}

五,实现文件IO模型之一阻塞,等同于休眠
阻塞:当进程在读取外部设备的资源(数据),资源没有准备好,进程就会休眠
文件io模型:
(一)非阻塞
(二)阻塞
(三)多路复用–select/poll
(四)异步信号通知faync
linux应用中,大部分的函数接口都是阻塞
scanf();
read();
write();
accept();

驱动中需要调用
(一)将当前进程加入到等待队列头中
add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
(二)将当前进程状态设置成TASK_INTERRUPTIBLE(可中断状态)
set_current_state(TASK_INTERRUPTIBLE)
(三)让出调度--休眠
schedule(void)

更加智能的接口,等同于上面的三个接口:
wait_event_interruptible(wq, condition)
驱动如何去写代码
(一)等待队列头
wait_queue_head_t
(二)在需要等待(没有数据)的时候,进行休眠
wait_event_interruptible(wq, condition)  //内部会构建一个等待队列项/节点wait_queue_t
(三)在一个合适的时候(有数据),会将进程唤醒
wake_up_interruptible(wait_queue_head_t q)
用法:
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;

六,非阻塞
在读写的时候,如果没有数据,立刻返回,并且返回一个出错码
用的会比较少,因为比较耗资源

open("/dev/key0", O_RDWR | O_NONBLOCK);
-----------------------------------------
驱动中需要去区分,当前模式是阻塞还是非阻塞
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
	return -EAGAIN;

七,多路复用–select和poll
poll的应用:
(一)需要打开多个文件(多个设备)
(二)利用poll来实现监控fd的读,写,出错

#include 
int poll(struct poollfd *fds, nfds_t nfds, int timeout);
参数1:表示多个文件描述符集合
struct pollfd描述的是文件描述符的信息
struct pollfd{
	int fd; //文件描述符
	short event: //希望监控fd的什么事件:读,写,出错
		POLLIN 读,
		POLLOUT 写,
		POLLERR 出错
	short revents;   /*结果描述,表示当前的fd是否有读,写,出错
					用于判断,值为
					POLLIN 读,
					POLLOUT 写,
					POLLERR出错	
					*/
};
参数2:被监控的fd的个数
参数3:监控的时间:
			正数:表示监控多少ms
			负数:无限的时间监控
			0:等待0ms,类似于非阻塞
返回值:负数:出错
大于0:表示fd中有数据
等于0:时间到

八,如果应用中使用poll对设备文件进行了监控,那么设备驱动就必须实现poll接口

unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
	//返回一个mask值
	unsigned int mask;
	//调用poll_wait,将当前的等待队列注册系统中
	poll_wait(filp, &key_dev->wq_head, pts);
	//1,当没有数据的时候返回一个0
	if(!key_dev->key_state)
		mask = 0;
	//2,有数据返回一个POLLIN
	if(!key_dev->key_state)
		mask |= POLLIN;
	return mask;
}

九,异步信号通知:当有数据的时候,驱动会发送信号(SIGIO)给应用,就可以异步去读写数据,不用主动去读写

(一)应用--处理信号,主要是读写数据
void catch_signale(int signo)
{
	if(signo == SIGIO)
	{
		printf("we got sigal SIGIO");
		//读取数据
		read(fd, &event, sizeof(struct key_event));
		if(event.code == KEY_ENTER)
		{
			if(event.value)
			{
				printf("APP__ key enter pressed\n");
			}else
			{
				printf("APP__ key enter up\n");
			}
		}
	}
}
//1,设置信号处理方法
signal(SIGIO, catch_signale);
//2,将当前进程设置成SIGIO的属主进程
fcntl(fd, F_SETOWN, getpid());
//3,将Io模式设置成异步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
(二)驱动--发送信号
1,需要和进程进行关联--记录信号该发送给谁
实现一个fasync的接口
int key_drv_fasync的接口
int key_drv_fasync(int fd, struct file *filp, int on)
{
	//只需要调用一个函数记录信号该发送给谁
	return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
2,在某个特定的时候去发送信号,在有数据的时候
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);

十,中断的下半部
(一)softirq:处理比较快,但是内核级别的机制,需要修改整个内核源码,不推荐也不常用
(二)tasklet:内部实现实际调用了softirq
(三)workqueue:工作队列

(四)tasklet:
struct tasklet_struct
{
	struct tasklet_strcuct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);  //下半部的实现逻辑
	unsigned long data;
};
1,初始化
struct tasklet_struct mytasklet;
tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
例子:
void key_tasklet_half_irq(unsigned long data)
{
	//表示有数据,需要去唤醒整个进程/等待队列
	wake_up_interruptible(&key_dev->wq_head);
	//同时设置标志位
	key_dev->key_state = 1;
	//发送信号
	kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);
2,在上半部中放入到内核线程中--启动
//启动下半部
tasklet_schedule(&key_dev->mytasklet);
3,模块卸载的时候:
tasklet_kill(&key_dev->mytasklet);
(五)工作队列和工作
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,初始化
void work_irq_half(struct work_struct *work)
{
	printk("-----%s--------\n", __FUNCTION__);
	//表示有数据,需要去唤醒整个进程/等待队列
	wake_up_interruptible(&key_dev->wq_head);
	//同时设置标志位
	key_dev->key_state = 1;
	//发送信号
	kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
struct work_struct mywork;
INIT_WORK(struct work_struct *work, work_func_t func);
2,在上半部中放入到内核线程中--启动
schedule_work(&key_dev->mywork);

你可能感兴趣的:(嵌入式,嵌入式)