【linux kernel】start_kernel详解系列之【setup_arch】

start_kernel详解系列之【setup_arch】

文章目录

        • start_kernel详解系列之【setup_arch】
          • 一、开篇
          • 二、setup_arch函数分析
            • (2-1)配置处理器
            • (2-2)设置machine_desc结构变量参数和machine_name字符串
            • (2-3)设置init_mm结构的参数
            • (2-4)备份命令行参数
            • (2-5)设置linux启动早期参数
            • (2-6)页表初始化
            • (2-7)请求标准资源
            • (2-8)创建设备树节点
            • (2-9)构建cpu逻辑映射关系
            • (2-10)psci初始化
            • (2-11)对称多处理器下的初始化
            • (2-12)架构早期初始化
          • 三、结尾

一、开篇

​ 路一直走,现来到了setup_arch这个重磅函数,该函数是一个架构相关函数。文本以ARM架构,linux版本4.1.15为例,来分析该函数。先来一份该函数的概览:

函数定义如下(/arch/arm/setup.c):

void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	machine_desc = mdesc;
	machine_name = mdesc->name;
	dump_stack_set_arch_desc("%s", mdesc->name);

	if (mdesc->reboot_mode != REBOOT_HARD)
		reboot_mode = mdesc->reboot_mode;

	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code   = (unsigned long) _etext;
	init_mm.end_data   = (unsigned long) _edata;
	init_mm.brk	   = (unsigned long) _end;

	/* populate cmd_line too for later use, preserving boot_command_line */
	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
	*cmdline_p = cmd_line;

	parse_early_param();

	early_paging_init(mdesc, lookup_processor_type(read_cpuid_id()));
	setup_dma_zone(mdesc);
	sanity_check_meminfo();
	arm_memblock_init(mdesc);

	paging_init(mdesc);
	request_standard_resources(mdesc);

	if (mdesc->restart)
		arm_pm_restart = mdesc->restart;

	unflatten_device_tree();

	arm_dt_init_cpu_maps();
	psci_init();
#ifdef CONFIG_SMP
	if (is_smp()) {
		if (!mdesc->smp_init || !mdesc->smp_init()) {
			if (psci_smp_available())
				smp_set_ops(&psci_smp_ops);
			else if (mdesc->smp)
				smp_set_ops(mdesc->smp);
		}
		smp_init_cpus();
		smp_build_mpidr_hash();
	}
#endif

	if (!is_smp())
		hyp_mode_check();

	reserve_crashkernel();

#ifdef CONFIG_MULTI_IRQ_HANDLER
	handle_arch_irq = mdesc->handle_irq;
#endif

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
	conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
	conswitchp = &dummy_con;
#endif
#endif

	if (mdesc->init_early)
		mdesc->init_early();
}

以上代码为setup_arch()函数的具体定义,函数内容比较长,下文将分析具体的函数功能和作用。


二、setup_arch函数分析
(2-1)配置处理器

配置处理器由setup_processor()完成,定义如下(/arch/arm/kernel/setup.c):

static void __init setup_processor(void)
{
	struct proc_info_list *list;

	/*
	 * locate processor in the list of supported processor
	 * types.  The linker builds this table for us from the
	 * entries in arch/arm/mm/proc-*.S
	 */
	list = lookup_processor_type(read_cpuid_id());
	if (!list) {
		pr_err("CPU configuration botched (ID %08x), unable to continue.\n",
		       read_cpuid_id());
		while (1);
	}

	cpu_name = list->cpu_name;
	__cpu_architecture = __get_cpu_architecture();

#ifdef MULTI_CPU
	processor = *list->proc;
#endif
#ifdef MULTI_TLB
	cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
	cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
	cpu_cache = *list->cache;
#endif

	pr_info("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
		cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
		proc_arch[cpu_architecture()], get_cr());

	snprintf(init_utsname()->machine, __NEW_UTS_LEN + 1, "%s%c",
		 list->arch_name, ENDIANNESS);
	snprintf(elf_platform, ELF_PLATFORM_SIZE, "%s%c",
		 list->elf_name, ENDIANNESS);
	elf_hwcap = list->elf_hwcap;

	cpuid_init_hwcaps();

#ifndef CONFIG_ARM_THUMB
	elf_hwcap &= ~(HWCAP_THUMB | HWCAP_IDIVT);
#endif
#ifdef CONFIG_MMU
	init_default_cache_policy(list->__cpu_mm_mmu_flags);
#endif
	erratum_a15_798181_init();

	elf_hwcap_fixup();

	cacheid_init();
	cpu_init();
}

