第十四章 中断自我整理(二)-中断控制器初始化

14.2 中断初始化 内核启动流程:https://www.cnblogs.com/lcw/p/3337937.html,在内核启动过程中先屏蔽所有中断,初始化中断,初始化IRQ,初始化软中断,使能中断涉及的函数有: start_kernel...

14.2 中断初始化

    内核启动流程:https://www.cnblogs.com/lcw/p/3337937.html,在内核启动过程中先屏蔽所有中断,初始化中断,初始化IRQ,初始化软中断,使能中断涉及的函数有:
start_kernel();  //init\main.c
        local_irq_disable();
        early_irq_init();    //问题1:early_针对的是所有irq吗?都init了些什么?
        init_IRQ();           //问题2:IRQ都init了什么呢?
        softirq_init();      //问题3:softirq都init了什么呢?暂时不看
        local_irq_enable();
    依次搞明白上面3个问题……
    声明下后面的学习主要参考了好几个大佬的系列文章,有的一时找不到链接了,没有商业利益,只是学习。
        蜗窝科技系列    http://www.wowotech.net/linux_kenrel/gic_driver.html
        海风怒吼系列    http://www.lilonghua.top/linux/src/linux4x_gpio1_gpiolib.html
        彭东林系列    https://blog.csdn.net/oggyyq42448/article/details/54772947
        arnoldlu    https://www.cnblogs.com/arnoldlu/p/8659981.html

14.2.1early_irq_init()

    一搜内核代码发现在kernel\irq\irqdesc.c中有2个early_irq_init();看了下层级关系,
#ifdef CONFIG_SPARSE_IRQ
        early_irq_init(void);   // NO.1
    #else
        early_irq_init(void);   // NO.2
    那就看下这个CONFIG_SPARSE_IRQ是什么意思吧,内核中全局搜索发现在arch\arm\configs\100ask_imx6ull_defconfig 的line 70有:
CONFIG_SPARSE_IRQ=y,字面意思应该确定是否配置共享IRQ( Interrupt Request),很明显是配置,那就执行NO.1。

early_irq_init(void)
{
    initcnt = arch_probe_nr_irqs(); //预计分配desc的个数
    {
        nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS; //128
    }

    for (i = 0; i < initcnt; i++) {
        desc = alloc_desc(i, node, 0, NULL, NULL); 
        set_bit(i, allocated_irqs);
        irq_insert_desc(i, desc); 
    }
}

   

     问题:6ull有128的IRQ是全部都分配了desc吗?

    似乎清楚了该函数的作用,即为每个IRQ分配desc并将其插入到radix tree(基数树)中。

attachments-2020-06-wJk7Hup25ed4f40ce3a51.png    在讲课文档中有“注意:如果内核配置了CONFIG_SPARSE_IRQ,那么它就会用基数树(radix tree)来代替irq_desc数组。

     知识点:radix tree,来自维基百科“在计算机科学中,基数树(也叫基数特里树或压缩前缀树)是一种数据结构,是一种更节省空间的Trie(前缀树),其中作为唯一子节点的每个节点都与其父节点合并,边既可以表示为元素序列又可以表示为单个元素。”也是一棵树,父节点和子节点排列。
attachments-2020-06-W9cm5SxR5ed4f42a135d1.jpeg
    重要结构体:struct irq_desc
    在include/linux/irqdesc.h中定义。它包含几个重要的结构体,可以参考这个层级(借用韦老师的讲课图):
attachments-2020-06-ekf2rX2m5ed4f478a1029.jpeg

    可惜图片不是很清楚,这里irq_desc不应该是数据形式,应该是radix tree形式。
    自己画了一个简易结构图:各个结构体中成员的含义可以很容易找到,不多说了,只想用简短的语言来概括下。
attachments-2020-06-Gd5nZXlh5ed4f4d7c8c3d.png
    
    对应下图(来自网络)尝试总结一下:当发生中断后,首先获取触发中断的hwirq,然后通过irq domain的map函数翻译成virq(虚拟中断号),然后通过virq就可以获取对应的irq_desc。调用irq_desc->handle_irq来处理底层中断操作,并遍历action list执行action->handler识别是哪个hwirq发生中断。一般irq_desc->handle_irq是由厂家提供,action->handler是自己写的中断处理函数。
attachments-2020-06-jQhxHorC5ed4f54dadf6a.gif

