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 <sel4utils/gen_config.h>
15
16#include <inttypes.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20
21#include <sel4/sel4.h>
22#include <vka/vka.h>
23#include <vka/object.h>
24#include <vspace/vspace.h>
25#include <sel4runtime.h>
26#include <sel4utils/api.h>
27#include <sel4utils/mapping.h>
28#include <sel4utils/thread.h>
29#include <sel4utils/util.h>
30#include <sel4utils/arch/util.h>
31#include <sel4utils/helpers.h>
32#include <utils/stack.h>
33
34
35int sel4utils_configure_thread(vka_t *vka, vspace_t *parent, vspace_t *alloc, seL4_CPtr fault_endpoint,
36                               seL4_CNode cspace, seL4_Word cspace_root_data, sel4utils_thread_t *res)
37{
38
39    sel4utils_thread_config_t config = {0};
40    config = thread_config_fault_endpoint(config, fault_endpoint);
41    config = thread_config_cspace(config, cspace, cspace_root_data);
42    config = thread_config_create_reply(config);
43    return sel4utils_configure_thread_config(vka, parent, alloc, config, res);
44}
45
46int sel4utils_configure_thread_config(vka_t *vka, vspace_t *parent, vspace_t *alloc,
47                                      sel4utils_thread_config_t config, sel4utils_thread_t *res)
48{
49    memset(res, 0, sizeof(sel4utils_thread_t));
50
51    int error = vka_alloc_tcb(vka, &res->tcb);
52    if (error == -1) {
53        ZF_LOGE("vka_alloc tcb failed");
54        sel4utils_clean_up_thread(vka, alloc, res);
55        return -1;
56    }
57
58    if (!config.no_ipc_buffer) {
59        res->ipc_buffer_addr = (seL4_Word) vspace_new_ipc_buffer(alloc, &res->ipc_buffer);
60
61        if (res->ipc_buffer_addr == 0) {
62            ZF_LOGE("ipc buffer allocation failed");
63            return -1;
64        }
65    }
66
67    if (config_set(CONFIG_KERNEL_MCS) && config.create_reply) {
68        if (vka_alloc_reply(vka, &res->reply)) {
69            ZF_LOGE("Failed to allocate reply");
70            sel4utils_clean_up_thread(vka, alloc, res);
71            return -1;
72        }
73        res->own_reply = true;
74    } else {
75        res->reply.cptr = config.reply;
76    }
77
78    if (config.sched_params.create_sc) {
79        if (!config_set(CONFIG_KERNEL_MCS)) {
80            ZF_LOGE("Cannot create a scheduling context on a non-RT kernel");
81            sel4utils_clean_up_thread(vka, alloc, res);
82            return -1;
83        }
84
85        /* allocate a scheduling context */
86        if (vka_alloc_sched_context(vka, &res->sched_context)) {
87            ZF_LOGE("Failed to allocate sched context");
88            sel4utils_clean_up_thread(vka, alloc, res);
89            return -1;
90        }
91
92        /* configure the scheduling context */
93        if (config_set(CONFIG_KERNEL_MCS)) {
94            error = api_sched_ctrl_configure(config.sched_params.sched_ctrl, res->sched_context.cptr,
95                                             config.sched_params.budget, config.sched_params.period,
96                                             config.sched_params.extra_refills, config.sched_params.badge);
97        }
98        if (error != seL4_NoError) {
99            ZF_LOGE("Failed to configure sched context");
100            sel4utils_clean_up_thread(vka, alloc, res);
101            return -1;
102        }
103        res->own_sc = true;
104    } else {
105        res->sched_context.cptr = config.sched_params.sched_context;
106    }
107    seL4_Word null_cap_data = seL4_NilData;
108    error = api_tcb_configure(res->tcb.cptr, config.fault_endpoint,
109                              seL4_CapNull,
110                              res->sched_context.cptr,
111                              config.cspace,
112                              config.cspace_root_data, vspace_get_root(alloc),
113                              null_cap_data, res->ipc_buffer_addr, res->ipc_buffer);
114
115    if (error != seL4_NoError) {
116        ZF_LOGE("TCB configure failed with seL4 error code %d", error);
117        sel4utils_clean_up_thread(vka, alloc, res);
118        return -1;
119    }
120
121    /* only set the prio fields if the value is > 0. As we just allocated the
122     * TCB above, the prio and mcp are already 0. */
123    if (config.sched_params.mcp) {
124        error = seL4_TCB_SetMCPriority(res->tcb.cptr, config.sched_params.auth,
125                                       config.sched_params.mcp);
126        if (error) {
127            ZF_LOGE("Failed to set mcpriority, %d", error);
128            return -1;
129        }
130    }
131
132    if (config.sched_params.priority) {
133        error = seL4_TCB_SetPriority(res->tcb.cptr, config.sched_params.auth,
134                                     config.sched_params.priority);
135        if (error) {
136            ZF_LOGE("Failed to set priority, %d", error);
137            sel4utils_clean_up_thread(vka, alloc, res);
138            return -1;
139        }
140    }
141
142    if (config.custom_stack_size) {
143        res->stack_size = config.stack_size;
144    } else {
145        res->stack_size = BYTES_TO_4K_PAGES(CONFIG_SEL4UTILS_STACK_SIZE);
146    }
147
148    if (res->stack_size > 0) {
149        res->stack_top = vspace_new_sized_stack(alloc, res->stack_size);
150
151        if (res->stack_top == NULL) {
152            ZF_LOGE("Stack allocation failed!");
153            sel4utils_clean_up_thread(vka, alloc, res);
154            return -1;
155        }
156
157        res->initial_stack_pointer = res->stack_top;
158    }
159
160    return 0;
161}
162
163int sel4utils_start_thread(sel4utils_thread_t *thread, sel4utils_thread_entry_fn entry_point,
164                           void *arg0, void *arg1, int resume)
165{
166    seL4_UserContext context = {0};
167    size_t context_size = sizeof(seL4_UserContext) / sizeof(seL4_Word);
168
169    size_t tls_size = sel4runtime_get_tls_size();
170    /* make sure we're not going to use too much of the stack */
171    if (tls_size > thread->stack_size * PAGE_SIZE_4K / 8) {
172        ZF_LOGE("TLS would use more than 1/8th of the application stack %zu/%zu", tls_size, thread->stack_size);
173        return -1;
174    }
175    uintptr_t tls_base = (uintptr_t)thread->initial_stack_pointer - tls_size;
176    uintptr_t tp = (uintptr_t)sel4runtime_write_tls_image((void *)tls_base);
177    seL4_IPCBuffer *ipc_buffer_addr = (void *)thread->ipc_buffer_addr;
178    sel4runtime_set_tls_variable(tp, __sel4_ipc_buffer, ipc_buffer_addr);
179
180    uintptr_t aligned_stack_pointer = ALIGN_DOWN(tls_base, STACK_CALL_ALIGNMENT);
181
182    int error = sel4utils_arch_init_local_context(entry_point, arg0, arg1,
183                                                  (void *) thread->ipc_buffer_addr,
184                                                  (void *) aligned_stack_pointer,
185                                                  &context);
186    if (error) {
187        return error;
188    }
189
190    error = seL4_TCB_WriteRegisters(thread->tcb.cptr, false, 0, context_size, &context);
191    if (error) {
192        return error;
193    }
194
195    error = seL4_TCB_SetTLSBase(thread->tcb.cptr, tp);
196    if (error) {
197        return error;
198    }
199
200    if (resume) {
201        return seL4_TCB_Resume(thread->tcb.cptr);
202    }
203    return 0;
204}
205
206void sel4utils_clean_up_thread(vka_t *vka, vspace_t *alloc, sel4utils_thread_t *thread)
207{
208    if (thread->tcb.cptr != 0) {
209        vka_free_object(vka, &thread->tcb);
210    }
211
212    if (thread->ipc_buffer_addr != 0) {
213        vspace_free_ipc_buffer(alloc, (seL4_Word *) thread->ipc_buffer_addr);
214    }
215
216    if (thread->stack_top != 0) {
217        vspace_free_sized_stack(alloc, thread->stack_top, thread->stack_size);
218    }
219
220    if (thread->own_sc && thread->sched_context.cptr != 0) {
221        vka_free_object(vka, &thread->sched_context);
222    }
223
224    if (thread->own_reply && thread->reply.cptr != 0) {
225        vka_free_object(vka, &thread->reply);
226    }
227
228    memset(thread, 0, sizeof(sel4utils_thread_t));
229}
230
231void sel4utils_print_fault_message(seL4_MessageInfo_t tag, const char *thread_name)
232{
233    seL4_Fault_t fault = seL4_getFault(tag);
234
235    switch (seL4_Fault_get_seL4_FaultType(fault)) {
236    case seL4_Fault_VMFault:
237        assert(seL4_MessageInfo_get_length(tag) == seL4_VMFault_Length);
238        printf("%sPagefault from [%s]: %s %s at PC: %p vaddr: %p, FSR %p%s\n",
239               COLOR_ERROR,
240               thread_name,
241               sel4utils_is_read_fault() ? "read" : "write",
242               seL4_Fault_VMFault_get_PrefetchFault(fault) ? "prefetch fault" : "fault",
243               (void *)seL4_Fault_VMFault_get_IP(fault),
244               (void *)seL4_Fault_VMFault_get_Addr(fault),
245               (void *)seL4_Fault_VMFault_get_FSR(fault),
246               COLOR_NORMAL);
247        break;
248
249    case seL4_Fault_UnknownSyscall:
250        assert(seL4_MessageInfo_get_length(tag) == seL4_UnknownSyscall_Length);
251        printf("%sBad syscall from [%s]: scno %"PRIuPTR" at PC: %p%s\n",
252               COLOR_ERROR,
253               thread_name,
254               seL4_Fault_UnknownSyscall_get_Syscall(fault),
255               (void *) seL4_Fault_UnknownSyscall_get_FaultIP(fault),
256               COLOR_NORMAL
257              );
258
259        break;
260
261    case seL4_Fault_UserException:
262        assert(seL4_MessageInfo_get_length(tag) == seL4_UserException_Length);
263        printf("%sInvalid instruction from [%s] at PC: %p%s\n",
264               COLOR_ERROR,
265               thread_name,
266               (void *)seL4_Fault_UserException_get_FaultIP(fault),
267               COLOR_NORMAL);
268        break;
269
270    case seL4_Fault_CapFault:
271        printf("%sCap fault from [%s] in phase %s\nPC = %p\nCPtr = %p%s\n",
272               COLOR_ERROR, thread_name,
273               seL4_Fault_CapFault_get_InRecvPhase(fault) ? "receive" : "send",
274               (void *) seL4_Fault_CapFault_get_IP(fault),
275               (void *) seL4_Fault_CapFault_get_Addr(fault),
276               COLOR_NORMAL);
277        break;
278#ifdef CONFIG_KERNEL_MCS
279    case seL4_Fault_Timeout:
280        printf("Timeout fault from %s\n", thread_name);
281        break;
282#endif
283
284    default:
285        /* What? Why are we here? What just happened? */
286        printf("Unknown fault from [%s]: %"PRIuPTR" (length = %"PRIuPTR")\n", thread_name, seL4_MessageInfo_get_label(tag), seL4_MessageInfo_get_length(tag));
287        break;
288    }
289}
290
291static int
292fault_handler(char *name, seL4_CPtr endpoint)
293{
294    seL4_MessageInfo_t info;
295    while (1) {
296        /* sleep so other things can run */
297        info = api_wait(endpoint, NULL);
298        sel4utils_print_fault_message(info, name);
299    }
300    return 0;
301}
302
303int
304sel4utils_start_fault_handler(seL4_CPtr fault_endpoint, vka_t *vka, vspace_t *vspace,
305                              seL4_CPtr cspace, seL4_Word cap_data, char *name,
306                              sel4utils_thread_t *res)
307{
308    int error = sel4utils_configure_thread(vka, vspace, vspace, 0, cspace,
309                                           cap_data, res);
310
311    if (error) {
312        ZF_LOGE("Failed to configure fault handling thread\n");
313        return -1;
314    }
315
316    return sel4utils_start_thread(res, (sel4utils_thread_entry_fn)fault_handler, name,
317                                  (void *) fault_endpoint, 1);
318}
319
320int
321sel4utils_checkpoint_thread(sel4utils_thread_t *thread, sel4utils_checkpoint_t *checkpoint, bool suspend)
322{
323    assert(checkpoint != NULL);
324
325    int error = seL4_TCB_ReadRegisters(thread->tcb.cptr, suspend, 0, sizeof(seL4_UserContext) / sizeof(seL4_Word),
326            &checkpoint->regs);
327    if (error) {
328        ZF_LOGE("Failed to read registers of tcb while checkpointing\n");
329        return error;
330    }
331
332    checkpoint->sp = sel4utils_get_sp(checkpoint->regs);
333#ifdef CONFIG_ARCH_X86_64
334    if (config_set(CONFIG_SYSENTER)) {
335        /* on x64, using sysenter, the kernel ABI expects rcx to be set to rsp,
336         * and rdx to be set to the fault instruction. Simulate this behaviour here
337         * before resuming. Note that this will only work for threads checkpointed
338         * at sysenter, e.g. while in a system call (eg seL4_Recv). */
339        checkpoint->regs.rcx = checkpoint->regs.rsp;
340        checkpoint->regs.rdx = checkpoint->regs.rip;
341    } else if (config_set(CONFIG_SYSCALL)) {
342        /* on x64, using syscall, when a thread is in the kernel,
343         * sp is stored in rbx. So use rbx for stack calculations */
344         checkpoint->sp = checkpoint->regs.rbx;
345    }
346#endif /* CONFIG_ARCH_X86_64 */
347
348    size_t stack_size = (uintptr_t) thread->stack_top - checkpoint->sp;
349    checkpoint->stack = malloc(stack_size);
350    if (checkpoint->stack == NULL) {
351        ZF_LOGE("Failed to malloc stack of size %zu\n", stack_size);
352        return -1;
353    }
354
355    memcpy(checkpoint->stack, (void *) checkpoint->sp, stack_size);
356    checkpoint->thread = thread;
357
358    return error;
359}
360
361int
362sel4utils_checkpoint_restore(sel4utils_checkpoint_t *checkpoint, bool free_memory, bool resume)
363{
364    assert(checkpoint != NULL);
365
366    size_t stack_size = (uintptr_t) checkpoint->thread->stack_top - checkpoint->sp;
367    memcpy((void *) checkpoint->sp, checkpoint->stack, stack_size);
368
369    int error = seL4_TCB_WriteRegisters(checkpoint->thread->tcb.cptr, resume, 0,
370            sizeof(seL4_UserContext) / sizeof (seL4_Word),
371            &checkpoint->regs);
372    if (error) {
373        ZF_LOGE("Failed to restore registers of tcb while restoring checkpoint\n");
374        return error;
375    }
376
377    if (free_memory) {
378       sel4utils_free_checkpoint(checkpoint);
379    }
380
381    return error;
382}
383
384void
385sel4utils_free_checkpoint(sel4utils_checkpoint_t *checkpoint)
386{
387    free(checkpoint->stack);
388}
389
390int sel4utils_set_sched_affinity(sel4utils_thread_t *thread, sched_params_t params) {
391#if CONFIG_MAX_NUM_NODES > 1
392#ifdef CONFIG_KERNEL_MCS
393    return api_sched_ctrl_configure(params.sched_ctrl, thread->sched_context.cptr, params.budget, params.period,
394                                    params.extra_refills, params.badge);
395
396#else
397    return seL4_TCB_SetAffinity(thread->tcb.cptr, params.core);
398#endif
399#else
400    return -ENOSYS;
401#endif
402}
403