linux 中断子系统

0、说明

        对于嵌入式系统开发来说,中断使用尤为重要。linux中断用在很多方面,如最简单的按键触发的中断事件,网卡收包后的中断等等。

        文章参考了韦东山老师中断讲解内容。重点偏向于中断自身的功能及gic驱动。对于设计的内核其他知识,如异常向量表具体的初始化等在另外章节。

1、环境

2、中断概念

2.1 异常

       异常概念大于中断,中断也是异常的一种。

  • 指令未定义
  • 指令、数据访问有问题
  • SWI(软中断)
  • 快中断
  • 中断

2.2 arm关于中断的处理流程

初始化:

        a. 设置中断源,让它可以感知外部中断事件

        b. 设置中断控制器(可以屏蔽某个中断,优先级)

        c. 设置CPU总开关(使能中断)

CPU正常执行用户程序

CPU探测中断是否发生(硬件完成)

        CPU 每执行完一条指令都会检查有无中断/异常产生

CPU处理中断事件

        跳转到异常向量地址(硬件完成)

        a. 保存现场(各种寄存器)

        b. 处理异常(中断):分辨中断源,再调用不同的处理函数

        c. 恢复现场

2.3 异常向量表

        发生不同异常时的硬件跳转地址。

_start: b	reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18**
	ldr	pc, _fiq

        向量表一般存储在0地址,但不是固定的地址,存在vector base寄存器,可以在该寄存器中设置中断向量表的基址。

        中断向量表中,各子异常地址偏移是固定的。

        ARM架构异常向量表在0x0地址和0xffff0000(虚拟地址)。

内核异常向量表的建立:

        在内核前期汇编阶段,会将向量表拷贝到物理空间,并映射0xffff0000到该物理空间,当异常发生时在异常向量表中进行跳转。如发生中断,则通过gic驱动提供的irq_handle(handle_arch)_irq)进行中断类型判断与处理。

2.4 栈在中断中的核心作用

        当发生异常时,如中断,会跳去中断向量表处理中断事件,那么被中断的APP,如何办,如当前CPU在计算a+b,此时CPU中r0开始的寄存器可能存储的是APP的参数,这个就需要记录r0-r15寄存器值到当前app的栈中,即为保护现场。

        如CPU当前在计算a=a+b,首先C需要被转换为汇编语言,然后CPU取指执行。

CPU运行时,先去取得指令,再执行指令:
① 把内存a的值读入CPU寄存器R0
② 把内存b的值读入CPU寄存器R1
③ 把R0、R1累加,存入R0
④ 把R0的值写入内存a

对于程序A被程序B中断的情况,如下图。可以是中断或者进程调度触发:

2.5 进程与线程浅析 

        在Linux中:资源分配的单位是进程,调度的单位是线程。

        同一进程中存在共享内容,且每个线程有自己的栈,多个线程参与调度。

2.6 中断处理函数-上下部

        linux多进程在不断的进行调度,如果中断终止了进程APP的执行,进入中断处理函数,如果中断处理函数执行很多内容,会导致进程一直无法被调度,从而出现APP卡顿的情况出现,因此中断处理函数中不应该执行太复杂太耗时的操作。

        下半部可以通过tasklet(小任务)软中断、work queue(工作队列)来实现。

tasklet

        适合中断处理操作比较简单的场景。通过软中断实现,上半部执行后开中断,可以接收新中断,此时处理下半部,处理完成后APP进程开始调度。

        因此tasklet只适合中断耗时不长的场景,因为软中断下半部执行过程中,虽然开了中断,但是进行是不会被调度,长时间处理下半部依然会导致app卡顿。

work queue

        通过内核线程,在上半部完成后,唤醒内核线程处理下半部,内核线程与app共同参与调度。schedule_work会把当前work放入system_wq队列,且会唤醒内核线程,内核线程会处理system_wq中的任务。

        因此可在内核上半部中调用schedule_work,将下半部加入工作队列,并唤醒处理下半部的内核线程参与调度,当抢到CPU资源时进行处理。

2.7 threaded irq技术

        在SMP环境下,为每个中断都创建一个内核线程,且多个中断的线程可被创建在不同CPU上,从而充分利用多核的优势。

2.8 硬件中断与软中断

        按键中断、网卡中断、系统时钟首先发生硬件中断,然后在中断处理函数中,使能软件中断,在每次硬件中断完成后会判断是够需要处理软中断。若系统时钟中断为10ms,则软中断在10ms内会被执行到。

        软中断,如上面说的tasklet,用于中断下半部。

3、中断下半部处理机制

request_irq及相关数据结构

tasklet与work queue

4、GIC驱动分析

