blob: a1f130e55ffd7f999823a64fd736215d72d6c8d3 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/*
* This file provides routines that can be called by other libraries to access
* platform-specific devices such as the serial port. Perhaps some day this may
* be refactored into a more structured userspace driver model, but for now we
* just provide the bare minimum for userspace to access basic devices such as
* the serial port on any platform.
*/
#include <autoconf.h>
#include <sel4platsupport/gen_config.h>
#include <sel4muslcsys/gen_config.h>
#include <assert.h>
#include <sel4/sel4.h>
#include <sel4/bootinfo.h>
#include <sel4/invocation.h>
#include <sel4platsupport/device.h>
#include <sel4platsupport/platsupport.h>
#include <vspace/page.h>
#include <simple/simple_helpers.h>
#include <vspace/vspace.h>
#include "plat_internal.h"
#include <stddef.h>
#include <stdio.h>
#include <vka/capops.h>
#include <string.h>
#include <sel4platsupport/arch/io.h>
#include <simple-default/simple-default.h>
#include <utils/util.h>
enum serial_setup_status {
NOT_INITIALIZED = 0,
START_REGULAR_SETUP,
START_FAILSAFE_SETUP,
SETUP_COMPLETE
};
static enum serial_setup_status setup_status = NOT_INITIALIZED;
/* Some globals for tracking initialisation variables. This is currently just to avoid
* passing parameters down to the platform code for backwards compatibility reasons. This
* is strictly to avoid refactoring all existing platform code */
static vspace_t *vspace = NULL;
static UNUSED simple_t *simple = NULL;
static vka_t *vka = NULL;
/* To keep failsafe setup we need actual memory for a simple and a vka */
static simple_t _simple_mem;
static vka_t _vka_mem;
/* Hacky constants / data structures for a failsafe mapping */
#define DITE_HEADER_START ((seL4_Word)__executable_start - 0x1000)
static seL4_CPtr device_cap = 0;
extern char __executable_start[];
#if !(defined(CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR) && defined(CONFIG_DEBUG_BUILD))
static void *__map_device_page(void *cookie, uintptr_t paddr, size_t size,
int cached, ps_mem_flags_t flags);
static ps_io_ops_t io_ops = {
.io_mapper = {
.io_map_fn = &__map_device_page,
.io_unmap_fn = NULL,
},
};
#endif
/* completely hacky way of getting a virtual address. This is used a last ditch attempt to
* get serial device going so we can print out an error */
static seL4_Word platsupport_alloc_device_vaddr(seL4_Word bits)
{
seL4_Word va;
va = DITE_HEADER_START - (BIT(bits));
/* Ensure we are aligned to bits. If not, round down. */
va = va & ~((BIT(bits)) - 1);
return va;
}
static void *__map_device_page_failsafe(void *cookie UNUSED, uintptr_t paddr, size_t size,
int cached UNUSED, ps_mem_flags_t flags UNUSED)
{
int bits = CTZ(size);
int error;
seL4_Word vaddr = 0;
vka_object_t dest;
if (device_cap != 0) {
/* we only support a single page for the serial */
for (;;);
}
error = sel4platsupport_alloc_frame_at(vka, paddr, bits, &dest);
if (error != seL4_NoError) {
goto error;
}
device_cap = dest.cptr;
vaddr = platsupport_alloc_device_vaddr(bits);
error =
seL4_ARCH_Page_Map(
device_cap,
seL4_CapInitThreadPD,
vaddr,
seL4_AllRights,
0
);
error:
if (error)
for (;;);
assert(!error);
return (void *)vaddr;
}
static void *__map_device_page_regular(void *cookie UNUSED, uintptr_t paddr, size_t size,
int cached UNUSED, ps_mem_flags_t flags UNUSED)
{
int bits = CTZ(size);
void *vaddr;
int error;
vka_object_t dest;
error = sel4platsupport_alloc_frame_at(vka, paddr, bits, &dest);
if (error) {
ZF_LOGF("Failed to get cap for serial device frame");
}
vaddr = vspace_map_pages(vspace, &dest.cptr, NULL, seL4_AllRights, 1, bits, 0);
if (!vaddr) {
ZF_LOGF("Failed to map serial device :(\n");
for (;;);
}
device_cap = dest.cptr;
return (void *)vaddr;
}
void *__map_device_page(void *cookie, uintptr_t paddr, size_t size,
int cached, ps_mem_flags_t flags)
{
if (setup_status == START_REGULAR_SETUP && vspace) {
return __map_device_page_regular(cookie, paddr, size, cached, flags);
} else if (setup_status == START_FAILSAFE_SETUP || !vspace) {
return __map_device_page_failsafe(cookie, paddr, size, cached, flags);
}
printf("Unknown setup status!\n");
for (;;);
}
/*
* This function is designed to be called when creating a new cspace/vspace,
* and the serial port needs to be hooked in there too.
*/
void platsupport_undo_serial_setup(void)
{
/* Re-initialise some structures. */
setup_status = NOT_INITIALIZED;
if (device_cap) {
cspacepath_t path;
seL4_ARCH_Page_Unmap(device_cap);
vka_cspace_make_path(vka, device_cap, &path);
vka_cnode_delete(&path);
vka_cspace_free(vka, device_cap);
device_cap = 0;
vka = NULL;
}
}
/* Initialise serial input interrupt. */
void platsupport_serial_input_init_IRQ(void)
{
}
int platsupport_serial_setup_io_ops(ps_io_ops_t *io_ops)
{
int err = 0;
if (setup_status == SETUP_COMPLETE) {
return 0;
}
err = __plat_serial_init(io_ops);
if (!err) {
setup_status = SETUP_COMPLETE;
}
return err;
}
int platsupport_serial_setup_bootinfo_failsafe(void)
{
int err = 0;
if (setup_status == SETUP_COMPLETE) {
return 0;
}
memset(&_simple_mem, 0, sizeof(simple_t));
memset(&_vka_mem, 0, sizeof(vka_t));
#if defined(CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR) && defined(CONFIG_DEBUG_BUILD)
/* only support putchar on a debug kernel */
setup_status = SETUP_COMPLETE;
#else
setup_status = START_FAILSAFE_SETUP;
simple_default_init_bootinfo(&_simple_mem, platsupport_get_bootinfo());
simple = &_simple_mem;
vka = &_vka_mem;
simple_make_vka(simple, vka);
#ifdef CONFIG_ARCH_X86
sel4platsupport_get_io_port_ops(&io_ops.io_port_ops, simple, vka);
#endif
err = platsupport_serial_setup_io_ops(&io_ops);
#endif
return err;
}
int platsupport_serial_setup_simple(
vspace_t *_vspace __attribute__((unused)),
simple_t *_simple __attribute__((unused)),
vka_t *_vka __attribute__((unused)))
{
int err = 0;
if (setup_status == SETUP_COMPLETE) {
return 0;
}
if (setup_status != NOT_INITIALIZED) {
printf("Trying to initialise a partially initialised serial. Current setup status is %d\n", setup_status);
assert(!"You cannot recover");
return -1;
}
#if defined(CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR) && defined(CONFIG_DEBUG_BUILD)
/* only support putchar on a debug kernel */
setup_status = SETUP_COMPLETE;
#else
/* start setup */
setup_status = START_REGULAR_SETUP;
vspace = _vspace;
simple = _simple;
vka = _vka;
#ifdef CONFIG_ARCH_X86
sel4platsupport_get_io_port_ops(&io_ops.io_port_ops, simple, vka);
#endif
err = platsupport_serial_setup_io_ops(&io_ops);
/* done */
vspace = NULL;
simple = NULL;
/* Don't reset vka here */
#endif
return err;
}
static void __serial_setup()
{
int started_regular __attribute__((unused)) = 0;
/* this function is called if we attempt to do serial and it isn't setup.
* we now need to handle this somehow */
switch (setup_status) {
case START_FAILSAFE_SETUP:
/* we're stuck. */
abort();
break;
case START_REGULAR_SETUP:
started_regular = 1;
case NOT_INITIALIZED:
#ifdef CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR
setup_status = SETUP_COMPLETE;
printf("\nWarning: using printf before serial is set up. This only works as your\n");
printf("printf is backed by seL4_Debug_PutChar()\n");
started_regular = 1;
#else
/* attempt failsafe initialization and print something out */
platsupport_serial_setup_bootinfo_failsafe();
if (started_regular) {
printf("Regular serial setup failed.\n"
"This message coming to you courtesy of failsafe serial\n"
"Your vspace has been clobbered but we will keep running to get any more error output\n");
} else {
printf("You attempted to print before initialising the libsel4platsupport serial device!\n");
while (1);
}
#endif /* CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR */
break;
case SETUP_COMPLETE:
break;
}
}
void NO_INLINE
#ifdef CONFIG_LIB_SEL4_MUSLC_SYS_ARCH_PUTCHAR_WEAK
WEAK
#endif
__arch_putchar(int c)
{
if (setup_status != SETUP_COMPLETE) {
__serial_setup();
}
__plat_putchar(c);
}
size_t NO_INLINE
#ifdef CONFIG_LIB_SEL4_MUSLC_SYS_ARCH_PUTCHAR_WEAK
WEAK
#endif
__arch_write(char *data, size_t count)
{
for (size_t i = 0; i < count; i++) {
__arch_putchar(data[i]);
}
return count;
}
int __arch_getchar(void)
{
if (setup_status != SETUP_COMPLETE) {
__serial_setup();
}
return __plat_getchar();
}