LINUX-内核-中断分析-中断向量表(2)-mips

时间:2022-10-15 04:22:55

mips中断概念

在《MIPS体系结构透视》的第5章说到,在MIPS中,中断、陷阱、系统调用和任何可以中断程序正常执行流的情况全被都被称为异常。
以上这种统一到“异常”的概念及其逻辑当然会体现在MIPS的异常入口点的设计中,特别如MIPS中断入口点的引出。


MIPS的异常入口点(中断入口点)及异常向量概念的引出

非向量化中断

根据《MIPS体系结构透视》第5章介绍,类似x86这样的CISC处理器根据所发生的异常(用x86的话来说就是同步中断+异步中断)的种类把CPU指向不同的入口,具体到异步中断,也即是根据激活的中断输入信号在不同的入口点处理这些中断,这种做法叫做“向量化中断”,而MIPS则不这样做,理由有3:

  1. CISC需要花费时间指向不同的中断入口,然后操作系统软件再从入口处获得一个序号,再花些时间调到通用中断处理程序。MIPS认为这是一件繁琐的事情;
  2. 即使是用硬件来分析这么多种中断,这个硬件会异常复杂,MIPS认为可以用软件以其他的逻辑,更高的效率来应对这个问题;
  3. 在类似MIPS这样的RISC架构中,CPU比外设快得实在太多。考虑到中断服务程序一般都要和外设打交道,外设相对“慢”的速度导致这种和外设的交互要花费大量的时间,此时,MIPS自己实现的中断入口分配代码的耗时相对而言就不算是什么性能瓶颈了,不需要去用所谓“向量化中断”;
  4. 对于应用于嵌入式系统的RISC体系来说,要面对的外设多种多样,且各种形式的嵌入式硬件的外设都变化多端,如果给每一种可能的外设的中断都像x86那样使用“向量化中断”的话,无法避免会很浪费向量资源。
    LINUX-内核-中断分析-中断向量表(2)-mips
    综合以上理由,除了个别特殊中断外,MIPS将几乎所有的外部中断都看作是同一种异常,同时也就为几乎所有的外部中断都只准备了一个所谓异常入口。而在x86系统中,32~238的中断向量全部供给了外部中断使用。

向量化异常–异常向量

虽然将所有外部中断都看作是同一种异常,但是MIPS体系结构中异常也是分很多种的。因此,虽然外部中断没有被向量化,但是MIPS中还是将异常给向量化了。根据《MIPS体系结构透视》第3章表3-2的介绍,通过Cause寄存器中的Excode值,MIPS定义并可以识别32种异常。MIPS为这32种异常准备了一个所谓异常向量表,这个异常向量表体现到内核代码里面本质上就是一个叫做exception_handlers[32]的数组。该数组的每一项都是一个函数指针,其中0号异常即为所有外部中断的统一入口点。
当异常发生后,位于地址0x80000180处的函数except_vec3_generic()会根据Cause寄存器中的ExcCode位的值,调用exception_handlers[32]中相应成员的那个函数指针执行相关的处理函数。这个 exception_handlers[32]是traps.c下面定义的一个全局变量。

---------------------------------- arch\mips\kernel\traps.c
unsigned long exception_handlers[32];

LINUX-内核-中断分析-中断向量表(2)-mips


MIPS体系异常向量表的初始化

虽然中断没有被向量化,但是异常被向量化了,所以MIPS体系结构依然存在一个对异常向量表的初始化,而且同样是在trap_init()中进行。
在中我们说到x86的trap_init()本质上是在给idt_table赋值,而MIPS体系中不存在门的概念,取而代之的是上一节提到的exception_handlers[]数组。
在x86的trap_init()中调用了一系列的set_intr_gate()、set_system_intr_gate()、set_task_gate()函数来设置各种门。而在MIPS的trap_init()中则是全部使用set_except_vector(int n, void *addr)函数来为32个数组成员赋值。而赋值的方式就是直接给exception_handlers[]这个全局变量的各个成员赋值—-实在是够直接!!!


初始化大蓝图

LINUX-内核-中断分析-中断向量表(2)-mips