14.2.2init_IRQ()

    看下这个函数的作用是什么???
arch/arm/kernel/irq.c
void __init init_IRQ(void) //这是一个根据体系结构不同而不同的IRQ初始化函数
{
    针对6ull指向: //arch\arm\mach-imx\mach-imx6ul.c
    machine_desc->init_irq();
}
 
//////////////////////////////
static void __init imx6ul_init_irq(void)
{
    imx_gpc_check_dt();
    imx_init_revision_from_anatop();
    imx_src_init();
    irqchip_init();
     {
        struct of_device_id __irqchip_of_table[];
        of_irq_init();
    }
    imx6_pm_ccm_init("fsl,imx6ul-ccm");
}
    of_device_id结构体数组__irqchip_of_table存放所有irqchips。
/*
 * Struct used for matching a device
 */
struct of_device_id {
    char name[32]; //名字
    char type[32]; //类型
    char compatible[128]; //与设备树节点的compatible属性匹配
    const void *data; //驱动私有数据
};
    of_irq_init()-Scan and init matching interrupt controllers in DT,解释的很清楚了,按照interrupt controller的连接关系从root开始,依次初始化每一个interrupt controller。都初始化些什么呢?
    根据前面分析知道中断控制器的层级是GIC-GPC-GPIOx+others,只看下GIC和GPIOs的初始化函数吧。从层级来看先初始化GIC。

14.4.2.1.gic_init_bases()

    具体代码如下:
//drivers/irqchip/irq-gic.c
 
IRQCHIP_DECLARE(cortex_a7_gic,"arm,cortex-a7-gic", gic_of_init);
 
gic_of_init
__gic_init_bases(gic, -1, &node->fwnode); 
     --->__gic_init_bases()
         --->set_handle_irq(gic_handle_irq);  
         --->gic_init_bases(gic, irq_start, handle);//主处理过程
 
if (parent) { //处理interrupt级联后面再说
    irq = irq_of_parse_and_map(node, 0); -解析second GIC的interrupts属性,并进行mapping,返回IRQ number
    gic_cascade_irq(gic_cnt, irq);
}
void __init gic_init_bases(unsigned int gic_nr, int irq_start,
               void __iomem *dist_base, void __iomem *cpu_base,
               u32 percpu_offset, struct device_node *node)
{
    irq_hw_number_t hwirq_base;
    struct gic_chip_data *gic;
    int gic_irqs, irq_base, i;
    
    /*
    * For primary GICs, skip over SGIs.
    * For secondary GICs, skip over PPIs, too.
    */
    if (gic == &gic_data[0] && (irq_start & 31) > 0) {
        hwirq_base = 16;  
        if (irq_start != -1)
            irq_start = (irq_start & ~31) + 16;
        } else {
            hwirq_base = 32; 
        } // >32 SPIs
    
    gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
    
   
    irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id());
    --->alloc_descs()   
        
        gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
                    hwirq_base, &gic_irq_domain_ops, gic);
    知识点:virq存在全局变量allocated_irqs中,它的定义:
static DECLARE_BITMAP(, IRQ_BITMAP_BITS);
    对于ARM SOC使用线性映射,即最大virq为NR_IRQs,6ull平台是128个。
#ifdef CONFIG_SPARSE_IRQ
        # define IRQ_BITMAP_BITS	(NR_IRQS + 8196)
   #else
        # define IRQ_BITMAP_BITS	NR_IRQS
    #endif
    
函数irq_domain_add_legacy()形参中有个变量struct irq_domain_ops gic_irq_domain_ops,看下它里面的callback函数,
//drivers/irqchip/irq-gic.c
static const struct irq_domain_ops gic_irq_domain_ops = {
	.map = gic_irq_domain_map, 
	.unmap = gic_irq_domain_unmap,
};
    这里只有map/ummap函数,不用xlate函数吗?7个GIC都用这一个map函数?

    看下是怎么注册domain和映射的,
struct irq_domain *irq_domain_add_legacy()
{
    struct irq_domain *domain;
    domain = __irq_domain_add(of_node, first_hwirq + size, //
                  first_hwirq + size, 0, ops, host_data);
    if (!domain)
        return NULL;
    irq_domain_associate_many(domain, first_irq, first_hwirq, size); //
    return domain;
}
    
    __irq_domain_add()的功能比较清晰,初始化各个成员,调用将该irq domain挂入irq_domain_list的全局列表。接着看下是怎么映射的?
void irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base,
			       irq_hw_number_t hwirq_base, int count)
{
	struct device_node *of_node;
	int i;
    
    /*count<-size<-gic_irqs 把6ull中所有的gic都注册和映射一遍,回顾下前面的内容是不是只有7个gics*/
	for (i = 0; i < count; i++) { 
		irq_domain_associate(domain, irq_base + i, hwirq_base + i);
	}
}
    irq_domain_associate()在看这个函数之前补充一个知识点,hwirq和virq的映射分为3中映射:线性映射、Radix tree映射和不映射,而Radix tree映射只用于powerPC和MIPS,所以6ull使用线性映射
int irq_domain_associate()
{
	irq_data->hwirq = hwirq;
	irq_data->domain = domain;
	if (domain->ops->map) {
		ret = domain->ops->map(domain, virq, hwirq); 
	}

	if (hwirq < domain->revmap_size) {
		domain->linear_revmap[hwirq] = virq; 

	irq_clear_status_flags(virq, IRQ_NOREQUEST); //该IRQ已经可以申请了,因此clear相关flag
}
    看了前面的那个map函数gic_irq_domain_map(),形参分别是domain、virq和hwirq。
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
	struct gic_chip_data *gic = d->host_data;

    /* 根据hwirq各个找各自的set_info,先只看瞎SPI的 */
	if (hw < 32) {
		irq_set_percpu_devid(irq);
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data, //SGI/PPI
				    handle_percpu_devid_irq, NULL, NULL);
		irq_set_status_flags(irq, IRQ_NOAUTOEN);
	} else {
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data, //SPI
				    , NULL, NULL);
		irq_set_probe(irq);
	}
	return 0;
}
    irq_domain_set_info()中有个形参是handle_fasteoi_irq(),搜索知道它是处理EOI类型中断处理函数,https://en.wikipedia.org/wiki/End_of_interrupt在可编程中断控制器(PIC)中确认中断事件之前触发的电平中断。也就是所有的SPI中断都归总到这里。go on……
handle_fasteoi_irq(desc)
    --->handle_irq_event(desc)
        --->handle_irq_event_percpu(desc)
            --->__handle_irq_event_percpu(desc, &flags)
                --->action->handler(irq, action->dev_id)
    小结:handle_fasteoi_irq()处理所有的SPI中断,根据中断描述符desc依次找到它们各自的action->handler。
    现在只是初始化肯定不会去执行handler,只是指定吧,当中断发送时,会__irq_wake_thread(desc, action)唤醒对应中断的内核线程。后面再说了,不懂。

    返回:
void irq_domain_set_info()
{
	irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
	__irq_set_handler(virq, handler, 0, handler_name);
    --->__irq_do_set_handler(desc, handle, is_chained, name);
        --->desc->handle_irq = handle;     
     
	irq_set_handler_data(virq, handler_data);
}
    看下参数的传递过程,handle_fasteoi_irq-> handler->   handle->   desc->handle_irq,好吧其实就是上面的小结,handle_fasteoi_irq()被指定为处理所有的SPI中断的desc->handle_irq。回看上面那张图,handle_fasteoi_irq()就是highlevel irq-events handler,个人理解:highlevel就是比后面的handlers高一级,当中断过来后,先去执行highlevel handler再去执行lowlevel handler。

    修改之前画的重要结构体的那个层级图,
attachments-2020-06-W7SRd5ZR5ed4f751f1d54.png
    小结:初始化的过程就是为各个中断控制器创建一个domain,将其添加到domain列表中。然后通过hwirq mapping出virq,指定highlevel handle_irq,指定lowlevel handler。

    已知6ull的中断层级如图(韦老师上课图),层级关系是怎么实现的呢?
attachments-2020-06-PSwkIH8F5ed4f7a66c76c.png
    依然参考蜗窝科技等,反正都是参考大佬的文章,就不每次都说了。

1、root GIC初始化过程
    直接跳到gic_init_bases()
void __init gic_init_bases()
{
    /*
    * For primary GICs, skip over SGIs. //对于root GIC跳过16个SGIs
    */
    if (gic == &gic_data[0] && (irq_start & 31) > 0) {
        
    
    gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
    
    //从virq = 16 开始向allocated_irqs申请deces,搞不懂为啥不直接跳过PPIs呢?
    irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id());
    --->alloc_descs()   //申请descs
        /*向系统注册irq domain并创建映射*/
        gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
                    hwirq_base, &gic_irq_domain_ops, gic);
    经过映射之后,hwirq和virq的关系就创建了,存在一一对应的关系。
