GIC控制器引出
- 首先对于每个处理器而言都有各自的中断源,从上图可以看到PPIs和FIQ以及IRQ,而FIQ和IRQ是直接连接到处理器的,没有经过分发器。
PPIs(Private Peripheral Interrupt)即私有外设中断,该中断连接到外设上,每个处理器核心都有各 自的私有外设中断;中断号为 16~31。
SPIs(Shared Peripheral Interrupt)即共享外设中断,该中断是共享中断号的,对每个处理器而言都 是可见的;中断号为 32~1019。 - 从上图可以看到所有的外部中断和内部中断都由GIC来管理,且每个核都有各自的FIQ和IRQ以及PPIs,
但所有的核共用SPI中断
- 随着处理器的复杂度增加,一个GIC不够用了,就出现多GIC。为了方便管理出现GIC Domain方式
(中断号+域)为了更好的上层管理引入了虚拟中断号(软中断IRQ与硬件中断hw IRQ),其中默认IRQ为软中断号,而通过datasheet查的中断号为硬件中断号。且软硬件需要映射才能找到对方
GIC控制器驱动
(INTC支持中断号有限,且无法照顾到多核处理器之间的中断线路问题)
- 目前Cortex-A7系列之后全部使用GIC中断控制器,且linux内核为其实现了一套完整的GIC控制器的驱动源码,我们只需在设备树中添加GIC中断控制器节点即可。例如我们datasheet告诉我们此控制器用的是GIC-400中断控制器,此驱动是linux内核提供的通用驱动
gic: interrupt-controller@1c81000 { compatible = "arm,gic-400";//指定需要匹配的中断控制器驱动 reg = <0x01c81000 0x1000>,//此控制器的寄存器,分发配置GICD <0x01c82000 0x2000>,//CPU接口GICIF <0x01c84000 0x2000>,//访问CPU的虚拟接口 <0x01c86000 0x2000>;//地址位选择处理 interrupt-controller;//表明此节点是一个中断控制器 #interrupt-cells = <3>; interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;//中断号PPI 9以及触发状态 };
- 在中断控制器框架图我们可以看到中断多达1020个,如果事先全部为这些中断号分别分配一盒虚拟中断号,这将会占很多空间,在实际开发的过程中断号使用有十几个已经很多了,因此Linux内核在初始化中段映射的时候并不会将虚拟和硬件中断的连接关系全部分配。只会去在设备树dtb文件中定义的中断进行映射。所以在使用中断之前必须必须在设备树中定义中断节点,否则无法注册中断
- 添加控制器节点
intc: interrupt-controller@1c20400 { compatible = "xxx, xxx-inct"; reg = <0x01c20400 0x400>; interrupt-controller; #interrupt-cells = <1>;//中断控制器的字节点与孙子节点只有哟个cell };
- 中断注册
linux内核提供了中断接口函数,包括中断的申请、释放、失能、使能
1* 中断申请函数request_irq(62, btn_irq, IRQF_TRIGGER_FALLING, "btn",(void*)&key_value); request_irq(unsigned int irq,//硬件中断号,数据手册 irq_handler_t handler,//中断句柄 unsigned long flags,//中断触发标志 const char* name,//中断名 void* dev)//设备ID,只要是唯一的即可 //对于线程化中断而言,其拆分了两个部分(上下半部) request_threaded_irq( unsigned int irq, irq_handler_t handler,//触发的中断服务函数(上半部) irq_handler_t thread_fn,//中断的下半部 unsigned long flags, const char *name, void *dev);
-
中断释放函数
extern const void *free_irq(unsigned int,//中断号
void *);//中断设备 -
中断enable / disable
需求:在一定时间内关闭中断、然后开启中断。linux内核提供了API
extern void disable_irq(unsigned int irq);//关闭单个
extern void enable_irq(unsigned int irq); -
关闭全局中断
#define local_irq_enable() do { raw_local_irq_enable(); } while (0)
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)
写一个ADC按键中断:(按键按下->ADC触发->ADC读数据->判断按键值)
-
对应用程序而言,就是不断的读取按键(文件),这样的话,如果大部分情况下无中断,那么程序一直会做无用功。
-
此时,我们可以先去读按键,驱动程序会立刻让其睡眠,等到有中断到来的时候再将其唤醒。linux提供了一个等待的队列就是用来存放需要睡眠的任务,当有中断来的时候,在中断句柄中将其唤醒。
-
添加节点(里面定义中断号)
mykey: mykey@1c22800 {
compatible = “mykey”;
reg = <0x01c22800 0x4>, <0x01c22804 0x4>, <0x01c22808 0x4>, <0x01c2280c 0x4>;
interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
status = “okay”;
}; -
定义了一个btn_waitq的等待队列,等待队列实际上是一个链表结构
DECLARE_WAIT_QUEUE_HEAD(btn_waitq);wait_event_interruptible(wq_head, //wq_head等待队列中
condition)//是一个标志,1为唤醒,0为睡眠
wake_up_interruptible(x)//该函数是将x等待队列的所有任务全部唤醒使用platform框架,目的是我们不必自己来实现硬件中断号与软件中断号的映射关系,这个过程由 GIC驱动来做了(在解析dtb文件时,我们只需要在设备树中指定interrupts属性即可)
-
上述的图其实,也回顾之前关于platform的驱动的重要特点,在内核会解析设备树并自动生成 plaform_device,此时不需要我们自己再写设备文件了。
- static int btn_probe(struct platform_device *pdev) {//此处省略设备注册以及 IO 重映射
ret = devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0), btn_irq, 0, "mykey",
(void*)&key_value);}
- static irqreturn_t btn_irq(int irq, void *dev_id) {
wake_up_interruptible(&btn_waitq); /* 唤醒休眠的进程,即调用 read 函数的进程 */
ev_press = 1; *keyadc_ints |= ((1<<0)|(1<<1) | (1<<2) |(1<<3)|(1<<4));
return IRQ_HANDLED; }
- static ssize_t btn_cdev_read(struct file * file, char __user * userbuf, size_t count, loff_t * off) {
int ret;unsigned int adc_value;
adc_value = *(keyadc_data);//定义一个变量来存放LRADC的数据
//将当前进程放入等待队列 button_waitq 中,并且释放 CPU 进入睡眠状态
wait_event_interruptible(btn_waitq, ev_press!=0);
ret = copy_to_user(userbuf, &adc_value, 4);//将取得的按键值传给上层应用
printk(KERN_WARNING"key adc = %d\n",(adc_value&(0x3f)));
printk(KERN_WARNING"key statue = 0x%x\n",*(keyadc_ints));
ev_press = 0;//按键已经处理可以继续睡眠}
- static int btn_cdev_open (struct inode * inode, struct file * file) {
//需要对LRADC进行初始化,周期、触发模式、转换延时时间
*keyadc_intc |= ((1<<0)|(1<<1) | (1<<2) |(1<<3)|(1<<4));
*keyadc_ctrl |= (1<<0);}