Linux中断子系统之IPI核间中断
一、IPI概况
在SMP系统中,IPI用于传递核间事件「inter-core event」。为了使用硬件体系架构相关的特殊功能,要借助中断控制器提供的功能使IPI从一个核传递给另外其他核。因此,在中断控制器驱动初始化时将调用set_smp_cross_call()函数去设定触发IPI的回调函数。IPI不仅需要能够被一个核或多个核捕获,而且还需要能够被中断处理函数辨识出。在使用GIC中断控制器的系统中,小于等于15的硬中断号「hwirq」都属于软件生成中断「SGI,Software Generated Interrupt」。在Linux Kernel操作系统中,默认定义了8中IPI中断
enum ipi_msg_type {
IPI_WAKEUP, /* 唤醒其他CPU */
IPI_TIMER, /* 调用tick时钟设备「tick-clock device」的事件处理程序 */
IPI_RESCHEDULE, /* 唤醒挂起的任务 */
IPI_CALL_FUNC, /* 让其他CPU执行特定的函数 */
IPI_CPU_STOP, /* 暂停其他CPU */
IPI_IRQ_WORK, /* 执行当前CPU的irq_work */
IPI_COMPLETION,
NR_IPI,
IPI_CPU_BACKTRACE = NR_IPI,
MAX_IPI
};
二、IPI处理函数的注册及调用
当ARM core收到IRQ后,会触发cpu的irq异常,会跳转到linux kernel的irq异常向量表,在该向量表中,会调用gicv3的gic_handle_irq函数。在Linux Kernel5.0之前,在gic_handle_irq()的gic处理函数中,会判断硬件中断号,中断小于16的属于SGI中断,单独走handle_IPI()函数,用于处理核间中断。5.0之后不在区分,统一在handle_domain_irq()中处理(参见中断机制)。
在Linux Kernel 5.0为硬件中断号0-7的每个中断号都注册了一个中断处理函数,其指向ipi_handler()函数。
void __init set_smp_ipi_range(int ipi_base, int n)
{
int i;
WARN_ON(n < MAX_IPI);
nr_ipi = min(n, MAX_IPI);
for (i = 0; i < nr_ipi; i++) {
int err;
err = request_percpu_irq(ipi_base + i, ipi_handler,
"IPI", &irq_stat);
WARN_ON(err);
ipi_desc[i] = irq_to_desc(ipi_base + i);
irq_set_status_flags(ipi_base + i, IRQ_HIDDEN);
}
ipi_irq_base = ipi_base;
/* Setup the boot CPU immediately */
ipi_setup(smp_processor_id());
}
在触发中断异常后,它和一般的request_irq注册的中断没有什么不同,都是从异常向量表调过来,然后调用到这个ipi_handler()程序,该程序再调用do_handle_IPI()真正处理核间通信的任务。
static void do_handle_IPI(int ipinr)
{
unsigned int cpu = smp_processor_id();
if ((unsigned)ipinr < NR_IPI)
trace_ipi_entry_rcuidle(ipi_types[ipinr]);
switch (ipinr) {
case IPI_RESCHEDULE:
scheduler_ipi();
break;
case IPI_CALL_FUNC:
generic_smp_call_function_interrupt();
break;
case IPI_CPU_STOP:
local_cpu_stop();
break;
case IPI_CPU_CRASH_STOP:
if (IS_ENABLED(CONFIG_KEXEC_CORE)) {
ipi_cpu_crash_stop(cpu, get_irq_regs());
unreachable();
}
break;
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
case IPI_TIMER:
tick_receive_broadcast();
break;
#endif
#ifdef CONFIG_IRQ_WORK
case IPI_IRQ_WORK:
irq_work_run();
break;
#endif
#ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL
case IPI_WAKEUP:
WARN_ONCE(!acpi_parking_protocol_valid(cpu),
"CPU%u: Wake-up IPI outside the ACPI parking protocol\n",
cpu);
break;
#endif
default:
pr_crit("CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
break;
}
if ((unsigned)ipinr < NR_IPI)
trace_ipi_exit_rcuidle(ipi_types[ipinr]);
}
三、触发核间中断
核间中断的相关的API:
1)smp_call_function(func, info, wait) //在所有其它处理器执行一个函数;
2)smp_call_function_single(int cpuid, smp_call_func_t func, void *info,int wait) //在指定处理器执行一个函数;
3)smp_send_reschedule(int cpu) //指定处理器重新调度进程。
SMP(IPI)的这类函数,最终都是调用到gic_send_sgi()
,给core发送要给SGI中断
static void gic_send_sgi(u64 cluster_id, u16 tlist, unsigned int irq)
{
u64 val;
val = (MPIDR_TO_SGI_AFFINITY(cluster_id, 3) |
MPIDR_TO_SGI_AFFINITY(cluster_id, 2) |
irq << ICC_SGI1R_SGI_ID_SHIFT |
MPIDR_TO_SGI_AFFINITY(cluster_id, 1) |
MPIDR_TO_SGI_RS(cluster_id) |
tlist << ICC_SGI1R_TARGET_LIST_SHIFT);
pr_devel("CPU%d: ICC_SGI1R_EL1 %llx\n", smp_processor_id(), val);
gic_write_sgi1r(val);
}
static inline void gic_write_sgi1r(u64 val)
{
write_sysreg_s(val, SYS_ICC_SGI1R_EL1); //寄存器
}