attachments-2020-06-wqxi4jqP5ed4f7d6a17b8.png
2、次级gic初始化过程
    也是直接跳到gic_init_bases()
void __init gic_init_bases()
{
    /*
    * For secondary GICs, skip over PPIs, too
    */
   
    
    gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
    
    //从virq = 32开始向allocated_irqs申请deces,
    irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id());
    --->alloc_descs()   //申请descs
        /*向系统注册irq domain并创建映射*/
        gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
                    hwirq_base, &gic_irq_domain_ops, gic);
    下面这话和图不是很确定,特此声明,经过映射之后,hwirq和virq的关系就创建了,存在一一对应的关系,另外假设gic有4个子中断hwirq,编号为0、1、2、3,则映射关系如图:
attachments-2020-06-ntchT77D5ed4f81236443.png

    自己对上图持怀疑态度,这里理解有点绕,借用下韦老师的图。
attachments-2020-06-68zJ7mOD5ed4f8317f770.png

    到此为止吧,再绕就把自己绕晕了。

14.4.2.2irq_of_parse_and_map()
    回到前面的gic_of_init()函数,还有irq_of_parse_and_map()没有执行,
gic_of_init
__gic_init_bases(gic, -1, &node->fwnode); 
     --->__gic_init_bases()
         --->gic_init_bases(gic, irq_start, handle);//主处理过程
 
if (parent) { //处理interrupt级联后面再说
    irq = irq_of_parse_and_map(node, 0); -解析second GIC的interrupts属性,并进行mapping,返回IRQ number
    gic_cascade_irq(gic_cnt, irq);
}
    在分析这个函数之前,先明确2个定义:
    1、Generic Interrupt Controller 通用中断控制器;
    2、Global Interrupt Controller 全局中断控制器;
     2个名词都可以缩写为GIC或gic,为了便于区分可以规定通用中断控制器用小写的gics表示,多个,全局中断控制器用大写表示GIC,只有1个,即root GIC。
root GIC没有parent,NULL,不会执行irq_of_parse_and_map(),其他gic存在级联关系,有各自的parent或是共同的parent,如前面图:

    看下代码,其实已经知道了该函数的作用是创建级联关系,也就是parse and map。
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
	struct of_phandle_args oirq;

	if (of_irq_parse_one(dev, index, &oirq)) //解析gic的属性和找到parent
		return 0;
	return irq_create_of_mapping(&oirq);  //创建映射
}
    出现一个新的结构体of_phandle_args,看下成员:
#define MAX_PHANDLE_ARGS 16
struct of_phandle_args {
	struct device_node *np;   //指向gics
	int args_count;           //interrupts属性个数
	uint32_t args[MAX_PHANDLE_ARGS];  //interrupts属性
};
    只看irq_create_of_mapping():
unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_fwspec fwspec;

	of_phandle_args_to_fwspec(irq_data, &fwspec);
	return irq_create_fwspec_mapping(&fwspec);
}
    又有一个新的结构体irq_fwspec ,看下成员,好像也是一个desc啊!
/**
 * struct irq_fwspec - generic IRQ specifier structure

struct irq_fwspec {
	struct fwnode_handle *fwnode;
	int param_count;
	u32 param[IRQ_DOMAIN_IRQ_SPEC_PARAMS];
};
    看下函数形参传递过程:of_phandle_args oirq -> of_phandle_args *irq_data ->irq_fwspec fwspec,通过of_phandle_args_to_fwspec()转化了一下。

    继续irq_create_fwspec_mapping()
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
	struct irq_domain *domain;
	struct irq_data *irq_data;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	int virq;
 
     /* 找到前面初始化的domain */
     = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
    	
     irq_domain_translate(domain, fwspec, &hwirq, &type))	
     //gic_irq_domain_translate()跳过SGI和PPI,找到SPI
        d->ops->translate(d, fwspec, hwirq, type); 
        
     // 
     = irq_find_mapping(domain, hwirq); 
        
     virq = irq_create_mapping(domain, hwirq); //创建映射

	irq_data = irq_get_irq_data(virq);

	irqd_set_trigger_type(irq_data, type);
	return virq;
}
    知识点:types
