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