点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
3.4.1.2 IPIPE对Linux中断号的改造
在IPIPE domain中,IPIPE_NR_IRQS代表中断总数量,在代码中经常用到,最具代表的就是下图中定义struct ipipe_irqdesc irqs[IPIPE_NR_IRQS].
先列一下宏定义的位置,然后分析一下。
arch/arm64/include/asm/ipipe_base.h:
#define IPIPE_NR_ROOT_IRQS 1024
#define IPIPE_NR_XIRQS IPIPE_NR_ROOT_IRQS
include/linux/ipipe_domain.h:
#define __bpl_up(x) (((x)+(BITS_PER_LONG-1)) & ~(BITS_PER_LONG-1))
/* Number of virtual IRQs (must be a multiple of BITS_PER_LONG) */
#define IPIPE_NR_VIRQS BITS_PER_LONG
/* First virtual IRQ # (must be aligned on BITS_PER_LONG) */
#define IPIPE_VIRQ_BASE __bpl_up(IPIPE_NR_XIRQS)
/* Total number of IRQ slots */
#define IPIPE_NR_IRQS (IPIPE_VIRQ_BASE+IPIPE_NR_VIRQS)
IPIPE_NR_IRQS由两部分构成,分别分析一下。
第一部分IPIPE_VIRQ_BASE,等同于__bpl_up(IPIPE_NR_XIRQS),即1024。
__bpl_up(x)宏定义的作用,是使得输入的x和BITS_PER_LONG向上对齐,bpl是bits per long三个单词的缩写。假设BITS_PER_LONG是64,那么x返回的值总是和64向上对齐,即总是64的整倍数。用下面的shell可以验证,其中~(64-1) = 0xFFC0。
因为IPIPE_NR_XIRQS(不知道这个X是代指什么)是1024,与64对齐,所以IPIPE_VIRQ_BASE也是1024,等同于IPIPE_NR_XIRQS。
IPIPE_VIRQ_BASE代表第一个virtual IRQ的编号,这就引出了第二部分IPIPE_NR_VIRQS。
第二部分IPIPE_NR_VIRQS,等同于BITS_PER_LONG,即64。
这里的VIRQ和前面讨论的虚拟中断标志位完全不是一个概念哦,千万不能混淆。VIRQ的前10个,分别为7个NR_IPI和3个IPIPE_OOB_IPI_NR,接下来分别展开说一下。
arch/arm64/include/asm/hardirq.h:
#define NR_IPI 7
arch/arm64/include/asm/ipipe_base.h:
/*
* Out-of-band IPIs are directly mapped to SGI1-3, instead of
* multiplexed over SGI0 like regular in-band messages.
*/
#define IPIPE_IPI_BASE IPIPE_VIRQ_BASE
#define IPIPE_OOB_IPI_NR 3
#define IPIPE_CRITICAL_IPI (IPIPE_IPI_BASE + NR_IPI)
#define IPIPE_HRTIMER_IPI (IPIPE_IPI_BASE + NR_IPI + 1)
#define IPIPE_RESCHEDULE_IPI (IPIPE_IPI_BASE + NR_IPI + 2)
前7个NR_IPI,是Linux原始定义的7个IPI中断,全称是Inter-Processor Interrupt,用于CPU core之间的通信。Linux内核可以通过smp_cross_call->__smp_cross_call-> gic_raise_softirq,从当前CPU core向其它CPU core来触发IPI中断。smp_cross_call调用函数指针__smp_cross_call,而__smp_cross_call由GIC V3中断控制器驱动(irq-gic-v3.c)调用gic_smp_init-> set_smp_cross_call(gic_raise_softirq)初始化为gic_raise_softirq。所以,CPU core之间的IPI中断,是依赖GIC V3中断控制器的,本质是调用gic_raise_softirq。注意这里面的softirq,其实就是GIC V3里面定义的SGI中断,即Software Generated Interrupt.
arch/arm64/kernel/smp.c:
//Linux原始定义的7个IPI中断
enum ipi_msg_type {
IPI_RESCHEDULE,
IPI_CALL_FUNC,
IPI_CPU_STOP,
IPI_CPU_CRASH_STOP,
IPI_TIMER,
IPI_IRQ_WORK,
IPI_WAKEUP
};
void (*__smp_cross_call)(const struct cpumask *, unsigned int);
void __init set_smp_cross_call(void (*fn)(const struct cpumask *, unsigned int))
{
__smp_cross_call = fn;
}
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
{
trace_ipi_raise(target, ipi_types[ipinr]);
__smp_cross_call(target, ipinr);
}
drivers/irqchip/irq-gic-v3.c:
static void gic_smp_init(void)
{
set_smp_cross_call(gic_raise_softirq);
cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
"irqchip/arm/gicv3:starting",
gic_starting_cpu, NULL);
}
后面3个IPIPE_OOB_IPI_NR,就算是IPIPE自己额外新增的3个IPI中断了。实时内核可以通过ipipe_send_ipi->smp_cross_call->__smp_cross_call->gic_raise_softirq来触发IPI中断。这3个IPIPE_OOB_IPI_NR各自又封装了接口:
ipipe/core.c:
// IPIPE_CRITICAL_IPI
unsigned long ipipe_critical_enter(void (*syncfn)(void))
include/xenomai/pipeline/pipeline.h:
//IPIPE_RESCHEDULE_IPI
static inline void pipeline_send_resched_ipi(const struct cpumask *dest)
// IPIPE_HRTIMER_IPI
static inline void pipeline_send_timer_ipi(const struct cpumask *dest)
总结起来,IPIPE_NR_IRQS两部分之和:IPIPE_NR_XIRQS(1024)和IPIPE_NR_VIRQS(64)。它们只是干巴巴的数字,实际的意义是什么呢?结合GIC V3来看一下!
参考《GICv3_Software_Overview_Official_Release_B》,下表描述了GIC V3支持的INTID(硬件中断号)的范围。
SGI (Software Generated Interrupt):软件触发的中断。Linux内核可以通过写GICD_SGIR寄存器来触发一个中断事件,用于CPU core之间的通信。
PPI (Private Peripheral Interrupt):私有外设中断。这是每个核心私有的中断。PPI会送达到指定的CPU上,应用场景有CPU本地时钟。
SPI (Shared Peripheral Interrupt):软件触发的中断。软件可以通过写GICD_SGIR寄存器来触发一个中断事件,一般用于核间通信。
LPI (Locality-specific Peripheral Interrupt):LPI是GICv3中的新特性,是基于消息的中断。当前GIC V3驱动irq-gic-v3.c的参数gicv3_nolpi默认为0,所以默认是默认支持LPI的。
GIC V3的PPI+SPI+LPI是硬件中断号,在Linux中一般用int hwirq来表示,下面都用hwirq来代指硬件中断号。为了方便管理中断号,在Linux中还定义了逻辑中断号,一般用int virq来表示,每个virq都对应一个struct irq_desc数据结构,下面都用virq来代指逻辑中断号。逻辑中断号virq和硬件中断号hwirq的映射关系,不是简单的相等关系。从CPU硬件的角度来说,hwirq是固定分配好的,但是逻辑中断号virq是通过位图变量allocated_irqs按照先申请先得的规则分配的。二者的映射关系是在内核启动过程中建立,函数调用关系如下,irq_create_fwspec_mapping返回的就是virq,在这里就不具体展开了,还是回到主题来梳理一下逻辑中断号virq的范围。
如刚刚提到的,逻辑中断号virq是通过位图变量allocated_irqs按照先申请先得的规则分配的,所以virq的范围就要看位图变量allocated_irqs到底是多少bit位。
kernel/irq/irqdesc.c:
static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS);
//分配virq时,首先找到空闲的bit位
__irq_alloc_descs-> bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS, from, cnt, 0);
IRQ_BITMAP_BITS具体是多大呢?64或者64+8196,取决于CONFIG_SPARSE_IRQ。
include/asm-generic/irq.h:
/*
* NR_IRQS is the upper bound of how many interrupts can be handled
* in the platform. It is used to size the static irq_map array,
* so don't make it too big.
*/
#ifndef NR_IRQS
#define NR_IRQS 64
#endif
kernel/irq/internals.h:
#ifdef CONFIG_SPARSE_IRQ
# define IRQ_BITMAP_BITS (NR_IRQS + 8196)
#else
# define IRQ_BITMAP_BITS NR_IRQS
#endif
CONFIG_SPARSE_IRQ代表如何选择struct irq_desc的管理方式。对于指定的virq,如何快速的找到对应的struct irq_desc结构体?如果CONFIG_SPARSE_IRQ=y,就用radix tree,否则就要静态数组。当前默认是CONFIG_SPARSE_IRQ=y。
kernel/irq/irqdesc.c:
#ifdef CONFIG_SPARSE_IRQ
……
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);
……
#else /* !CONFIG_SPARSE_IRQ */
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
……
#endif
截止目前,我们分析出了3组和中断相关数字:
IPIPE_NR_IRQS = IPIPE_NR_XIRQS + IPIPE_NR_VIRQS
GIC V3 INTID = SGI + hwirq(PPI + SPI + LPI)
Linux virq的数量 = IRQ_BITMAP_BITS
它们的关系到底是什么呢?接下来用一副图来揭秘!
IPIPE_NR_XIRQS的值默认是1024,如果hwirq的总数超过1024,映射过程就会出错。
IPIPE_NR_VIRQS对应ARM64 GIC V3中断处理器的SGI中断。Linux原始定义的7个IPI中断,会占用7个SGI中断号:SGI0~SGI6。但是IPIPE来了之后,会强制Linux 7个IPI中断共享SGI0。具体是咋做到的,后面会结合代码再分析。
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!
标签:1.2,中断,irq,IPI,smp,3.4,Linux,IPIPE,NR From: https://blog.csdn.net/aspirestro/article/details/142221747