47

龙芯中断初探(一)

 4 years ago
source link: https://www.tuicool.com/articles/a6JjEnU
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.

最近几个月调了很多中断的bug,啃了很久的源码。整理了一些东西,大佬们笑纳。
离开了架构谈中断都是不深刻的,大佬们肯定玩腻了X86了,今天就以龙芯内核(龙芯官网即可获得:git://cgit.loongnix.org/kernel/linux-3.10.git)为例简单介绍一下哈。中断在内核中的生命周期主要分为三个部分:初始化,注册和中断处理,剩余的所有事情都是硬件完成的。这部分打算分享四节内容:中断初始化、注册中断处理以及中断排错。这次先分享前两部分内容。
在一切的前面,先看看中断相关的数据结构。

0 1

中断相关数据结构

话不多说,上图

Zz2MbyQ.png!web

linux kernel中,对于每一个外设的IRQ都用struct irq_desc来描述,我们称之中断描述符(struct irq_desc)。linux kernel中会有一个数据结构保存了关于所有IRQ的中断描述符信息,我们称之中断描述符DB(上图中的数据结构)。当发生中断后,首先获取触发中断的HW interupt ID,然后通过irq domain翻译成IRQ nuber,然后通过IRQ number就可以获取对应的中断描述符。调用中断描述符中的highlevel irq-events handler来进行中断处理就OK了。而highlevel irq-events handler主要进行下面两个操作:

(1)调用中断描述符的底层irq chip driver进行mask,ack等callback函数,进行interrupt flow control。

(2)调用该中断描述符上的action list中的specific handler(我们用这个术语来区分具体中断handler和high level的handler)。

接下来看中断初始化,很多架构中断初始化代码都在arch目录下,龙芯采用mips架构,因此关注点在arch/mips/目录中,一般架构都是定义好某个中断号被哪个设备占用,初始化工作是:申请中断处理需要的desc结构,每个扫描设备给每个desc填充好handle_irq 和chip结构,给中断号绑定处理函数。中断注册都是在init阶段通过两个接口完成的,为什么是两个接口呢,因为龙芯一部分中断是CPU控制,另一部分由桥片控制。由一个宏定义分开,宏定义定义在arch下,名字一般是XXX_IRQ_BASE,XXX是架构名,例如:LS7A_PCH_IRQ_BASE,。中断号小于该宏是有CPU直接控制的中断,比如说时钟中断,串口中断等等。大于该宏则是桥片上的,例如:显卡、网卡、硬盘等等。这也跟龙芯自己的硬件设计有关。

0 2

中断初始化

这个很容易理解,中断初始化肯定在内核启动中,因为内核非常需要时钟等中断。那就先从start_kernel开始看吧,这个函数执行的功能十分冗杂,现在我们只关注irq相关的部分。差不多翻两页以后,看到下面这个代码块:

 1 if (initcall_debug)
 2            initcall_debug_enable();
 3
 4    context_tracking_init();
 5    /* init some links before init_ISA_irqs() */
 6    early_irq_init();
 7    init_IRQ();
 8    tick_init();
 9    rcu_init_nohz();
10    init_timers();
11    hrtimers_init();
12    softirq_init();
13    timekeeping_init();
14    time_init();
15    printk_safe_init();
16    perf_event_init();
17    profile_init();
18    call_function_init();
19    WARN(!irqs_disabled(), "Interrupts were enabled early\n");
20
21    early_boot_irqs_disabled = false;
22    local_irq_enable();

这是除了start_kenrel一进来就disable_irq之后第一个出现irq字样的代码块了,各种init irq,nice,盘他!先去看看early_init_irq。定义在kernel下。那就是通用的咯,那就应该是初始化前的准备工作,没关系,先进去看看。

 1int __init early_irq_init(void)
 2{
 3    int i, initcnt, node = first_online_node; 
 4    struct irq_desc *desc;
 5    init_irq_default_affinity();
 6    /* Let arch update nr_irqs and return the nr of preallocated irqs */
 7    initcnt = arch_probe_nr_irqs();
 8    printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",
 9           NR_IRQS, nr_irqs, initcnt);
10
11    if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
12            nr_irqs = IRQ_BITMAP_BITS;
13
14    if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
15            initcnt = IRQ_BITMAP_BITS;
16
17    if (initcnt > nr_irqs)
18            nr_irqs = initcnt;
19
20    for (i = 0; i < initcnt; i++)
21    {
22            desc = alloc_desc(i, node, 0, NULL, NULL);
23            set_bit(i, allocated_irqs);
24            irq_insert_desc(i, desc);
25    }
26    return arch_early_irq_init();
27}

好,设置irq affinity标志,计算中断数量,分配中断描述符,中断描述符插入DB,然后去arch_early_irq_init,再跳进去看看,恩,kernel下的一个空函数。ok,这时候中断描述符DB已经创建完成了,只是每个desc还是空的。