(2-2)设置machine_desc结构变量参数和machine_name字符串

setup_processor()函数运行完成后,会调用setup_machine_fdt()函数解析struct machine_desc,该变量在linux内核中用于描述具体的架构信息和设置具体架构下的操作函数。这里传入该函数的参数是设备树blob的物理地址,在linux arm架构下,这里传入的参数为__atags_pointer。定义如下(/arch/arm/kernel/head-common.S):
【linux kernel】start_kernel详解系列之【setup_arch】_第1张图片

str r2 ,[r6]	@将R2寄存器的值,传送到地址为r6的(存储器)内存中。
	.long	__atags_pointer			@ r6

setup_machine_fdt()函数定义如下(/arch/arm/kernel/devtree.c):

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	const struct machine_desc *mdesc, *mdesc_best = NULL;

#ifdef CONFIG_ARCH_MULTIPLATFORM
	DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
	MACHINE_END

	mdesc_best = &__mach_desc_GENERIC_DT;
#endif

	if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
		return NULL;
	//调用of_flat_dt_match_machine()函数解析mdesc。该函数将迭代匹配表,去查找匹配的machine。
    //【mdesc_best】:在没有匹配的情况下返回特定于机器的ptr。
    //【arch_get_next_mach】:回调函数。用于返回下一个兼容匹配表。
	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
	//如果mdesc为null,将执行该if判断语句。
	if (!mdesc) {
		const char *prop;
		int size;
		unsigned long dt_root;

		early_print("\nError: unrecognized/unsupported "
			    "device tree compatible list:\n[ ");

		dt_root = of_get_flat_dt_root();
		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		while (size > 0) {
			early_print("'%s' ", prop);
			size -= strlen(prop) + 1;
			prop += strlen(prop) + 1;
		}
		early_print("]\n\n");

		dump_machine_table(); /* does not return */
	}

	/* 有时候firmware可能会提供错误的数据 ,所以需要dt_fixup函数去处理 */
	if (mdesc->dt_fixup)
		mdesc->dt_fixup();

	early_init_dt_scan_nodes();

	/* 更改machine号以匹配我们正在使用的mdesc */
	__machine_arch_type = mdesc->nr;

	return mdesc;
}

setup_machine_fdt()用于解析传递给内核的dtb,并设置machine变量参数。如果在r2中将一个dtb传递给内核,那么内核将使用它来选择正确的machine_desc并设置系统。在ARM架构下,高版本的linux内核已全面支持设备树并以该种方式解析machine_desc并设置系统。


对设备树启动结构的解析由函数arch_get_next_mach()完成。在实际的设备树驱动中,这里以NXP的imx6ull处理器为例,其驱动源码会使用设备树来描述处理器相关的信息,如下代码定义:

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
	.map_io		= imx6ul_map_io,
	.init_irq	= imx6ul_init_irq,
	.init_machine	= imx6ul_init_machine,
	.init_late	= imx6ul_init_late,
	.dt_compat	= imx6ul_dt_compat,
MACHINE_END

从以上代码片段可见,使用了两个重要的宏定义:
DT_MACHINE_START宏定义将定义一个static const struct machine_desc __mach_desc_xxx的变量,并指定struct machine_desc结构中的相关数据成员,并将其放置于.arch.info.init初始化节段中。

arch_get_next_mach()函数中,会去遍历.arch.info.init节段,取出放置于里面的struct machine_desc进行操作。该函数具体实现如下(/arch/arm/kernel/devtree.c):

static const void * __init arch_get_next_mach(const char *const **match)
{
	static const struct machine_desc *mdesc = __arch_info_begin;
	const struct machine_desc *m = mdesc;

	if (m >= __arch_info_end)
		return NULL;

	mdesc++;
	*match = m->dt_compat;

	return m;
}

上述代码中,__arch_info_begin__arch_info_end分别表示.arch.info.init节段的开始和结束,声明如下:

extern const struct machine_desc __arch_info_begin[], __arch_info_end[];

定义如下(/arch/arm/kernel/vmlinux.lds):

 .init.arch.info : {
  __arch_info_begin = .;
  *(.arch.info.init)
  __arch_info_end = .;
 }
(2-3)设置init_mm结构的参数

