卷一 内核源代码分析 第二章 异常 2.2.1 cortex A9多核处理器的中断控制器GIC---2.2.2 Mt6577的中断体系 图书试读版-请勿转载


作者 [email protected]


2.2.1cortex A9多核处理器的中断控制器GIC

对于多核处理器来说,中断有着新的使命---处理器间通信,在CA9以前,每种SOC的中断控制器是自己实现的,但是到了CA9 SMP以后,中断控制器成为了ARM规范的一部分,各家的处理器都遵循arm 中断控制器GIC规范:《IHI0048A_gic_architecture_spec》。其中原因在于,对于非SMP的架构,中断控制器就是控制中断的功能,完成中断的记录、ack、屏蔽等功能即可,各家厂商爱怎么玩就怎么玩。但是到了SMP结构下,除了ack、记录、屏蔽之外,中断控制器还面临如下问题:
中断分发给哪颗处理器
处理器间通信如何实现--处理器通信本质上就是一种中断
处理器局部中断


ARM提供的中断控制器GIC,就是解决以上问题,根据GIC规范,中断安排如下。参见《IHI0048A_gic_architecture_spec》的“Figure 2-1 GIC logical partitioning into Distributor and CPU interfaces”


32-1019 SPI   全局中断,可指定分发到不同的处理器
16-31 PPP    处理器局部中断,处理器局部可见
0-15 SGI     软件产生中断,由cpu写ICDSGIR产生,可指定分发到不同的处理器


而GIC本身分成两份
GIC的DIST部分,这部分是所有CPU公用的
每个CPU自己CPU interface部分, 这部分控制寄存器地址,只能被CPU局部可见,ARM推荐各SOC厂商将该地址实现为相同位置。


以上概念在GIC描述结构,struct gic_chip_data得到体现: 


struct gic_chip_data {
unsigned int irq_offset;   //中断号的偏移量
void __percpu __iomem **dist_base; //记录每颗CPU访问GIC Dist的基地址
void __percpu __iomem **cpu_base; //记录每颗CPU访问GIC CPU interface的基地址,每颗处理器看到同样的地方,但是访问的却是不同的寄存器
    …
unsigned int gic_irqs;
};






2.2.2 Mt6577的中断体系


MTK与Android是绝配。在智能机时代, MTK尽管开始走了段弯路,但是很快拨乱反正。MTK的智能机时代策略,与其功能机时代如出一辙,这与其市场定位有关。
MTK手机处理器的技术路线,不追求最快,但一定会在市场需要的准时出现;不追求最强,但一定能流畅运行最新版本的Android系统与应用。
MTK的公板,从器件选型、电路板设计到Android系统实现,MTK亲力亲为几乎做好了所有技术上的工作。MTK的参考设计总是能做到与大陆手机产业链完美匹配,着力支持手机供应链中最常用、供货最有保障器件和外设,而同一种功能器件和外设,进行多家验证,以确保整机商供应链的安全。由此,良性循环,华南的IC、模具、LCD、SMT等产业链以MTK马首是瞻,总是自觉的将自己产品与MTK公板的兼容性作为其重要工作。


  本节分析MT6577的中断体系。Mt6577的Datasheet不是个公开的文档,况且该文档里也并没有清晰的阐述其中断体系的结构。 但是通过U8836d公开的代码却可以完整的分析出mt6577的中断架构。Mt6577的中断体系的最高层依然遵循GIC规范,可参见ARM公司release相关技术文件。Mt6577中的实现可参见u8836d公开的代码中的文件:
//“U8836D\mediatek\platform\mt6577\kernel\core\include\mach\mt6577_irq.c” 


//16个软件中断
#define NR_GIC_SGI              16
//16个处理器局部中断,在mt6577其实只使用了5个,从第27号开始
#define NR_GIC_PPI              16
//128个全局中断
#define MT6577_NR_SPI           (128)
#define NR_MT6577_IRQ_LINE      (NR_GIC_SGI + NR_GIC_PPI + MT6577_NR_SPI) 
//全局中断从第32号开始
#define GIC_PRIVATE_SIGNALS     32 
//处理器局部中断的起始号
#define GIC_PPI_OFFSET          (27)
//如下定义了5个处理器局部中断
#define GIC_PPI_GLOBAL_TIMER    (GIC_PPI_OFFSET + 0)
#define GIC_PPI_LEGACY_FIQ      (GIC_PPI_OFFSET + 1)
#define GIC_PPI_PRIVATE_TIMER   (GIC_PPI_OFFSET + 2)
#define GIC_PPI_WATCHDOG_TIMER  (GIC_PPI_OFFSET + 3)
#define GIC_PPI_LEGACY_IRQ      (GIC_PPI_OFFSET + 4)


