1 /*
2  * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3  *
4  * SPDX-License-Identifier: GPL-2.0-only
5  */
6 
7 #include <types.h>
8 #include <api/failures.h>
9 #include <object/structures.h>
10 
11 /* functions to manage the circular buffer of
12  * sporadic budget replenishments (refills for short).
13  *
14  * The circular buffer always has at least one item in it.
15  *
16  * Items are appended at the tail (the back) and
17  * removed from the head (the front). Below is
18  * an example of a queue with 4 items (h = head, t = tail, x = item, [] = slot)
19  * and max size 8.
20  *
21  * [][h][x][x][t][][][]
22  *
23  * and another example of a queue with 5 items
24  *
25  * [x][t][][][][h][x][x]
26  *
27  * The queue has a minimum size of 1, so it is possible that h == t.
28  *
29  * The queue is implemented as head + tail rather than head + size as
30  * we cannot use the mod operator on all architectures without accessing
31  * the fpu or implementing divide.
32  */
33 
34 /* return the index of the next item in the refill queue */
refill_next(sched_context_t * sc,word_t index)35 static inline word_t refill_next(sched_context_t *sc, word_t index)
36 {
37     return (index == sc->scRefillMax - 1u) ? (0) : index + 1u;
38 }
39 
40 #ifdef CONFIG_PRINTING
41 /* for debugging */
print_index(sched_context_t * sc,word_t index)42 UNUSED static inline void print_index(sched_context_t *sc, word_t index)
43 {
44 
45     printf("index %lu, Amount: %llx, time %llx\n", index, refill_index(sc, index)->rAmount,
46            refill_index(sc, index)->rTime);
47 }
48 
refill_print(sched_context_t * sc)49 UNUSED static inline void refill_print(sched_context_t *sc)
50 {
51     printf("Head %lu tail %lu\n", sc->scRefillHead, sc->scRefillTail);
52     word_t current = sc->scRefillHead;
53     /* always print the head */
54     print_index(sc, current);
55 
56     while (current != sc->scRefillTail) {
57         current = refill_next(sc, current);
58         print_index(sc, current);
59     }
60 
61 }
62 #endif /* CONFIG_PRINTING */
63 #ifdef CONFIG_DEBUG_BUILD
64 /* check a refill queue is ordered correctly */
refill_ordered(sched_context_t * sc)65 static UNUSED bool_t refill_ordered(sched_context_t *sc)
66 {
67     if (isRoundRobin(sc)) {
68         return true;
69     }
70 
71     word_t current = sc->scRefillHead;
72     word_t next = refill_next(sc, sc->scRefillHead);
73 
74     while (current != sc->scRefillTail) {
75         if (!(refill_index(sc, current)->rTime + refill_index(sc, current)->rAmount <= refill_index(sc, next)->rTime)) {
76             refill_print(sc);
77             return false;
78         }
79         current = next;
80         next = refill_next(sc, current);
81     }
82 
83     return true;
84 }
85 
86 #define REFILL_SANITY_START(sc) ticks_t _sum = refill_sum(sc); assert(isRoundRobin(sc) || refill_ordered(sc));
87 #define REFILL_SANITY_CHECK(sc, budget) \
88     do { \
89         assert(refill_sum(sc) == budget); assert(isRoundRobin(sc) || refill_ordered(sc)); \
90     } while (0)
91 
92 #define REFILL_SANITY_END(sc) \
93     do {\
94         REFILL_SANITY_CHECK(sc, _sum);\
95     } while (0)
96 #else
97 #define REFILL_SANITY_START(sc)
98 #define REFILL_SANITY_CHECK(sc, budget)
99 #define REFILL_SANITY_END(sc)
100 #endif /* CONFIG_DEBUG_BUILD */
101 
102 /* compute the sum of a refill queue */
refill_sum(sched_context_t * sc)103 static UNUSED ticks_t refill_sum(sched_context_t *sc)
104 {
105     ticks_t sum = refill_head(sc)->rAmount;
106     word_t current = sc->scRefillHead;
107 
108     while (current != sc->scRefillTail) {
109         current = refill_next(sc, current);
110         sum += refill_index(sc, current)->rAmount;
111     }
112 
113     return sum;
114 }
115 
116 /* pop head of refill queue */
refill_pop_head(sched_context_t * sc)117 static inline refill_t refill_pop_head(sched_context_t *sc)
118 {
119     /* queues cannot be smaller than 1 */
120     assert(!refill_single(sc));
121 
122     UNUSED word_t prev_size = refill_size(sc);
123     refill_t refill = *refill_head(sc);
124     sc->scRefillHead = refill_next(sc, sc->scRefillHead);
125 
126     /* sanity */
127     assert(prev_size == (refill_size(sc) + 1));
128     assert(sc->scRefillHead < sc->scRefillMax);
129     return refill;
130 }
131 
132 /* add item to tail of refill queue */
refill_add_tail(sched_context_t * sc,refill_t refill)133 static inline void refill_add_tail(sched_context_t *sc, refill_t refill)
134 {
135     /* cannot add beyond queue size */
136     assert(refill_size(sc) < sc->scRefillMax);
137 
138     word_t new_tail = refill_next(sc, sc->scRefillTail);
139     sc->scRefillTail = new_tail;
140     *refill_tail(sc) = refill;
141 
142     /* sanity */
143     assert(new_tail < sc->scRefillMax);
144 }
145 
maybe_add_empty_tail(sched_context_t * sc)146 static inline void maybe_add_empty_tail(sched_context_t *sc)
147 {
148     if (isRoundRobin(sc)) {
149         /* add an empty refill - we track the used up time here */
150         refill_t empty_tail = { .rTime = refill_head(sc)->rTime };
151         refill_add_tail(sc, empty_tail);
152         assert(refill_size(sc) == MIN_REFILLS);
153     }
154 }
155 
156 #ifdef ENABLE_SMP_SUPPORT
refill_new(sched_context_t * sc,word_t max_refills,ticks_t budget,ticks_t period,word_t core)157 void refill_new(sched_context_t *sc, word_t max_refills, ticks_t budget, ticks_t period, word_t core)
158 #else
159 void refill_new(sched_context_t *sc, word_t max_refills, ticks_t budget, ticks_t period)
160 #endif
161 {
162     sc->scPeriod = period;
163     sc->scRefillHead = 0;
164     sc->scRefillTail = 0;
165     sc->scRefillMax = max_refills;
166     assert(budget >= MIN_BUDGET);
167     /* full budget available */
168     refill_head(sc)->rAmount = budget;
169     /* budget can be used from now */
170     refill_head(sc)->rTime = NODE_STATE_ON_CORE(ksCurTime, core);
171     maybe_add_empty_tail(sc);
172     REFILL_SANITY_CHECK(sc, budget);
173 }
174 
refill_update(sched_context_t * sc,ticks_t new_period,ticks_t new_budget,word_t new_max_refills)175 void refill_update(sched_context_t *sc, ticks_t new_period, ticks_t new_budget, word_t new_max_refills)
176 {
177 
178     /* refill must be initialised in order to be updated - otherwise refill_new should be used */
179     assert(sc->scRefillMax > 0);
180 
181     /* this is called on an active thread. We want to preserve the sliding window constraint -
182      * so over new_period, new_budget should not be exceeded even temporarily */
183 
184     /* move the head refill to the start of the list - it's ok as we're going to truncate the
185      * list to size 1 - and this way we can't be in an invalid list position once new_max_refills
186      * is updated */
187     *refill_index(sc, 0) = *refill_head(sc);
188     sc->scRefillHead = 0;
189     /* truncate refill list to size 1 */
190     sc->scRefillTail = sc->scRefillHead;
191     /* update max refills */
192     sc->scRefillMax = new_max_refills;
193     /* update period */
194     sc->scPeriod = new_period;
195 
196     if (refill_ready(sc)) {
197         refill_head(sc)->rTime = NODE_STATE_ON_CORE(ksCurTime, sc->scCore);
198     }
199 
200     if (refill_head(sc)->rAmount >= new_budget) {
201         /* if the heads budget exceeds the new budget just trim it */
202         refill_head(sc)->rAmount = new_budget;
203         maybe_add_empty_tail(sc);
204     } else {
205         /* otherwise schedule the rest for the next period */
206         refill_t new = { .rAmount = (new_budget - refill_head(sc)->rAmount),
207                          .rTime = refill_head(sc)->rTime + new_period
208                        };
209         refill_add_tail(sc, new);
210     }
211 
212     REFILL_SANITY_CHECK(sc, new_budget);
213 }
214 
schedule_used(sched_context_t * sc,refill_t new)215 static inline void schedule_used(sched_context_t *sc, refill_t new)
216 {
217     if (unlikely(refill_tail(sc)->rTime + refill_tail(sc)->rAmount >= new.rTime)) {
218         /* Merge overlapping or adjacent refill.
219          *
220          * refill_update can produce a tail refill that will overlap
221          * with new refills when time is charged to the head refill.
222          *
223          * Preemption will cause the head refill to be partially
224          * charged. When the head refill is again later charged the
225          * additionally charged amount will be added where the new
226          * refill ended such that they are merged here. This ensures
227          * that (beyond a refill being split as it is charged
228          * incrementally) a refill split is only caused by a thread
229          * blocking. */
230         refill_tail(sc)->rAmount += new.rAmount;
231     } else if (likely(!refill_full(sc))) {
232         /* Add tail normally */
233         refill_add_tail(sc, new);
234     } else {
235         /* Delay existing tail to merge */
236         refill_tail(sc)->rTime = new.rTime - refill_tail(sc)->rAmount;
237         refill_tail(sc)->rAmount += new.rAmount;
238     }
239 }
240 
refill_head_overlapping(sched_context_t * sc)241 static bool_t refill_head_overlapping(sched_context_t *sc)
242 {
243     if (!refill_single(sc)) {
244         ticks_t amount = refill_head(sc)->rAmount;
245         ticks_t tail = refill_head(sc)->rTime + amount;
246         return refill_index(sc, refill_next(sc, sc->scRefillHead))->rTime <= tail;
247     } else {
248         return false;
249     }
250 }
251 
refill_budget_check(ticks_t usage)252 void refill_budget_check(ticks_t usage)
253 {
254     sched_context_t *sc = NODE_STATE(ksCurSC);
255     assert(!isRoundRobin(sc));
256     REFILL_SANITY_START(sc);
257 
258     /*
259      * We charge entire refills in a loop until we end up with a partial
260      * refill or at a point where we can't place refills into the future
261      * without integer overflow.
262      *
263      * Verification actually requires that the current time is at least
264      * 3 * MAX_PERIOD from the INT64_MAX value, so to ease relation to
265      * that assertion we ensure that we never delate a refill past this
266      * point in the future.
267      */
268     while (refill_head(sc)->rAmount <= usage && refill_head(sc)->rTime < MAX_RELEASE_TIME) {
269         usage -= refill_head(sc)->rAmount;
270 
271         if (refill_single(sc)) {
272             refill_head(sc)->rTime += sc->scPeriod;
273         } else {
274             refill_t old_head = refill_pop_head(sc);
275             old_head.rTime += sc->scPeriod;
276             schedule_used(sc, old_head);
277         }
278     }
279 
280     /*
281      * If the head time is still sufficiently far from the point of
282      * integer overflow then the usage must be smaller than the head.
283      */
284     if (usage > 0 && refill_head(sc)->rTime < MAX_RELEASE_TIME) {
285         assert(refill_head(sc)->rAmount > usage);
286         refill_t used = (refill_t) {
287             .rAmount = usage,
288             .rTime = refill_head(sc)->rTime + sc->scPeriod,
289         };
290 
291         refill_head(sc)->rAmount -= usage;
292         /* We need to keep the head refill no more than a period before
293          * the start of the tail refill. This ensures that new refills
294          * are never added before the tail refill (breaking the ordered
295          * invariant). This code actually keeps the head refill no more
296          * than a period before the end of the tail refill (which is
297          * stronger than necessary) but is what is used in the current
298          * proofs. In combination with the merging behaviour of
299          * schedule_used, the following will still ensure that
300          * incremental charging of a refill across preemptions only
301          * produces a single new refill one period in the future. */
302         refill_head(sc)->rTime += usage;
303         schedule_used(sc, used);
304     }
305 
306     /* Ensure the head refill has the minimum budget */
307     while (refill_head(sc)->rAmount < MIN_BUDGET) {
308         refill_t head = refill_pop_head(sc);
309         refill_head(sc)->rAmount += head.rAmount;
310         /* Delay head to ensure the subsequent refill doesn't end any
311          * later (rather than simply combining refills). */
312         refill_head(sc)->rTime -= head.rAmount;
313     }
314 
315     REFILL_SANITY_END(sc);
316 }
317 
318 
refill_unblock_check(sched_context_t * sc)319 void refill_unblock_check(sched_context_t *sc)
320 {
321 
322     if (isRoundRobin(sc)) {
323         /* nothing to do */
324         return;
325     }
326 
327     /* advance earliest activation time to now */
328     REFILL_SANITY_START(sc);
329     if (refill_ready(sc)) {
330         refill_head(sc)->rTime = NODE_STATE_ON_CORE(ksCurTime, sc->scCore);
331         NODE_STATE(ksReprogram) = true;
332 
333         /* merge available replenishments */
334         while (refill_head_overlapping(sc)) {
335             refill_t old_head = refill_pop_head(sc);
336             refill_head(sc)->rTime = old_head.rTime;
337             refill_head(sc)->rAmount += old_head.rAmount;
338         }
339 
340         assert(refill_sufficient(sc, 0));
341     }
342     REFILL_SANITY_END(sc);
343 }
344