seL4/src/kernel/boot.c
Anna Lyons 8586b7f2b8 boot: refactor allocation of rootserver objects
Prior to this change, the boot process would dynamically allocate
memory for root server objects based on the order of initialisation.
Allocation was a best-fit algorithm.

This change preallocates all memory for root server objects to an
aligned untyped just after the user image. By allocating the objects in
order of size, allocation is greatly simplified and the ability to
reproduce the allocation offline based on the kernel and user image
sizes is increased.
2019-06-20 14:11:50 +10:00

587 lines
20 KiB
C

/*
* 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 <assert.h>
#include <kernel/boot.h>
#include <kernel/thread.h>
#include <machine/io.h>
#include <machine/registerset.h>
#include <model/statedata.h>
#include <arch/machine.h>
#include <arch/kernel/boot.h>
#include <arch/kernel/vspace.h>
#include <linker.h>
#include <plat/machine/hardware.h>
#include <util.h>
/* (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;
}
}
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 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 region_t 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 = MAX(cnode_size_bits, seL4_VSpaceBits);
max = MAX(max, extra_bi_size_bits);
start = ROUND_UP(start, max);
rootserver_mem.start = start;
rootserver_mem.end = start + calculate_rootserver_size(v_reg, extra_bi_size_bits);
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);
return (region_t) {
.start = start,
.end = 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)
{
/* 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]);
}
}
}