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 <sel4test-driver/gen_config.h>
14#include <assert.h>
15#include <stdio.h>
16#include <sel4/sel4.h>
17#include <vka/object.h>
18
19#include "../helpers.h"
20
21static volatile int revoking = 0;
22static volatile int preempt_count = 0;
23
24static int revoke_func(seL4_CNode service, seL4_Word index, seL4_Word depth)
25{
26    revoking = 1;
27    seL4_CNode_Revoke(service, index, depth);
28    revoking = 2;
29    return 0;
30}
31
32static int preempt_count_func(env_t env)
33{
34    while (revoking < 2) {
35        sel4test_ntfn_timer_wait(env);
36        if (revoking == 1) {
37            preempt_count++;
38        }
39    }
40    return 0;
41}
42
43static int create_cnode_table(env_t env, int num_cnode_bits, seL4_CPtr ep)
44{
45    /* Create as many cnodes as possible. We will copy the cap into all
46     * those cnodes. */
47#define CNODE_SIZE_BITS 12
48    int num_caps = 0;
49    int error = 0;
50
51    ZF_LOGD("    Creating %d caps .", BIT((num_cnode_bits + CNODE_SIZE_BITS)));
52    for (int i = 0; i < (1 << num_cnode_bits); i++) {
53        seL4_CPtr ctable = vka_alloc_cnode_object_leaky(&env->vka, CNODE_SIZE_BITS);
54        if (ctable == seL4_CapNull) {
55            return -1;
56        }
57
58        for (int j = 0; j < BIT(CNODE_SIZE_BITS); j++) {
59            error = seL4_CNode_Copy(
60                        ctable, j, CNODE_SIZE_BITS,
61                        env->cspace_root, ep, seL4_WordBits,
62                        seL4_AllRights);
63
64            test_check(!error);
65            num_caps++;
66        }
67        ZF_LOGD(".");
68    }
69
70    if (num_caps != BIT(num_cnode_bits + CNODE_SIZE_BITS)) {
71        ZF_LOGD("Created %d caps. Couldn't create the required number of caps %d",
72                num_caps,
73                BIT(num_cnode_bits + CNODE_SIZE_BITS));
74        return -1;
75    }
76
77    return error;
78}
79
80static int test_preempt_revoke_actual(env_t env, int num_cnode_bits)
81{
82    helper_thread_t revoke_thread, preempt_thread;
83    int error;
84    uint64_t timeout_val = 0;
85
86    /* Create an endpoint cap that will be derived many times. */
87    seL4_CPtr ep = vka_alloc_endpoint_leaky(&env->vka);
88
89    create_helper_thread(env, &revoke_thread);
90    create_helper_thread(env, &preempt_thread);
91
92    set_helper_priority(env, &preempt_thread, 101);
93    set_helper_priority(env, &revoke_thread, 100);
94
95    /* First create a ctable and fill it with copied ep caps
96     * and time the revoke operation
97     */
98    error = create_cnode_table(env, num_cnode_bits, ep);
99    test_error_eq(error, seL4_NoError);
100
101    uint64_t start, end, diff;
102    /* meaure revoke_func time.
103     * Note that we don't take caching and other effects (e.g. function calls vs.
104     * jmp costs into consideration, however, this should get us a better
105     * timeout value per target compared to setting a fixed timeout value that may
106     * fail on different targets.
107     */
108    start = sel4test_timestamp(env);
109    revoke_func(env->cspace_root, ep, seL4_WordBits);
110    end = sel4test_timestamp(env);
111
112    test_geq(end, start);
113
114    diff = end - start;
115
116    /* Set a timeout value to a third of the revoke operation time,
117     * to allow preemption to occur at least twice, as the test expects.
118     */
119    timeout_val = diff / 3;
120
121    /* Now, actually create a ctable that is gonna be used in the revoke
122     * test.
123     */
124    error = create_cnode_table(env, num_cnode_bits, ep);
125    test_error_eq(error, seL4_NoError);
126    sel4test_periodic_start(env, timeout_val);
127
128    /* Last thread to start runs first. */
129    revoking = 0;
130    preempt_count = 0;
131    start_helper(env, &preempt_thread, (helper_fn_t) preempt_count_func, (seL4_Word) env, 0, 0, 0);
132    start_helper(env, &revoke_thread, (helper_fn_t) revoke_func, env->cspace_root,
133                 ep, seL4_WordBits, 0);
134
135    wait_for_helper(&revoke_thread);
136
137    cleanup_helper(env, &preempt_thread);
138    cleanup_helper(env, &revoke_thread);
139
140    ZF_LOGD("    %d preemptions\n", preempt_count);
141
142    return preempt_count;
143}
144
145static int test_preempt_revoke(env_t env)
146{
147    for (int num_cnode_bits = 1; num_cnode_bits < 32; num_cnode_bits++) {
148        int result = test_preempt_revoke_actual(env, num_cnode_bits);
149        if (result > 1) {
150            return sel4test_get_result();
151        } else if (result == -1) {
152            /* At this point we assume this error is a problem where the time of
153             *  revoc_func < timer_resolution, which will cause set timeout to faule
154             *  return an error. Skip such small cnodes and only work with cnode sizes where
155             *  revoc_func > timer_resolution
156             */
157            continue;
158        }
159    }
160
161    ZF_LOGD("Couldn't trigger preemption point with millions of caps!\n");
162    test_assert(0);
163}
164DEFINE_TEST(PREEMPT_REVOKE, "Test preemption path in revoke", test_preempt_revoke, config_set(CONFIG_HAVE_TIMER))
165