/* * Copyright 2014, General Dynamics C4 Systems * * SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ARM_SMMU #include #endif /* pointer to the end of boot code/data in kernel image */ /* need a fake array to get the pointer from the linker script */ extern char ki_boot_end[1]; /* pointer to end of kernel image */ extern char ki_end[1]; #ifdef ENABLE_SMP_SUPPORT /* sync variable to prevent other nodes from booting * until kernel data structures initialized */ BOOT_DATA static volatile int node_boot_lock = 0; #endif /* ENABLE_SMP_SUPPORT */ #define ARCH_RESERVED 3 // kernel + user image + dtb #define MAX_RESERVED (ARCH_RESERVED + MODE_RESERVED) BOOT_DATA static region_t reserved[MAX_RESERVED]; BOOT_CODE static void arch_init_freemem(p_region_t ui_p_reg, p_region_t dtb_p_reg, v_region_t it_v_reg, word_t extra_bi_size_bits) { reserved[0].start = KERNEL_ELF_BASE; reserved[0].end = (pptr_t)ki_end; int index = 1; if (dtb_p_reg.start) { /* the dtb region could be empty */ reserved[index].start = (pptr_t) paddr_to_pptr(dtb_p_reg.start); reserved[index].end = (pptr_t) paddr_to_pptr(dtb_p_reg.end); index++; } if (MODE_RESERVED > 1) { printf("MODE_RESERVED > 1 unsupported!\n"); halt(); } if (ui_p_reg.start < PADDR_TOP) { region_t ui_reg = paddr_to_pptr_reg(ui_p_reg); if (MODE_RESERVED == 1) { if (ui_reg.end > mode_reserved_region[0].start) { reserved[index] = mode_reserved_region[0]; index++; reserved[index].start = ui_reg.start; reserved[index].end = ui_reg.end; } else { reserved[index].start = ui_reg.start; reserved[index].end = ui_reg.end; index++; reserved[index] = mode_reserved_region[0]; } index++; } else { reserved[index].start = ui_reg.start; reserved[index].end = ui_reg.end; index++; } } else if (MODE_RESERVED == 1) { reserved[index] = mode_reserved_region[0]; index++; } init_freemem(get_num_avail_p_regs(), get_avail_p_regs(), index, reserved, it_v_reg, extra_bi_size_bits); } BOOT_CODE static void init_irqs(cap_t root_cnode_cap) { unsigned i; for (i = 0; i <= maxIRQ ; i++) { setIRQState(IRQInactive, CORE_IRQ_TO_IRQT(0, i)); } setIRQState(IRQTimer, CORE_IRQ_TO_IRQT(0, KERNEL_TIMER_IRQ)); #ifdef CONFIG_ARM_HYPERVISOR_SUPPORT setIRQState(IRQReserved, CORE_IRQ_TO_IRQT(0, INTERRUPT_VGIC_MAINTENANCE)); setIRQState(IRQReserved, CORE_IRQ_TO_IRQT(0, INTERRUPT_VTIMER_EVENT)); #endif #ifdef CONFIG_TK1_SMMU setIRQState(IRQReserved, CORE_IRQ_TO_IRQT(0, INTERRUPT_SMMU)); #endif #ifdef CONFIG_ARM_ENABLE_PMU_OVERFLOW_INTERRUPT #ifdef KERNEL_PMU_IRQ setIRQState(IRQReserved, CORE_IRQ_TO_IRQT(0, KERNEL_PMU_IRQ)); #if (defined CONFIG_PLAT_TX1 && defined ENABLE_SMP_SUPPORT) //SELFOUR-1252 #error "This platform doesn't support tracking CPU utilisation on multicore" #endif /* CONFIG_PLAT_TX1 && ENABLE_SMP_SUPPORT */ #else #error "This platform doesn't support tracking CPU utilisation feature" #endif /* KERNEL_TIMER_IRQ */ #endif /* CONFIG_ARM_ENABLE_PMU_OVERFLOW_INTERRUPT */ #ifdef ENABLE_SMP_SUPPORT setIRQState(IRQIPI, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), irq_remote_call_ipi)); setIRQState(IRQIPI, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), irq_reschedule_ipi)); #endif /* provide the IRQ control cap */ write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapIRQControl), cap_irq_control_cap_new()); } #ifdef CONFIG_ARM_SMMU BOOT_CODE static void init_smmu(cap_t root_cnode_cap) { plat_smmu_init(); /*provide the SID and CB control cap*/ write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapSMMUSIDControl), cap_sid_control_cap_new()); write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapSMMUCBControl), cap_cb_control_cap_new()); } #endif BOOT_CODE static bool_t create_untypeds(cap_t root_cnode_cap, region_t boot_mem_reuse_reg) { seL4_SlotPos slot_pos_before; seL4_SlotPos slot_pos_after; slot_pos_before = ndks_boot.slot_pos_cur; create_device_untypeds(root_cnode_cap, slot_pos_before); create_kernel_untypeds(root_cnode_cap, boot_mem_reuse_reg, slot_pos_before); slot_pos_after = ndks_boot.slot_pos_cur; ndks_boot.bi_frame->untyped = (seL4_SlotRegion) { slot_pos_before, slot_pos_after }; return true; } /** This and only this function initialises the CPU. * * It does NOT initialise any kernel state. * @return For the verification build, this currently returns true always. */ BOOT_CODE static bool_t init_cpu(void) { bool_t haveHWFPU; #ifdef CONFIG_ARCH_AARCH64 if (config_set(CONFIG_ARM_HYPERVISOR_SUPPORT)) { if (!checkTCR_EL2()) { return false; } } #endif activate_global_pd(); if (config_set(CONFIG_ARM_HYPERVISOR_SUPPORT)) { vcpu_boot_init(); } #ifdef CONFIG_HARDWARE_DEBUG_API if (!Arch_initHardwareBreakpoints()) { printf("Kernel built with CONFIG_HARDWARE_DEBUG_API, but this board doesn't " "reliably support it.\n"); return false; } #endif /* Setup kernel stack pointer. * On ARM SMP, the array index here is the CPU ID */ #ifndef CONFIG_ARCH_ARM_V6 word_t stack_top = ((word_t) kernel_stack_alloc[SMP_TERNARY(getCurrentCPUIndex(), 0)]) + BIT(CONFIG_KERNEL_STACK_BITS); #if defined(ENABLE_SMP_SUPPORT) && defined(CONFIG_ARCH_AARCH64) /* the least 12 bits are used to store logical core ID */ stack_top |= getCurrentCPUIndex(); #endif setKernelStack(stack_top); #endif /* CONFIG_ARCH_ARM_V6 */ #ifdef CONFIG_ARCH_AARCH64 /* initialise CPU's exception vector table */ setVtable((pptr_t)arm_vector_table); #endif /* CONFIG_ARCH_AARCH64 */ haveHWFPU = fpsimd_HWCapTest(); /* Disable FPU to avoid channels where a platform has an FPU but doesn't make use of it */ if (haveHWFPU) { disableFpu(); } #ifdef CONFIG_HAVE_FPU if (haveHWFPU) { if (!fpsimd_init()) { return false; } } else { printf("Platform claims to have FP hardware, but does not!"); return false; } #endif /* CONFIG_HAVE_FPU */ cpu_initLocalIRQController(); #ifdef CONFIG_ENABLE_BENCHMARKS arm_init_ccnt(); #endif /* CONFIG_ENABLE_BENCHMARKS */ /* Export selected CPU features for access by PL0 */ armv_init_user_access(); initTimer(); return true; } /* This and only this function initialises the platform. It does NOT initialise any kernel state. */ BOOT_CODE static void init_plat(void) { initIRQController(); initL2Cache(); #ifdef CONFIG_ARM_SMMU plat_smmu_init(); #endif } #ifdef ENABLE_SMP_SUPPORT BOOT_CODE static bool_t try_init_kernel_secondary_core(void) { unsigned i; /* need to first wait until some kernel init has been done */ while (!node_boot_lock); /* Perform cpu init */ init_cpu(); for (i = 0; i < NUM_PPI; i++) { maskInterrupt(true, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), i)); } setIRQState(IRQIPI, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), irq_remote_call_ipi)); setIRQState(IRQIPI, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), irq_reschedule_ipi)); /* Enable per-CPU timer interrupts */ setIRQState(IRQTimer, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), KERNEL_TIMER_IRQ)); #ifdef CONFIG_ARM_HYPERVISOR_SUPPORT setIRQState(IRQReserved, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), INTERRUPT_VGIC_MAINTENANCE)); setIRQState(IRQReserved, CORE_IRQ_TO_IRQT(getCurrentCPUIndex(), INTERRUPT_VTIMER_EVENT)); #endif /* CONFIG_ARM_HYPERVISOR_SUPPORT */ NODE_LOCK_SYS; ksNumCPUs++; init_core_state(SchedulerAction_ResumeCurrentThread); return true; } BOOT_CODE static void release_secondary_cpus(void) { /* release the cpus at the same time */ node_boot_lock = 1; #ifndef CONFIG_ARCH_AARCH64 /* At this point in time the other CPUs do *not* have the seL4 global pd set. * However, they still have a PD from the elfloader (which is mapping mmemory * as strongly ordered uncached, as a result we need to explicitly clean * the cache for it to see the update of node_boot_lock * * For ARMv8, the elfloader sets the page table entries as inner shareable * (so is the attribute of the seL4 global PD) when SMP is enabled, and * turns on the cache. Thus, we do not need to clean and invaliate the cache. */ cleanInvalidateL1Caches(); plat_cleanInvalidateL2Cache(); #endif /* Wait until all the secondary cores are done initialising */ while (ksNumCPUs != CONFIG_MAX_NUM_NODES) { /* perform a memory release+acquire to get new values of ksNumCPUs */ __atomic_signal_fence(__ATOMIC_ACQ_REL); } } #endif /* ENABLE_SMP_SUPPORT */ /* Main kernel initialisation function. */ static BOOT_CODE bool_t try_init_kernel( paddr_t ui_p_reg_start, paddr_t ui_p_reg_end, sword_t pv_offset, vptr_t v_entry, paddr_t dtb_addr_start, paddr_t dtb_addr_end ) { cap_t root_cnode_cap; cap_t it_ap_cap; cap_t it_pd_cap; cap_t ipcbuf_cap; p_region_t ui_p_reg = (p_region_t) { ui_p_reg_start, ui_p_reg_end }; region_t ui_reg = paddr_to_pptr_reg(ui_p_reg); region_t dtb_reg; word_t extra_bi_size; pptr_t extra_bi_offset = 0; vptr_t extra_bi_frame_vptr; vptr_t bi_frame_vptr; vptr_t ipcbuf_vptr; create_frames_of_region_ret_t create_frames_ret; create_frames_of_region_ret_t extra_bi_ret; /* convert from physical addresses to userland vptrs */ v_region_t ui_v_reg; v_region_t it_v_reg; ui_v_reg.start = ui_p_reg_start - pv_offset; ui_v_reg.end = ui_p_reg_end - pv_offset; ipcbuf_vptr = ui_v_reg.end; bi_frame_vptr = ipcbuf_vptr + BIT(PAGE_BITS); extra_bi_frame_vptr = bi_frame_vptr + BIT(PAGE_BITS); /* If no DTB was provided, skip allocating extra bootinfo */ p_region_t dtb_p_reg = { dtb_addr_start, ROUND_UP(dtb_addr_end, PAGE_BITS) }; if (dtb_addr_start == 0) { extra_bi_size = 0; dtb_reg = (region_t) { 0, 0 }; } else { dtb_reg = paddr_to_pptr_reg(dtb_p_reg); extra_bi_size = sizeof(seL4_BootInfoHeader) + (dtb_reg.end - dtb_reg.start); } word_t extra_bi_size_bits = calculate_extra_bi_size_bits(extra_bi_size); /* The region of the initial thread is the user image + ipcbuf and boot info */ it_v_reg.start = ui_v_reg.start; it_v_reg.end = extra_bi_frame_vptr + BIT(extra_bi_size_bits); if (it_v_reg.end >= USER_TOP) { printf("Userland image virtual end address too high\n"); return false; } /* setup virtual memory for the kernel */ map_kernel_window(); /* initialise the CPU */ if (!init_cpu()) { return false; } /* debug output via serial port is only available from here */ printf("Bootstrapping kernel\n"); /* initialise the platform */ init_plat(); arch_init_freemem(ui_p_reg, dtb_p_reg, it_v_reg, extra_bi_size_bits); /* create the root cnode */ root_cnode_cap = create_root_cnode(); if (cap_get_capType(root_cnode_cap) == cap_null_cap) { return false; } /* create the cap for managing thread domains */ create_domain_cap(root_cnode_cap); /* initialise the IRQ states and provide the IRQ control cap */ init_irqs(root_cnode_cap); #ifdef CONFIG_ARM_SMMU /* initialise the SMMU and provide the SMMU control caps*/ init_smmu(root_cnode_cap); #endif populate_bi_frame(0, CONFIG_MAX_NUM_NODES, ipcbuf_vptr, extra_bi_size); /* put DTB in the bootinfo block, if present. */ seL4_BootInfoHeader header; if (dtb_reg.start) { header.id = SEL4_BOOTINFO_HEADER_FDT; header.len = sizeof(header) + dtb_reg.end - dtb_reg.start; *(seL4_BootInfoHeader *)(rootserver.extra_bi + extra_bi_offset) = header; extra_bi_offset += sizeof(header); memcpy((void *)(rootserver.extra_bi + extra_bi_offset), (void *)dtb_reg.start, dtb_reg.end - dtb_reg.start); extra_bi_offset += (dtb_reg.end - dtb_reg.start); } if (extra_bi_size > extra_bi_offset) { /* provde a chunk for any leftover padding in the extended boot info */ header.id = SEL4_BOOTINFO_HEADER_PADDING; header.len = (extra_bi_size - extra_bi_offset); *(seL4_BootInfoHeader *)(rootserver.extra_bi + extra_bi_offset) = header; } if (config_set(CONFIG_TK1_SMMU)) { ndks_boot.bi_frame->ioSpaceCaps = create_iospace_caps(root_cnode_cap); if (ndks_boot.bi_frame->ioSpaceCaps.start == 0 && ndks_boot.bi_frame->ioSpaceCaps.end == 0) { return false; } } else { ndks_boot.bi_frame->ioSpaceCaps = S_REG_EMPTY; } /* Construct an initial address space with enough virtual addresses * to cover the user image + ipc buffer and bootinfo frames */ it_pd_cap = create_it_address_space(root_cnode_cap, it_v_reg); if (cap_get_capType(it_pd_cap) == cap_null_cap) { return false; } /* Create and map bootinfo frame cap */ create_bi_frame_cap( root_cnode_cap, it_pd_cap, bi_frame_vptr ); /* create and map extra bootinfo region */ if (extra_bi_size > 0) { region_t extra_bi_region = { .start = rootserver.extra_bi, .end = rootserver.extra_bi + extra_bi_size }; extra_bi_ret = create_frames_of_region( root_cnode_cap, it_pd_cap, extra_bi_region, true, pptr_to_paddr((void *)extra_bi_region.start) - extra_bi_frame_vptr ); if (!extra_bi_ret.success) { return false; } ndks_boot.bi_frame->extraBIPages = extra_bi_ret.region; } #ifdef CONFIG_KERNEL_MCS init_sched_control(root_cnode_cap, CONFIG_MAX_NUM_NODES); #endif /* create the initial thread's IPC buffer */ ipcbuf_cap = create_ipcbuf_frame_cap(root_cnode_cap, it_pd_cap, ipcbuf_vptr); if (cap_get_capType(ipcbuf_cap) == cap_null_cap) { return false; } /* create all userland image frames */ create_frames_ret = create_frames_of_region( root_cnode_cap, it_pd_cap, ui_reg, true, pv_offset ); if (!create_frames_ret.success) { return false; } ndks_boot.bi_frame->userImageFrames = create_frames_ret.region; /* create/initialise the initial thread's ASID pool */ it_ap_cap = create_it_asid_pool(root_cnode_cap); if (cap_get_capType(it_ap_cap) == cap_null_cap) { return false; } write_it_asid_pool(it_ap_cap, it_pd_cap); #ifdef CONFIG_KERNEL_MCS NODE_STATE(ksCurTime) = getCurrentTime(); #endif /* create the idle thread */ if (!create_idle_thread()) { return false; } /* Before creating the initial thread (which also switches to it) * we clean the cache so that any page table information written * as a result of calling create_frames_of_region will be correctly * read by the hardware page table walker */ cleanInvalidateL1Caches(); /* create the initial thread */ tcb_t *initial = create_initial_thread( root_cnode_cap, it_pd_cap, v_entry, bi_frame_vptr, ipcbuf_vptr, ipcbuf_cap ); if (initial == NULL) { return false; } init_core_state(initial); /* create all of the untypeds. Both devices and kernel window memory */ if (!create_untypeds( root_cnode_cap, (region_t) { KERNEL_ELF_BASE, (pptr_t)ki_boot_end } /* reusable boot code/data */ )) { return false; } /* no shared-frame caps (ARM has no multikernel support) */ ndks_boot.bi_frame->sharedFrames = S_REG_EMPTY; /* finalise the bootinfo frame */ bi_finalise(); /* make everything written by the kernel visible to userland. Cleaning to PoC is not * strictly neccessary, but performance is not critical here so clean and invalidate * everything to PoC */ cleanInvalidateL1Caches(); invalidateLocalTLB(); if (config_set(CONFIG_ARM_HYPERVISOR_SUPPORT)) { invalidateHypTLB(); } ksNumCPUs = 1; /* initialize BKL before booting up other cores */ SMP_COND_STATEMENT(clh_lock_init()); SMP_COND_STATEMENT(release_secondary_cpus()); /* grab BKL before leaving the kernel */ NODE_LOCK_SYS; printf("Booting all finished, dropped to user space\n"); /* kernel successfully initialized */ return true; } BOOT_CODE VISIBLE void init_kernel( paddr_t ui_p_reg_start, paddr_t ui_p_reg_end, sword_t pv_offset, vptr_t v_entry, paddr_t dtb_addr_p, uint32_t dtb_size ) { bool_t result; paddr_t dtb_end_p = 0; if (dtb_addr_p) { dtb_end_p = dtb_addr_p + dtb_size; } #ifdef ENABLE_SMP_SUPPORT /* we assume there exists a cpu with id 0 and will use it for bootstrapping */ if (getCurrentCPUIndex() == 0) { result = try_init_kernel(ui_p_reg_start, ui_p_reg_end, pv_offset, v_entry, dtb_addr_p, dtb_end_p); } else { result = try_init_kernel_secondary_core(); } #else result = try_init_kernel(ui_p_reg_start, ui_p_reg_end, pv_offset, v_entry, dtb_addr_p, dtb_end_p); #endif /* ENABLE_SMP_SUPPORT */ if (!result) { fail("Kernel init failed for some reason :("); } #ifdef CONFIG_KERNEL_MCS NODE_STATE(ksCurTime) = getCurrentTime(); NODE_STATE(ksConsumed) = 0; #endif schedule(); activateThread(); }