接下来看init_IRQ——这个看名字就很重要的函数。ctrl+T,arch下的!!开心,一下就过去了。选择arch/mips/下的定义:

 1void __init init_IRQ(void)   
 2{
 3    int i;      
 4    unsigned int order = get_order(IRQ_STACK_SIZE);
 5    for (i = 0; i < NR_IRQS; i++)
 6            irq_set_noprobe(i);
 7    if (cpu_has_veic)
 8            clear_c0_status(ST0_IM);
 9    arch_init_irq();
10    for_each_possible_cpu(i) {
11            void *s = (void *)__get_free_pages(GFP_KERNEL, order);
12            irq_stack[i] = s;
13            pr_debug("CPU%d IRQ stack at 0x%p - 0x%p\n", i,
14                    irq_stack[i], irq_stack[i] + IRQ_STACK_SIZE);
15    }
16}

挨个设置noprobe,判断cpu有没有扩展中断控制模式(external interrupt controller mode, eic),调arch_init_irq,多CPU挨个申请页面作为中断栈。毫无疑问,注册流在arch_init_irq里,就这样一个一个的跳转,最后到这里:

 1void __init mach_init_irq(void)
 2{               
 3    int i;
 4    u64 intenset_addr;
 5    u64 introuter_lpc_addr;
 6    clear_c0_status(ST0_IM | ST0_BEV);
 7    mips_cpu_irq_init();
 8    if (loongson_pch)
 9            loongson_pch->init_irq();
10    /* setup CASCADE irq */
11    setup_irq(LOONGSON_BRIDGE_IRQ, &cascade_irqaction);
12    irq_set_chip_and_handler(LOONGSON_UART_IRQ,
13                    &loongson_irq_chip, handle_level_irq);
14    set_c0_status(STATUSF_IP2 | STATUSF_IP6);
15}

这里可以看到,init是分两个过程的,先是mips架构通用的mips_cpu_irq_init,再是调用桥片自己的init_irq。这就是我一开始说的,注册是分两步完成的。mips_cpu_irq_init函数负责初始cpu直接控制的中断,挨个设置chip和handle_irq.

 1static int mips_cpu_intc_map(struct irq_domain *d, unsigned int irq,
 2                          irq_hw_number_t hw)
 3{
 4    static struct irq_chip *chip;
 5
 6    if (hw < 2 && cpu_has_mipsmt) {
 7            /* Software interrupts are used for MT/CMT IPI */
 8            chip = &mips_mt_cpu_irq_controller;
 9    } else {
10            chip = &mips_cpu_irq_controller;
11    }
12
13    if (cpu_has_vint)
14            set_vi_handler(hw, plat_irq_dispatch);
15
16    irq_set_chip_and_handler(irq, chip, handle_percpu_irq);
17
18    return 0;
19 }

初始化流程还是比较简单,可以看到,在这里给每一个cpu产生的中断设置了chip和handle_irq。CPU检测到中断触发直接调用handle_percpu_irq最后到真正的中断处理函数。接下来看走msi中断的注册,这部分稍微复杂一点:

 1void __init ls7a_init_irq(void)
 2{
 3    writeq(0x0ULL, LS7A_INT_EDGE_REG);
 4    writeq(0x0ULL, LS7A_INT_STATUS_REG);
 5    /* Mask all interrupts except LPC (bit 19) */
 6    writeq(0xfffffffffff7ffffULL, LS7A_INT_MASK_REG);
 7    writeq(0xffffffffffffffffULL, LS7A_INT_CLEAR_REG);
 8
 9    /* Enable the LPC interrupt */
10    writel(0x80000000, LS7A_LPC_INT_CTL);
11    /* Clear all 18-bit interrupt bits */
12    writel(0x3ffff, LS7A_LPC_INT_CLR);
13
14    if (pci_msi_enabled())
15            loongson_pch->irq_dispatch = ls7a_msi_irq_dispatch;
16    ....
17
18    init_7a_irq(LS7A_IOAPIC_LPC_OFFSET          , LS7A_IOAPIC_LPC_IRQ          );
19    init_7a_irq(LS7A_IOAPIC_UART0_OFFSET        , LS7A_IOAPIC_UART0_IRQ        );
20    init_7a_irq(LS7A_IOAPIC_I2C0_OFFSET         , LS7A_IOAPIC_I2C0_IRQ         );
21
22      .........
23    }
24}

设置中断掩码、中断清除等寄存器,设置中断分发函数,调用init_7a_irq为每个外设中断设置handle_irq和chip结构。代码如下:

 1static void init_7a_irq(int dev, int irq) 
 2{
 3    *irq_mask  &= ~(1ULL << dev);
 4    *(volatile unsigned char *)(LS7A_IOAPIC_ROUTE_ENTRY + dev) = USE_7A_INT0;
 5    smp_mb();
 6    irq_set_chip_and_handler(irq, &pch_irq_chip, handle_level_irq);
 7    if(ls3a_msi_enabled) {
 8            *irq_msi_en |= 1ULL << dev;
 9            *(volatile unsigned char *)(irq_msi_vec+dev) = irq;
10            smp_mb();
11    }
12}

这里就是所有中断初始化流程了。看到这里很多人会觉得茫然了,按照上面的流程分析,第一个被初始化到的中断是MIPS_CPU_IRQ_BASE 也就是56,那前面的中断号干嘛去了?前面的中断号是故意留下来,具体原因先卖个关子 :wink:

未完待续。。。

jYrqQjv.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK