从arm手册可以看出,异常向量表在EL1~EL3都有,本文只涉及Linux kernel中的实现,即只涉及EL1和EL0。
参考Wiki:
optee中的异常向量表解读–中断处理解读
参考Wiki:
armv8/armv9中断系列详解-学这篇就够了
kernel版本:v6.9-rc6
(arch/arm64/kernel/head.S)
SYM_FUNC_START_LOCAL(__secondary_switched)
...
/* 向量表起始地址放到EL1向量表基址寄存器中 */
adr_l x5, vectors
msr vbar_el1, x5
isb
...
SYM_FUNC_END(__secondary_switched)
向量表入口:
(arch/arm64/kernel/entry.S)
/*
* Exception vectors.
*/
.pushsection ".entry.text", "ax"
.align 11
SYM_CODE_START(vectors)
kernel_ventry 1, t, 64, sync // Synchronous EL1t
kernel_ventry 1, t, 64, irq // IRQ EL1t
kernel_ventry 1, t, 64, fiq // FIQ EL1t
kernel_ventry 1, t, 64, error // Error EL1t
kernel_ventry 1, h, 64, sync // Synchronous EL1h
kernel_ventry 1, h, 64, irq // IRQ EL1h
kernel_ventry 1, h, 64, fiq // FIQ EL1h
kernel_ventry 1, h, 64, error // Error EL1h
kernel_ventry 0, t, 64, sync // Synchronous 64-bit EL0
kernel_ventry 0, t, 64, irq // IRQ 64-bit EL0
kernel_ventry 0, t, 64, fiq // FIQ 64-bit EL0
kernel_ventry 0, t, 64, error // Error 64-bit EL0
kernel_ventry 0, t, 32, sync // Synchronous 32-bit EL0
kernel_ventry 0, t, 32, irq // IRQ 32-bit EL0
kernel_ventry 0, t, 32, fiq // FIQ 32-bit EL0
kernel_ventry 0, t, 32, error // Error 32-bit EL0
SYM_CODE_END(vectors)
遗留问题:向量表中的t/h代表啥意思呢?
查找资料,有如下解释:
处理器模式为t(指Task模式,即用户态模式)
处理器模式为h(指Handler模式,即内核态模式)
扩展:
向量表在kernel镜像中的链接位置
(include/asm-generic/vmlinux.lds.h)
#define ENTRY_TEXT \
ALIGN_FUNCTION(); \
__entry_text_start = .; \
*(.entry.text) \
__entry_text_end = .;
(arch/arm64/kernel/vmlinux.lds.S)
SECTIONS
{
...
.text : ALIGN(SEGMENT_ALIGN) { /* Real text segment */
_stext = .; /* Text and read-only data */
IRQENTRY_TEXT
SOFTIRQENTRY_TEXT
ENTRY_TEXT
TEXT_TEXT
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
HYPERVISOR_TEXT
*(.gnu.warning)
}
. = ALIGN(SEGMENT_ALIGN);
_etext = .;
...
}
向量表分析
(arch/arm64/kernel/entry.S)
.macro kernel_ventry, el:req, ht:req, regsize:req, label:req
.align 7
...
b el\el\ht\()_\regsize\()_\label
...
.endm
(arch/arm64/kernel/entry.S)
.macro entry_handler el:req, ht:req, regsize:req, label:req
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
kernel_entry \el, \regsize
mov x0, sp
bl el\el\ht\()_\regsize\()_\label\()_handler
.if \el == 0
b ret_to_user
.else
b ret_to_kernel
.endif
SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
.endm
上述汇编代码片段中,汇编宏 kernel_ventry的参数结合el\el\ht()\regsize()\label()_handler函数对应如下不同异常的入口:
(arch/arm64/include/asm/exception.h)
asmlinkage void el1t_64_sync_handler(struct pt_regs *regs);
asmlinkage void el1t_64_irq_handler(struct pt_regs *regs);
asmlinkage void el1t_64_fiq_handler(struct pt_regs *regs);
asmlinkage void el1t_64_error_handler(struct pt_regs *regs);
asmlinkage void el1h_64_sync_handler(struct pt_regs *regs);
asmlinkage void el1h_64_irq_handler(struct pt_regs *regs);
asmlinkage void el1h_64_fiq_handler(struct pt_regs *regs);
asmlinkage void el1h_64_error_handler(struct pt_regs *regs);
asmlinkage void el0t_64_sync_handler(struct pt_regs *regs);
asmlinkage void el0t_64_irq_handler(struct pt_regs *regs);
asmlinkage void el0t_64_fiq_handler(struct pt_regs *regs);
asmlinkage void el0t_64_error_handler(struct pt_regs *regs);
asmlinkage void el0t_32_sync_handler(struct pt_regs *regs);
asmlinkage void el0t_32_irq_handler(struct pt_regs *regs);
asmlinkage void el0t_32_fiq_handler(struct pt_regs *regs);
asmlinkage void el0t_32_error_handler(struct pt_regs *regs);
kernel中异常向量表的具体实现
(arch/arm64/kernel/entry-common.c)
UNHANDLED(el1t, 64, sync)
UNHANDLED(el1t, 64, irq)
UNHANDLED(el1t, 64, fiq)
UNHANDLED(el1t, 64, error)
#define UNHANDLED(el, regsize, vector) \
asmlinkage void noinstr el##_##regsize##_##vector##_handler(struct pt_regs *regs) \
{ \
const char *desc = #regsize "-bit " #el " " #vector; \
__panic_unhandled(regs, desc, read_sysreg(esr_el1)); \
}
先把以el1h_64_irq_handler/el1h_64_fiq_handler为例,看下实现
(arch/arm64/kernel/entry-common.c)
asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
{
el1_interrupt(regs, handle_arch_irq);
}
asmlinkage void noinstr el1h_64_fiq_handler(struct pt_regs *regs)
{
el1_interrupt(regs, handle_arch_fiq);
}
static void noinstr el1_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
...
__el1_irq(regs, handler);
}
static __always_inline void __el1_irq(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
enter_from_kernel_mode(regs);
irq_enter_rcu();
do_interrupt_handler(regs, handler);
irq_exit_rcu();
arm64_preempt_schedule_irq();
exit_to_kernel_mode(regs);
}
static void do_interrupt_handler(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
struct pt_regs *old_regs = set_irq_regs(regs);
if (on_thread_stack())
call_on_irq_stack(regs, handler);
else
handler(regs); // 非线程化中断处理方式
set_irq_regs(old_regs);
}
(arch/arm64/kernel/irq.c)
static void default_handle_irq(struct pt_regs *regs)
{
panic("IRQ taken without a root IRQ handler\n");
}
static void default_handle_fiq(struct pt_regs *regs)
{
panic("FIQ taken without a root FIQ handler\n");
}
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init = default_handle_irq;
void (*handle_arch_fiq)(struct pt_regs *) __ro_after_init = default_handle_fiq;
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq != default_handle_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
pr_info("Root IRQ handler: %ps\n", handle_irq);
return 0;
}
int __init set_handle_fiq(void (*handle_fiq)(struct pt_regs *))
{
if (handle_arch_fiq != default_handle_fiq)
return -EBUSY;
handle_arch_fiq = handle_fiq;
pr_info("Root FIQ handler: %ps\n", handle_fiq);
return 0;
}
(drivers/irqchip/irq-gic-v3.c)
static int __init gic_init_bases(phys_addr_t dist_phys_base,
void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
...
set_handle_irq(gic_handle_irq);
...
}
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
...
err = gic_init_bases(dist_phys_base, dist_base, rdist_regs,
nr_redist_regions, redist_stride, &node->fwnode);
...
}
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))
__gic_handle_irq_from_irqsoff(regs);
else
__gic_handle_irq_from_irqson(regs);
}
static void __gic_handle_irq_from_irqson(struct pt_regs *regs)
{
...
__gic_handle_irq(irqnr, regs);
}
static void __gic_handle_irq(u32 irqnr, struct pt_regs *regs)
{
...
if (generic_handle_domain_irq(gic_data.domain, irqnr)) { // generic_handle_domain_irq接口往下调用是gic的处理流程,此处就不展开了
WARN_ONCE(true, "Unexpected interrupt (irqnr %u)\n", irqnr);
gic_deactivate_unhandled(irqnr);
}
}
()
()
()
参考Wiki:
armv8/armv9中断系列详解-软件篇-Linux kernel中断相关软件导读
补充:
在早期版本中,向量表式这样的,基本大同小异。
从表中可以看出来,Linux kernel里主要实现了EL1/EL0的同步异常和中断;
(CONFIG_COMPAT 是 Linux 内核中一个配置选项的宏,用于控制兼容性相关的特性。当启用了 CONFIG_COMPAT 宏时,Linux 内核会支持在 64 位系统上运行 32 位的用户空间程序。这样做的目的是为了确保新版本的 Linux 内核仍然能够运行旧版本的 32 位用户空间程序,以提供更好的兼容性。)
参考资料:
《linux内核深度解析_余华兵》4.1.3 异常向量表