然后从32号中断开始定义了104个全局中断
#define MT6577_L2CCINTR_IRQ_ID              (GIC_PRIVATE_SIGNALS + 0)

#define MT6577_USB0_IRQ_ID                  (GIC_PRIVATE_SIGNALS + 8)
#define MT6577_USB1_IRQ_ID                  (GIC_PRIVATE_SIGNALS + 9)

#define MT6577_EINT_IRQ_ID                  (GIC_PRIVATE_SIGNALS + 96)

#define MT6577_EINT_DIRECT7_IRQ_ID          (GIC_PRIVATE_SIGNALS + 104)


以上中断体系的实现是通过GIC的配置来完成,而GIC的配置在初始化过程中完成。GIC初始化包括DIST的初始化,和每颗CPU自己interface的初始化。DIST初始化由CPU0完成,然后CPU0再完成自己备份的interface初始化。在CPU1起来之后再完成CPU1的相关interface初始化。


CPU0的工作:


void __init mt_init_irq(void)
{   …
//DIST的初始化
mt_gic_dist_init();
//CPU0的interface初始化
    mt_gic_cpu_init();
}


static void mt_gic_dist_init(void)
{
    unsigned int i;
u32 cpumask = 1 << smp_processor_id();
    cpumask |= cpumask << 8;
    cpumask |= cpumask << 16;


    writel(0, GIC_DIST_BASE + GIC_DIST_CTRL);


    /*
     从32号开始, 设置SPI中断电平触发,N-N模式。GIC从GIC_DIST_CONFIG --0xC00开始是中断分发寄存器ICDICFR,每个SPI对应2位。
     */
    for (i = 32; i < (MT6577_NR_SPI + 32); i += 16) {
        writel(0, GIC_DIST_BASE + GIC_DIST_CONFIG + i * 4 / 16);
    }


    /*
      所有SPI分发给CPU0。这部代码只有CPU0才能执行到。GIC从GIC_DIST_TARGET --0x800开始是中断分发寄存器,长度根据支持的SPI不同而不同,每个中断对应8位,哪一位置1,该中断发到哪颗处理器。Cpumask每8位的最低位都置1,所以从32号中断开始,SPI都分发CPU0。
     */
    for (i = 32; i < (MT6577_NR_SPI + 32); i += 4) {
        writel(cpumask, GIC_DIST_BASE + GIC_DIST_TARGET + i * 4 / 4);
    }


    /*
GIC从GIC_DIST_PRI --0x400开始是中断优先级寄存器,长度根据支持的SPI不同而不同,每个中断对应8位,这里把所有SPI优先级都设置为相同优先级。
*/
    for (i = 32; i < NR_MT6577_IRQ_LINE; i += 4) {
        writel(0xA0A0A0A0, GIC_DIST_BASE + GIC_DIST_PRI + i * 4 / 4);
    }


    /*
    GIC从GIC_DIST_ENABLE_CLEAR --0x180开始是中断屏蔽寄存器ICDICER,长度根据支持的SPI不同而不同,每个中断对应1位,置1屏蔽该中断。
    */
    for (i = 32; i < NR_MT6577_IRQ_LINE; i += 32) {
        writel(0xFFFFFFFF, GIC_DIST_BASE + GIC_DIST_ENABLE_CLEAR + i * 4 / 32); 
    }
    /*
     把挂到linux中断描述数组里去,每个中断一个“struct irq_desc”,以前是个数组,现在可选成radix树。每个中断一个“struct irq_desc”,记录两个中断处理的关键数据结构“struct irq_chip”和 irq_flow_handler_t。前者用来对付中断控制器,在mt6577上是“struct irq_chip mt_irq_chip”,后者是中断处理程序,分成“void handle_level_irq(unsigned int irq, struct irq_desc *desc)”—用来对付电平触发,和“void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)”—用来对付边沿触发。
     */
    for (i = GIC_PPI_OFFSET; i < NR_MT6577_IRQ_LINE; i++) {
        irq_set_chip_and_handler(i, &mt_irq_chip, handle_level_irq);
        set_irq_flags(i, IRQF_VALID | IRQF_PROBE);
    }
#ifdef CONFIG_FIQ_DEBUGGER
    irq_set_chip_and_handler(FIQ_DBG_SGI, &mt_irq_chip, handle_level_irq);
    set_irq_flags(FIQ_DBG_SGI, IRQF_VALID | IRQF_PROBE);
#endif


    /*
     GIC从GIC_ICDISR --0x80开始是中断屏蔽寄存器ICDISR,长度根据支持的SPI不同而不同,每个中断对应1位,置0表示该中断是安全中断,置1表示该中断是非安全中断,这里全置1
     */
    for (i = 32; i < NR_IRQS; i += 32)
    {
        writel(0xFFFFFFFF, GIC_ICDISR + 4 * (i / 32));
    }


    /*
     ICDDCR位于0x,中断总开关,打开
     */
    writel(3, GIC_DIST_BASE + GIC_DIST_CTRL);
}


