setup_arch()源码分析

setup_arch()

start_kernel()中第一个重要的、体系结构密切相关的函数是 setup_arch(),定义在 arch/loongarch/kernel/setup.c中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void __init setup_arch(char **cmdline_p)
{
cpu_probe();
early_init();

#ifdef CONFIG_EARLY_PRINTK
setup_early_printk();
#endif
bootcmdline_init(cmdline_p);

init_initrd();
platform_init();
finalize_initrd();
cpu_report();

arch_mem_init(cmdline_p);

resource_init();
#ifdef CONFIG_SMP
plat_smp_setup();
#endif
prefill_possible_map();

cpu_cache_init();
paging_init();
boot_cpu_trap_init();
}

cpu_probe()

用来探测 CPU 类型、ID号、版本等等。探测的主要依据是 PRID 寄存器,即协处理器 0 中的 15 号寄存器。PRID 是一个 32 位寄存器,最高 8 位保留,次高 8 位是公司 ID,第三个 8 位是处理器 ID,最后一个 8 位是修订号 ID。

early_init()

1
2
3
4
5
6
void __init early_init(void)
{
fw_init_cmdline();
fw_init_environ();
early_memblock_init();
}
  • fw_init_cmdline():还记得 kernel_entry 里面保存的 fw_arg0fw_arg3 这几个变量吗?现在就要开始用了。prom_init_cmdline()中处理前两个变量,其中 fw_arg0 是参数的个数,fw_arg1 是参数的字符串数组。这个函数建立来自 BIOS 或 BootLoader 的内核命令行参数,以便给后面start_kernel()中的setup_command_line() 进 一 步 处 理 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void __init fw_init_cmdline(void)
    {
    int i;

    fw_argc = fw_arg0;
    _fw_argv = (long *)fw_arg1;
    _fw_envp = (long *)fw_arg2;

    arcs_cmdline[0] = '\0';
    for (i = 1; i < fw_argc; i++) {
    strlcat(arcs_cmdline, fw_argv(i), COMMAND_LINE_SIZE);
    if (i < (fw_argc - 1))
    strlcat(arcs_cmdline, " ", COMMAND_LINE_SIZE);
    }
    }
  • fw_init_environ():用于初始化环境变量,环境变量来源于 fw_arg2。引入了类似于 UEFI 的 LEFI 接口,fw_arg2 仅仅提供一个地址,该地址指向 BIOS 中的一片数据区,数据区有着特定的结构,可以通过它获得丰富的接口信息。LEFI 接口规范所使用的各种数据结果定义在 arch/loongarch/include/asm/mach-loongson64/boot_param.h 中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void __init fw_init_environ(void)
    {
    efi_bp = (struct bootparamsinterface *)_fw_envp;
    loongson_sysconf.bpi_ver = get_bpi_version(&efi_bp->signature);

    register_addrs_set(smp_group, TO_UNCAC(0x1fe01000), 16);
    register_addrs_set(loongson_chipcfg, TO_UNCAC(0x1fe00180), 16);
    register_addrs_set(loongson_chiptemp, TO_UNCAC(0x1fe0019c), 16);
    register_addrs_set(loongson_freqctrl, TO_UNCAC(0x1fe001d0), 16);

    if (list_find(efi_bp->extlist))
    pr_warn("Scan bootparam failed\n");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    struct loongsonlist_mem_map {     /* 内存分布图 */
    struct _extention_list_hdr header; /* {"M", "E", "M"} */
    u8 map_count;
    struct loongson_mem_map {
    u32 mem_type;
    u64 mem_start;
    u64 mem_size;
    } __packed map[LOONGSON3_BOOT_MEM_MAP_MAX];
    } __packed;

    struct loongson_system_configuration { /* CPU信息 */
    int bpi_ver;
    int nr_cpus;
    int nr_nodes;
    int nr_pch_pics;
    int boot_cpu_id;
    int cores_per_node;
    int cores_per_package;
    char *cpuname;
    u64 vgabios_addr;
    };

    extern struct loongsonlist_mem_map *loongson_mem_map;
    extern struct loongson_system_configuration loongson_sysconf;
    ......
  • early_memblock_init():对boot_param.h里定义声明的内存分布图进行初始化。

    通常情况下我们使用的是支持 NUMA 的版本,它首先初始化 NUMA 节点的距离矩阵,然后逐个解析内存分布图并将最终结果保存于 loongson_memmap。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void __init early_memblock_init(void)
    {
    int i;
    u32 mem_type;
    u64 mem_start, mem_end, mem_size;

    /* parse memory information */
    for (i = 0; i < loongson_mem_map->map_count; i++) {
    mem_type = loongson_mem_map->map[i].mem_type;
    mem_start = loongson_mem_map->map[i].mem_start;
    mem_size = loongson_mem_map->map[i].mem_size;
    mem_end = mem_start + mem_size;

    switch (mem_type) {
    case ADDRESS_TYPE_SYSRAM:
    memblock_add(mem_start, mem_size);
    if (max_low_pfn < (mem_end >> PAGE_SHIFT))
    max_low_pfn = mem_end >> PAGE_SHIFT;
    break;
    }
    }
    memblock_set_current_limit(PFN_PHYS(max_low_pfn));
    }

Others

  • bootcmdline_init(cmdline_p):启动命令行的初始化;

  • init_initrd():初始化临时根文件系统,如果正常,则使用它们;

    Linux初始RAM磁盘(initrd)是在系统引导过程中挂载的一个临时根文件系统,用来支持两阶段的引导过程。 initrd文件中包含了各种可执行程序和驱动程序,它们可以用来挂载实际的根文件系统,然后再将这个 initrd RAM磁盘卸载,并释放内存。

  • platform_init():控制台相关初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    void __init platform_init(void)
    {
    /* init base address of io space */
    set_io_port_base((unsigned long)
    ioremap(LOONGSON_LIO_BASE, LOONGSON_LIO_SIZE));

    efi_init();
    #ifdef CONFIG_ACPI_TABLE_UPGRADE
    acpi_table_upgrade();
    #endif
    #ifdef CONFIG_ACPI
    acpi_gbl_use_default_register_widths = false;
    acpi_boot_table_init();
    acpi_boot_init();
    #endif

    #ifndef CONFIG_NUMA
    fw_init_memory();
    #else
    fw_init_numa_memory();
    #endif
    dmi_setup();
    smbios_parse();
    pr_info("The BIOS Version: %s\n", b_info.bios_version);

    efi_runtime_init();

    register_smp_ops(&loongson3_smp_ops);
    }
  • finalize_initrd():将 initrd/initramfs 所在的内存段设置为保留。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    static unsigned long __init init_initrd(void)
    {
    /*
    * Board specific code or command line parser should have
    * already set up initrd_start and initrd_end. In these cases
    * perfom sanity checks and use them if all looks good.
    */
    if (!initrd_start || initrd_end <= initrd_start)
    goto disable;

    if (initrd_start & ~PAGE_MASK) {
    pr_err("initrd start must be page aligned\n");
    goto disable;
    }
    if (initrd_start < PAGE_OFFSET) {
    pr_err("initrd start < PAGE_OFFSET\n");
    goto disable;
    }

    ROOT_DEV = Root_RAM0;

    return 0;
    disable:
    initrd_start = 0;
    initrd_end = 0;
    return 0;
    }

    static void __init finalize_initrd(void)
    {
    unsigned long size = initrd_end - initrd_start;

    if (size == 0) {
    pr_info("Initrd not found or empty");
    goto disable;
    }
    if (__pa(initrd_end) > PFN_PHYS(max_low_pfn)) {
    pr_err("Initrd extends beyond end of memory");
    goto disable;
    }

    memblock_reserve(__pa(initrd_start), size);
    initrd_below_start_ok = 1;

    pr_info("Initial ramdisk at: 0x%lx (%lu bytes)\n",
    initrd_start, size);
    return;
    disable:
    pr_cont(" - disabling initrd\n");
    initrd_start = 0;
    initrd_end = 0;
    }
  • cpu_report()

arch_mem_init(char *cmdline_p)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
* arch_mem_init - initialize memory management subsystem
*/
static void __init arch_mem_init(char **cmdline_p)
{
if (usermem)
pr_info("User-defined physical RAM map overwrite\n");

check_kernel_sections_mem();

#ifndef CONFIG_NUMA
memblock_set_node(0, PHYS_ADDR_MAX, &memblock.memory, 0);
#endif

/*
* Prevent memblock from allocating high memory.
* This cannot be done before max_low_pfn is detected, so up
* to this point is possible to only reserve physical memory
* with memblock_reserve; memblock_alloc* can be used
* only after this point
*/
memblock_set_current_limit(PFN_PHYS(max_low_pfn));

/*
* In order to reduce the possibility of kernel panic when failed to
* get IO TLB memory under CONFIG_SWIOTLB, it is better to allocate
* low memory as small as possible before plat_swiotlb_setup(), so
* make sparse_init() using top-down allocation.
*/
memblock_set_bottom_up(false);
sparse_init();
memblock_set_bottom_up(true);
#ifdef CONFIG_64BIT
plat_swiotlb_setup();

dma_contiguous_reserve(PFN_PHYS(max_low_pfn));
#endif
memblock_dump_all();

early_memtest(PFN_PHYS(ARCH_PFN_OFFSET), PFN_PHYS(max_low_pfn));
}
  • sparse_init():即稀疏内存模型初始化。内存模型指的是物理地址空间分布的模型,Linux 内核支持三种内存模型:平坦模型,非连续模型和稀疏模型。包括龙芯在内的现代体系结构大都采用了比较自由的稀疏模型。如果不采用稀疏模型,sparse_init()是空操作;如果采用稀疏模型,sparse_init()会初始化一些稀疏模型专有的数据结构(如全局区段描述符数组 mem_section[]及其附带的页描述符数组)。

  • plat_swiotlb_setup():定义在 arch/loongarch/loongson64/dma.c 中。先我们简单介绍一下 SWIOTLB,这是一种 DMA API。龙芯 3 号的访存能力是 48 位,但是由于芯片组或者设备本身的限制,设备的访存能力往往没有这么大。比如龙芯的顶级 I/O 总线(HT 总线)位宽只有 40 位,一部分 PCI 设备的访存能力只有 32 位,而 ISA/LPC 设备的访问能力甚至只有 24 位。为了让任意设备能够对任意内存地址发起 DMA 访问,就必须在硬件上设置一个“DMA 地址-物理地址”翻译表,或者由内核在设备可访问的地址范围内预先准备一块内存做中转站。许多 X86 处理器在硬件上提供翻译表,称为 IOMMU;龙芯没有IOMMU,于是提供了软件中转站,也就是 SWIOTLB。plat_swiotlb_setup()调用 swiotlb_init()初始化 SWIOTLB 的元数据并在 32 位地址范围内分配中转缓冲区(缺省为 64MB),然后注册了一个 DMA API 操作集 loongson_linear_dma_map_ops。操作集里面的“物理地址-DMA地址”转换函数(即 loongson_linear_dma_map_ops 中的 phys_to_dma 和 dma_to_phys 两个函数指针)是同芯片组相关的:

    1
    2
    3
    4
    5
    6
    7
    8
    void __init plat_swiotlb_setup(void)
    {
    swiotlb_init(1);
    node_id_offset = ((readl(LS7A_DMA_CFG) & LS7A_DMA_NODE_MASK) >> LS7A_DMA_NODE_SHF) + 36;

    xlate_ops.phys_to_dma = loongson_phys_to_dma;
    xlate_ops.dma_to_phys = loongson_dma_to_phys;
    }

Others

  • resource_init()

  • plat_smp_setup()

  • prefill_possible_map():它会建立合理的逻辑 CPU 的 possible 值。prefill_possible_map()会通过 set_cpu_possible()来更新 cpu_possible_mask,最后将 possible 值赋给全局变量 nr_cpu_ids。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #ifdef CONFIG_SMP
    static void __init prefill_possible_map(void)
    {
    int i, possible;

    possible = num_processors + disabled_cpus;
    if (possible > nr_cpu_ids)
    possible = nr_cpu_ids;

    pr_info("SMP: Allowing %d CPUs, %d hotplug CPUs\n",
    possible, max((possible - num_processors), 0));

    for (i = 0; i < possible; i++)
    set_cpu_possible(i, true);
    for (; i < NR_CPUS; i++)
    set_cpu_possible(i, false);

    nr_cpu_ids = possible;
    }
    #else
    static inline void prefill_possible_map(void) {}
    #endif
  • cpu_cache_init()

    在龙芯处理器手册中,将 P-Cache 称之为一级 Cache,将 V-Cache 称之为二级 Cache,将 S-Cache 称之为三级 Cache。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void cpu_cache_init(void)
    {
    probe_pcache();
    #ifdef CONFIG_64_BIT
    probe_vcache();
    probe_scache();
    #endif
    shm_align_mask = PAGE_SIZE - 1;

    }

    r4k_cache_init()通过调用 probe_pcache()probe_vcache()setup_scache()完成各级 Cache的容量、行大小和相联度探测。然后给各个 Cache 刷新操作函数赋值(刷新即 Flush,对于指令 Cache 指的是作废,对于数据 Cache 指的是写回并作废)。

page_init()

该函数初始化各个内存页面管理区(Zone)。页面管理区的类型包括 ZONE_DMA、ZONE_DMA32、ZONE_NORMAL 和 ZONE_HIGHME几种。ZONE_DMA 区包括所有物理地址为小于 16MB 的页面,设置这个区的目的是为ISA/LPC 等 DMA 能力只有 24 位地址的设备服务。ZONE_DMA32 区包括所有 ZONE_DMA区之外的物理地址小于 4GB 的页面,设置这个区的目的是为 DMA 能力只有 32 位地址的 PCI设备服务。设置 ZONE_HIGHMEM 的目的是为物理地址超过线性地址表达能力的内存服务:对于 32 位的 MIPS 内核,线性地址表达能力只有 512M,因此 512M 以外的页面被放置到ZONE_HIGHMEM 区;对于 64 位的 MIPS 内核,物理地址暂时还没有超过线性地址的表达能力,因此通常不设置 ZONE_HIGHMEM 区。ZONE_NORMAL 区则包括了上述几个区以外的所有页面。在初始化每个 Zone 的时候,会调用 init_page_count()将每个页帧的初始引用计数设置为 1。因为此时此刻内存还处于 BootMem 管理器的控制下,这些页帧尚未转交到伙伴系统(内存页帧管理器),不是自由页帧(自由页帧的引用计数为 1),不可以被伙伴系统的页帧分配函数分配。

boot_cpu_trap_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void __init boot_cpu_trap_init(void)
{
unsigned long size = (64 + 14) * vec_size;

memblock_set_bottom_up(true);
eentry = (unsigned long)memblock_alloc(size, 1 << fls(size));
tlbrentry = (unsigned long)memblock_alloc(PAGE_SIZE, PAGE_SIZE);
printk("eentry %lx, tlbrentry %lx\n", eentry, tlbrentry);
memblock_set_bottom_up(false);

setup_vint_size(vec_size);
configure_exception_vector();

if (!cpu_data[0].asid_cache)
cpu_data[0].asid_cache = asid_first_version(0);

mmgrab(&init_mm);
current->active_mm = &init_mm;
BUG_ON(current->mm);
enter_lazy_tlb(&init_mm, current);

tlb_init();
TLBMISS_HANDLER_SETUP();
}
  • vec_size:向量大小

  • eentry:通用例外入口地址

  • tlbrentry:tlb refill例外入口地址

  • configure_exception_vector():配置例外向量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    static void configure_exception_vector(void)
    {
    #ifdef CONFIG_64BIT
    csr_writeq(eentry, LOONGARCH_CSR_EENTRY);
    csr_writeq(eentry, LOONGARCH_CSR_MERRENTRY);
    csr_writeq(tlbrentry, LOONGARCH_CSR_TLBRENTRY);
    #endif

    #ifdef CONFIG_32BIT
    csr_writel(eentry + 0x4000, LOONGARCH_CSR_EENTRY);
    csr_writel(tlbrentry, LOONGARCH_CSR_TLBRENTRY);
    #endif
    }
  • tlb_init():主要做的事就是handle_tlb_refill

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    void setup_tlb_handler(void)
    {
    static int run_once = 0;

    setup_pw();
    output_pgtable_bits_defines();

    /* The tlb handlers are generated only once */
    if (!run_once) {
    memcpy((void *)tlbrentry, handle_tlb_refill, 0x80);
    local_flush_icache_range(tlbrentry, tlbrentry + 0x80);
    run_once++;
    }
    }

    void tlb_init(void)
    {
    write_csr_pagesize(PS_DEFAULT_SIZE);
    #ifdef CONFIG_64BIT
    write_csr_stlbpgsize(PS_DEFAULT_SIZE);
    #endif
    if (read_csr_pagesize() != PS_DEFAULT_SIZE)
    panic("MMU doesn't support PAGE_SIZE=0x%lx", PAGE_SIZE);

    setup_tlb_handler();
    local_flush_tlb_all();
    }

    handle_tlb_refill

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    SYM_FUNC_START(handle_tlb_refill)
    csrwr t0, LOONGARCH_CSR_KS0
    csrwr t1, LOONGARCH_CSR_KS1
    csrwr ra, EXCEPTION_KS2

    csrrd t0, LOONGARCH_CSR_PGD
    csrrd t1, LOONGARCH_CSR_BADV
    srli.w t1, t1, 0x16
    slli.w t1, t1, 0x2
    add.w t0, t0, t1
    ld.w t0, t0, 0

    csrrd t1, LOONGARCH_CSR_BADV
    srli.w t1, t1, 0xa
    andi t1, t1, 0xff8
    add.w t0, t0, t1

    ld.w t1, t0, 0
    srli.w ra, t1, 0xc
    slli.w ra, ra, 0x8
    andi t1, t1, 0xff
    add.w t1, t1, ra
    csrwr t1, LOONGARCH_CSR_TLBELO0

    ld.w t1, t0, 0x4
    srli.w ra, t1, 0xc
    slli.w ra, ra, 0x8
    andi t1, t1, 0xff
    add.w t1, t1, ra
    csrwr t1, LOONGARCH_CSR_TLBELO1

    tlbfill
    csrrd t0, LOONGARCH_CSR_KS0
    csrrd t1, LOONGARCH_CSR_KS1
    csrrd ra, EXCEPTION_KS2
    ertn
    SYM_FUNC_END(handle_tlb_refill)
  • TLBMISS_HANDLER_SETUP():主要工作是建立内核页全局目录的基地址。

trap_init()

其余的例外在start_kernel()中的trap_init()中。

trap_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void __init trap_init(void)
{
long i;
void *vec_start;

/* Initialise exception handlers */
for (i = 0; i < 64; i++)
set_handler(i * vec_size, handle_reserved, vec_size);

/* Set interrupt vector handler */
for (i = EXCCODE_INT_START; i < EXCCODE_INT_END; i++) {
vec_start = vi_table[i - EXCCODE_INT_START];
set_handler(i * vec_size, vec_start, vec_size);
}
#ifdef CONFIG_32BIT
set_handler(EXCCODE_GENERIC * vec_size , except_vec_vi_handler, vec_size);
#endif

set_handler(EXCCODE_TLBL * vec_size, handle_tlb_load, vec_size);
set_handler(EXCCODE_TLBS * vec_size, handle_tlb_store, vec_size);
set_handler(EXCCODE_TLBI * vec_size, handle_tlb_load, vec_size);
set_handler(EXCCODE_TLBM * vec_size, handle_tlb_modify, vec_size);
set_handler(EXCCODE_TLBRI * vec_size, handle_tlb_rixi, vec_size);
set_handler(EXCCODE_TLBXI * vec_size, handle_tlb_rixi, vec_size);
set_handler(EXCCODE_ADE * vec_size, handle_ade, vec_size);
set_handler(EXCCODE_ALE * vec_size, handle_ale, vec_size);
set_handler(EXCCODE_SYS * vec_size, handle_syscall, vec_size);
set_handler(EXCCODE_BP * vec_size, handle_bp, vec_size);
set_handler(EXCCODE_INE * vec_size, handle_ri, vec_size);
set_handler(EXCCODE_IPE * vec_size, handle_ri, vec_size);
set_handler(EXCCODE_FPDIS * vec_size, handle_fpu, vec_size);
set_handler(EXCCODE_LSXDIS * vec_size, handle_lsx, vec_size);
set_handler(EXCCODE_LASXDIS * vec_size, handle_lasx, vec_size);
set_handler(EXCCODE_FPE * vec_size, handle_fpe, vec_size);
set_handler(EXCCODE_BTDIS * vec_size, handle_lbt, vec_size);
set_handler(EXCCODE_WATCH * vec_size, handle_watch, vec_size);

cache_error_setup();

local_flush_icache_range(eentry, eentry + 0x400);
}

except_vec_vi_handler:32位LOONGARCH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
SYM_FUNC_START(except_vec_vi_handler)

csrwr t0, LOONGARCH_CSR_KS0
csrwr t1, LOONGARCH_CSR_KS1

csrrd t0, LOONGARCH_CSR_ESTAT
srli.w t1, t0, 0x10 /* get Ecode */
andi t1, t1, 0x3f
beq t1, zero, 1f /* if irq */

csrrd t0, LOONGARCH_CSR_EENTRY
slli.w t1, t1, 0x9 /* get ex entry shift = Ecode * vec_size */
add.w t0, t0, t1
lu12i.w t1, 0x4
sub.w t0, t0, t1
jirl zero,t0 , 0 /* go to exception_handler */

1:
SAVE_ALL docfi=0
CLI
TRACE_IRQS_OFF

ld.w s0, tp, TI_REGS
st.w sp, tp, TI_REGS
move s1, sp

csrrd t0, LOONGARCH_CSR_TMID
la.abs t1, irq_stack
slli.w t0, t0, LONGLOG
ld.w t0, t1, 0

li t1, ~(_THREAD_SIZE-1)
and t1, t1, sp
beq t0, t1, 2f

li t1, _IRQ_STACK_START
add.w sp, t0, t1
st.w s1,sp, 0

2:
la.abs t0, plat_irq_dispatch
jirl ra, t0, 0

move sp, s1

la.abs t0, ret_from_irq
jirl zero, t0, 0

SYM_FUNC_END(except_vec_vi_handler)

中断请求相关

start_kernel()中的init_IRQ()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __init init_IRQ(void)
{
int i;
unsigned int order = get_order(IRQ_STACK_SIZE);

for (i = 0; i < NR_IRQS; i++)
irq_set_noprobe(i);

arch_init_irq();

for_each_possible_cpu(i) {
void *s = (void *)__get_free_pages(GFP_KERNEL, order);

per_cpu(irq_stack, i) = (unsigned long)s;
pr_debug("CPU%d IRQ stack at 0x%lx - 0x%lx\n", i,
per_cpu(irq_stack, i), per_cpu(irq_stack, i) + IRQ_STACK_SIZE);
}
}

调用arch_init_irq()

1
2
3
4
5
6
7
8
9
10
11
12
void __init arch_init_irq(void)
{
clear_csr_ecfg(ECFG0_IM);
clear_csr_estat(ESTATF_IP);

setup_IRQ();
#ifdef CONFIG_SMP
set_vi_handler(EXCCODE_IPI, loongson3_ipi_interrupt);
#endif

set_csr_ecfg(ECFGF_IP0 | ECFGF_IP1 | ECFGF_IPI | ECFGF_PC);
}

setup_IRQ():这一步的主要工作就是初始化通过 Device-Tree描述的中断控制器(即“IRQ 芯片”,数据结构用 irq_chip 描述)。

1
2
3
4
void __init setup_IRQ(void)
{
irqchip_init();
}