在《MIPS体系结构透视》的第5章说到,在MIPS中,中断、陷阱、系统调用和任何可以中断程序正常执行流的情况全被都被称为异常。
以上这种统一到“异常”的概念及其逻辑当然会体现在MIPS的异常入口点的设计中,特别如MIPS中断入口点的引出。
根据《MIPS体系结构透视》第5章介绍,类似x86这样的CISC处理器根据所发生的异常(用x86的话来说就是同步中断+异步中断)的种类把CPU指向不同的入口,具体到异步中断,也即是根据激活的中断输入信号在不同的入口点处理这些中断,这种做法叫做“向量化中断”,而MIPS则不这样做,理由有3:
虽然将所有外部中断都看作是同一种异常,但是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];
虽然中断没有被向量化,但是异常被向量化了,所以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[]这个全局变量的各个成员赋值—-实在是够直接!!!
这个trap_init()本身一直都是一个体系相关函数,在MIPS体系中,该函数有两个主要工作:
函数本身是体系平台无关的,主要任务也是为所有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()。
init_IRQ()是一个体系相关函数,在MIPS中,除了将所有的NR_IRQS个irq_desc中设置noprobe外,就是调用一个MIPS专用的arch_init_irq()函数,这个函数是体系且平台相关的,里面主要是设置一堆IRQ的irq_chip和电流处理函数,在arch_init_irq()内部,仅有mips_cpu_irq_init()是仅体系相关(体系相关但平台无关)。
下面以QCA的WIFI芯片AR955X为例进行简要说明。
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中设置从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中负责设置从43开始的GPIO相关的32个IRQ。将这32个IRQ的irq_chip都设置为叫做“ATH GPIO”的ath_gpio_intr_controller型中断控制器,将电流处理函数设置为handle_percpu_irq()。
955x中5G无线外接芯片使用IRQ2号,因此事实上该函数覆盖了mips_cpu_irq_init()对IRQ2的设置,对IRQ2进行了重新设置,将IRQ2的irq_chip设置为叫做”dummy”的dummy_irq_chip型中断控制器,将电流处理函数设置为handle_percpu_irq()。