25

龙芯中断再探(三)

 4 years ago
source link: https://www.tuicool.com/articles/zeYZRnr
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

“ 作者简介 张释文,毕业于西安邮电大学,现就职于武汉深之度科技有限公司

前面简单介绍中断初始化和注册的基本流程,这里接上文留下来的两部分内容,中断处理和中断调试技巧。 从内核开发的角度来讲说,前文是科普文,帮助理解接下来的内容,本文没有那么枯燥,可以玩起来。 最后的调试技巧是自己总结的,帮助我修复了工作中遇到90%的中断错误。 如果大佬有更好的技巧,一定分享出来哈。

众所周知,中断处理统一入口是 do_IRQ 函数,经过一层层调用真正到处理函数。今天的分享主要集中在调用用  do_IRQ 之前操作。 CPU检测到有中断发生,然后调用 handle_int 函数,代码如下:

BUILD_ROLLBACK_PROLOGUE handle_int

NESTED(handle_int, PT_SIZE, sp)

#ifdef CONFIG_TRACE_IRQFLAGS

.set push

.set noat

mfc0 k0, CP0_STATUS

#if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)

and k0, ST0_IEP

bnez k0, 1f

mfc0 k0, CP0_EPC

.set noreorder

j k0

rfe

#else

and k0, ST0_IE

bnez k0, 1f

eret

#endif

......

jal plat_irq_dispatch

/* Restore sp */

move sp, s1

j ret_from_irq

#ifdef CONFIG_CPU_MICROMIPS

nop

#endif

END(handle_int)

__INIT

这是mips汇编,语言只是工具。这里做中断处理前保存现场等准备工作。最后有一个跳转,转到 plat_irq_dispatch 函数。看,这就是熟悉的领域啦。

asmlinkage void plat_irq_dispatch(void)

{

unsigned int pending;

pending = read_c0_cause() & read_c0_status() & ST0_IM;

/* machine-specific plat_irq_dispatch */

mach_irq_dispatch(pending);

}

函数功能很简单,从寄存器中读出 pending ,然后传入 mach_irq_dispatch 函数。这个寄存器是保存中断状态和来源,经过一系列位移运算得到一个 pending。

void mach_irq_dispatch(unsigned int pending)

{

if (pending & CAUSEF_IP7)

do_IRQ(LOONGSON_TIMER_IRQ);

#if defined(CONFIG_SMP)

if (pending & CAUSEF_IP6)

loongson3_ipi_interrupt(NULL);

#endif

if (pending & CAUSEF_IP3)

loongson_pch->irq_dispatch();

if (pending & CAUSEF_IP2)

{

irqs_pci = LOONGSON_INT_ROUTER_ISR(0) & 0xf0;

irq = ffs(irqs_pci)

do_IRQ(irq - 1);

}

if (pending & UNUSED_IPS) {

pr_err("%s : spurious interrupt\n", __func__);

spurious_interrupt();

}

}

前面看到 pending 是根据中断的状态和来源计算得到的,这个函数以 pending 为依据做中断映射,也叫中断分发。如果是时钟中断,直接处理。 causef_ipX 这些表示状态的宏挺重要的,在 start_kernel 阶段用到的位置很多,然而我没研究明白具体的含义,orz。 mips 手册里介绍了,感兴趣的大佬可以研究一下,然后分享出来。大多数中断,会经过 loongson3_ipi_interruptloongson_pch->irq_dispatch 这两个函数映射出去。先看看 loo ngson_pch->irq_dispatch

static volatile unsigned long long *irq_status = (volatile unsigned long long *)((LS7A_IOAPIC_INT_STATUS));

void ls7a_irq_dispatch(void)

{

/* read irq status register */

unsigned long long irqs = *irq_status;

while(irqs){

irq = __ffs(irqs);

irqs &= ~(1ULL<<irq);

/* handled by local core */

if ((local_irq & (0x1ULL << irq)) || ls7a_ipi_irq2pos[LS7A_IOAPIC_IRQ_BASE + irq] == (unsigned char)-1) {

do_IRQ(LS7A_IOAPIC_IRQ_BASE + irq);

continue;

}

irqd = irq_get_irq_data(LS7A_IOAPIC_IRQ_BASE + irq);

cpumask_and(&affinity, irqd->common->affinity, cpu_active_mask);

if (cpumask_empty(&affinity)) {

do_IRQ(LS7A_IOAPIC_IRQ_BASE + irq);

continue;

}

irq_cpu[irq] = cpumask_next(irq_cpu[irq], &affinity);

if (irq_cpu[irq] >= nr_cpu_ids)

irq_cpu[irq] = cpumask_first(&affinity);

if (irq_cpu[irq] == cpu) {

do_IRQ(LS7A_IOAPIC_IRQ_BASE + irq);

continue;

}

/* balanced by other cores */

loongson3_send_irq_by_ipi(irq_cpu[irq], (0x1<<(ls7a_ipi_irq2pos[LS7A_IOAPIC_IRQ_BASE + irq])));

}

}

从中断寄存器读取 status 值, irq_status 存储的是中断寄存器的地址。判断中断应该在哪个核上处理,调用 do_IRQ 。中断初始化时候,创建了两个数组 ls7a_ipi_irq2posls7a_ipi_pos2irq ,其中 ls7a_ipi_irq2pos 用以记录某个中断在某个核上处理次数。这里我们要关注的是中断号是怎么的得来的,函数 do_IRQ 的参数即是真正的中断号,是通过中断寄存器中的内容计算得来的。这个计算的过程就是中断映射(或者中断分发)。

下面是另一个中断映射函数—— loongson3_ipi_interrupt:

