1/*
2 * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 */
6
7#include <object/reply.h>
8
9void reply_push(tcb_t *tcb_caller, tcb_t *tcb_callee, reply_t *reply, bool_t canDonate)
10{
11    sched_context_t *sc_donated = tcb_caller->tcbSchedContext;
12
13    assert(tcb_caller != NULL);
14    assert(reply != NULL);
15    assert(reply->replyTCB == NULL);
16
17    assert(call_stack_get_callStackPtr(reply->replyPrev) == 0);
18    assert(call_stack_get_callStackPtr(reply->replyNext) == 0);
19
20    /* tcb caller should not be in a existing call stack */
21    assert(thread_state_get_replyObject(tcb_caller->tcbState) == 0);
22
23    /* unlink callee and reply - they may not have been linked already,
24     * if this rendesvous is occuring when seL4_Recv is called,
25     * however, no harm in overring 0 with 0 */
26    thread_state_ptr_set_replyObject(&tcb_callee->tcbState, 0);
27
28    /* link caller and reply */
29    reply->replyTCB = tcb_caller;
30    thread_state_ptr_set_replyObject(&tcb_caller->tcbState, REPLY_REF(reply));
31    setThreadState(tcb_caller, ThreadState_BlockedOnReply);
32
33    if (sc_donated != NULL && tcb_callee->tcbSchedContext == NULL && canDonate) {
34        reply_t *old_caller = sc_donated->scReply;
35
36        /* check stack integrity */
37        assert(old_caller == NULL ||
38               SC_PTR(call_stack_get_callStackPtr(old_caller->replyNext)) == sc_donated);
39
40        /* push on to stack */
41        reply->replyPrev = call_stack_new(REPLY_REF(old_caller), false);
42        if (old_caller) {
43            old_caller->replyNext = call_stack_new(REPLY_REF(reply), false);
44        }
45        reply->replyNext = call_stack_new(SC_REF(sc_donated), true);
46        sc_donated->scReply = reply;
47
48        /* now do the actual donation */
49        schedContext_donate(sc_donated, tcb_callee);
50    }
51}
52
53/* Pop the head reply from the call stack */
54void reply_pop(reply_t *reply, tcb_t *tcb)
55{
56    assert(reply != NULL);
57    assert(thread_state_get_tsType(reply->replyTCB->tcbState) == ThreadState_BlockedOnReply);
58    assert(thread_state_get_replyObject(tcb->tcbState) == REPLY_REF(reply));
59    assert(reply->replyTCB == tcb);
60
61    /* unlink tcb and reply */
62    reply_unlink(reply, tcb);
63
64    word_t next_ptr = call_stack_get_callStackPtr(reply->replyNext);
65    word_t prev_ptr = call_stack_get_callStackPtr(reply->replyPrev);
66
67    if (likely(next_ptr != 0)) {
68        assert(call_stack_get_isHead(reply->replyNext));
69
70        /* give it back */
71        if (tcb->tcbSchedContext == NULL) {
72            /* only give the SC back if our SC is NULL. This prevents
73             * strange behaviour when a thread is bound to an sc while it is
74             * in the BlockedOnReply state. The semantics in this case are that the
75             * SC cannot go back to the caller if the caller has received another one */
76            schedContext_donate(SC_PTR(next_ptr), tcb);
77        }
78
79        SC_PTR(next_ptr)->scReply = REPLY_PTR(prev_ptr);
80        if (prev_ptr != 0) {
81            REPLY_PTR(prev_ptr)->replyNext = reply->replyNext;
82            assert(call_stack_get_isHead(REPLY_PTR(prev_ptr)->replyNext));
83        }
84
85        reply->replyPrev = call_stack_new(0, false);
86        reply->replyNext = call_stack_new(0, false);
87    }
88}
89
90/* Remove a reply from the middle of the call stack */
91void reply_remove(reply_t *reply, tcb_t *tcb)
92{
93    assert(reply->replyTCB == tcb);
94    assert(thread_state_get_tsType(tcb->tcbState) == ThreadState_BlockedOnReply);
95    assert(thread_state_get_replyObject(tcb->tcbState) == REPLY_REF(reply));
96
97    word_t next_ptr = call_stack_get_callStackPtr(reply->replyNext);
98    word_t prev_ptr = call_stack_get_callStackPtr(reply->replyPrev);
99
100    if (likely(next_ptr)) {
101        if (likely(call_stack_get_isHead(reply->replyNext))) {
102            /* head of the call stack -> just pop */
103            reply_pop(reply, tcb);
104            return;
105        }
106        /* not the head, remove from middle - break the chain */
107        REPLY_PTR(next_ptr)->replyPrev = call_stack_new(0, false);
108        reply_unlink(REPLY_PTR(reply), tcb);
109    } else {
110        /* removing start of call chain */
111        reply_unlink(reply, tcb);
112    }
113
114    if (prev_ptr) {
115        REPLY_PTR(prev_ptr)->replyNext = call_stack_new(0, false);
116    }
117
118    reply->replyPrev = call_stack_new(0, false);
119    reply->replyNext = call_stack_new(0, false);
120}
121
122void reply_remove_tcb(tcb_t *tcb)
123{
124    assert(thread_state_get_tsType(tcb->tcbState) == ThreadState_BlockedOnReply);
125    reply_t *reply = REPLY_PTR(thread_state_get_replyObject(tcb->tcbState));
126    word_t next_ptr = call_stack_get_callStackPtr(reply->replyNext);
127    word_t prev_ptr = call_stack_get_callStackPtr(reply->replyPrev);
128
129    if (next_ptr) {
130        if (call_stack_get_isHead(reply->replyNext)) {
131            SC_PTR(next_ptr)->scReply = NULL;
132        } else {
133            REPLY_PTR(next_ptr)->replyPrev = call_stack_new(0, false);
134        }
135    }
136
137    if (prev_ptr) {
138        REPLY_PTR(prev_ptr)->replyNext = call_stack_new(0, false);
139    }
140
141    reply->replyPrev = call_stack_new(0, false);
142    reply->replyNext = call_stack_new(0, false);
143    reply_unlink(reply, tcb);
144}
145