路一直走,现来到了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_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();
}
在setup_processor()
函数运行完成后,会调用setup_machine_fdt()
函数解析struct machine_desc
,该变量在linux内核中用于描述具体的架构信息和设置具体架构下的操作函数。这里传入该函数的参数是设备树blob的物理地址,在linux arm架构下,这里传入的参数为__atags_pointer
。定义如下(/arch/arm/kernel/head-common.S):
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 = .;
}
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;
在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
将被赋值。
setup_arch()
的形参为char **cmdline_p,linux内核启动过程中,在start_kernel()函数中调用该函数时传入的参数是:command_line参数的地址。故在setup_arch()
函数中将使用以下语句:
*cmdline_p = cmd_line;
把linux内核的启动参数设置到start_kernel()
函数中定义的command_line
变量。
页表初始化是一个架构相关函数,由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、启动内存初始化。
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);
}
在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);
}
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));
}
psci是(Power State Coordination Interface)的缩写,是一个由ARM定义的电源管理接口规范。因为本篇文章分析的setup_arch()
函数是基于ARM架构,所以setup_arch()
函数中会调用psci_init()
函数初始化psci,此处不再展开分析了。
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)所需的偏移量。
如果struct machine_desc *mdesc
结构变量中设置了init_early
早期初始化函数,那么在setup_arch()
中将调用所设置的init_early函数执行早期的初始化。如下代码片段:
if (mdesc->init_early)
mdesc->init_early();
至此,在ARM架构下,对于setup_arch()
函数的分析就完毕了。内容不算深入,请容许小生后续深入补充吧,O(∩_∩)O哈哈~。
小生由于精力和知识有限,如遇分享的文章存在不妥的地方,请多多批评,也可与我一起讨论。!
搜索关注【嵌入式小生】wx公众号获取更多精彩内容>>>>