/* * Copyright 2014, General Dynamics C4 Systems * * This software may be distributed and modified according to the terms of * the GNU General Public License version 2. Note that NO WARRANTY is provided. * See "LICENSE_GPLv2.txt" for details. * * @TAG(GD_GPL) */ #include #include #include #include #include #include #include #include #include #include #include #include /* (node-local) state accessed only during bootstrapping */ ndks_boot_t ndks_boot BOOT_DATA; rootserver_mem_t rootserver BOOT_DATA; static region_t rootserver_mem BOOT_DATA; BOOT_CODE bool_t insert_region(region_t reg) { word_t i; assert(reg.start <= reg.end); if (is_reg_empty(reg)) { return true; } for (i = 0; i < MAX_NUM_FREEMEM_REG; i++) { if (is_reg_empty(ndks_boot.freemem[i])) { ndks_boot.freemem[i] = reg; return true; } } #ifdef CONFIG_ARCH_ARM /* boot.h should have calculated MAX_NUM_FREEMEM_REG correctly. * If we've run out, then something is wrong. * Note that the capDL allocation toolchain does not know about * MAX_NUM_FREEMEM_REG, so throwing away regions may prevent * capDL applications from being loaded! */ printf("Can't fit memory region 0x%lx-0x%lx, try increasing MAX_NUM_FREEMEM_REG (currently %d)\n", reg.start, reg.end, (int)MAX_NUM_FREEMEM_REG); assert(!"Ran out of freemem slots"); #else printf("Dropping memory region 0x%lx-0x%lx, try increasing MAX_NUM_FREEMEM_REG (currently %d)\n", reg.start, reg.end, (int)MAX_NUM_FREEMEM_REG); #endif return false; } BOOT_CODE static pptr_t alloc_rootserver_obj(word_t size_bits, word_t n) { pptr_t allocated = rootserver_mem.start; /* allocated memory must be aligned */ assert(allocated % BIT(size_bits) == 0); rootserver_mem.start += (n * BIT(size_bits)); /* we must not have run out of memory */ assert(rootserver_mem.start <= rootserver_mem.end); memzero((void *) allocated, n * BIT(size_bits)); return allocated; } BOOT_CODE static word_t rootserver_max_size_bits(word_t extra_bi_size_bits) { word_t cnode_size_bits = CONFIG_ROOT_CNODE_SIZE_BITS + seL4_SlotBits; word_t max = MAX(cnode_size_bits, seL4_VSpaceBits); return MAX(max, extra_bi_size_bits); } BOOT_CODE static word_t calculate_rootserver_size(v_region_t v_reg, word_t extra_bi_size_bits) { /* work out how much memory we need for root server objects */ word_t size = BIT(CONFIG_ROOT_CNODE_SIZE_BITS + seL4_SlotBits); size += BIT(seL4_TCBBits); // root thread tcb size += 2 * BIT(seL4_PageBits); // boot info + ipc buf size += BIT(seL4_ASIDPoolBits); size += extra_bi_size_bits > 0 ? BIT(extra_bi_size_bits) : 0; size += BIT(seL4_VSpaceBits); // root vspace /* for all archs, seL4_PageTable Bits is the size of all non top-level paging structures */ return size + arch_get_n_paging(v_reg) * BIT(seL4_PageTableBits); } BOOT_CODE static void maybe_alloc_extra_bi(word_t cmp_size_bits, word_t extra_bi_size_bits) { if (extra_bi_size_bits >= cmp_size_bits && rootserver.extra_bi == 0) { rootserver.extra_bi = alloc_rootserver_obj(extra_bi_size_bits, 1); } } BOOT_CODE void create_rootserver_objects(pptr_t start, v_region_t v_reg, word_t extra_bi_size_bits) { /* the largest object the PD, the root cnode, or the extra boot info */ word_t cnode_size_bits = CONFIG_ROOT_CNODE_SIZE_BITS + seL4_SlotBits; word_t max = rootserver_max_size_bits(extra_bi_size_bits); word_t size = calculate_rootserver_size(v_reg, extra_bi_size_bits); rootserver_mem.start = start; rootserver_mem.end = start + size; maybe_alloc_extra_bi(max, extra_bi_size_bits); /* the root cnode is at least 4k, so it could be larger or smaller than a pd. */ #if (CONFIG_ROOT_CNODE_SIZE_BITS + seL4_SlotBits) > seL4_VSpaceBits rootserver.cnode = alloc_rootserver_obj(cnode_size_bits, 1); maybe_alloc_extra_bi(seL4_VSpaceBits, extra_bi_size_bits); rootserver.vspace = alloc_rootserver_obj(seL4_VSpaceBits, 1); #else rootserver.vspace = alloc_rootserver_obj(seL4_VSpaceBits, 1); maybe_alloc_extra_bi(cnode_size_bits, extra_bi_size_bits); rootserver.cnode = alloc_rootserver_obj(cnode_size_bits, 1); #endif /* at this point we are up to creating 4k objects - which is the min size of * extra_bi so this is the last chance to allocate it */ maybe_alloc_extra_bi(seL4_PageBits, extra_bi_size_bits); rootserver.asid_pool = alloc_rootserver_obj(seL4_ASIDPoolBits, 1); rootserver.ipc_buf = alloc_rootserver_obj(seL4_PageBits, 1); rootserver.boot_info = alloc_rootserver_obj(seL4_PageBits, 1); /* TCBs on aarch32 can be larger than page tables in certain configs */ #if seL4_TCBBits >= seL4_PageTableBits rootserver.tcb = alloc_rootserver_obj(seL4_TCBBits, 1); #endif /* paging structures are 4k on every arch except aarch32 (1k) */ word_t n = arch_get_n_paging(v_reg); rootserver.paging.start = alloc_rootserver_obj(seL4_PageTableBits, n); rootserver.paging.end = rootserver.paging.start + n * BIT(seL4_PageTableBits); /* for most archs, TCBs are smaller than page tables */ #if seL4_TCBBits < seL4_PageTableBits rootserver.tcb = alloc_rootserver_obj(seL4_TCBBits, 1); #endif /* we should have allocated all our memory */ assert(rootserver_mem.start == rootserver_mem.end); } BOOT_CODE void write_slot(slot_ptr_t slot_ptr, cap_t cap) { slot_ptr->cap = cap; slot_ptr->cteMDBNode = nullMDBNode; mdb_node_ptr_set_mdbRevocable(&slot_ptr->cteMDBNode, true); mdb_node_ptr_set_mdbFirstBadged(&slot_ptr->cteMDBNode, true); } /* Our root CNode needs to be able to fit all the initial caps and not * cover all of memory. */ compile_assert(root_cnode_size_valid, CONFIG_ROOT_CNODE_SIZE_BITS < 32 - seL4_SlotBits && BIT(CONFIG_ROOT_CNODE_SIZE_BITS) >= seL4_NumInitialCaps && BIT(CONFIG_ROOT_CNODE_SIZE_BITS) >= (seL4_PageBits - seL4_SlotBits)) BOOT_CODE cap_t create_root_cnode(void) { /* write the number of root CNode slots to global state */ ndks_boot.slot_pos_max = BIT(CONFIG_ROOT_CNODE_SIZE_BITS); cap_t cap = cap_cnode_cap_new( CONFIG_ROOT_CNODE_SIZE_BITS, /* radix */ wordBits - CONFIG_ROOT_CNODE_SIZE_BITS, /* guard size */ 0, /* guard */ rootserver.cnode /* pptr */ ); /* write the root CNode cap into the root CNode */ write_slot(SLOT_PTR(rootserver.cnode, seL4_CapInitThreadCNode), cap); return cap; } /* Check domain scheduler assumptions. */ compile_assert(num_domains_valid, CONFIG_NUM_DOMAINS >= 1 && CONFIG_NUM_DOMAINS <= 256) compile_assert(num_priorities_valid, CONFIG_NUM_PRIORITIES >= 1 && CONFIG_NUM_PRIORITIES <= 256) BOOT_CODE void create_domain_cap(cap_t root_cnode_cap) { /* Check domain scheduler assumptions. */ assert(ksDomScheduleLength > 0); for (word_t i = 0; i < ksDomScheduleLength; i++) { assert(ksDomSchedule[i].domain < CONFIG_NUM_DOMAINS); assert(ksDomSchedule[i].length > 0); } cap_t cap = cap_domain_cap_new(); write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapDomain), cap); } BOOT_CODE cap_t create_ipcbuf_frame_cap(cap_t root_cnode_cap, cap_t pd_cap, vptr_t vptr) { clearMemory((void *)rootserver.ipc_buf, PAGE_BITS); /* create a cap of it and write it into the root CNode */ cap_t cap = create_mapped_it_frame_cap(pd_cap, rootserver.ipc_buf, vptr, IT_ASID, false, false); write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapInitThreadIPCBuffer), cap); return cap; } BOOT_CODE void create_bi_frame_cap(cap_t root_cnode_cap, cap_t pd_cap, vptr_t vptr) { /* create a cap of it and write it into the root CNode */ cap_t cap = create_mapped_it_frame_cap(pd_cap, rootserver.boot_info, vptr, IT_ASID, false, false); write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapBootInfoFrame), cap); } BOOT_CODE word_t calculate_extra_bi_size_bits(word_t extra_size) { if (extra_size == 0) { return 0; } word_t clzl_ret = clzl(ROUND_UP(extra_size, seL4_PageBits)); /* If region is bigger than a page, make sure we overallocate rather than underallocate */ if (extra_size & ((1 << clzl_ret) - 1)) { clzl_ret--; } return seL4_WordBits - 1 - clzl_ret; } BOOT_CODE void populate_bi_frame(node_id_t node_id, word_t num_nodes, vptr_t ipcbuf_vptr, word_t extra_bi_size) { clearMemory((void *) rootserver.boot_info, BI_FRAME_SIZE_BITS); if (extra_bi_size) { clearMemory((void *) rootserver.extra_bi, calculate_extra_bi_size_bits(extra_bi_size)); } /* initialise bootinfo-related global state */ ndks_boot.bi_frame = BI_PTR(rootserver.boot_info); ndks_boot.slot_pos_cur = seL4_NumInitialCaps; BI_PTR(rootserver.boot_info)->nodeID = node_id; BI_PTR(rootserver.boot_info)->numNodes = num_nodes; BI_PTR(rootserver.boot_info)->numIOPTLevels = 0; BI_PTR(rootserver.boot_info)->ipcBuffer = (seL4_IPCBuffer *) ipcbuf_vptr; BI_PTR(rootserver.boot_info)->initThreadCNodeSizeBits = CONFIG_ROOT_CNODE_SIZE_BITS; BI_PTR(rootserver.boot_info)->initThreadDomain = ksDomSchedule[ksDomScheduleIdx].domain; BI_PTR(rootserver.boot_info)->extraLen = extra_bi_size; } BOOT_CODE bool_t provide_cap(cap_t root_cnode_cap, cap_t cap) { if (ndks_boot.slot_pos_cur >= ndks_boot.slot_pos_max) { printf("Kernel init failed: ran out of cap slots\n"); return false; } write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), ndks_boot.slot_pos_cur), cap); ndks_boot.slot_pos_cur++; return true; } BOOT_CODE create_frames_of_region_ret_t create_frames_of_region( cap_t root_cnode_cap, cap_t pd_cap, region_t reg, bool_t do_map, sword_t pv_offset ) { pptr_t f; cap_t frame_cap; seL4_SlotPos slot_pos_before; seL4_SlotPos slot_pos_after; slot_pos_before = ndks_boot.slot_pos_cur; for (f = reg.start; f < reg.end; f += BIT(PAGE_BITS)) { if (do_map) { frame_cap = create_mapped_it_frame_cap(pd_cap, f, pptr_to_paddr((void *)(f - pv_offset)), IT_ASID, false, true); } else { frame_cap = create_unmapped_it_frame_cap(f, false); } if (!provide_cap(root_cnode_cap, frame_cap)) return (create_frames_of_region_ret_t) { S_REG_EMPTY, false }; } slot_pos_after = ndks_boot.slot_pos_cur; return (create_frames_of_region_ret_t) { (seL4_SlotRegion) { slot_pos_before, slot_pos_after }, true }; } BOOT_CODE cap_t create_it_asid_pool(cap_t root_cnode_cap) { cap_t ap_cap = cap_asid_pool_cap_new(IT_ASID >> asidLowBits, rootserver.asid_pool); write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapInitThreadASIDPool), ap_cap); /* create ASID control cap */ write_slot( SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapASIDControl), cap_asid_control_cap_new() ); return ap_cap; } BOOT_CODE bool_t create_idle_thread(void) { pptr_t pptr; #ifdef ENABLE_SMP_SUPPORT for (int i = 0; i < CONFIG_MAX_NUM_NODES; i++) { #endif /* ENABLE_SMP_SUPPORT */ pptr = (pptr_t) &ksIdleThreadTCB[SMP_TERNARY(i, 0)]; NODE_STATE_ON_CORE(ksIdleThread, i) = TCB_PTR(pptr + TCB_OFFSET); configureIdleThread(NODE_STATE_ON_CORE(ksIdleThread, i)); #ifdef CONFIG_DEBUG_BUILD setThreadName(NODE_STATE_ON_CORE(ksIdleThread, i), "idle_thread"); #endif SMP_COND_STATEMENT(NODE_STATE_ON_CORE(ksIdleThread, i)->tcbAffinity = i); #ifdef ENABLE_SMP_SUPPORT } #endif /* ENABLE_SMP_SUPPORT */ return true; } BOOT_CODE tcb_t *create_initial_thread(cap_t root_cnode_cap, cap_t it_pd_cap, vptr_t ui_v_entry, vptr_t bi_frame_vptr, vptr_t ipcbuf_vptr, cap_t ipcbuf_cap) { tcb_t *tcb = TCB_PTR(rootserver.tcb + TCB_OFFSET); tcb->tcbTimeSlice = CONFIG_TIME_SLICE; Arch_initContext(&tcb->tcbArch.tcbContext); /* derive a copy of the IPC buffer cap for inserting */ deriveCap_ret_t dc_ret = deriveCap(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapInitThreadIPCBuffer), ipcbuf_cap); if (dc_ret.status != EXCEPTION_NONE) { printf("Failed to derive copy of IPC Buffer\n"); return NULL; } /* initialise TCB (corresponds directly to abstract specification) */ cteInsert( root_cnode_cap, SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapInitThreadCNode), SLOT_PTR(rootserver.tcb, tcbCTable) ); cteInsert( it_pd_cap, SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapInitThreadVSpace), SLOT_PTR(rootserver.tcb, tcbVTable) ); cteInsert( dc_ret.cap, SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapInitThreadIPCBuffer), SLOT_PTR(rootserver.tcb, tcbBuffer) ); tcb->tcbIPCBuffer = ipcbuf_vptr; /* Set the root thread's IPC buffer */ Arch_setTCBIPCBuffer(tcb, ipcbuf_vptr); setRegister(tcb, capRegister, bi_frame_vptr); setNextPC(tcb, ui_v_entry); /* initialise TCB */ tcb->tcbPriority = seL4_MaxPrio; tcb->tcbMCP = seL4_MaxPrio; setupReplyMaster(tcb); setThreadState(tcb, ThreadState_Running); ksCurDomain = ksDomSchedule[ksDomScheduleIdx].domain; ksDomainTime = ksDomSchedule[ksDomScheduleIdx].length; assert(ksCurDomain < CONFIG_NUM_DOMAINS && ksDomainTime > 0); SMP_COND_STATEMENT(tcb->tcbAffinity = 0); /* create initial thread's TCB cap */ cap_t cap = cap_thread_cap_new(TCB_REF(tcb)); write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapInitThreadTCB), cap); #ifdef CONFIG_DEBUG_BUILD setThreadName(tcb, "rootserver"); #endif return tcb; } BOOT_CODE void init_core_state(tcb_t *scheduler_action) { #ifdef CONFIG_HAVE_FPU NODE_STATE(ksActiveFPUState) = NULL; #endif #ifdef CONFIG_DEBUG_BUILD /* add initial threads to the debug queue */ NODE_STATE(ksDebugTCBs) = NULL; if (scheduler_action != SchedulerAction_ResumeCurrentThread && scheduler_action != SchedulerAction_ChooseNewThread) { tcbDebugAppend(scheduler_action); } tcbDebugAppend(NODE_STATE(ksIdleThread)); #endif NODE_STATE(ksSchedulerAction) = scheduler_action; NODE_STATE(ksCurThread) = NODE_STATE(ksIdleThread); } BOOT_CODE static bool_t provide_untyped_cap( cap_t root_cnode_cap, bool_t device_memory, pptr_t pptr, word_t size_bits, seL4_SlotPos first_untyped_slot ) { bool_t ret; cap_t ut_cap; word_t i = ndks_boot.slot_pos_cur - first_untyped_slot; if (i < CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS) { ndks_boot.bi_frame->untypedList[i] = (seL4_UntypedDesc) { pptr_to_paddr((void *)pptr), 0, 0, size_bits, device_memory }; ut_cap = cap_untyped_cap_new(MAX_FREE_INDEX(size_bits), device_memory, size_bits, pptr); ret = provide_cap(root_cnode_cap, ut_cap); } else { printf("Kernel init: Too many untyped regions for boot info\n"); ret = true; } return ret; } BOOT_CODE bool_t create_untypeds_for_region( cap_t root_cnode_cap, bool_t device_memory, region_t reg, seL4_SlotPos first_untyped_slot ) { word_t align_bits; word_t size_bits; while (!is_reg_empty(reg)) { /* Determine the maximum size of the region */ size_bits = seL4_WordBits - 1 - clzl(reg.end - reg.start); /* Determine the alignment of the region */ if (reg.start != 0) { align_bits = ctzl(reg.start); } else { align_bits = size_bits; } /* Reduce size bits to align if needed */ if (align_bits < size_bits) { size_bits = align_bits; } if (size_bits > seL4_MaxUntypedBits) { size_bits = seL4_MaxUntypedBits; } if (size_bits >= seL4_MinUntypedBits) { if (!provide_untyped_cap(root_cnode_cap, device_memory, reg.start, size_bits, first_untyped_slot)) { return false; } } reg.start += BIT(size_bits); } return true; } BOOT_CODE bool_t create_kernel_untypeds(cap_t root_cnode_cap, region_t boot_mem_reuse_reg, seL4_SlotPos first_untyped_slot) { word_t i; region_t reg; /* if boot_mem_reuse_reg is not empty, we can create UT objs from boot code/data frames */ if (!create_untypeds_for_region(root_cnode_cap, false, boot_mem_reuse_reg, first_untyped_slot)) { return false; } /* convert remaining freemem into UT objects and provide the caps */ for (i = 0; i < MAX_NUM_FREEMEM_REG; i++) { reg = ndks_boot.freemem[i]; ndks_boot.freemem[i] = REG_EMPTY; if (!create_untypeds_for_region(root_cnode_cap, false, reg, first_untyped_slot)) { return false; } } return true; } BOOT_CODE void bi_finalise(void) { seL4_SlotPos slot_pos_start = ndks_boot.slot_pos_cur; seL4_SlotPos slot_pos_end = ndks_boot.slot_pos_max; ndks_boot.bi_frame->empty = (seL4_SlotRegion) { slot_pos_start, slot_pos_end }; } static inline pptr_t ceiling_kernel_window(pptr_t p) { /* Adjust address if it exceeds the kernel window * Note that we compare physical address in case of overflow. */ if (pptr_to_paddr((void *)p) > PADDR_TOP) { p = PPTR_TOP; } return p; } /* we can't delcare arrays on the stack, so this is space for * the below function to use. */ static BOOT_DATA region_t avail_reg[MAX_NUM_FREEMEM_REG]; /** * Dynamically initialise the available memory on the platform. * A region represents an area of memory. */ BOOT_CODE void init_freemem(word_t n_available, const p_region_t *available, word_t n_reserved, region_t *reserved, v_region_t it_v_reg, word_t extra_bi_size_bits) { /* Force ordering and exclusivity of reserved regions */ for (word_t i = 0; n_reserved > 0 && i < n_reserved - 1; i++) { assert(reserved[i].start <= reserved[i].end); assert(reserved[i].end <= reserved[i + 1].start); } /* Force ordering and exclusivity of available regions */ assert(n_available > 0); for (word_t i = 0; i < n_available - 1; i++) { assert(available[i].start < available[i].end); assert(available[i].end <= available[i + 1].start); } for (word_t i = 0; i < MAX_NUM_FREEMEM_REG; i++) { ndks_boot.freemem[i] = REG_EMPTY; } /* convert the available regions to pptrs */ for (word_t i = 0; i < n_available; i++) { avail_reg[i] = paddr_to_pptr_reg(available[i]); avail_reg[i].end = ceiling_kernel_window(avail_reg[i].end); avail_reg[i].start = ceiling_kernel_window(avail_reg[i].start); } word_t a = 0; word_t r = 0; /* Now iterate through the available regions, removing any reserved regions. */ while (a < n_available && r < n_reserved) { if (reserved[r].start == reserved[r].end) { /* reserved region is empty - skip it */ r++; } else if (avail_reg[a].start >= avail_reg[a].end) { /* skip the entire region - it's empty now after trimming */ a++; } else if (reserved[r].end <= avail_reg[a].start) { /* the reserved region is below the available region - skip it*/ r++; } else if (reserved[r].start >= avail_reg[a].end) { /* the reserved region is above the available region - take the whole thing */ insert_region(avail_reg[a]); a++; } else { /* the reserved region overlaps with the available region */ if (reserved[r].start <= avail_reg[a].start) { /* the region overlaps with the start of the available region. * trim start of the available region */ avail_reg[a].start = MIN(avail_reg[a].end, reserved[r].end); r++; } else { assert(reserved[r].start < avail_reg[a].end); /* take the first chunk of the available region and move * the start to the end of the reserved region */ region_t m = avail_reg[a]; m.end = reserved[r].start; insert_region(m); if (avail_reg[a].end > reserved[r].end) { avail_reg[a].start = reserved[r].end; r++; } else { a++; } } } } /* no more reserved regions - add the rest */ for (; a < n_available; a++) { if (avail_reg[a].start < avail_reg[a].end) { insert_region(avail_reg[a]); } } /* now try to fit the root server objects into a region */ word_t i = MAX_NUM_FREEMEM_REG - 1; if (!is_reg_empty(ndks_boot.freemem[i])) { printf("Insufficient MAX_NUM_FREEMEM_REG"); halt(); } /* skip any empty regions */ for (; is_reg_empty(ndks_boot.freemem[i]) && i >= 0; i--); /* try to grab the last available p region to create the root server objects * from. If possible, retain any left over memory as an extra p region */ word_t size = calculate_rootserver_size(it_v_reg, extra_bi_size_bits); word_t max = rootserver_max_size_bits(extra_bi_size_bits); for (; i >= 0; i--) { word_t next = i + 1; pptr_t start = ROUND_DOWN(ndks_boot.freemem[i].end - size, max); if (start >= ndks_boot.freemem[i].start) { create_rootserver_objects(start, it_v_reg, extra_bi_size_bits); if (i < MAX_NUM_FREEMEM_REG) { ndks_boot.freemem[next].end = ndks_boot.freemem[i].end; ndks_boot.freemem[next].start = start + size; } ndks_boot.freemem[i].end = start; break; } else if (i < MAX_NUM_FREEMEM_REG) { ndks_boot.freemem[next] = ndks_boot.freemem[i]; } } }