#define IRQ_TYPE_NONE		0
#define IRQ_TYPE_EDGE_RISING 1
#define IRQ_TYPE_EDGE_FALLING 2
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH 4
#define IRQ_TYPE_LEVEL_LOW 8

    还是看下irq_create_mapping(),最终还是调用的irq_domain_associate()。
/**
 * irq_create_mapping() - Map a hardware interrupt into linux irq space
 */
unsigned int irq_create_mapping(struct irq_domain *domain,
				irq_hw_number_t hwirq)
{
	struct device_node *of_node;
	int virq;

	of_node = irq_domain_get_of_node(domain);

	/* Allocate a virtual interrupt number */
	virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);

	irq_domain_associate(domain, virq, hwirq)
            --->domain->ops->map(domain, virq, hwirq); //gic_irq_domain_map()
            --->domain->linear_revmap[hwirq] = virq;
	return virq;
}
    说实话还是有点模糊,比如6ull参考手册里面的72是IRQ(virq)吗?哪对应的hwirq是多少?72-32 = 40?

    还得回到gic_of_init()里面还有一个函数没有分析到,gic_cascade_irq(gic_cnt, irq),看下函数定义:
void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq)
{
	BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
	irq_set_chained_handler_and_data(irq, gic_handle_cascade_irq,
					 &gic_data[gic_nr]);
}

gic_handle_cascade_irq()
--->generic_handle_irq(cascade_irq)
    --->generic_handle_irq_desc(desc)
        --->desc->handle_irq(desc)
    分析下来就是执行每个desc的handle_irq函数,前面知道了对应SPIs统一设置为handle_fasteoi_irq()。


做个小结:
1、对root GIC和gics进行初始化,为它们分别注册一个domain,如GIC_domain、GPC_domain、GPIOs_domain;
2、每个domain都有hwirq和virq,通过hwirq计算出virq申请descs,映射它们的关系并存放在linear_revmap[]中;
3、通过map callback函数指定hwirq>32的virq的desc->handle-irq->handle_fasteoi_irq(),指定action->handler;
4、对于root GIC来说它的hwirq来源是GPC、GPIOs等gics。
5、众多的gics有双重身份,一是做为作为独立的gic,对应有自己专属的domain,管理自己的gic_hwirq和gic_virq;二是作为连接到root GIC上的GIC_hwirq(对应有GIC_virq,有handler)。

    摘录:我们假设场景如下:设备D连接到secondary gic的125号硬件中断上,而secondary gic连接到root GIC的75号硬件中断上。当设备D产生中断的时候,CPU首先看到的是root gic上的75号中断(在gic_handle_irq函数中处理),然后才进入secondary gic的irq doamin(在gic_handle_cascade_irq函数中处理)。

其实还是有点模糊,有几个问题困扰:
    问题:在初始化root GIC时,75号hwirq是怎么映射出virq的?在初始化secondary gic(GPIO4)时,125号hwirq是怎么映射出virq的?在irq_of_parse_and_map函数执行过程中,125号hwirq是怎么映射出virq的不需要操作了吧?只是指定一个handle处理函数就行了吧???

    问题:hwriq和virq是针对domain来讲的,比如GPIO4的domain中的hwirq都有几个?是0-31的pins对应32个hwirq吗?virq呢?0-15对应72virq,16-31对应73virq吗?假如是的话,72和73到了GPC的domain是不是就变成该domain的hwirq了呢?

    问题:desc是给virq(IRQ)分配的,不是给hwirq分配的!这个IRQ是对应参考手册中的0~128个IRQ吗?

    其实可以完成不用关注hwirq,驱动的视角和CPU一样,只关心virq,通过解析device_node的interrupts属性match到domain,xlate出hwirq和type,通过maping出virq,找到virq的desc,进而找到->handle。

    这个就到此结束!不要再纠结下去了。开启新的篇章!当发生中断时是怎么处理的?还有一个模块是中断服务程序的注册,中断程序的处理。

    内核在启动的时候创建了庞大的hwirq和virq映射关系,指定了各个gics的handle_irq,下面分析下当中断发生时,内核是怎么应用这庞大的体系去处理的!

    脑壳疼……





1 条评论&回复

请先 登录 后评论
zxq
zxq

11 篇文章

作家榜 »

  1. 百问网-周老师 19 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. Infinite 5 文章
  7. 谢工 5 文章
  8. Litchi_Zheng 5 文章