4.1 interrupt相关数据结构

4.1.1 irq_desc

        一个硬件中断对应一个irq_desc结构体,描述该硬件中断信息。

类型成员名功能
irq_flow_handler_thandle_irqhighlevel irq-events handler
irq_datairq_data数据
struct irqaction    *action the irq action chain

4.1.2 irq_data

        中断数据信息

/**                                                                                         
 * struct irq_data - per irq chip data passed down to chip functions                        
 * @mask:       precomputed bitmask for accessing the chip registers                        
 * @irq:        interrupt number                                                            
 * @hwirq:      hardware interrupt number, local to the interrupt domain                    
 * @common:     point to data shared by all irqchips                                        
 * @chip:       low level interrupt hardware access                                         
 * @domain:     Interrupt translation domain; responsible for mapping                       
 *          between hwirq number and linux irq number.                                      
 * @parent_data:    pointer to parent struct irq_data to support hierarchy                  
 *          irq_domain                                                                      
 * @chip_data:      platform-specific per-chip private data for the chip                    
 *          methods, to allow shared chip implementations                                   
 */                                                                                         
struct irq_data {                                                                           
    u32         mask;                                                                       
    unsigned int        irq;                                                                
    unsigned long       hwirq;                                                              
    struct irq_common_data  *common;                                                        
    struct irq_chip     *chip;                                                              
    struct irq_domain   *domain;                                                            
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY                                                         
    struct irq_data     *parent_data;                                                       
#endif                                                                                      
    void            *chip_data;                                                             
};     

4.1.3 irq_chip

        提供中断操作函数,如屏蔽中断,开中断,使能,非使能等。

        irq_mask等函数提供给handle_irq函数调用。

struct irq_chip {                                                  
    struct device   *parent_device;                                
    const char  *name;                                             
    unsigned int    (*irq_startup)(struct irq_data *data);         
    void        (*irq_shutdown)(struct irq_data *data);            
    void        (*irq_enable)(struct irq_data *data);              
    void        (*irq_disable)(struct irq_data *data);             
                                                                   
    void        (*irq_ack)(struct irq_data *data);                 
    void        (*irq_mask)(struct irq_data *data);                
    void        (*irq_mask_ack)(struct irq_data *data);            
    void        (*irq_unmask)(struct irq_data *data);              
    void        (*irq_eoi)(struct irq_data *data);           
	
	...
	
}

创建domain: irq_domain_add_legacy

gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base, 16, &gic_irq_domain_ops, gic);
    domain = __irq_domain_add(of_node_to_fwnode(of_node), first_hwirq + size,first_hwirq + size, 0, ops, host_data);
        domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size), GFP_KERNEL,of_node_to_nid(to_of_node(fwnode)));
        domain->ops = ops;//gic_irq_domain_ops

4.2 gic中断处理逻辑

        gic在用到某个中断时,分配irq_desc,并确定virq以及hwirq和virq的对应关系(gic irq_domain)。

        一个hwirq对应一个中断源时:一个hwirq对应一个irq_desc

        一个hwirq对应多中断源时:一个hwirq对应多个irq_desc,如gpio中断。多个irq_desc在 gpio的irq_domain中记录对应关系。gic读取gpio寄存器确认是哪个gpio发生的中断,然后在irq_domain中找到对应的irq_desc,调用处理函数。

每个中断控制器都有irq_domain。

        中断发生-硬件自动跳转到异常向量表-执行irq_handle函数-查询gic控制器确认中断是哪个hwirq-在gic irq_domain中找到对应的irq_desc项-执行irq_desc中的handle-handle调用action-action执行用户中断函数

4.3 gic驱动分析

4.3.1 gic几大要素

  1. 提供irq_handle函数,异常向量表会跳转执行该函数
  2. 中断控制器通过irq_domain结构体记录hwirq和virq的对应关系
  3. 提供irq_desc的handle调用action函数
  4. 提供中断屏蔽和清除函数

4.3.2 设备树

        gic中断控制器设备树中的描述 

        intc: interrupt-controller@f8f01000 {
            compatpble = "arm,cortex-a9-gic";
            #interrupt-cells = <3>;
            interrupt-controller;
            reg = <0xF8F01000 0x1000>,
                  <0xF8F00100 0x100>;
        };

        对应驱动程序:

        kernel/drivers/irqchip/irq-gic.c

        将IRQCHIP_DECLARE宏展开:

        IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);

IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
OF_DECLARE_2(irqchip, cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init)
_OF_DECLARE(irqchip, cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init, of_init_fn_2) 