void loongson3_ipi_interrupt(struct pt_regs *regs)

{

/* Load the ipi register to figure out what we're supposed to do */

action = loongson3_ipi_read32(ipi_status0_regs[cpu_logical_map(cpu)]);

irqs = action >> IPI_IRQ_OFFSET;

/* Clear the ipi register to clear the interrupt */

loongson3_ipi_write32((u32)action, ipi_clear0_regs[cpu_logical_map(cpu)]);

if (action & SMP_RESCHEDULE_YOURSELF)

scheduler_ipi();

if (action & SMP_CALL_FUNCTION) {

irq_enter();

generic_smp_call_function_interrupt();

irq_exit();

}

if (irqs) {

int irq;

switch (loongson_pch->board_type) {

case RS780E:

while ((irq = ffs(irqs))) {

do_IRQ(irq-1);

irqs &= ~(1<<(irq-1));

}

break;

case LS2H:

do_IRQ(irqs);

break;

case LS7A:

while ((irq = ffs(irqs))) {

irq1 = ls7a_ipi_pos2irq[irq-1];

do_IRQ(irq1);

irqs &= ~(1<<(irq-1));

}

break;

default:

break;

}

}

}

从寄存器中读取 action ,清除 ipi 寄存器,根据 action 判断如何处理这个中断。 和上文一样,这里我们主要关注中断号是怎么来的 irq 是根据 irqs 计算来的, irqs action 移位得来的, action 是从寄存器读来的。 可以看到不同的主板类型对中断号的映射是不一样的。

02中断调试技巧

这节内容是在上文基础上的一个提升,解 bug 过程中一点不成熟的小经验跟大家分享出来,大佬们见笑。调了很多很多中断错误,回头总结的时候发现中断错误80%都是出在了中断映射部分。上文说过,中断初始化分为两个部分:显示 arch 下调用 irq_set_chip_and_handler 设置中断 high level handler 和驱动中调用 request_irq 注册。

nobody care的中断错误

产生这样的错误是因为,既没有设置 high level handler, 也没有人注册这个中断,但是CPU扫到了该中断。所以告诉内核没有人关注这个中断,这是个异常。一般这种错误产生的原因都是中断被映射错了位置,几乎不可能有程序员在驱动代码中,忘记注册或者是忘记设置 high level handler 了。当然,哪怕是真忘了,这个驱动都注册不成功哪来的中断呢?

感兴趣的大佬可以试试看,故意把中断映射到一个没有人用的中断上,改一下映射函数即可获得。顺便挨个感受硬盘,显卡,声卡, input 等设备中断不工作是什么样的体验,请一定要保证PC上有一个可以正常启动的内核。

好,回到正题,这种错误开发人员第一需要知道得到就是这本来是哪个中断,?这里我没找到一个很好的从正面跟下去的办法,只有一些迂回方法。

  • dmesg。 对于内核开发来讲, dmesg 简直是本命啊。有一些驱动是需要和cpu很频繁的交互的,比如说硬盘,如果中断出错了,紧接着,ata驱动就报错了。所以在这个中断错误日志,紧挨着的位置会有一些驱动错误信息打印,这就是一个突破口。假如说 dmesg 并没有错误日志、开发人员忽略了日志怎么办呢?

  • 接下来就是起来看 /proc/interrupt,interrupt 文件中某一个中断没上来或者异常少,那就是它啦。当然这要在内核起来的前提下,就算系统没起来,也是有办法可以看到 proc 的。如果说,内核起不来呢?

  • 这种情况略棘手了,要么就是根据经验判断,因为影响内核启动的中断就那么几个,根据经验加上内核 hang 位置,很容易定位。运气好可能碰到一个 panic ,这就更好判断啦。如果没有这个经验,那就老老实实调试吧。不过别担心,内核起不来的问题一般都挺好调试的。

  • 此时,你已经知道是某个中断被错误的映射到某个位置。接下来就简单了,去映射入口看,为什么会被映射错呢。可能是 irq balance 算错了,可能是中断偏移量加错了等等。

bad irq中断错误

这个错误和上面的 nobody care 错误很相似,区别就是: nobody care 错误没设置 high level handler,bad irq 错误设置了。

做个实验,在中断 init 时候,增加一个 irq_set_chip_and_handler ,这里注意里面填充的中断号没有驱动在使用的且不超出范围的。然后在中断映射时候,把某一个特定的中断映射过来。你就能自制中断错误,是不是很有意思嘞。

这个错误用的错误定位方法和上文一样,先看 dmesg ,看 interrupt 和调试。

一些看上去怪怪的问题

这类问题虽然原因类似,但是表现千奇百怪。比如说掉盘,声音响着响着突然不响了,桌面突然卡了等等。

一般来讲,中断问题是比较严重的问题,很多会影响系统起不来,就算系统能起来桌面也进不来。但是有个这样的例外,中断映射错了,错误位置恰好是有一个驱动在用这个中断,这个驱动注册时候又恰好设置了共享中断(不要惊讶,这种情况很多的。 INT 中断线很少,所以大部分驱动注册时候都不会选择独占中断的)。又或者是 irq balance 做的有问题,分散到特定核上会出现中断号计算错误的情况,这样的表现就是刚开始是好的,用着用着驱动死了,过一会又好了这样的怪怪的现象。

这些问题头疼的地方在于,不太可能会怀疑到中断上来。虽然是同一个原因导致的,但是这种错误的表现没有共性。所以当你遇到这样有点怪的问题时,在 menuconfig 尽可能的关掉一些驱动会有帮助。驱动一关,对应中断就不会被注册,这就有可能会得到 bad irq 报错信, 帮助定位。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK