seL4/src/arch/arm/kernel/boot.c
2016-05-27 11:29:16 +10:00

376 lines
10 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 <machine/io.h>
#include <model/statedata.h>
#include <object/interrupt.h>
#include <arch/machine.h>
#include <arch/kernel/boot.h>
#include <arch/kernel/vspace.h>
#include <arch/benchmark.h>
#include <arch/user_access.h>
#include <arch/object/iospace.h>
#include <arch/linker.h>
#include <plat/machine/hardware.h>
#include <machine.h>
/* 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];
/**
* Split mem_reg about reserved_reg. If memory exists in the lower
* segment, insert it. If memory exists in the upper segment, return it.
*/
BOOT_CODE static region_t
insert_region_excluded(region_t mem_reg, region_t reserved_reg)
{
region_t residual_reg = mem_reg;
bool_t result UNUSED;
if (reserved_reg.start < mem_reg.start) {
/* Reserved region is below the provided mem_reg. */
mem_reg.end = 0;
mem_reg.start = 0;
/* Fit the residual around the reserved region */
if (reserved_reg.end > residual_reg.start) {
residual_reg.start = reserved_reg.end;
}
} else if (mem_reg.end > reserved_reg.start) {
/* Split mem_reg around reserved_reg */
mem_reg.end = reserved_reg.start;
residual_reg.start = reserved_reg.end;
} else {
/* reserved_reg is completely above mem_reg */
residual_reg.start = 0;
residual_reg.end = 0;
}
/* Add the lower region if it exists */
if (mem_reg.start < mem_reg.end) {
result = insert_region(mem_reg);
assert(result);
}
/* Validate the upper region */
if (residual_reg.start > residual_reg.end) {
residual_reg.start = residual_reg.end;
}
return residual_reg;
}
BOOT_CODE static void
init_freemem(region_t ui_reg)
{
word_t i;
bool_t result UNUSED;
region_t cur_reg;
region_t res_reg[] = {
{
.start = kernelBase,
.end = (pptr_t)ki_end
},
{
.start = ui_reg.start,
.end = ui_reg.end
},
{
.start = (PD_ASID_SLOT + 0) << pageBitsForSize(ARMSection),
.end = (PD_ASID_SLOT + 1) << pageBitsForSize(ARMSection)
}
};
for (i = 0; i < MAX_NUM_FREEMEM_REG; i++) {
ndks_boot.freemem[i] = REG_EMPTY;
}
/* Force ordering and exclusivity of reserved regions. */
assert(res_reg[0].start < res_reg[0].end);
assert(res_reg[1].start < res_reg[1].end);
assert(res_reg[2].start < res_reg[2].end);
assert(res_reg[0].end <= res_reg[1].start);
assert(res_reg[1].end <= res_reg[2].start);
for (i = 0; i < get_num_avail_p_regs(); i++) {
cur_reg = paddr_to_pptr_reg(get_avail_p_reg(i));
/* Adjust region if it exceeds the kernel window
* Note that we compare physical address in case of overflow.
*/
if (pptr_to_paddr((void*)cur_reg.end) > PADDR_TOP) {
cur_reg.end = PPTR_TOP;
}
if (pptr_to_paddr((void*)cur_reg.start) > PADDR_TOP) {
cur_reg.start = PPTR_TOP;
}
cur_reg = insert_region_excluded(cur_reg, res_reg[0]);
cur_reg = insert_region_excluded(cur_reg, res_reg[1]);
cur_reg = insert_region_excluded(cur_reg, res_reg[2]);
if (cur_reg.start != cur_reg.end) {
result = insert_region(cur_reg);
assert(result);
}
}
}
BOOT_CODE static void
init_irqs(cap_t root_cnode_cap)
{
irq_t i;
for (i = 0; i <= maxIRQ; i++) {
setIRQState(IRQInactive, i);
}
setIRQState(IRQTimer, KERNEL_TIMER_IRQ);
if (config_set(ARM_HYP)) {
setIRQState(IRQReserved, INTERRUPT_VGIC_MAINTENANCE);
}
if (config_set(CONFIG_ARM_SMMU)) {
setIRQState(IRQReserved, INTERRUPT_SMMU);
}
/* provide the IRQ control cap */
write_slot(SLOT_PTR(pptr_of_cap(root_cnode_cap), seL4_CapIRQControl), cap_irq_control_cap_new());
}
/* This and only this function initialises the CPU. It does NOT initialise any kernel state. */
BOOT_CODE static void
init_cpu(void)
{
activate_global_pd();
if (config_set(ARM_HYP)) {
vcpu_restore(NULL);
}
}
/* This and only this function initialises the platform. It does NOT initialise any kernel state. */
BOOT_CODE static void
init_plat(void)
{
initIRQController();
initTimer();
initL2Cache();
}
/* Main kernel initialisation function. */
static BOOT_CODE bool_t
try_init_kernel(
paddr_t ui_p_reg_start,
paddr_t ui_p_reg_end,
int32_t pv_offset,
vptr_t v_entry
)
{
cap_t root_cnode_cap;
cap_t it_ap_cap;
cap_t it_pd_cap;
cap_t ipcbuf_cap;
region_t ui_reg = paddr_to_pptr_reg((p_region_t) {
ui_p_reg_start, ui_p_reg_end
});
pptr_t bi_frame_pptr;
vptr_t bi_frame_vptr;
vptr_t ipcbuf_vptr;
create_frames_of_region_ret_t create_frames_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);
/* 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 = bi_frame_vptr + BIT(PAGE_BITS);
/* setup virtual memory for the kernel */
map_kernel_window();
/* initialise the CPU */
init_cpu();
/* debug output via serial port is only available from here */
printf("Bootstrapping kernel\n");
/* initialise the platform */
init_plat();
/* make the free memory available to alloc_region() */
init_freemem(ui_reg);
/* 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);
/* create the IRQ CNode */
if (!create_irq_cnode()) {
return false;
}
/* initialise the IRQ states and provide the IRQ control cap */
init_irqs(root_cnode_cap);
/* create the bootinfo frame */
bi_frame_pptr = allocate_bi_frame(0, 1, ipcbuf_vptr);
if (!bi_frame_pptr) {
return false;
}
if (config_set(CONFIG_ARM_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;
}
}
/* 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_pptr,
bi_frame_vptr
);
/* create the initial thread's IPC buffer */
ipcbuf_cap = create_ipcbuf_frame(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);
/* 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 */
if (!create_initial_thread(
root_cnode_cap,
it_pd_cap,
v_entry,
bi_frame_vptr,
ipcbuf_vptr,
ipcbuf_cap
)) {
return false;
}
/* convert the remaining free memory into UT objects and provide the caps */
if (!create_untypeds(
root_cnode_cap,
(region_t) {
kernelBase, (pptr_t)ki_boot_end
} /* reusable boot code/data */
)) {
return false;
}
/* create device frames */
if (!create_device_frames(root_cnode_cap)) {
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();
invalidateTLB();
if (config_set(ARM_HYP)) {
invalidateHypTLB();
}
#if CONFIG_MAX_NUM_TRACE_POINTS > 0
armv_init_ccnt();
#endif /* CONFIG_MAX_NUM_TRACE_POINTS > 0 */
/* Export selected CPU features for access by PL0 */
armv_init_user_access();
/* kernel successfully initialized */
return true;
}
BOOT_CODE VISIBLE void
init_kernel(
paddr_t ui_p_reg_start,
paddr_t ui_p_reg_end,
int32_t pv_offset,
vptr_t v_entry
)
{
bool_t result;
result = try_init_kernel(ui_p_reg_start,
ui_p_reg_end,
pv_offset,
v_entry);
if (!result) {
fail ("Kernel init failed for some reason :(");
}
schedule();
activateThread();
}