static const struct of_device_id __of_table_cortex_a9_gic
	__used __section("__irqchip_of_table") = { 
		.compatible = "arm,cortex-a9-gic",
		.data = gic_of_init
	}

4.3.3 gic初始化流程-gic_of_init

        根据__irqchip_of_table,可以找到of_irq_init中使用。进而分析整个gic的加载初始化过程。

start_kernel (init\main.c)
    init_IRQ (arch\arm\kernel\irq.c)
    	irqchip_init (drivers\irqchip\irqchip.c)
    		of_irq_init (drivers\of\irq.c)
    			desc->irq_init_cb = match->data;
                ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent);

最终gic_of_init被调用。

gic_of_init
    //映射设备树中的地址
    gic_of_setup(gic, node);
    __gic_init_bases
        //提供中断向量表中处理函数
        set_handle_irq(gic_handle_irq);
            handle_arch_irq = handle_irq;
        gic_init_chip(gic, NULL, name, false);
            //初始化gic_chip,里面有屏蔽中断等函数
            gic->chip = gic_chip;
        gic_init_bases(gic, handle);
            //初始化gic->domain,提供相关能力
            gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base, \
                16, &gic_irq_domain_ops, gic);

gic_irq_domain_ops

4.3.4 设备树的解析与中断节点的创建

        内核启动阶段完成对设备书解析。并创建中断的映射关系,并将中断信息存储在设备树对应的platform_device的resoure中。

4.3.5 中断向量表函数handle_arch_irq 

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    //读取寄存器,获取发生哪个硬件中断
    irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
    irqnr = irqstat & GICC_IAR_INT_ID_MASK; 
    //处理domain中的irqnr硬件中断对应的virq
    handle_domain_irq(gic->domain, irqnr, regs);
        //找到hwirq对应的virq 
        irq = irq_find_mapping(domain, hwirq);
        //调用处理函数
        generic_handle_irq(irq);
            generic_handle_irq_desc(desc);
                //以下函数由解析设备树时domain中的alloc函数设置->handle_fasteoi_irq
                desc->handle_irq(desc);
}

调用handle_irq函数处理中断流程,最终调用用户注册的中断函数。

handle_fasteoi_irq
    struct irq_chip *chip = desc->irq_data.chip;
    mask_irq(desc);
    handle_irq_event(desc);
        //调用desc中的action函数,也就是request_irq时的函数
    cond_unmask_eoi_irq(desc, chip);
    

5、虚拟二级中断控制器驱动

        如果gic称之为一级中断控制器的话,这里将GPIO、gmac等称为二级中断控制器。

在UG585 Interrupts一节列出了ZYNQ 中断对应的硬件中断号:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/random.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
/* FIXME: for gpio_get_value() replace this with direct register read */
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/bug.h>
#include <linux/random.h>

static struct irq_domain *virtual_intc_domain;

static int virtual_intc_get_hwirq(void)
{
	return get_random_int() & 0x3;
}

static void virtual_intc_irq_handler(struct irq_desc *desc)
{
	/* 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
	int hwirq;
	
	struct irq_chip *chip = irq_desc_get_chip(desc);

	chained_irq_enter(chip, desc);

	/* a. 分辨中断 */
	hwirq = virtual_intc_get_hwirq();

	/* b. 调用irq_desc[].handle_irq(handleC) */
	generic_handle_irq(irq_find_mapping(virtual_intc_domain, hwirq));

	chained_irq_exit(chip, desc);
}

