376 lines
10 KiB
C
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();
|
|
}
|
|
|