//该函数在每颗CPU被激活时得到执行
static void mt_gic_cpu_init(void)
{
    int i;


    /*
     关闭PPI,打开SGI。PPI需要用到时调用“void mt_enable_ppi(int irq)”打开,在u8836d系统里只有local timer是PPI中断在其初始化时将自己对应使能位打开,SGI必须全打开,SGI用来做处理器间中断的基础。
     */
    writel(0xffff0000, GIC_DIST_BASE + GIC_DIST_ENABLE_CLEAR);
    writel(0x0000ffff, GIC_DIST_BASE + GIC_DIST_ENABLE_SET);


    /* 设置PPI SGI中断优先级为0x80*/
    for (i = 0; i < 32; i += 4)
        writel(0x80808080, GIC_DIST_BASE + GIC_DIST_PRI + i * 4 / 4);


    /*设置PPI SGI中断优先为非secure中断*/
    writel(0xFFFFFFFF, GIC_ICDISR);
   
/*操作自己的CPU INTERFACE的ICCPMR寄存器,只有优先级高于0xF0的中断才会送入本处理器*/
    writel(0xF0, GIC_CPU_BASE + GIC_CPU_PRIMASK);


/*操作自己的CPU INTERFACE的ICCICR寄存器,做如下设置:
允许向本处理器送secure中断
允许向本处理器送非secure中断
把secure中断送到本处理器的FIQ中断线
中断抢占通过secure binary point register判断
一个ICCIAR的secure读,导致非secure最高优先级pending中断的ack
/


    writel(0x1F, GIC_CPU_BASE + GIC_CPU_CTRL);


    dsb();
}




值得注意的是mt6577全局中断的实现,又分为两种。
对于集成在SOC里的外设,直接对应一个全局中断,如“MT6577_USB1_IRQ_ID”,在其驱动初始时直接将其中断处理函数注册到内核的中断处理里数组中:“request_irq(nIrq, musbfsh->isr, IRQF_TRIGGER_LOW, dev_name(dev), musbfsh)”
而对于位于SCO之外的外设,则通过MT6577_EINT_IRQ_ID 进行转发:
Mt6577在内核里准备了一个外设中断处理函数的数组:
typedef struct 
{
    void (*eint_func[EINT_MAX_CHANNEL])(void);
    unsigned int eint_auto_umask[EINT_MAX_CHANNEL];
} eint_func;


这些外设通过“void mt65xx_eint_registration(…)”注册自己的中断函数。
比如触屏控制器gt813,“mediatek\custom\out\huaqin77_cu_ics2\kernel\touchpanel\ Gt813_driver.c”通过该函数注册自己的中断处理函数:  
mt65xx_eint_registration(…, tpd_eint_interrupt_handler, 1); 


当这些SOC外外设中断发生以后,mt6577将在GIC的MT6577_EINT_IRQ_ID上产生中断,从而触发其中断处理函数“static irqreturn_t mt65xx_eint_isr(int irq, void *dev_id)”。在该函数里将检查“eint_func”数组,进一步触发对应的的处理函数。

你可能感兴趣的:(kernel,驱动开发,linux内核,arm-linux,smp,mt6577)