static void virtual_intc_irq_ack(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_mask(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_mask_ack(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_unmask(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_eoi(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static struct irq_chip virtual_intc_irq_chip = {
	.name			= "100ask_virtual_intc",
	.irq_ack	   = virtual_intc_irq_ack	   ,
	.irq_mask	   = virtual_intc_irq_mask	   ,
	.irq_mask_ack  = virtual_intc_irq_mask_ack ,
	.irq_unmask    = virtual_intc_irq_unmask   ,
	.irq_eoi	   = virtual_intc_irq_eoi	   ,
};

static int virtual_intc_irq_map(struct irq_domain *h, unsigned int virq,
			  irq_hw_number_t hw)
{
	/* 1. 给virq提供处理函数
	 * 2. 提供irq_chip用来mask/unmask中断
	 */
	
	irq_set_chip_data(virq, h->host_data);
	//irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_edge_irq); /* handle_edge_irq就是handleC */
	irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_level_irq); /* handle_level_irq就是handleC */
	//irq_set_nested_thread(virq, 1);
	//irq_set_noprobe(virq);

	return 0;
}


static const struct irq_domain_ops virtual_intc_domain_ops = {
	.xlate = irq_domain_xlate_onetwocell,
	.map   = virtual_intc_irq_map,
};

static int virtual_intc_probe(struct platform_device *pdev)
{	
	struct device_node *np = pdev->dev.of_node;
	int irq_to_parent;
	int irq_base;
	
	/* 1. virutal intc 会向GIC发出n号中断 */
	/* 1.1 从设备树里获得virq_n */
	irq_to_parent = platform_get_irq(pdev, 0);
	printk("virtual_intc_probe irq_to_parent = %d\n", irq_to_parent);
	
	/* 1.2 设置它的irq_desc[].handle_irq, 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
	irq_set_chained_handler_and_data(irq_to_parent, virtual_intc_irq_handler, NULL);

	
	/* 2. 分配/设置/注册一个irq_domain */
	irq_base = irq_alloc_descs(-1, 0, 4, numa_node_id());
	printk("virtual_intc_probe irq_base = %d\n", irq_base);

	virtual_intc_domain = irq_domain_add_legacy(np, 4, irq_base, 0,
					     &virtual_intc_domain_ops, NULL);
	
	return 0;
}
static int virtual_intc_remove(struct platform_device *pdev)
{
	return 0;
}



static const struct of_device_id virtual_intc_of_match[] = {
	{ .compatible = "100ask,virtual_intc", },
	{ },
};


static struct platform_driver virtual_intc_driver = {
	.probe		= virtual_intc_probe,
	.remove		= virtual_intc_remove,
	.driver		= {
		.name	= "100ask_virtual_intc",
		.of_match_table = of_match_ptr(virtual_intc_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_intc_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_intc_driver);
}


/* 2. 出口函数 */
static void __exit virtual_intc_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_intc_driver);
}

module_init(virtual_intc_init);
module_exit(virtual_intc_exit);

MODULE_LICENSE("GPL");

6、二级中断控制器驱动-GPIO

 6.1 GPIO中断控制器设备树

        gpio产生中断后,向上一级gic发出52号SPI中断。

        //gic一级中断控制器
        intc: interrupt-controller@f8f01000 {
            compatible = "arm,cortex-a9-gic";
            #interrupt-cells = <3>;
            interrupt-controller;
            reg = <0xF8F01000 0x1000>,
                  <0xF8F00100 0x100>;
        };
    
        //gpio二级中断控制器
        gpio0: gpio@e000a000 {
            compatible = "xlnx,zynq-gpio-1.0";
            #gpio-cells = <2>;
            clocks = <&clkc 42>;
            gpio-controller;
            interrupt-controller;
            #interrupt-cells = <2>;
            interrupt-parent = <&intc>;
            interrupts = <0 20 4>;
            reg = <0xe000a000 0x1000>;
        };

        interrupts = <0 20 4>;即SPI类型的第20个中断类型,对应手册中的spi_status_0[20] 。

6.2 GPIO中断控制器驱动

        可以看出ZYNQ支持的几十个GPIO,只会向GIC发出一个SPI-20中断,那么如何知道是哪个GPIO发生的中断,需要读取GPIO寄存器来判断。因此二级GPIO中断控制器驱动中需要自己读取硬件完成中断源判断。

        一个硬件中断会对应一个irq_desc。需要设置52号中断中的irq_desc的handle函数去找到每个gpio中断对应的irq_desc。

 上图来自韦东山老师笔记

6.2.1 GPIO控制器驱动中的IRQ数据结构

        一个zynq_gpio结构体对应一个GPIO控制器,zynq_gpio中包含一个gpio_chip,gpio_chip中包含一个gpio_irq_chip irq。gpio_irq_chip中包含irq_chip,irq_domain。

//GPIO控制器中的中断配置
zynq_gpio_probe
	//提供irqchip
	girq->chip = &zynq_gpio_edge_irqchip;
	//提供handler
	girq->parent_handler = zynq_gpio_irqhandler;
	girq->handler = handle_level_irq;

	gpiochip_add_data(chip, gpio);
		gpiochip_add_data_with_key
			gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
			...
			gpiochip_irqchip_init_valid_mask(gc);
			gpiochip_irqchip_init_hw(gc);
			gpiochip_add_irqchip(gc, lock_key, request_key);
				//配置domain
				gc->irq.domain = irq_domain_add_simple(np, gc->ngpio, gc->irq.first, ops, gc);  
				//提供handler函数
				irq_set_chained_handler_and_data(gc->irq.parents[i], gc->irq.parent_handler, data);

待确认:

        系统启动初始化阶段创建了部分irq_desc。在内核解析设备树找到gpio控制器的时候,在gic的domain中找不到对应关系,则创建gpio的irq_desc,并插入到树种,然后创建对应关系。

关于irq_desc的初始化

http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值