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 <string.h>
13#include <stdio.h>
14
15#include <sel4/sel4.h>
16#include <vka/capops.h>
17#include <allocman/allocman.h>
18#include <allocman/vka.h>
19#include <allocman/bootstrap.h>
20#include <sel4utils/thread.h>
21#include <serial_server/parent.h>
22#include <serial_server/client.h>
23
24#include "../test.h"
25#include "../helpers.h"
26
27#define SERSERV_TEST_PRIO_SERVER    (seL4_MaxPrio - 1)
28#define SERSERV_TEST_PRIO_CLIENT    (seL4_MinPrio)
29
30#define SERSERV_TEST_N_CLIENTS      (1)
31
32#define SERSERV_TEST_ALLOCMAN_PREALLOCATED_MEMSIZE (64 * 1024)
33#define SERSERV_TEST_UT_SIZE        (512 * 1024)
34
35static const char *test_str = "Hello, world!\n";
36
37
38/* These next few tests test the same things from client threads.
39 *
40 * We spawn a bunch of helper threads and then ask those helper threads to
41 * run these same tests, and then have the helpers return the values they
42 * got when they called the Server.
43 *
44 * We keep it simple as well by using some global state to pass data to the
45 * client threads so they can do their work. Then, so that each client knows
46 * which one it is, we pass the clients a numeric ID invented by the test
47 * to identify that client.
48 */
49typedef struct _client_test_data {
50    helper_thread_t thread;
51    /* "badged_server_ep_cspath2" is used in the disconnect/reconnect tests:
52     * the client can't reconnect using the same badged ep as the first
53     * connection.
54     *
55     * We could make each client seL4_Call() the main thread and ask
56     * it to re-mint the ep-cap to each client, but that imposes extra
57     * complexity for a simple test. So for that particular test we just mint 2
58     * ep caps from the start.
59     */
60    volatile void **parent_used_pages_list, *client_used_pages_list;
61    int n_used_pages;
62    cspacepath_t badged_server_ep_cspath, badged_server_ep_cspath2, ut_512k_cspath;
63    serial_client_context_t conn;
64} client_test_data_t;
65
66typedef int (client_test_fn)(client_test_data_t *, vka_t *, vspace_t *);
67
68struct {
69    vka_t *vka;
70    vspace_t *vspace;
71    simple_t *simple;
72    client_test_data_t client_test_data[SERSERV_TEST_N_CLIENTS];
73    uint8_t allocman_mem[SERSERV_TEST_N_CLIENTS][SERSERV_TEST_ALLOCMAN_PREALLOCATED_MEMSIZE];
74} static client_globals;
75
76/* These next few functions (client_*_main())are the actual tests that are run
77 * by the client threads and processes for the concurrency tests.
78 */
79
80/** This function will guide a client thread to connect to the server and return the
81 * result.
82 */
83static int client_connect_main(client_test_data_t *self, vka_t *vka, vspace_t *vspace)
84{
85    int error = 0;
86    serial_client_context_t conn;
87
88    error = serial_server_client_connect(self->badged_server_ep_cspath.capPtr,
89                                         vka, vspace,
90                                         &conn);
91    return error;
92}
93
94/** This function will guide a client thread to connect to the server, send a
95 * printf() request, and return the result.
96 */
97static int client_printf_main(client_test_data_t *self, vka_t *vka, vspace_t *vspace)
98{
99    int error;
100    serial_client_context_t conn;
101
102    error = serial_server_client_connect(self->badged_server_ep_cspath.capPtr,
103                                         vka, vspace,
104                                         &conn);
105    if (error != 0) {
106        return error;
107    }
108
109    error = serial_server_printf(&conn, test_str);
110    if (error != strlen(test_str)) {
111        return -1;
112    }
113    return 0;
114}
115
116/** This function will guide a client thread to connect to the server, send a
117 * write() request, and return the result.
118 */
119static int client_write_main(client_test_data_t *self, vka_t *vka, vspace_t *vspace)
120{
121    int error;
122    serial_client_context_t conn;
123
124    error = serial_server_client_connect(self->badged_server_ep_cspath.capPtr,
125                                         vka, vspace,
126                                         &conn);
127    if (error != 0) {
128        return error;
129    }
130
131    error = serial_server_write(&conn, test_str, strlen(test_str));
132    if (error != strlen(test_str)) {
133        return -1;
134    }
135    return 0;
136}
137
138/** This function will guide a client thread to connect to the server, send a
139 * printf() request, then a write() request, then disconnect(), and then
140 * reconnect to the Server and send another printf() and another write(),
141 * and return the result.
142 */
143static int client_disconnect_reconnect_printf_write_main(client_test_data_t *self, vka_t *vka, vspace_t *vspace)
144{
145    int error;
146    serial_client_context_t conn;
147
148    error = serial_server_client_connect(self->badged_server_ep_cspath.capPtr,
149                                         vka, vspace,
150                                         &conn);
151    if (error != 0) {
152        return error;
153    }
154    error = serial_server_printf(&conn, test_str);
155    if (error != strlen(test_str)) {
156        return -1;
157    }
158    error = serial_server_write(&conn, test_str, strlen(test_str));
159    if (error != strlen(test_str)) {
160        return error;
161    }
162
163    serial_server_disconnect(&conn);
164
165    error = serial_server_client_connect(self->badged_server_ep_cspath2.capPtr,
166                                         vka, vspace,
167                                         &conn);
168    if (error != 0) {
169        return error;
170    }
171    error = serial_server_printf(&conn, test_str);
172    if (error != strlen(test_str)) {
173        return -1;
174    }
175    error = serial_server_write(&conn, test_str, strlen(test_str));
176    if (error != strlen(test_str)) {
177        return -1;
178    }
179    return 0;
180}
181
182/** This function will guide a client thread to connect to the server, and then
183 * send a kill() request.
184 */
185static int client_disconnect_main(client_test_data_t *self, vka_t *vka, vspace_t *vspace)
186{
187    int error;
188    serial_client_context_t conn;
189
190    error = serial_server_client_connect(self->badged_server_ep_cspath.capPtr,
191                                         vka, vspace,
192                                         &conn);
193    if (error != 0) {
194        return error;
195    }
196
197    serial_server_disconnect(&conn);
198    return 0;
199}
200
201static void init_file_globals(struct env *env)
202{
203    memset(client_globals.client_test_data, 0,
204           sizeof(client_globals.client_test_data));
205
206    memset(client_globals.allocman_mem, 0, sizeof(client_globals.allocman_mem));
207
208    client_globals.vka = &env->vka;
209    client_globals.vspace = &env->vspace;
210    client_globals.simple = &env->simple;
211}
212
213static int mint_server_ep_to_clients(struct env *env, bool mint_2nd_ep)
214{
215    int error;
216
217    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
218        /* Ask the serserv library to mint the Server's EP to all the new clients. */
219        client_test_data_t *curr_client = &client_globals.client_test_data[i];
220
221        if (curr_client->thread.is_process) {
222            curr_client->badged_server_ep_cspath.capPtr = serial_server_parent_mint_endpoint_to_process(
223                                                              &curr_client->thread.process);
224            if (curr_client->badged_server_ep_cspath.capPtr == 0) {
225                return -1;
226            }
227        } else {
228            error = serial_server_parent_vka_mint_endpoint(&env->vka,
229                                                           &curr_client->badged_server_ep_cspath);
230            if (error != 0) {
231                return -1;
232            }
233        }
234
235        if (mint_2nd_ep) {
236            if (curr_client->thread.is_process) {
237                curr_client->badged_server_ep_cspath2.capPtr = serial_server_parent_mint_endpoint_to_process(
238                                                                   &curr_client->thread.process);
239                if (curr_client->badged_server_ep_cspath2.capPtr == 0) {
240                    return -1;
241                }
242            } else {
243                error = serial_server_parent_vka_mint_endpoint(&env->vka,
244                                                               &curr_client->badged_server_ep_cspath2);
245                if (error != 0) {
246                    return -1;
247                }
248            }
249        }
250    }
251    return 0;
252}
253
254static void create_clients(struct env *env, bool is_process)
255{
256    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
257        client_test_data_t *curr_client = &client_globals.client_test_data[i];
258
259        if (is_process) {
260            create_helper_process(env, &curr_client->thread);
261        } else {
262            create_helper_thread(env, &curr_client->thread);
263        }
264    }
265}
266
267/** Guides a client "process" to set up a vspace_t instance for its new VSpace.
268 *
269 * This basically entails setting up an allocman_t instance, followed by a
270 * vka_t instance and finally a vspace_t instance. The function requires an
271 * untyped of a "reasonable" size (you determine this based on what this new
272 * process will be doing), a list of used pages in the new process' VSpace,
273 * some allocman initial mem and your pointers.
274 *
275 * @param ut_cap CPtr to an untyped of "reasonable" size that was copied to the
276 *               new process before this function was invoked.
277 * @param ut_size_bits The size in bits of the untyped "ut_cap".
278 * @param used_pages_list A NULL-terminated list of virtual pages that are
279 *                        already occupied within this new process' VSpace.
280 * @param allocman_mem Initial memory set aside for bootstrapping an allocman_t.
281 * @param allocman_mem_size Size of the initial allocman_t memory.
282 * @param allocman[out] Pointer to a pointer to an uninitialized allocman_t
283 *                      instance. Will be returned with an initialized
284 *                      allocman_t if this function completes successfully.
285 * @param vka[out] Pointer to an uninitialized vka_t instance.
286 * @param vspace[out] Pointer to an uninitialized vspace_t instance.
287 * @param alloc_data[out] Pointer to an uninitialized alloc data structure.
288 * @return 0 on success; non-zero on error.
289 */
290static int setup_client_process_allocman_vka_and_vspace(seL4_CPtr ut_cap, size_t ut_size_bits,
291                                                        seL4_CPtr first_free_cptr,
292                                                        volatile void **used_pages_list,
293                                                        uint8_t *allocman_mem, size_t allocman_mem_size,
294                                                        allocman_t **allocman,
295                                                        vka_t *vka, vspace_t *vspace,
296                                                        sel4utils_alloc_data_t *alloc_data)
297{
298    cspacepath_t ut_cspath;
299    uintptr_t paddr;
300    int error;
301
302    if (used_pages_list == NULL || allocman_mem == NULL
303        || allocman == NULL || vspace == NULL || vka == NULL
304        || alloc_data == NULL || allocman_mem_size == 0) {
305        return seL4_InvalidArgument;
306    }
307    *allocman = bootstrap_use_current_1level(SEL4UTILS_CNODE_SLOT,
308                                             TEST_PROCESS_CSPACE_SIZE_BITS,
309                                             first_free_cptr,
310                                             BIT(TEST_PROCESS_CSPACE_SIZE_BITS),
311                                             allocman_mem_size,
312                                             allocman_mem);
313    if (*allocman == NULL) {
314        return -1;
315    }
316
317    allocman_make_vka(vka, *allocman);
318    vka_cspace_make_path(vka, ut_cap, &ut_cspath);
319    error = allocman_utspace_add_uts(*allocman, 1, &ut_cspath,
320                                     &ut_size_bits,
321                                     &paddr, ALLOCMAN_UT_KERNEL);
322    if (error != 0) {
323        return error;
324    }
325
326    /* Pass the shmem page containing the list of already allocated frames, to
327     * the vspace bootstrap.
328     */
329    return sel4utils_bootstrap_vspace_leaky(vspace, alloc_data,
330                                            SEL4UTILS_PD_SLOT, vka,
331                                            (void **)used_pages_list);
332}
333
334/** Common entry point for all client thread and processes alike.
335 *
336 * This is what is passed to start_helper so that it can do its init work.
337 * This function invokes the actual test routine after it finishes working.
338 *
339 * Determines whether the new client is a thread or process. If it's a process
340 * we do some extra processing, vis-a-vis setting up the new process' own
341 * vka, vspace and allocman, so it can call the library.
342 *
343 * @param _test_fn The actual test function that is to be invoked.
344 * @param _used_pages_list List of used virtual pages in the VSpace of the new
345 *                         client. This is used to initialize the new client's
346 *                         vspace_t instance.
347 * @param n_used_pages Number of elements in the _used_pages_list.
348 * @param thread_num The "ID" of this thread as the parent assigned it.
349 * @return 0 on success; non-zero on error.
350 */
351static int client_common_entry_point(seL4_Word _test_fn, seL4_Word _used_pages_list,
352                                     seL4_Word n_used_pages, seL4_Word thread_num)
353{
354    bool is_process;
355    client_test_data_t *client_data;
356    volatile void **used_pages_list = (volatile void **)_used_pages_list;
357    client_test_fn *test_fn = (client_test_fn *)_test_fn;
358    int error;
359    vka_t *vka, _vka;
360    vspace_t *vspace, _vspace;
361    sel4utils_alloc_data_t alloc_data;
362    allocman_t *allocman;
363
364    is_process = used_pages_list != NULL;
365
366    if (is_process) {
367        /* +1 because the list is NULL terminated and we want to get past the
368         * end of the list.
369         */
370        client_data = (client_test_data_t *)&used_pages_list[n_used_pages + 1];
371        vka = &_vka;
372        vspace = &_vspace;
373        error = setup_client_process_allocman_vka_and_vspace(client_data->ut_512k_cspath.capPtr,
374                                                             BYTES_TO_SIZE_BITS(SERSERV_TEST_UT_SIZE),
375                                                             client_data->ut_512k_cspath.capPtr + 0x8,
376                                                             used_pages_list,
377                                                             client_globals.allocman_mem[thread_num],
378                                                             SERSERV_TEST_ALLOCMAN_PREALLOCATED_MEMSIZE,
379                                                             &allocman,
380                                                             vka, vspace,
381                                                             &alloc_data);
382        test_eq(error, 0);
383    } else {
384        client_data = &client_globals.client_test_data[thread_num];
385        vka = client_globals.vka;
386        vspace = client_globals.vspace;
387    }
388    return test_fn(client_data, vka, vspace);
389}
390
391static void start_clients(struct env *env, client_test_fn *test_fn)
392{
393    bool is_process = client_globals.client_test_data[0].thread.is_process;
394
395    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
396        client_test_data_t *curr_client = &client_globals.client_test_data[i];
397        /* The client threads don't need a list of used pages. The client
398         * processes do need it -- so if the used pages list is NULL, the newly
399         * spawned client assumes it's a thread and not a process.
400         */
401        volatile void **used_pages_list = ((is_process)
402                                           ? (volatile void **)curr_client->client_used_pages_list
403                                           : NULL);
404
405        set_helper_priority(env, &curr_client->thread, SERSERV_TEST_PRIO_CLIENT);
406        start_helper(env, &curr_client->thread, &client_common_entry_point,
407                     (seL4_Word)test_fn,
408                     (seL4_Word)used_pages_list,
409                     curr_client->n_used_pages,
410                     i);
411    }
412}
413
414static void cleanup_clients(struct env *env)
415{
416    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
417        helper_thread_t *curr_client = &client_globals.client_test_data[i].thread;
418        cleanup_helper(env, curr_client);
419    }
420}
421
422/** Compiles a list of pages that libsel4utils allocated while creating a
423 * thread in a new VSpace.
424 *
425 * This is useful if you want your new process to be able to initialize its own
426 * vspace_t, because you will need to have a list of pages that are occupied in
427 * the new VSpace, in order to get a usable vspace_t for that VSpace.
428 * You need to allocate the memory for page_list yourself.
429 *
430 * @param page_list The base vaddr of a page in the PARENT VSpace (the thread
431 *                  that is spawning the new thread), that will be used as the
432 *                  shared mem for telling the new thread which pages in its
433 *                  VSpace are occupied.
434 * @param p Initialized sel4utils_process_t handle for the new thread.
435 * @return 0 on success; Positive integer count of the number of used pages
436 *         whose vaddrs were filled into the page_list on success.
437 */
438static int fill_sel4utils_used_pages_list(volatile void **page_list, sel4utils_process_t *p)
439{
440    seL4_Word sel4utils_stack_n_pages, sel4utils_stack_base_vaddr;
441
442    if (page_list == NULL || p == NULL) {
443        return -1;
444    }
445
446    /* Need to fill in the vaddrs for its stack and IPC buffer.
447     */
448
449    /* libsel4utils adds another page for guard */
450    sel4utils_stack_n_pages = BYTES_TO_4K_PAGES(CONFIG_SEL4UTILS_STACK_SIZE) + 1;
451    sel4utils_stack_base_vaddr = (uintptr_t)p->thread.stack_top - (sel4utils_stack_n_pages * BIT(seL4_PageBits));
452    for (int i = 0; i < sel4utils_stack_n_pages; i++) {
453        *page_list = (void *)sel4utils_stack_base_vaddr;
454        sel4utils_stack_base_vaddr += BIT(seL4_PageBits);
455        page_list++;
456    }
457
458    /* Next take care of the IPC buffer */
459    *page_list = (void *)p->thread.ipc_buffer_addr;
460    page_list++;
461    /* NULL terminate the list */
462    *page_list = NULL;
463
464    /* Stack pages + IPC buffer */
465    return sel4utils_stack_n_pages + 1;
466}
467
468/** Shares a used page list to a target process that was created by sel4utils.
469 *
470 * @param page_list Page that has been filled out with a list of pages used by
471 *                  sel4utils_configure_process.
472 * @param n_entries Number of entries in the page_list.
473 * @param from_vspace Initialized vspace_t for the parent process (that is doing
474 *                    the spawning).
475 * @param p Initialized sel4utils_process_t for the new process.
476 * @param target_vaddr[out] The vaddr that the NEW process can access the
477 *                          used pages list at, within ITS own VSpace.
478 * @return Negative integer on failure. Positive integer on success, which
479 *         represents the number of ADDITIONAL used pages filled in by this
480 *         function invocation.
481 */
482static int share_sel4utils_used_pages_list(volatile void **page_list, size_t n_entries,
483                                           vspace_t *from_vspace,
484                                           sel4utils_process_t *p,
485                                           volatile void **target_vaddr)
486{
487    vspace_t *to_vspace;
488
489    if (page_list == NULL || from_vspace == NULL || p == NULL
490        || target_vaddr == NULL) {
491        return -1;
492    }
493
494    to_vspace = &p->vspace;
495    *target_vaddr = vspace_share_mem(from_vspace, to_vspace,
496                                     (void *)ALIGN_DOWN((uintptr_t)page_list, BIT(seL4_PageBits)),
497                                     1, seL4_PageBits,
498                                     seL4_AllRights, true);
499    if (*target_vaddr == NULL) {
500        return -1;
501    }
502    /* Don't forget to add the offset back if the page_list vaddr was not
503     * page aligned
504     */
505    *target_vaddr = (void *)((uintptr_t) * target_vaddr
506                             + ((uintptr_t)page_list & (BIT(seL4_PageBits) - 1)));
507    /* Next, take care of the shmem page we ourselves created in this
508     * function
509     */
510    page_list[n_entries] = *target_vaddr;
511    return 1;
512}
513
514/** For each new client process, fill out a list of virtual pages that will be
515 * occupied in its VSpace before it even begins executing.
516 *
517 * The client will use this list to initialize a vspace_t instance. This is
518 * called in the Parent, before the clients are allowed to execute.
519 * @return 0 on success; non-zero on error.
520 */
521static int setup_pclient_used_pages_lists(struct env *env)
522{
523    int error;
524
525    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
526        client_test_data_t *curr_client = &client_globals.client_test_data[i];
527
528        /* Allocate the page in our VSpace */
529        curr_client->parent_used_pages_list = (volatile void **)vspace_new_pages(
530                                                  &env->vspace,
531                                                  seL4_AllRights,
532                                                  1, seL4_PageBits);
533        if (curr_client->parent_used_pages_list == NULL) {
534            return -1;
535        }
536
537        /* Give the page to fill_sel4utils_used_pages and let it fill out the
538         * pages it used in the new process' VSpace.
539         */
540        curr_client->n_used_pages = fill_sel4utils_used_pages_list(
541                                        curr_client->parent_used_pages_list,
542                                        &curr_client->thread.process);
543        if (curr_client->n_used_pages < 0) {
544            return -1;
545        }
546
547        /* Share the page containing the used pages list with the new process */
548        error = share_sel4utils_used_pages_list(curr_client->parent_used_pages_list,
549                                                curr_client->n_used_pages,
550                                                &env->vspace,
551                                                &curr_client->thread.process,
552                                                &curr_client->client_used_pages_list);
553        if (error < 0) {
554            return -1;
555        }
556        curr_client->n_used_pages += error;
557        /* NULL terminate the list since we added to it. */
558        curr_client->parent_used_pages_list[curr_client->n_used_pages] = NULL;
559    }
560    return 0;
561}
562
563/** The new client processes each need an untyped of some size so they can
564 * allocate memory.
565 *
566 * 512k seems reasonably large (if not a little too large). Allocate an untyped
567 * for each new client process. This is called in the parent, before the test
568 * clients are allowed to execute.
569 * @return 0 on success; Non-zero on error.
570 */
571static int alloc_untypeds_for_clients(struct env *env)
572{
573    int error;
574
575    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
576        client_test_data_t *curr_client = &client_globals.client_test_data[i];
577        vka_object_t tmp;
578
579        error = vka_alloc_untyped(&env->vka, BYTES_TO_SIZE_BITS(SERSERV_TEST_UT_SIZE), &tmp);
580        if (error != 0) {
581            return error;
582        }
583
584        vka_cspace_make_path(&env->vka, tmp.cptr, &curr_client->ut_512k_cspath);
585        curr_client->ut_512k_cspath.capPtr = sel4utils_move_cap_to_process(
586                                                 &curr_client->thread.process,
587                                                 curr_client->ut_512k_cspath,
588                                                 &env->vka);
589        if (curr_client->ut_512k_cspath.capPtr == 0) {
590            return -1;
591        }
592    }
593    return 0;
594}
595
596static void copy_client_data_to_shmem(struct env *env)
597{
598    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
599        client_test_data_t *curr_client = &client_globals.client_test_data[i];
600        /* +1 because the list is NULL terminated and we want to get past the
601         * end of the list.
602         */
603        void *client_data_copy = &curr_client->parent_used_pages_list[
604                              curr_client->n_used_pages + 1];
605
606        memcpy(client_data_copy, curr_client, sizeof(*curr_client));
607    }
608}
609
610static int concurrency_test_common(struct env *env, bool is_process, client_test_fn *test_fn, bool mint_2nd_ep_cap)
611{
612    int error;
613
614    error = serial_server_parent_spawn_thread(&env->simple,
615                                              &env->vka, &env->vspace,
616                                              SERSERV_TEST_PRIO_SERVER);
617
618    test_eq(error, 0);
619
620    init_file_globals(env);
621    create_clients(env, is_process);
622    error = mint_server_ep_to_clients(env, mint_2nd_ep_cap);
623    test_eq(error, 0);
624
625    if (is_process) {
626        /* Setup the extra things that a new VSpace/CSpace needs.
627         */
628        error = setup_pclient_used_pages_lists(env);
629        test_eq(error, 0);
630        error = alloc_untypeds_for_clients(env);
631        test_eq(error, 0);
632        copy_client_data_to_shmem(env);
633    };
634
635    start_clients(env, test_fn);
636
637    for (int i = 0; i < SERSERV_TEST_N_CLIENTS; i++) {
638        error = wait_for_helper(&client_globals.client_test_data[i].thread);
639        test_eq(error, 0);
640    }
641
642    cleanup_clients(env);
643    return 0;
644}
645
646static int test_client_connect(struct env *env)
647{
648    int error;
649
650    error = concurrency_test_common(env, false, &client_connect_main, false);
651    test_eq(error, 0);
652    return sel4test_get_result();
653}
654DEFINE_TEST(SERSERV_CLIENT_001, "Connect from client threads",
655            test_client_connect, true)
656
657static int
658test_client_printf(struct env *env)
659{
660    int error;
661
662    error = concurrency_test_common(env, false, &client_printf_main, false);
663    test_eq(error, 0);
664    return sel4test_get_result();
665}
666DEFINE_TEST(SERSERV_CLIENT_002, "Printf from client threads",
667            test_client_printf, true)
668
669static int
670test_client_write(struct env *env)
671{
672    int error;
673
674    error = concurrency_test_common(env, false, &client_write_main, false);
675    test_eq(error, 0);
676    return sel4test_get_result();
677}
678DEFINE_TEST(SERSERV_CLIENT_003, "Write from client threads",
679            test_client_write, true)
680
681static int
682test_client_disconnect_reconnect_printf_write(struct env *env)
683{
684    int error;
685
686    error = concurrency_test_common(env, false, &client_disconnect_reconnect_printf_write_main, true);
687    test_eq(error, 0);
688    return sel4test_get_result();
689}
690DEFINE_TEST(SERSERV_CLIENT_004, "Printf, then write, then reset connection, and "
691            "Printf, then write again, from client threads",
692            test_client_disconnect_reconnect_printf_write, true)
693
694static int
695test_client_kill(struct env *env)
696{
697    int error;
698    cspacepath_t parent_badged_server_ep_cspath;
699    serial_client_context_t conn;
700
701    error = concurrency_test_common(env, false, &client_disconnect_main, false);
702    test_eq(error, 0);
703
704    error = serial_server_parent_vka_mint_endpoint(&env->vka,
705                                                   &parent_badged_server_ep_cspath);
706    test_eq(error, 0);
707
708    error = serial_server_client_connect(parent_badged_server_ep_cspath.capPtr,
709                                         &env->vka, &env->vspace, &conn);
710    test_eq(error, 0);
711
712    error = serial_server_kill(&conn);
713    test_eq(error, 0);
714    return sel4test_get_result();
715}
716DEFINE_TEST(SERSERV_CLIENT_005, "Connect, then disconnect from server on all "
717            "threads, then kill server from parent thread",
718            test_client_kill, true)
719
720static int
721test_client_process_connect(struct env *env)
722{
723    int error;
724
725    error = concurrency_test_common(env, true, &client_connect_main, false);
726    test_eq(error, 0);
727    return sel4test_get_result();
728}
729DEFINE_TEST(SERSERV_CLI_PROC_001, "Connect to server from a client in another "
730            "VSpace and CSpace", test_client_process_connect, true)
731
732static int
733test_client_process_printf(struct env *env)
734{
735    int error;
736
737    error = concurrency_test_common(env, true, &client_printf_main, false);
738    test_eq(error, 0);
739    return sel4test_get_result();
740}
741DEFINE_TEST(SERSERV_CLI_PROC_002, "Connect to server and printf(, true), from a client "
742            "in another VSpace and CSpace", test_client_process_printf, true)
743
744static int
745test_client_process_write(struct env *env)
746{
747    int error;
748
749    error = concurrency_test_common(env, true, &client_write_main, false);
750    test_eq(error, 0);
751    return sel4test_get_result();
752}
753DEFINE_TEST(SERSERV_CLI_PROC_003, "Connect to server and write(, true), from a client "
754            "in another VSpace and CSpace", test_client_process_write, true)
755
756static int
757test_client_process_disconnect_reconnect_printf_write(struct env *env)
758{
759    int error;
760
761    error = concurrency_test_common(env, true,
762                                    &client_disconnect_reconnect_printf_write_main,
763                                    true);
764    test_eq(error, 0);
765    return sel4test_get_result();
766}
767DEFINE_TEST(SERSERV_CLI_PROC_004, "Connect to server, printf(), write(, true), then "
768            "disconnect, then reconnect and printf() and write() again, from "
769            "clients in other VSpaces and CSpaces",
770            test_client_process_disconnect_reconnect_printf_write, true)
771
772static int
773test_client_process_kill(struct env *env)
774{
775    int error;
776    serial_client_context_t conn;
777    cspacepath_t badged_server_ep_cspath;
778
779    error = concurrency_test_common(env, true, &client_disconnect_main, false);
780    test_eq(error, 0);
781
782    error = serial_server_parent_vka_mint_endpoint(&env->vka, &badged_server_ep_cspath);
783    test_eq(error, 0);
784
785    error = serial_server_client_connect(badged_server_ep_cspath.capPtr,
786                                         &env->vka, &env->vspace, &conn);
787    test_eq(error, 0);
788    error = serial_server_kill(&conn);
789    test_eq(error, 0);
790    return sel4test_get_result();
791}
792DEFINE_TEST(SERSERV_CLI_PROC_005, "Connect to server then disconnect on all "
793            "clients (in different VSpace/CSpace containers), and finally kill "
794            "the server from the parent",
795            test_client_process_kill, true)
796