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