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#include <stdio.h>
13#include <string.h>
14#include <stdarg.h>
15#include <stdlib.h>
16#include <stdbool.h>
17
18#include <sel4/sel4.h>
19#include <sel4utils/strerror.h>
20#include <vka/vka.h>
21#include <vka/object.h>
22#include <vka/object_capops.h>
23
24#include "serial_server.h"
25#include <serial_server/parent.h>
26
27seL4_Error
28serial_server_parent_spawn_thread(simple_t *parent_simple, vka_t *parent_vka,
29                                  vspace_t *parent_vspace,
30                                  uint8_t priority)
31{
32    const size_t shmem_max_size = SERIAL_SERVER_SHMEM_MAX_SIZE;
33    seL4_Error error;
34    size_t shmem_max_n_pages;
35    cspacepath_t parent_cspace_cspath;
36    seL4_MessageInfo_t tag;
37
38    if (parent_simple == NULL || parent_vka == NULL || parent_vspace == NULL) {
39        return seL4_InvalidArgument;
40    }
41
42    memset(get_serial_server(), 0, sizeof(serial_server_context_t));
43
44    /* Get a CPtr to the parent's root cnode. */
45    shmem_max_n_pages = BYTES_TO_4K_PAGES(shmem_max_size);
46    vka_cspace_make_path(parent_vka, 0, &parent_cspace_cspath);
47
48    get_serial_server()->server_vka = parent_vka;
49    get_serial_server()->server_vspace = parent_vspace;
50    get_serial_server()->server_cspace = parent_cspace_cspath.root;
51    get_serial_server()->server_simple = parent_simple;
52
53    /* Allocate the Endpoint that the server will be listening on. */
54    error = vka_alloc_endpoint(parent_vka, &get_serial_server()->server_ep_obj);
55    if (error != 0) {
56        ZF_LOGE(SERSERVP"spawn_thread: failed to alloc endpoint, err=%d.",
57                error);
58        return error;
59    }
60
61    /* And also allocate a badged copy of the Server's endpoint that the Parent
62     * can use to send to the Server. This is used to allow the Server to report
63     * back to the Parent on whether or not the Server successfully bound to a
64     * platform serial driver.
65     *
66     * This badged endpoint will be reused by the library as the Parent's badged
67     * Endpoint cap, if the Parent itself ever chooses to connect() to the
68     * Server later on.
69     */
70
71    get_serial_server()->parent_badge_value = serial_server_badge_value_alloc();
72    if (get_serial_server()->parent_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
73        error = seL4_NotEnoughMemory;
74        goto out;
75    }
76
77    error = vka_mint_object(parent_vka, &get_serial_server()->server_ep_obj,
78                            &get_serial_server()->_badged_server_ep_cspath,
79                            seL4_AllRights,
80                            get_serial_server()->parent_badge_value);
81    if (error != 0) {
82        ZF_LOGE(SERSERVP"spawn_thread: Failed to mint badged Endpoint cap to "
83                "server.\n"
84                "\tParent cannot confirm Server thread successfully spawned.");
85        goto out;
86    }
87
88    /* Allocate enough Cnode slots in our CSpace to enable us to receive
89     * frame caps from our clients, sufficient to cover "shmem_max_size".
90     * The problem here is that we're sort of forced to assume that we get
91     * these slots contiguously. If they're not, we have a problem.
92     *
93     * If a client tries to send us too many frames, we respond with an error,
94     * and indicate our shmem_max_size in the SSMSGREG_RESPONSE
95     * message register.
96     */
97    get_serial_server()->frame_cap_recv_cspaths = calloc(shmem_max_n_pages,
98                                                         sizeof(cspacepath_t));
99    if (get_serial_server()->frame_cap_recv_cspaths == NULL) {
100        error = seL4_NotEnoughMemory;
101        goto out;
102    }
103
104    for (size_t i = 0; i < shmem_max_n_pages; i++) {
105        error = vka_cspace_alloc_path(parent_vka,
106                                      &get_serial_server()->frame_cap_recv_cspaths[i]);
107        if (error != 0) {
108            ZF_LOGE(SERSERVP"spawn_thread: Failed to alloc enough cnode slots "
109                    "to receive shmem frame caps equal to %zd bytes.",
110                    shmem_max_size);
111            goto out;
112        }
113    }
114
115    sel4utils_thread_config_t config = thread_config_default(parent_simple, parent_cspace_cspath.root,
116                                                             seL4_NilData, get_serial_server()->server_ep_obj.cptr, priority);
117    error = sel4utils_configure_thread_config(parent_vka, parent_vspace, parent_vspace,
118                                              config, &get_serial_server()->server_thread);
119    if (error != 0) {
120        ZF_LOGE(SERSERVP"spawn_thread: sel4utils_configure_thread failed "
121                "with %d.", error);
122        goto out;
123    }
124
125    NAME_THREAD(get_serial_server()->server_thread.tcb.cptr, "serial server");
126    error = sel4utils_start_thread(&get_serial_server()->server_thread,
127                                   (sel4utils_thread_entry_fn)&serial_server_main,
128                                   NULL, NULL, 1);
129    if (error != 0) {
130        ZF_LOGE(SERSERVP"spawn_thread: sel4utils_start_thread failed with "
131                "%d.", error);
132        goto out;
133    }
134
135    /* When the Server is spawned, it will reply to tell us whether or not it
136     * successfully bound itself to the platform serial device. Block here
137     * and wait for that reply.
138     */
139    seL4_SetMR(SSMSGREG_FUNC, FUNC_SERVER_SPAWN_SYNC_REQ);
140    tag = seL4_MessageInfo_new(0, 0, 0, SSMSGREG_SPAWN_SYNC_REQ_END);
141    tag = seL4_Call(get_serial_server()->_badged_server_ep_cspath.capPtr, tag);
142
143    /* Did all go well with the server? */
144    if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_SERVER_SPAWN_SYNC_ACK) {
145        ZF_LOGE(SERSERVP"spawn_thread: Server thread sync message after spawn "
146                "was not a SYNC_ACK as expected.");
147        error = seL4_InvalidArgument;
148        goto out;
149    }
150    error = seL4_MessageInfo_get_label(tag);
151    if (error != 0) {
152        ZF_LOGE(SERSERVP"spawn_thread: Server thread failed to bind to the "
153                "platform serial device.");
154        goto out;
155    }
156
157    get_serial_server()->shmem_max_size = shmem_max_size;
158    get_serial_server()->shmem_max_n_pages = shmem_max_n_pages;
159    return 0;
160
161out:
162    if (get_serial_server()->frame_cap_recv_cspaths != NULL) {
163        for (size_t i = 0; i < shmem_max_n_pages; i++) {
164            /* Since the array was allocated with calloc(), it was zero'd out. So
165             * those indexes that didn't get allocated will have NULL in them.
166             * Break early on the first index that has NULL.
167             */
168            if (get_serial_server()->frame_cap_recv_cspaths[i].capPtr == 0) {
169                break;
170            }
171            vka_cspace_free_path(parent_vka, get_serial_server()->frame_cap_recv_cspaths[i]);
172        }
173    }
174    free(get_serial_server()->frame_cap_recv_cspaths);
175
176    if (get_serial_server()->_badged_server_ep_cspath.capPtr != 0) {
177        vka_cspace_free_path(parent_vka, get_serial_server()->_badged_server_ep_cspath);
178    }
179    if (get_serial_server()->parent_badge_value != SERIAL_SERVER_BADGE_VALUE_EMPTY) {
180        serial_server_badge_value_free(get_serial_server()->parent_badge_value);
181    }
182    vka_free_object(parent_vka, &get_serial_server()->server_ep_obj);
183    return error;
184}
185
186int
187serial_server_parent_vka_mint_endpoint(vka_t *client_vka,
188                                       cspacepath_t *badged_server_ep_cspath)
189{
190    if (client_vka == NULL || badged_server_ep_cspath == NULL) {
191        return seL4_InvalidArgument;
192    }
193
194    seL4_Word new_badge_value = serial_server_badge_value_alloc();
195    if (new_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
196        return -1;
197    }
198
199    /* Mint the Endpoint to a new client. */
200    return vka_mint_object_inter_cspace(get_serial_server()->server_vka,
201                                        &get_serial_server()->server_ep_obj,
202                                        client_vka,
203                                        badged_server_ep_cspath,
204                                        seL4_AllRights,
205                                        new_badge_value);
206}
207
208static inline void
209parent_ep_obj_to_cspath(cspacepath_t *result)
210{
211    if (result == NULL) {
212        return;
213    }
214    vka_cspace_make_path(get_serial_server()->server_vka,
215                         get_serial_server()->server_ep_obj.cptr,
216                         result);
217}
218
219int
220serial_server_allocate_client_badged_ep(cspacepath_t dest_slot)
221{
222    cspacepath_t server_ep_cspath;
223
224    seL4_Word new_badge_value = serial_server_badge_value_alloc();
225    if (new_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
226        return -1;
227    }
228
229    parent_ep_obj_to_cspath(&server_ep_cspath);
230    return vka_cnode_mint(&dest_slot, &server_ep_cspath, seL4_AllRights,
231                          new_badge_value);
232}
233
234seL4_CPtr
235serial_server_parent_mint_endpoint_to_process(sel4utils_process_t *p)
236{
237    if (p == NULL) {
238        return 0;
239    }
240
241    cspacepath_t server_ep_cspath;
242    seL4_Word new_badge_value = serial_server_badge_value_alloc();
243    if (new_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
244        return -1;
245    }
246
247    parent_ep_obj_to_cspath(&server_ep_cspath);
248    return sel4utils_mint_cap_to_process(p, server_ep_cspath,
249                                         seL4_AllRights,
250                                         new_badge_value);
251}
252