init_mm是一个全局的结构变量,定义如下(/mm/init-mm.c):

struct mm_struct init_mm = {
	.mm_rb		= RB_ROOT,
	.pgd		= swapper_pg_dir,
	.mm_users	= ATOMIC_INIT(2),
	.mm_count	= ATOMIC_INIT(1),
	.mmap_sem	= __RWSEM_INITIALIZER(init_mm.mmap_sem),
	.page_table_lock =  __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
	.mmlist		= LIST_HEAD_INIT(init_mm.mmlist),
	INIT_MM_CONTEXT(init_mm)
};

setup_arch()函数会设置init_mm结构的几个变量参数,代码如下所示:

init_mm.start_code = (unsigned long) _text;
init_mm.end_code   = (unsigned long) _etext;
init_mm.end_data   = (unsigned long) _edata;
init_mm.brk	   = (unsigned long) _end;

(2-4)备份命令行参数

setup_arch()函数中还将备份命令行参数,见以下代码:

strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

boot_command_line变量代表的是linux的启动命令行参数,该参数值是在setup_machine_fdt()函数运行完成后将被赋值。在该函数下实际完成工作的是early_init_dt_scan_nodes()函数(/drivers/of/fdt.c):

void __init early_init_dt_scan_nodes(void)
{
	/* Retrieve various information from the /chosen node */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

	/* Initialize {size,address}-cells info */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

	/* Setup memory, calling early_init_dt_add_memory_arch */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

当上述第4行代码执行完成后,boot_command_line将被赋值。


(2-5)设置linux启动早期参数

setup_arch()的形参为char **cmdline_p,linux内核启动过程中,在start_kernel()函数中调用该函数时传入的参数是:command_line参数的地址。故在setup_arch()函数中将使用以下语句:

*cmdline_p = cmd_line;

把linux内核的启动参数设置到start_kernel()函数中定义的command_line变量。


(2-6)页表初始化

页表初始化是一个架构相关函数,由paging_init()函数完成,定义如下:

void __init paging_init(const struct machine_desc *mdesc)
{
	void *zero_page;
    
	//1、根据当前CPU的使用情况,调整PMD的section表项。
	build_mem_type_table();
    //2、准备页page页表。
	prepare_page_table();
    //3、低端内存映射。
	map_lowmem();
    //4、dma映射
	dma_contiguous_remap();
    //5、设置设备映射。该函数主要用于异常向量表空间地址的重定向和复制。
	devicemaps_init(mdesc);
	kmap_init();
    //6、初始化TCM内存。
	tcm_init();
	//7、设置最上层page集的pmd表的地址。
	top_pmd = pmd_off_k(0xffff0000);

	//8、分配零页。
	zero_page = early_alloc(PAGE_SIZE);
	//9、启动内存初始化。
	bootmem_init();

	empty_zero_page = virt_to_page(zero_page);
	__flush_dcache_page(NULL, empty_zero_page);
}

paging_init()函数用于设置页表,初始化zone内存映射,并设置零页、坏页和坏页表等。具体的操作过程如下:

1、根据当前CPU的使用情况,调整PMD的section表项。

2、准备page页表。

3、低端内存映射。

4、DMA映射。

5、设置设备映射。该函数主要用于异常向量表空间地址的重定向和复制。

6、初始化TCM内存。

7、设置最上层page集的pmd表的地址。

8、分配零页。

9、启动内存初始化。


(2-7)请求标准资源

linux内核中使用struct resource描述其资源,定义如下(/include/linu/ioport.h):

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

资源是树形的,允许嵌套。

setup_arch()函数中使用request_standard_resources()函数来请求标准的资源。该函数定义如下(/arch/arm/kernel/setup.c):

static void __init request_standard_resources(const struct machine_desc *mdesc)
{
	struct memblock_region *region;
	struct resource *res;

	kernel_code.start   = virt_to_phys(_text);
	kernel_code.end     = virt_to_phys(_etext - 1);
	kernel_data.start   = virt_to_phys(_sdata);
	kernel_data.end     = virt_to_phys(_end - 1);

	for_each_memblock(memory, region) {
		res = memblock_virt_alloc(sizeof(*res), 0);
		res->name  = "System RAM";
		res->start = __pfn_to_phys(memblock_region_memory_base_pfn(region));
		res->end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;
		res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;

		request_resource(&iomem_resource, res);

		if (kernel_code.start >= res->start &&
		    kernel_code.end <= res->end)
			request_resource(res, &kernel_code);
		if (kernel_data.start >= res->start &&
		    kernel_data.end <= res->end)
			request_resource(res, &kernel_data);
	}

	if (mdesc->video_start) {
		video_ram.start = mdesc->video_start;
		video_ram.end   = mdesc->video_end;
		request_resource(&iomem_resource, &video_ram);
	}

	/*
	 * Some machines don't have the possibility of ever
	 * possessing lp0, lp1 or lp2
	 */
	if (mdesc->reserve_lp0)
		request_resource(&ioport_resource, &lp0);
	if (mdesc->reserve_lp1)
		request_resource(&ioport_resource, &lp1);
	if (mdesc->reserve_lp2)
		request_resource(&ioport_resource, &lp2);
}

(2-8)创建设备树节点

setup_arch()函数中调用unflatten_device_tree()函数从设备树中创建设备树的device_nodes设备节点。该函数定义如下(/drivers/of/fdt.c):

void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, &of_root,
				early_init_dt_alloc_memory_arch);

	/* 获取指向“/chosen”和“/alias”节点的指针,以便在任何地方使用 */
	of_alias_scan(early_init_dt_alloc_memory_arch);
}

