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#include <autoconf.h>
14#include <sel4platsupport/gen_config.h>
15
16#include <sel4platsupport/io.h>
17#ifdef CONFIG_ARCH_ARM
18#include <platsupport/clock.h>
19#include <platsupport/mux.h>
20#endif
21#include <utils/util.h>
22#include <vspace/page.h>
23
24#include <vspace/vspace.h>
25#include <vka/capops.h>
26
27#include <stdint.h>
28#include <stdlib.h>
29
30typedef struct io_mapping {
31    /* address we returned to the user */
32    void *returned_addr;
33    /* base address of the mapping with respect to the vspace */
34    void *mapped_addr;
35    size_t num_pages;
36    size_t page_size;
37    size_t page_size_bits;
38    /* caps for the mappings (s) */
39    seL4_CPtr *caps;
40    /* allocation cookie for allocation(s) */
41    seL4_Word *alloc_cookies;
42    struct io_mapping *next, *prev;
43} io_mapping_t;
44
45typedef struct sel4platsupport_io_mapper_cookie {
46    vspace_t *vspace;
47    vka_t *vka;
48    io_mapping_t *head;
49} sel4platsupport_io_mapper_cookie_t;
50
51static void free_node(io_mapping_t *node)
52{
53    assert(node);
54    if (node->caps) {
55        free(node->caps);
56    }
57    if (node->alloc_cookies) {
58        free(node->alloc_cookies);
59    }
60    free(node);
61}
62
63static io_mapping_t *new_node(size_t num_pages)
64{
65    io_mapping_t *ret = calloc(1, sizeof(io_mapping_t));
66
67    if (!ret) {
68        return NULL;
69    }
70
71    ret->caps = calloc(num_pages, sizeof(seL4_CPtr));
72    if (!ret->caps) {
73        free_node(ret);
74        return NULL;
75    }
76
77    ret->alloc_cookies = calloc(num_pages, sizeof(seL4_Word));
78    if (!ret->alloc_cookies) {
79        free_node(ret);
80        return NULL;
81    }
82
83    ret->num_pages = num_pages;
84    return ret;
85}
86
87static void destroy_node(vka_t *vka, io_mapping_t *mapping)
88{
89    cspacepath_t path;
90    for (size_t i = 0; i < mapping->num_pages; i++) {
91        /* free the allocation */
92        vka_utspace_free(vka, kobject_get_type(KOBJECT_FRAME, mapping->page_size_bits),
93                         mapping->page_size_bits, mapping->alloc_cookies[i]);
94        /* free the caps */
95        vka_cspace_make_path(vka, mapping->caps[i], &path);
96        vka_cnode_delete(&path);
97        vka_cspace_free(vka, mapping->caps[i]);
98    }
99    free_node(mapping);
100}
101
102static void insert_node(sel4platsupport_io_mapper_cookie_t *io_mapper, io_mapping_t *node)
103{
104    node->prev = NULL;
105    node->next = io_mapper->head;
106    if (io_mapper->head) {
107        io_mapper->head->prev = node;
108    }
109    io_mapper->head = node;
110}
111
112static io_mapping_t *find_node(sel4platsupport_io_mapper_cookie_t *io_mapper, void *returned_addr)
113{
114    io_mapping_t *current;
115    for (current = io_mapper->head; current; current = current->next) {
116        if (current->returned_addr == returned_addr) {
117            return current;
118        }
119    }
120    return NULL;
121}
122
123static void remove_node(sel4platsupport_io_mapper_cookie_t *io_mapper, io_mapping_t *node)
124{
125    if (node->prev) {
126        node->prev->next = node->next;
127    } else {
128        assert(io_mapper->head == node);
129        io_mapper->head = node->next;
130    }
131    if (node->next) {
132        node->next->prev = node->prev;
133    }
134}
135
136static void *sel4platsupport_map_paddr_with_page_size(sel4platsupport_io_mapper_cookie_t *io_mapper, uintptr_t paddr,
137                                                      size_t size, size_t page_size_bits, bool cached)
138{
139
140    vka_t *vka = io_mapper->vka;
141    vspace_t *vspace = io_mapper->vspace;
142
143    /* search at start of page */
144    int page_size = BIT(page_size_bits);
145    uintptr_t start = ROUND_DOWN(paddr, page_size);
146    uintptr_t offset = paddr - start;
147    size += offset;
148
149    io_mapping_t *mapping = new_node(BYTES_TO_SIZE_BITS_PAGES(size, page_size_bits));
150    assert(mapping->num_pages << page_size_bits >= size);
151    if (!mapping) {
152        ZF_LOGE("Failed to allocate node for %zu pages", mapping->num_pages);
153        return NULL;
154    }
155    mapping->page_size_bits = page_size_bits;
156
157    seL4_Word type = kobject_get_type(KOBJECT_FRAME, mapping->page_size_bits);
158    /* allocate all of the physical frame caps */
159    for (unsigned int i = 0; i < mapping->num_pages; i++) {
160        /* allocate a cslot */
161        int error = vka_cspace_alloc(vka, &mapping->caps[i]);
162        if (error) {
163            ZF_LOGE("cspace alloc failed");
164            assert(error == 0);
165            /* we don't clean up as everything has gone to hell */
166            return NULL;
167        }
168
169        /* create a path */
170        cspacepath_t path;
171        vka_cspace_make_path(vka, mapping->caps[i], &path);
172
173        /* allocate the frame */
174        error = vka_utspace_alloc_at(vka, &path, type, page_size_bits, start + (i * page_size),
175                                     &mapping->alloc_cookies[i]);
176        if (error) {
177            /* free this slot, and then do general cleanup of the rest of the slots.
178             * this avoids a needless seL4_CNode_Delete of this slot, as there is no
179             * cap in it */
180            vka_cspace_free(vka, mapping->caps[i]);
181            mapping->num_pages = i;
182            goto error;
183        }
184    }
185
186    /* Now map the frames in */
187    mapping->mapped_addr = vspace_map_pages(vspace, mapping->caps, mapping->alloc_cookies, seL4_AllRights,
188                                            mapping->num_pages,
189                                            mapping->page_size_bits, cached);
190    if (mapping->mapped_addr != NULL) {
191        /* fill out and insert node */
192        mapping->returned_addr = mapping->mapped_addr + offset;
193        insert_node(io_mapper, mapping);
194        return mapping->returned_addr;
195    }
196error:
197    destroy_node(vka, mapping);
198    return NULL;
199}
200
201static void *sel4platsupport_map_paddr(void *cookie, uintptr_t paddr, size_t size, int cached,
202                                       UNUSED ps_mem_flags_t flags)
203{
204    if (!cookie) {
205        ZF_LOGE("cookie is NULL");
206        return NULL;
207    }
208
209    sel4platsupport_io_mapper_cookie_t *io_mapper = (sel4platsupport_io_mapper_cookie_t *)cookie;
210    int frame_size_index = 0;
211    /* find the largest reasonable frame size */
212    while (frame_size_index + 1 < SEL4_NUM_PAGE_SIZES) {
213        if (size >> sel4_page_sizes[frame_size_index + 1] == 0) {
214            break;
215        }
216        frame_size_index++;
217    }
218
219    /* try mapping in this and all smaller frame sizes until something works */
220    for (int i = frame_size_index; i >= 0; i--) {
221        void *result = sel4platsupport_map_paddr_with_page_size(io_mapper, paddr, size, sel4_page_sizes[i], cached);
222        if (result) {
223            return result;
224        }
225    }
226
227    /* shit out of luck */
228    ZF_LOGE("Failed to find a way to map address %p", (void *)paddr);
229    return NULL;
230}
231
232static void sel4platsupport_unmap_vaddr(void *cookie, void *vaddr, UNUSED size_t size)
233{
234    if (!cookie) {
235        ZF_LOGE("cookie is NULL");
236    }
237
238    sel4platsupport_io_mapper_cookie_t *io_mapper = cookie;
239
240    vspace_t *vspace = io_mapper->vspace;
241    vka_t *vka = io_mapper->vka;
242    io_mapping_t *mapping = find_node(io_mapper, vaddr);
243
244    if (!mapping) {
245        ZF_LOGF("Tried to unmap vaddr %p, which was never mapped in", vaddr);
246        return;
247    }
248
249    /* unmap the pages */
250    vspace_unmap_pages(vspace, mapping->mapped_addr, mapping->num_pages, mapping->page_size_bits,
251                       VSPACE_PRESERVE);
252
253    /* clean up the node */
254    remove_node(io_mapper, mapping);
255    destroy_node(vka, mapping);
256}
257
258int sel4platsupport_new_io_mapper(vspace_t *vspace, vka_t *vka, ps_io_mapper_t *io_mapper)
259{
260    sel4platsupport_io_mapper_cookie_t *cookie = calloc(1, sizeof(sel4platsupport_io_mapper_cookie_t));
261    if (!cookie) {
262        ZF_LOGE("Failed to allocate %zu bytes", sizeof(sel4platsupport_io_mapper_cookie_t));
263        return -1;
264    }
265
266    cookie->vspace = vspace;
267    cookie->vka = vka;
268    io_mapper->cookie = cookie;
269    io_mapper->io_map_fn = sel4platsupport_map_paddr;
270    io_mapper->io_unmap_fn = sel4platsupport_unmap_vaddr;
271
272    return 0;
273}
274
275int sel4platsupport_new_malloc_ops(ps_malloc_ops_t *ops)
276{
277    ps_new_stdlib_malloc_ops(ops);
278    return 0;
279}
280
281static char *sel4platsupport_io_fdt_get(void *cookie)
282{
283    return cookie != NULL ? (char *) cookie : NULL;
284}
285
286int sel4platsupport_new_fdt_ops(ps_io_fdt_t *io_fdt, simple_t *simple, ps_malloc_ops_t *malloc_ops)
287{
288    if (!io_fdt || !simple || !malloc_ops) {
289        ZF_LOGE("arguments are NULL");
290        return -1;
291    }
292
293    ssize_t block_size = simple_get_extended_bootinfo_length(simple, SEL4_BOOTINFO_HEADER_FDT);
294    if (block_size > 0) {
295        int error = ps_calloc(malloc_ops, 1, block_size, &io_fdt->cookie);
296        if (error) {
297            ZF_LOGE("Failed to allocate %zu bytes for the FDT", block_size);
298            return -1;
299        }
300
301        /* Copy the FDT from the extended bootinfo */
302        ssize_t copied_size = simple_get_extended_bootinfo(simple, SEL4_BOOTINFO_HEADER_FDT,
303                                                           io_fdt->cookie, block_size);
304        if (copied_size != block_size) {
305            ZF_LOGE("Failed to copy the FDT");
306            ZF_LOGF_IF(ps_free(malloc_ops, block_size, io_fdt->cookie),
307                       "Failed to clean-up after a failed operation!");
308            return -1;
309        }
310
311        /* Cut off the bootinfo header from the start of the buffer */
312        ssize_t fdt_size = block_size - sizeof(seL4_BootInfoHeader);
313        void *fdt_start = io_fdt->cookie + sizeof(seL4_BootInfoHeader);
314        memmove(io_fdt->cookie, fdt_start, fdt_size);
315
316        /* Trim off the extra bytes at the end of the FDT */
317        void *fdt_end = io_fdt->cookie + fdt_size;
318        memset(fdt_end, 0, sizeof(seL4_BootInfoHeader));
319    } else {
320        /* No FDT is available so just set the cookie to NULL */
321        io_fdt->cookie = NULL;
322    }
323
324    /* Set the function pointer inside the io_fdt interface */
325    io_fdt->get_fn = sel4platsupport_io_fdt_get;
326
327    return 0;
328}
329
330int sel4platsupport_new_io_ops(vspace_t *vspace, vka_t *vka, simple_t *simple, ps_io_ops_t *io_ops)
331{
332    memset(io_ops, 0, sizeof(ps_io_ops_t));
333
334    int error = 0;
335
336    /* Initialise the interfaces which do not require memory allocation/need to be initialised first */
337    error = sel4platsupport_new_malloc_ops(&io_ops->malloc_ops);
338    if (error) {
339        return error;
340    }
341
342    /* Now allocate the IO-specific interfaces (the ones that can be found in this file) */
343    error = sel4platsupport_new_io_mapper(vspace, vka, &io_ops->io_mapper);
344    if (error) {
345        return error;
346    }
347
348    error = sel4platsupport_new_fdt_ops(&io_ops->io_fdt, simple, &io_ops->malloc_ops);
349    if (error) {
350        free(io_ops->io_mapper.cookie);
351        io_ops->io_mapper.cookie = NULL;
352        return error;
353    }
354
355    error = sel4platsupport_new_irq_ops(&io_ops->irq_ops, vka, simple, DEFAULT_IRQ_INTERFACE_CONFIG,
356                                        &io_ops->malloc_ops);
357    if (error) {
358        free(io_ops->io_mapper.cookie);
359        io_ops->io_mapper.cookie = NULL;
360        ssize_t fdt_size = simple_get_extended_bootinfo_length(simple, SEL4_BOOTINFO_HEADER_FDT);
361        if (fdt_size > 0) {
362            /* The FDT is available on this platform and we actually copied it, so we free it */
363            ps_free(&io_ops->malloc_ops, fdt_size, &io_ops->io_fdt.cookie);
364        }
365        return error;
366    }
367
368    return 0;
369}
370