setup_arch()

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

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()

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() 进 一 步 处 理 。

    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 中。

    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");
    }
    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。

    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():控制台相关初始化

    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 所在的内存段设置为保留。

    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)

/*
 * 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 两个函数指针)是同芯片组相关的:

    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。

    #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。

    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()

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():配置例外向量

    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

    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

    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()

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

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()

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()

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 描述)。

void __init setup_IRQ(void)
{
        irqchip_init();
}

标签: linux, kernel

添加新评论