setup_arch()
start_kernel()
中第一个重要的、体系结构密切相关的函数是 setup_arch()
,定义在 arch/loongarch/kernel/setup.c
中。
1 | void __init setup_arch(char **cmdline_p) |
cpu_probe()
用来探测 CPU 类型、ID号、版本等等。探测的主要依据是 PRID 寄存器,即协处理器 0 中的 15 号寄存器。PRID 是一个 32 位寄存器,最高 8 位保留,次高 8 位是公司 ID,第三个 8 位是处理器 ID,最后一个 8 位是修订号 ID。
early_init()
1 | void __init early_init(void) |
fw_init_cmdline()
:还记得kernel_entry
里面保存的fw_arg0
~fw_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
15void __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
13void __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
25struct 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
23void __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
29void __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();
acpi_table_upgrade();
acpi_gbl_use_default_register_widths = false;
acpi_boot_table_init();
acpi_boot_init();
fw_init_memory();
fw_init_numa_memory();
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
52static 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 | /* |
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
8void __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
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;
}
static inline void prefill_possible_map(void) {}cpu_cache_init()
在龙芯处理器手册中,将 P-Cache 称之为一级 Cache,将 V-Cache 称之为二级 Cache,将 S-Cache 称之为三级 Cache。
1
2
3
4
5
6
7
8
9
10void cpu_cache_init(void)
{
probe_pcache();
probe_vcache();
probe_scache();
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 | void __init boot_cpu_trap_init(void) |
vec_size
:向量大小eentry
:通用例外入口地址tlbrentry
:tlb refill例外入口地址configure_exception_vector()
:配置例外向量1
2
3
4
5
6
7
8
9
10
11
12
13static void configure_exception_vector(void)
{
csr_writeq(eentry, LOONGARCH_CSR_EENTRY);
csr_writeq(eentry, LOONGARCH_CSR_MERRENTRY);
csr_writeq(tlbrentry, LOONGARCH_CSR_TLBRENTRY);
csr_writel(eentry + 0x4000, LOONGARCH_CSR_EENTRY);
csr_writel(tlbrentry, LOONGARCH_CSR_TLBRENTRY);
}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
27void 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);
write_csr_stlbpgsize(PS_DEFAULT_SIZE);
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
37SYM_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 | void __init trap_init(void) |
except_vec_vi_handler
:32位LOONGARCH
1 | SYM_FUNC_START(except_vec_vi_handler) |
中断请求相关
start_kernel()
中的init_IRQ()
1 | void __init init_IRQ(void) |
调用arch_init_irq()
1 | void __init arch_init_irq(void) |
setup_IRQ()
:这一步的主要工作就是初始化通过 Device-Tree描述的中断控制器(即“IRQ 芯片”,数据结构用 irq_chip 描述)。
1 | void __init setup_IRQ(void) |