点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
3.4.1.3 IPIPE interrupt log数据结构
IPIPE interrupt log的概念,来自于IPIPE中断分发的核心函数__ipipe_dispatch_irq(kernel/ipipe/core.c)里面的注释。这个函数的具体流程会在后面的章节讨论,这里只讨论interrupt log的数据结构是怎么设计的。
第一步,先把中断记录到interrupt log。看一下这个截取自__ipipe_dispatch_irq 函数的图片。
把中断记录到interrupt log,核心函数就是__ipipe_set_irq_pending。从字面很好理解,暂时不处理中断,可不就是让中断处于pending状态嘛。从上图还能看出来,在特殊情况下(IPIPE_IRQF_NOSYNC),head域也不立即处理中断,会记录到interrupt log。下面看一下__ipipe_set_irq_pending的真面目!
这个函数的定义,与include/linux/ipipe_domain.h中的两个宏定义IPIPE_IRQ_MAPSZ/ __IPIPE_IRQMAP_LEVELS相关。根据上一章的分析,IPIPE_NR_IRQS等于1088,所以IPIPE_IRQ_MAPSZ是17,__IPIPE_IRQMAP_LEVELS是2.
在kernel/ipipe/core.c,找到__ipipe_set_irq_pending的定义:
#if __IPIPE_IRQMAP_LEVELS == 4
……
#elif __IPIPE_IRQMAP_LEVELS == 3
……
#else /* __IPIPE_IRQMAP_LEVELS == 2 */
/* Must be called hw IRQs off. */
void __ipipe_set_irq_pending(struct ipipe_domain *ipd, unsigned int irq)
{
struct ipipe_percpu_domain_data *p = ipipe_this_cpu_context(ipd);
int l0b = irq / BITS_PER_LONG;
IPIPE_WARN_ONCE(!hard_irqs_disabled());
if (likely(!test_bit(IPIPE_LOCK_FLAG, &ipd->irqs[irq].control))) {
__set_bit(irq, p->irqpend_map);
__set_bit(l0b, &p->irqpend_0map);
} else
__set_bit(irq, p->irqheld_map);
p->irqall[irq]++;
}
EXPORT_SYMBOL_GPL(__ipipe_set_irq_pending);
#endif
第5行,根据上面的分析,只关注__IPIPE_IRQMAP_LEVELS等于2的情况。
第9行,根据传入的struct ipipe_domain *ipd,找到struct ipipe_percpu_domain_data *p。对于支持SMP的多core CPU,当中断发生时,GIC V3一定是把中断送给某个CPU core来处理。所以,interrupt log也是每个CPU core自己单独记录的,它其实对应的就是ipipe_percpu_domain_data的成员irqpend_map。irqpend_map实际上就是一个数组,类型是unsigned long即每个数组元素提供64个bit位,数组长度是17即总共提供64*17=1088个bit位来标记中断是否pending。
struct ipipe_percpu_domain_data {
unsigned long status; /* <= Must be first in struct. */
unsigned long irqpend_0map;
……
unsigned long irqpend_map[IPIPE_IRQ_MAPSZ];
unsigned long irqheld_map[IPIPE_IRQ_MAPSZ];
unsigned long irqall[IPIPE_NR_IRQS];
struct ipipe_domain *domain;
int coflags;
};
第10行,数组irqpend_map相当于把1088个bit平分成17个区域(每区域64个bit),用中断号直接对64(BITS_PER_LONG)取整,得到的就是此中断所对应的区域编号。这里,把这个区域编号存入变量l0b,它的名字是level 0 bit的缩写。
第15行,利用linux的位图操作函数,直接把irq在数组irqpend_map中对应的bit置位为1,代表此中断pending。这里提出一个问题哈:置位的位置,属于完数组irqpend_map的哪个元素?答案其实就是第10行的l0b。
第16行,把第10行得出的区域编号l0b作为索引值,在变量irqpend_0map中置位对应的bit。所以,irqpend_0map的每个bit就代表每个区域中是否至少有1个bit处于pending状态。所以,只要irqpend_0map的值不等于0,就代表一定有中断处于pengding状态。
第18行,如果中断irq处于IPIPE_LOCK_FLAG状态,不会把中断记录到irqpend_map中,而是记录到irqheld_map中。只要root domain才会标记irq是否处于IPIPE_LOCK_FLAG状态,这里不做深入讨论,后面在分析中断流程的时候再展开。
第20行,对每个中断irq,记录一下在此CPU core总共发生了多少次。
第二步,从interrupt log中查询出未处理的中断,进行处理,为方便描述简称interrupt log回放。核心函数就是__ipipe_next_irq,它是__ipipe_set_irq_pending的反向操作。从下图的函数调用关系可知,IPIPE总是通过调用__ipipe_sync_stage来完成interrupt log回放。
按照__IPIPE_IRQMAP_LEVELS等于2,来分析一下__ipipe_next_irq。
#if __IPIPE_IRQMAP_LEVELS == 4
……
#elif __IPIPE_IRQMAP_LEVELS == 3
……
#else /* __IPIPE_IRQMAP_LEVELS == 2 */
static inline int __ipipe_next_irq(struct ipipe_percpu_domain_data *p)
{
unsigned long l0m, l1m;
int l0b, l1b;
l0m = p->irqpend_0map;
if (unlikely(l0m == 0))
return -1;
l0b = __ipipe_ffnz(l0m);
l1m = p->irqpend_map[l0b];
if (unlikely(l1m == 0))
return -1;
l1b = __ipipe_ffnz(l1m);
__clear_bit(l1b, &p->irqpend_map[l0b]);
if (p->irqpend_map[l0b] == 0)
__clear_bit(l0b, &p->irqpend_0map);
return l0b * BITS_PER_LONG + l1b;
}
#endif
第11~13行,如之前对__ipipe_set_irq_pending的分析,只要l0m即irqpeng_0map的值为0,代表没有任何中断记录在数组中irqpend_map。
第15~16行,从l0m(level 0 map)即irqpend_0map中找到第一个非0的bit的编号l0b。这个编号l0b作为下标从数组irqpend_map取出一个元素l1m= irqpend_map[l0b],这个l1m就代表一个64bit长的区域,在此区域中一定存在着一个bit不为0.
第17~18行,如果判断成立,肯定是bug。
第20~21行,从l1m(level 1 map)即irqpend_map[l0b]中找到第一个不为0的bit的编号l1b,然后把这个bit位在irqpend_map[l0b]中清零。
第22~23行,如果irqpend_map[l0b]此时已经等于0,代表此64bit长的区域已经没有任何一个中断pend了,把irqpeng_0map中的l0b位清零。
第25行,l0b*64+l1b就是找到的penging的irq编号。
分析起来挺绕的,其实从遍历数组irqpend_map[17]的每个元素,从中找到非0的bit。只不过irqpend_0map的bit位能够标记具体哪个数组元素里面存在非0的bit,所以不再需要用for循环一遍一遍的遍历数组。
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!