(2-9)构建cpu逻辑映射关系

​ linux内核启动过程中需要构建和初始化cpu的逻辑映射,在ARM架构下,该部分操作由arm_dt_init_cpu_maps()函数完成。该函数具体功能是:从设备树中检索cpu节点,并构建一个包含与逻辑cpu相关的MPIDR值的cpu逻辑映射数组。使用解析的cpu节点数更新cpu possible mask。如下代码创建了一个局部的tmp_map数组,并使用MPIDR_INVALID值初始化数组中所有元素(即将数组中的所有元素的值设置为MPIDR_INVALID)。

	u32 tmp_map[NR_CPUS] = { [0 ... NR_CPUS-1] = MPIDR_INVALID };

CPU逻辑映射数组定义如下(/arch/arm/include/asm/smp_plat.h):

#define cpu_logical_map(cpu)	__cpu_logical_map[cpu]

arm_dt_init_cpu_maps()函数中将设置__cpu_logical_map数组的值,如下代码片段:

	for (i = 0; i < cpuidx; i++) {
		set_cpu_possible(i, true);
        //将tmp_map数组的值赋值给__cpu_logical_map数组。
		cpu_logical_map(i) = tmp_map[i];
		pr_debug("cpu logical map 0x%x\n", cpu_logical_map(i));
	}

(2-10)psci初始化

psci是(Power State Coordination Interface)的缩写,是一个由ARM定义的电源管理接口规范。因为本篇文章分析的setup_arch()函数是基于ARM架构,所以setup_arch()函数中会调用psci_init()函数初始化psci,此处不再展开分析了。


(2-11)对称多处理器下的初始化

​ linux-4.1.15版本下setup_arch()函数将调用以下代码进行smp相关的初始化:

	if (is_smp()) {
		if (!mdesc->smp_init || !mdesc->smp_init()) {
			if (psci_smp_available())
				smp_set_ops(&psci_smp_ops);
			else if (mdesc->smp)
				smp_set_ops(mdesc->smp);
		
		}
		smp_init_cpus();
		smp_build_mpidr_hash();
	}

​ 如果mdesc结构变量中没有指定用于初始化smp的初始化函数smp_init或者该函数返回false,那么将重新设置smp的操作集合struct smp_operations

​ 接着调用smp_init_cpus()函数初始化smp。调用smp_build_mpidr_hash()函数预先计算smp每个亲和度(affinity)所需的偏移量。


(2-12)架构早期初始化

​ 如果struct machine_desc *mdesc结构变量中设置了init_early早期初始化函数,那么在setup_arch()中将调用所设置的init_early函数执行早期的初始化。如下代码片段:

	if (mdesc->init_early)
		mdesc->init_early();

三、结尾

​至此,在ARM架构下,对于setup_arch()函数的分析就完毕了。内容不算深入,请容许小生后续深入补充吧,O(∩_∩)O哈哈~。


小生由于精力和知识有限,如遇分享的文章存在不妥的地方,请多多批评,也可与我一起讨论。!


搜索关注【嵌入式小生】wx公众号获取更多精彩内容>>>>

你可能感兴趣的:(小生聊【linux,kernel】,linux,kernel,linux,ARM,C语言,设备树)