setup_arch()源码分析
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_arg0~fw_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) {} #endifcpu_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_refillvoid 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();
}