trap_init()函数设置异常处理函数

这个trap_init()本身一直都是一个体系相关函数,在MIPS体系中,该函数有两个主要工作:

  1. 将统一异常处理入口函数excep_vec3_generic()加载到地址BASE+0X180;
  2. 为exception_handlers[]中的32种异常设置各自的处理函数;

early_irq_init()函数对IRQ子系统的前期初始化

函数本身是体系平台无关的,主要任务也是为所有irq_desc设置默认的irq_chip为no_irq_chip,默认的电源处理函数为handle_bad_irq()等,这些内容的代码各个体系都是一样的。

------kernel/irq/irqdesc.c------->
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
        struct module *owner)
{
    int cpu;

    desc->irq_data.irq = irq;
    desc->irq_data.chip = &no_irq_chip;
    desc->irq_data.chip_data = NULL;
    desc->irq_data.handler_data = NULL;
    desc->irq_data.msi_desc = NULL;
    irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
    irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
    desc->handle_irq = handle_bad_irq;
    desc->depth = 1;
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;
    desc->name = NULL;
    desc->owner = owner;
    for_each_possible_cpu(cpu)
        *per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
    desc_smp_init(desc, node);
}

第二步就与硬件平台相关了,调用arch_early_irq_init()进行一些平台相关的irq系统前期初始化。在x86体系中,arch_early_irq_init()主要是处理的8259和IOAPC的差异相关,这导致对前16个固定IRQ的处理会有些区别,但是这个函数基本上仅用于x86和ppc体系,包括MIPS在内的其他体系基本上arch_early_irq_init()函数就直接返回了,干脆直接取名叫做x86_arch_early_irq_init()。
LINUX-内核-中断分析-中断向量表(2)-mips

init_IRQ()对外设中断进行初始化

init_IRQ()是一个体系相关函数,在MIPS中,除了将所有的NR_IRQS个irq_desc中设置noprobe外,就是调用一个MIPS专用的arch_init_irq()函数,这个函数是体系且平台相关的,里面主要是设置一堆IRQ的irq_chip和电流处理函数,在arch_init_irq()内部,仅有mips_cpu_irq_init()是仅体系相关(体系相关但平台无关)。
LINUX-内核-中断分析-中断向量表(2)-mips
下面以QCA的WIFI芯片AR955X为例进行简要说明。

mips_cpu_irq_init()函数固定设置IP2~IP8这6个外部中断引脚的IRQ

mips_cpu_irq_init()函数基本上在所有的MIPS体系中都应该是一样的,与不同厂家不同平台无关,这从该函数位于文件arch/mips/kernel/irq_cpu.c中这一点也可以得到证实。从代码来看,该函数一股脑地将IP2~IP8这6个外部中断的irq_chip都设置为叫做“MIPS”的mips_cpu_irq_controller型中断控制器。

ath_misc_irq_init(ATH_MISC_IRQ_BASE)在AR955X中设置MISC相关IRQ

ath_misc_irq_init(ATH_MISC_IRQ_BASE) 在AR955X中设置从IRQ16开始的MISC相关的27个IRQ。将这27个IRQ的irq_chip都设置为叫做“ATH MISC”的ath_misc_irq_controller型中断控制器,将电流处理函数设置为handle_percpu_irq()。

ath_gpio_irq_init(ATH_GPIO_IRQ_BASE)在AR955X中设置GPIO相关IRQ

ath_gpio_irq_init(ATH_GPIO_IRQ_BASE)在AR955X中负责设置从43开始的GPIO相关的32个IRQ。将这32个IRQ的irq_chip都设置为叫做“ATH GPIO”的ath_gpio_intr_controller型中断控制器,将电流处理函数设置为handle_percpu_irq()。

ath_arch_init_irq()在AR955X中负责无线外界芯片的IRQ

955x中5G无线外接芯片使用IRQ2号,因此事实上该函数覆盖了mips_cpu_irq_init()对IRQ2的设置,对IRQ2进行了重新设置,将IRQ2的irq_chip设置为叫做”dummy”的dummy_irq_chip型中断控制器,将电流处理函数设置为handle_percpu_irq()。