1/* 2 * Copyright 2017, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12/* 13 * This file provides routines that can be called by other libraries to access 14 * platform-specific devices such as the serial port. Perhaps some day this may 15 * be refactored into a more structured userspace driver model, but for now we 16 * just provide the bare minimum for userspace to access basic devices such as 17 * the serial port on any platform. 18 */ 19 20#include <autoconf.h> 21#include <sel4platsupport/gen_config.h> 22#include <sel4muslcsys/gen_config.h> 23#include <assert.h> 24#include <sel4/sel4.h> 25#include <sel4/bootinfo.h> 26#include <sel4/invocation.h> 27#include <sel4platsupport/device.h> 28#include <sel4platsupport/platsupport.h> 29#include <vspace/page.h> 30#include <simple/simple_helpers.h> 31#include <vspace/vspace.h> 32#include "plat_internal.h" 33#include <stddef.h> 34#include <stdio.h> 35#include <vka/capops.h> 36#include <string.h> 37#include <sel4platsupport/arch/io.h> 38#include <simple-default/simple-default.h> 39#include <utils/util.h> 40 41enum serial_setup_status { 42 NOT_INITIALIZED = 0, 43 START_REGULAR_SETUP, 44 START_FAILSAFE_SETUP, 45 SETUP_COMPLETE 46}; 47static enum serial_setup_status setup_status = NOT_INITIALIZED; 48 49/* Some globals for tracking initialisation variables. This is currently just to avoid 50 * passing parameters down to the platform code for backwards compatibility reasons. This 51 * is strictly to avoid refactoring all existing platform code */ 52static vspace_t *vspace = NULL; 53static UNUSED simple_t *simple = NULL; 54static vka_t *vka = NULL; 55 56/* To keep failsafe setup we need actual memory for a simple and a vka */ 57static simple_t _simple_mem; 58static vka_t _vka_mem; 59 60/* Hacky constants / data structures for a failsafe mapping */ 61#define DITE_HEADER_START ((seL4_Word)__executable_start - 0x1000) 62static seL4_CPtr device_cap = 0; 63extern char __executable_start[]; 64 65#if !(defined(CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR) && defined(CONFIG_DEBUG_BUILD)) 66static void *__map_device_page(void *cookie, uintptr_t paddr, size_t size, 67 int cached, ps_mem_flags_t flags); 68 69static ps_io_ops_t io_ops = { 70 .io_mapper = { 71 .io_map_fn = &__map_device_page, 72 .io_unmap_fn = NULL, 73 }, 74}; 75 76#endif 77 78/* completely hacky way of getting a virtual address. This is used a last ditch attempt to 79 * get serial device going so we can print out an error */ 80static seL4_Word platsupport_alloc_device_vaddr(seL4_Word bits) 81{ 82 seL4_Word va; 83 84 va = DITE_HEADER_START - (BIT(bits)); 85 86 /* Ensure we are aligned to bits. If not, round down. */ 87 va = va & ~((BIT(bits)) - 1); 88 89 return va; 90} 91 92static void *__map_device_page_failsafe(void *cookie UNUSED, uintptr_t paddr, size_t size, 93 int cached UNUSED, ps_mem_flags_t flags UNUSED) 94{ 95 int bits = CTZ(size); 96 int error; 97 seL4_Word vaddr = 0; 98 vka_object_t dest; 99 100 if (device_cap != 0) { 101 /* we only support a single page for the serial */ 102 for (;;); 103 } 104 105 error = sel4platsupport_alloc_frame_at(vka, paddr, bits, &dest); 106 if (error != seL4_NoError) { 107 goto error; 108 } 109 device_cap = dest.cptr; 110 111 vaddr = platsupport_alloc_device_vaddr(bits); 112 error = 113 seL4_ARCH_Page_Map( 114 device_cap, 115 seL4_CapInitThreadPD, 116 vaddr, 117 seL4_AllRights, 118 0 119 ); 120 121error: 122 if (error) 123 for (;;); 124 125 assert(!error); 126 127 return (void *)vaddr; 128} 129 130static void *__map_device_page_regular(void *cookie UNUSED, uintptr_t paddr, size_t size, 131 int cached UNUSED, ps_mem_flags_t flags UNUSED) 132{ 133 int bits = CTZ(size); 134 void *vaddr; 135 int error; 136 vka_object_t dest; 137 138 error = sel4platsupport_alloc_frame_at(vka, paddr, bits, &dest); 139 if (error) { 140 ZF_LOGF("Failed to get cap for serial device frame"); 141 } 142 143 vaddr = vspace_map_pages(vspace, &dest.cptr, NULL, seL4_AllRights, 1, bits, 0); 144 if (!vaddr) { 145 ZF_LOGF("Failed to map serial device :(\n"); 146 for (;;); 147 } 148 device_cap = dest.cptr; 149 150 return (void *)vaddr; 151} 152 153void *__map_device_page(void *cookie, uintptr_t paddr, size_t size, 154 int cached, ps_mem_flags_t flags) 155{ 156 if (setup_status == START_REGULAR_SETUP && vspace) { 157 return __map_device_page_regular(cookie, paddr, size, cached, flags); 158 } else if (setup_status == START_FAILSAFE_SETUP || !vspace) { 159 return __map_device_page_failsafe(cookie, paddr, size, cached, flags); 160 } 161 printf("Unknown setup status!\n"); 162 for (;;); 163} 164 165/* 166 * This function is designed to be called when creating a new cspace/vspace, 167 * and the serial port needs to be hooked in there too. 168 */ 169void platsupport_undo_serial_setup(void) 170{ 171 /* Re-initialise some structures. */ 172 setup_status = NOT_INITIALIZED; 173 if (device_cap) { 174 cspacepath_t path; 175 seL4_ARCH_Page_Unmap(device_cap); 176 vka_cspace_make_path(vka, device_cap, &path); 177 vka_cnode_delete(&path); 178 vka_cspace_free(vka, device_cap); 179 device_cap = 0; 180 vka = NULL; 181 } 182} 183 184/* Initialise serial input interrupt. */ 185void platsupport_serial_input_init_IRQ(void) 186{ 187} 188 189int platsupport_serial_setup_io_ops(ps_io_ops_t *io_ops) 190{ 191 int err = 0; 192 if (setup_status == SETUP_COMPLETE) { 193 return 0; 194 } 195 err = __plat_serial_init(io_ops); 196 if (!err) { 197 setup_status = SETUP_COMPLETE; 198 } 199 return err; 200} 201 202int platsupport_serial_setup_bootinfo_failsafe(void) 203{ 204 int err = 0; 205 if (setup_status == SETUP_COMPLETE) { 206 return 0; 207 } 208 memset(&_simple_mem, 0, sizeof(simple_t)); 209 memset(&_vka_mem, 0, sizeof(vka_t)); 210#if defined(CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR) && defined(CONFIG_DEBUG_BUILD) 211 /* only support putchar on a debug kernel */ 212 setup_status = SETUP_COMPLETE; 213#else 214 setup_status = START_FAILSAFE_SETUP; 215 simple_default_init_bootinfo(&_simple_mem, platsupport_get_bootinfo()); 216 simple = &_simple_mem; 217 vka = &_vka_mem; 218 simple_make_vka(simple, vka); 219#ifdef CONFIG_ARCH_X86 220 sel4platsupport_get_io_port_ops(&io_ops.io_port_ops, simple, vka); 221#endif 222 err = platsupport_serial_setup_io_ops(&io_ops); 223#endif 224 return err; 225} 226 227int platsupport_serial_setup_simple( 228 vspace_t *_vspace __attribute__((unused)), 229 simple_t *_simple __attribute__((unused)), 230 vka_t *_vka __attribute__((unused))) 231{ 232 int err = 0; 233 if (setup_status == SETUP_COMPLETE) { 234 return 0; 235 } 236 if (setup_status != NOT_INITIALIZED) { 237 printf("Trying to initialise a partially initialised serial. Current setup status is %d\n", setup_status); 238 assert(!"You cannot recover"); 239 return -1; 240 } 241#if defined(CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR) && defined(CONFIG_DEBUG_BUILD) 242 /* only support putchar on a debug kernel */ 243 setup_status = SETUP_COMPLETE; 244#else 245 /* start setup */ 246 setup_status = START_REGULAR_SETUP; 247 vspace = _vspace; 248 simple = _simple; 249 vka = _vka; 250#ifdef CONFIG_ARCH_X86 251 sel4platsupport_get_io_port_ops(&io_ops.io_port_ops, simple, vka); 252#endif 253 err = platsupport_serial_setup_io_ops(&io_ops); 254 /* done */ 255 vspace = NULL; 256 simple = NULL; 257 /* Don't reset vka here */ 258#endif 259 return err; 260} 261 262static void __serial_setup() 263{ 264 int started_regular __attribute__((unused)) = 0; 265 /* this function is called if we attempt to do serial and it isn't setup. 266 * we now need to handle this somehow */ 267 switch (setup_status) { 268 case START_FAILSAFE_SETUP: 269 /* we're stuck. */ 270 abort(); 271 break; 272 case START_REGULAR_SETUP: 273 started_regular = 1; 274 case NOT_INITIALIZED: 275#ifdef CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR 276 setup_status = SETUP_COMPLETE; 277 printf("\nWarning: using printf before serial is set up. This only works as your\n"); 278 printf("printf is backed by seL4_Debug_PutChar()\n"); 279 started_regular = 1; 280#else 281 /* attempt failsafe initialization and print something out */ 282 platsupport_serial_setup_bootinfo_failsafe(); 283 if (started_regular) { 284 printf("Regular serial setup failed.\n" 285 "This message coming to you courtesy of failsafe serial\n" 286 "Your vspace has been clobbered but we will keep running to get any more error output\n"); 287 } else { 288 printf("You attempted to print before initialising the libsel4platsupport serial device!\n"); 289 while (1); 290 } 291#endif /* CONFIG_LIB_SEL4_PLAT_SUPPORT_USE_SEL4_DEBUG_PUTCHAR */ 292 break; 293 case SETUP_COMPLETE: 294 break; 295 } 296} 297 298void NO_INLINE 299#ifdef CONFIG_LIB_SEL4_MUSLC_SYS_ARCH_PUTCHAR_WEAK 300WEAK 301#endif 302__arch_putchar(int c) 303{ 304 if (setup_status != SETUP_COMPLETE) { 305 __serial_setup(); 306 } 307 __plat_putchar(c); 308} 309 310size_t NO_INLINE 311#ifdef CONFIG_LIB_SEL4_MUSLC_SYS_ARCH_PUTCHAR_WEAK 312WEAK 313#endif 314__arch_write(char *data, size_t count) 315{ 316 for (size_t i = 0; i < count; i++) { 317 __arch_putchar(data[i]); 318 } 319 return count; 320} 321 322int __arch_getchar(void) 323{ 324 if (setup_status != SETUP_COMPLETE) { 325 __serial_setup(); 326 } 327 return __plat_getchar(); 328} 329