Nothing to see here, move along meow
1use crate::cap::object::ObjectTag;
2use crate::cap::pool::POOL;
3use crate::mem::phys::BitmapFrameAllocator;
4use crate::proc::{BlockedReason, PROCESSES};
5use crate::sched;
6use crate::types::Priority;
7use lancer_core::header::KernelObjectHeader;
8use lancer_core::object_layout::{EndpointObject, KernelObject, SchedContextObject};
9
10fn attach_priority(pid: crate::types::Pid, priority: Priority) {
11 let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64);
12 let mut sc = SchedContextObject::init_default(header);
13 sc.budget_us = 10_000;
14 sc.period_us = 100_000;
15 sc.remaining_us = 10_000;
16 sc.priority = priority.raw();
17 sc.attached_pid = lancer_core::header::NONE_SENTINEL;
18 let (sc_id, sc_gen) =
19 crate::tests::helpers::alloc_typed(&mut POOL.lock(), ObjectTag::SchedContext, sc)
20 .expect("alloc sched context");
21 let mut ptable = PROCESSES.lock();
22 ptable[pid].attach_sched_context(sc_id, sc_gen, priority);
23}
24
25crate::kernel_test!(
26 fn pick_next_selects_highest_priority() {
27 let mut allocator = BitmapFrameAllocator;
28 let mut ptable = PROCESSES.lock();
29
30 let a_created = ptable.allocate(&mut allocator).expect("alloc A");
31 let b_created = ptable.allocate(&mut allocator).expect("alloc B");
32 ptable.start(a_created).expect("start A");
33 ptable.start(b_created).expect("start B");
34 let a_pid = a_created.pid();
35 let b_pid = b_created.pid();
36 drop(ptable);
37
38 attach_priority(a_pid, Priority::new(50));
39 attach_priority(b_pid, Priority::new(100));
40
41 let ptable = PROCESSES.lock();
42 let best = (0..crate::types::MAX_PIDS as u32).fold(
43 None::<(crate::types::Pid, Priority)>,
44 |best, idx| {
45 let pid = crate::types::Pid::new(idx);
46 let proc = match ptable.get(pid) {
47 Some(p) => p,
48 None => return best,
49 };
50 if !proc.is_runnable() {
51 return best;
52 }
53 let prio = proc.effective_priority();
54 match best {
55 Some((_, best_prio)) if prio <= best_prio => best,
56 _ => Some((pid, prio)),
57 }
58 },
59 );
60
61 assert!(
62 best.is_some_and(|(pid, _)| pid == b_pid),
63 "pick_next should select highest priority process (B)"
64 );
65 drop(ptable);
66
67 let mut ptable = PROCESSES.lock();
68 ptable.destroy(a_pid, &mut allocator);
69 ptable.destroy(b_pid, &mut allocator);
70 }
71);
72
73crate::kernel_test!(
74 fn pick_next_skips_blocked_processes() {
75 let mut allocator = BitmapFrameAllocator;
76 let mut ptable = PROCESSES.lock();
77
78 let a_created = ptable.allocate(&mut allocator).expect("alloc A");
79 let b_created = ptable.allocate(&mut allocator).expect("alloc B");
80 ptable.start(a_created).expect("start A");
81 ptable.start(b_created).expect("start B");
82 let a_pid = a_created.pid();
83 let b_pid = b_created.pid();
84 drop(ptable);
85
86 attach_priority(a_pid, Priority::new(200));
87 attach_priority(b_pid, Priority::new(50));
88
89 let mut ptable = PROCESSES.lock();
90 ptable.simulate_dispatch(a_pid);
91 let (ep_id, ep_gen) =
92 crate::tests::helpers::alloc_endpoint(&mut POOL.lock()).expect("alloc ep");
93 let _ = ptable[a_pid]
94 .block_on(crate::proc::BlockedReason::Sending(ep_id, ep_gen))
95 .expect("block A");
96
97 let best = (0..crate::types::MAX_PIDS as u32).fold(
98 None::<(crate::types::Pid, Priority)>,
99 |best, idx| {
100 let pid = crate::types::Pid::new(idx);
101 let proc = match ptable.get(pid) {
102 Some(p) => p,
103 None => return best,
104 };
105 if !proc.is_runnable() {
106 return best;
107 }
108 let prio = proc.effective_priority();
109 match best {
110 Some((_, best_prio)) if prio <= best_prio => best,
111 _ => Some((pid, prio)),
112 }
113 },
114 );
115
116 assert!(
117 best.is_some_and(|(pid, _)| pid == b_pid),
118 "should skip blocked A and select B"
119 );
120
121 ptable.destroy(a_pid, &mut allocator);
122 ptable.destroy(b_pid, &mut allocator);
123 let _ = POOL.lock().dec_ref_phys(ep_id, ep_gen);
124 }
125);
126
127crate::kernel_test!(
128 fn pick_next_skips_exhausted_budget() {
129 let mut allocator = BitmapFrameAllocator;
130 let mut ptable = PROCESSES.lock();
131
132 let a_created = ptable.allocate(&mut allocator).expect("alloc A");
133 let b_created = ptable.allocate(&mut allocator).expect("alloc B");
134 ptable.start(a_created).expect("start A");
135 ptable.start(b_created).expect("start B");
136 let a_pid = a_created.pid();
137 let b_pid = b_created.pid();
138 drop(ptable);
139
140 let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64);
141 let mut sc = SchedContextObject::init_default(header);
142 sc.budget_us = 1000;
143 sc.period_us = 10000;
144 sc.remaining_us = 0;
145 sc.priority = Priority::new(200).raw();
146 sc.replenish_at = u64::MAX;
147 sc.attached_pid = a_pid.raw();
148 let (sc_id, sc_gen) =
149 crate::tests::helpers::alloc_typed(&mut POOL.lock(), ObjectTag::SchedContext, sc)
150 .expect("alloc sched context");
151 {
152 let mut ptable = PROCESSES.lock();
153 ptable[a_pid].attach_sched_context(sc_id, sc_gen, Priority::new(200));
154 }
155
156 attach_priority(b_pid, Priority::new(50));
157
158 let ptable = PROCESSES.lock();
159 let mut pool = POOL.lock();
160 let now_us = crate::wcet::tsc::cycles_to_ns(crate::wcet::tsc::read_tsc()) / 1000;
161
162 let budget_ok_a = match ptable[a_pid].sched_context() {
163 None => true,
164 Some((sc_id, sc_gen)) => match pool.write_as::<SchedContextObject>(sc_id, sc_gen) {
165 Ok(sc) => {
166 crate::sched::context::replenish(sc, now_us);
167 !crate::sched::context::is_exhausted(sc)
168 }
169 Err(_) => true,
170 },
171 };
172 drop(pool);
173 drop(ptable);
174
175 assert!(!budget_ok_a, "A should have exhausted budget");
176
177 let ptable = PROCESSES.lock();
178 assert!(ptable[b_pid].is_runnable(), "B should be runnable");
179 drop(ptable);
180
181 let mut ptable = PROCESSES.lock();
182 ptable.destroy(a_pid, &mut allocator);
183 ptable.destroy(b_pid, &mut allocator);
184 }
185);
186
187crate::kernel_test!(
188 fn budget_consume_decrements_remaining() {
189 let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64);
190 let mut sc = SchedContextObject::init_default(header);
191 sc.budget_us = 1000;
192 sc.period_us = 10000;
193 sc.remaining_us = 1000;
194 sc.priority = Priority::new(100).raw();
195 assert!(sc.remaining_us == 1000);
196
197 crate::sched::context::consume(&mut sc, 300, 1000);
198 assert!(
199 sc.remaining_us == 700,
200 "remaining should be 700, got {}",
201 sc.remaining_us
202 );
203
204 crate::sched::context::consume(&mut sc, 500, 1500);
205 assert!(
206 sc.remaining_us == 200,
207 "remaining should be 200, got {}",
208 sc.remaining_us
209 );
210 }
211);
212
213crate::kernel_test!(
214 fn budget_consume_saturates_at_zero() {
215 let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64);
216 let mut sc = SchedContextObject::init_default(header);
217 sc.budget_us = 100;
218 sc.period_us = 1000;
219 sc.remaining_us = 100;
220 sc.priority = Priority::new(100).raw();
221
222 crate::sched::context::consume(&mut sc, 200, 1000);
223 assert!(sc.remaining_us == 0, "should saturate at 0");
224 assert!(
225 crate::sched::context::is_exhausted(&sc),
226 "should be exhausted"
227 );
228 }
229);
230
231crate::kernel_test!(
232 fn budget_replenish_resets_remaining() {
233 let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64);
234 let mut sc = SchedContextObject::init_default(header);
235 sc.budget_us = 1000;
236 sc.period_us = 5000;
237 sc.remaining_us = 0;
238 sc.replenish_at = 100;
239 sc.priority = Priority::new(100).raw();
240
241 crate::sched::context::replenish(&mut sc, 200);
242 assert!(
243 sc.remaining_us == 1000,
244 "remaining should be reset to budget"
245 );
246 assert!(
247 sc.replenish_at == 100 + 5000,
248 "replenish_at should advance by period from previous at"
249 );
250 }
251);
252
253crate::kernel_test!(
254 fn budget_replenish_ignores_early_call() {
255 let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64);
256 let mut sc = SchedContextObject::init_default(header);
257 sc.budget_us = 1000;
258 sc.period_us = 5000;
259 sc.remaining_us = 0;
260 sc.replenish_at = 500;
261 sc.priority = Priority::new(100).raw();
262
263 crate::sched::context::replenish(&mut sc, 100);
264 assert!(
265 sc.remaining_us == 0,
266 "should not replenish before replenish_at"
267 );
268 }
269);
270
271crate::kernel_test!(
272 fn effective_priority_propagation_chain() {
273 let mut allocator = BitmapFrameAllocator;
274 let mut ptable = PROCESSES.lock();
275
276 let a_created = ptable.allocate(&mut allocator).expect("alloc A");
277 let b_created = ptable.allocate(&mut allocator).expect("alloc B");
278 let c_created = ptable.allocate(&mut allocator).expect("alloc C");
279 ptable.start(a_created).expect("start A");
280 ptable.start(b_created).expect("start B");
281 ptable.start(c_created).expect("start C");
282 let a_pid = a_created.pid();
283 let b_pid = b_created.pid();
284 let c_pid = c_created.pid();
285
286 let (ep1_id, ep1_gen) =
287 crate::tests::helpers::alloc_endpoint(&mut POOL.lock()).expect("alloc ep1");
288 let (ep2_id, ep2_gen) =
289 crate::tests::helpers::alloc_endpoint(&mut POOL.lock()).expect("alloc ep2");
290
291 {
292 let mut pool = POOL.lock();
293 pool.write_as::<EndpointObject>(ep1_id, ep1_gen)
294 .expect("get ep1")
295 .holder = b_pid.raw();
296 pool.write_as::<EndpointObject>(ep2_id, ep2_gen)
297 .expect("get ep2")
298 .holder = c_pid.raw();
299 }
300
301 ptable.simulate_dispatch(a_pid);
302 let _ = ptable[a_pid]
303 .block_on(crate::proc::BlockedReason::Sending(ep1_id, ep1_gen))
304 .expect("block A");
305
306 ptable.simulate_dispatch(b_pid);
307 let _ = ptable[b_pid]
308 .block_on(crate::proc::BlockedReason::Sending(ep2_id, ep2_gen))
309 .expect("block B");
310
311 {
312 let pool = POOL.lock();
313 sched::propagate_priority(&mut ptable, &pool, a_pid, Priority::new(250));
314 }
315
316 assert!(
317 ptable[a_pid].effective_priority() >= Priority::new(250),
318 "A should have boosted priority"
319 );
320 assert!(
321 ptable[b_pid].effective_priority() >= Priority::new(250),
322 "B should have boosted priority via holder chain"
323 );
324 assert!(
325 ptable[c_pid].effective_priority() >= Priority::new(250),
326 "C should have boosted priority via holder chain"
327 );
328
329 ptable.destroy(a_pid, &mut allocator);
330 ptable.destroy(b_pid, &mut allocator);
331 ptable.destroy(c_pid, &mut allocator);
332 let _ = POOL.lock().dec_ref_phys(ep1_id, ep1_gen);
333 let _ = POOL.lock().dec_ref_phys(ep2_id, ep2_gen);
334 }
335);
336
337crate::kernel_test!(
338 fn priority_propagation_cycle_terminates() {
339 let mut allocator = BitmapFrameAllocator;
340 let mut ptable = PROCESSES.lock();
341
342 let a_created = ptable.allocate(&mut allocator).expect("alloc A");
343 let b_created = ptable.allocate(&mut allocator).expect("alloc B");
344 ptable.start(a_created).expect("start A");
345 ptable.start(b_created).expect("start B");
346 let a_pid = a_created.pid();
347 let b_pid = b_created.pid();
348
349 let (ep1_id, ep1_gen) =
350 crate::tests::helpers::alloc_endpoint(&mut POOL.lock()).expect("alloc ep1");
351 let (ep2_id, ep2_gen) =
352 crate::tests::helpers::alloc_endpoint(&mut POOL.lock()).expect("alloc ep2");
353
354 {
355 let mut pool = POOL.lock();
356 pool.write_as::<EndpointObject>(ep1_id, ep1_gen)
357 .expect("get ep1")
358 .holder = b_pid.raw();
359 pool.write_as::<EndpointObject>(ep2_id, ep2_gen)
360 .expect("get ep2")
361 .holder = a_pid.raw();
362 }
363
364 ptable.simulate_dispatch(a_pid);
365 let _ = ptable[a_pid]
366 .block_on(BlockedReason::Sending(ep1_id, ep1_gen))
367 .expect("block A");
368
369 ptable.simulate_dispatch(b_pid);
370 let _ = ptable[b_pid]
371 .block_on(BlockedReason::Sending(ep2_id, ep2_gen))
372 .expect("block B");
373
374 {
375 let pool = POOL.lock();
376 sched::propagate_priority(&mut ptable, &pool, a_pid, Priority::new(200));
377 }
378
379 assert!(
380 ptable[a_pid].effective_priority() >= Priority::new(200),
381 "A should have boosted priority after propagation"
382 );
383 assert!(
384 ptable[b_pid].effective_priority() >= Priority::new(200),
385 "B should have boosted priority (propagated through chain before cycle detected)"
386 );
387
388 ptable.destroy(a_pid, &mut allocator);
389 ptable.destroy(b_pid, &mut allocator);
390 let _ = POOL.lock().dec_ref_phys(ep1_id, ep1_gen);
391 let _ = POOL.lock().dec_ref_phys(ep2_id, ep2_gen);
392 }
393);
394
395crate::kernel_test!(
396 fn sched_create_rejects_huge_period() {
397 let budget_us: u64 = 1000;
398 let period_us: u64 = 4_000_000_000;
399 let max_period_us: u64 = 3_600_000_000;
400
401 assert!(
402 period_us > max_period_us,
403 "period exceeding MAX_PERIOD_US should be rejected by validation"
404 );
405
406 let valid =
407 budget_us > 0 && period_us > 0 && budget_us <= period_us && period_us <= max_period_us;
408 assert!(!valid, "huge period should fail validation");
409 }
410);
411
412crate::kernel_test!(
413 fn stale_sched_context_treated_as_exhausted() {
414 let mut allocator = BitmapFrameAllocator;
415 let mut ptable = PROCESSES.lock();
416
417 let proc_created = ptable.allocate(&mut allocator).expect("alloc proc");
418 ptable.start(proc_created).expect("start proc");
419 let pid = proc_created.pid();
420
421 let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64);
422 let mut sc = SchedContextObject::init_default(header);
423 sc.budget_us = 1000;
424 sc.period_us = 10000;
425 sc.remaining_us = 1000;
426 sc.priority = Priority::new(100).raw();
427 let (sc_id, sc_gen) =
428 crate::tests::helpers::alloc_typed(&mut POOL.lock(), ObjectTag::SchedContext, sc)
429 .expect("alloc sched context");
430
431 ptable[pid].attach_sched_context(sc_id, sc_gen, Priority::new(100));
432
433 let _ = POOL.lock().revoke_phys(sc_id, sc_gen);
434
435 assert!(
436 ptable[pid].sched_context().is_some(),
437 "process still references stale sched context"
438 );
439
440 {
441 let mut pool = POOL.lock();
442 let budget_ok = match ptable[pid].sched_context() {
443 None => true,
444 Some((id, generation)) => match pool.write_as::<SchedContextObject>(id, generation)
445 {
446 Ok(sc) => {
447 crate::sched::context::replenish(sc, 0);
448 !crate::sched::context::is_exhausted(sc)
449 }
450 Err(_) => false,
451 },
452 };
453 assert!(
454 !budget_ok,
455 "stale sched context must be treated as exhausted (budget_ok=false), not unlimited"
456 );
457 }
458
459 ptable.destroy(pid, &mut allocator);
460 }
461);