keos/lib.rs
1//! # KeOS: KAIST Educational Operating System
2//!
3//! Welcome to the **KeOS** project!
4//!
5//! Operating systems (OS) form the backbone of modern computing, managing
6//! hardware resources and providing a foundation for applications to run
7//! efficiently. Understanding how an OS works is crucial for grasping the inner
8//! workings of computer systems and software performance.
9//!
10//! **KeOS** is designed to offer a hands-on learning experience with core OS
11//! components. Through this project, you will explore the fundamental
12//! principles of OS design while building a minimal but functional operating
13//! system from the ground up.
14//!
15//! We prioritize simplicity, focusing on the most essential aspects of OS
16//! development. You won't have to worry about handling obscure edge cases or
17//! hidden test cases. **The score you receive after running the grading scripts
18//! is your final score.** Our goal is to make this project as straightforward
19//! as possible. If you have suggestions on how we can further minimize
20//! unnecessary complexity and focus on the core concepts, we encourage your
21//! feedback.
22//!
23//! ## ⚠️ IMPORTANT NOTES on GRADING
24//! - **DO NOT** make public forks of this project.
25//! - The **KeOS license explicitly prohibits** public redistribution of this
26//! work.
27//! - You **MUST NOT** share or distribute your work based on the provided
28//! template.
29//! - **Cheating, plagiarism, or uploading your code online is strictly
30//! prohibited** and will result in **disqualification**.
31//!
32//! Failure to comply may result in academic integrity violations.
33//!
34//! For the grading, please refer to the following policy:
35//! - Your submission **must pass all test cases without modifying the
36//! non-whitelisted code in each project**.
37//! - Submissions that **fail to compile** will receive **0 points**.
38//!
39//! ## Why Rust?
40//!
41//! We have chosen **Rust** for this project because of its **memory safety**,
42//! **zero-cost abstractions**, and most importantly, its **concurrency model**.
43//! These features make Rust an excellent choice for **operating system
44//! development**.
45//!
46//! In traditional system programming languages, concurrency and memory bugs
47//! such as data races, use-after-free errors, and null pointer dereferences are
48//! common. Rust prevents these issues at compile time by enforcing strict
49//! **ownership, borrowing, and lifetime rules**. This allows you to write
50//! **safe and efficient concurrent code** without sacrificing performance, and
51//! **reduces debugging time** for those bugs.
52//!
53//! By using Rust in **KeOS**, you will:
54//! - **Develop safe and efficient concurrent programs** without the risk of
55//! data races.
56//! - **Avoid common concurrency pitfalls** such as race conditions.
57//!
58//! ## Project Structure
59//!
60//! The KeOS project is divided into five projects:
61//!
62//! 1. **[`System Call`]** – Learn how the OS interacts with user applications.
63//! 2. **[`Memory Management`]** – Implement basic memory management and
64//! user-space execution.
65//! 3. **[`Advanced Memory Management`]** – Expand the KeOS's memory management
66//! system with advanced features.
67//! 4. **[`Process Management`]** – Implement the advanced process management,
68//! including round robin scheduler and sychronization primitives.
69//! 5. **[`File System`]** – Develop a simple yet functional filesystem for data
70//! storage.
71//!
72//! Each project builds upon the previous ones, helping you progressively
73//! develop a deeper understanding of OS design.
74//!
75//!
76//! ## Implementation Notes
77//!
78//! In **KeOS**, each process/thread is assigned a fixed execution stack of
79//! `STACK_SIZE` bytes. While KeOS attempts to detect stack overflows, its
80//! detection is not perfect. **A stack overflow may lead to mysterious kernel
81//! panics.** To avoid this:
82//! - **Avoid declaring large data structures on the stack.**
83//! ```rust
84//! let v: [u8; 0x200000]; // ERROR: This may cause a stack overflow
85//! ```
86//!
87//! - **Instead, allocate large data structures on the heap using `Box`.**
88//! ```rust
89//! let v = Box::new([0u8; 0x200000]); // OK: Allocates on the heap
90//! ```
91//!
92//! ## Implementation Strategy
93//!
94//! We recommend using a **"TODO-driven" approach** to build KeOS
95//! systematically. This method ensures an **incremental and structured**
96//! development process:
97//!
98//! 1. **Run the code** and identify `todo!()` placeholders that cause panics.
99//! 2. **Implement the missing functionality**, ensuring it aligns with the
100//! expected behavior described in the project requirements.
101//! 3. **Repeat** steps 1 and 2 until all test cases pass and the system behaves
102//! correctly.
103//!
104//! This approach allows you to build your OS **one step at a time**,
105//! making debugging and understanding the system easier.
106//!
107//! ## Getting Started
108//!
109//! To set up your **KeOS** development environment, run the following commands:
110//! ```bash
111//! $ mkdir keos
112//! $ cd keos
113//! $ curl https://raw.githubusercontent.com/casys-kaist/KeOS/refs/heads/main/scripts/install.sh | sh
114//! ```
115//!
116//! We recommend using VS Code as the editor, along with `rust-analyzer` for
117//! Rust support.
118//!
119//! - **DO NOT** make public forks of this project.
120//! - The **KeOS license explicitly prohibits** public redistribution of this
121//! work.
122//! - You **MUST NOT** share or distribute your work based on the provided
123//! template.
124//!
125//! Failure to comply may result in academic integrity violations.
126//!
127//! ### Selectively run tests
128//!
129//! In KeOS, you can run one or more specific test cases by passing their names
130//! as arguments to the test runner. For example:
131//!
132//! ```bash
133//! $ cargo run -- syscall::pipe_normal syscall::pipe_partial
134//! ```
135//!
136//! This command runs exactly the listed test cases, `syscall::pipe_normal` and
137//! `syscall::pipe_partial`. You may specify a single test case or multiple test
138//! cases, depending on your needs.
139//!
140//! ### Grading Policy
141//!
142//! During grading, we will **overwrite** all files **except those explicitly
143//! whitelisted** for each project. Any modifications to non-whitelisted files
144//! may result in a **zero score**, even if your implementation otherwise works
145//! correctly.
146//!
147//! You can run the `cargo grade` command to check your current score locally.
148//! This reported score will be treated as your **final grade**, as long as your
149//! submission complies with the whitelist policy.
150//!
151//! **⚠️ IMPORTANT NOTES:**
152//! - Your submission **must pass all test cases without modifying the test
153//! code**.
154//! - Submissions that **fail to compile** will receive **0 points**.
155//! - **Cheating, plagiarism, or uploading your code online is strictly
156//! prohibited** and will result in **disqualification**.
157//!
158//! Grading rubrics and the list of whitelisted files can be found in each
159//! grader's `.grade-target` file.
160//!
161//! ## Debugging with GDB
162//!
163//! KeOS supports debugging with **GDB** and **QEMU**. This section provides
164//! step-by-step instructions on how to set up and use GDB for effective
165//! debugging.
166//!
167//! ### Running GDB
168//!
169//! To launch KeOS in debug mode, run the following command **inside each grader
170//! directory**:
171//! ```bash
172//! $ GDB=1 cargo run <TESTCASE>
173//! ```
174//! You must specify a single test case to debug with a GDB.
175//!
176//! This starts **QEMU** and waits for a GDB connection on TCP **port 1234**.
177//! A `.gdbinit` script will also be generated to automate the debugging setup.
178//!
179//! In a **separate terminal**, start GDB using:
180//! ```bash
181//! $ rust-gdb keos_kernel
182//! ```
183//!
184//! **Why `rust-gdb`?**
185//! We recommend `rust-gdb`, as it provides better support for Rust-specific
186//! data structures and improves debugging readability.
187//!
188//! #### One-time setup
189//! Before using `rust-gdb`, you may need to modify your **`~/.gdbinit`** file
190//! to allow script execution. Add the following line:
191//! ```bash
192//! set auto-load safe-path /
193//! ```
194//!
195//! After launching `rust-gdb`, the execution will halt at the startup stage,
196//! showing output similar to this:
197//! ```bash
198//! $ rust-gdb
199//! warning: No executable has been specified and target does not support
200//! determining executable automatically. Try using the "file" command.
201//! 0x000000000000fff0 in ?? ()
202//! (gdb)
203//! ```
204//!
205//! Now, you can continue execution by typing:
206//!
207//! ### Inspect Each Core
208//!
209//! In **QEMU**, each **CPU core** is treated as a **separate thread**.
210//! When debugging multi-core execution, be aware that **some cores may panic
211//! while others continue running.**
212//!
213//! To inspect all active cores, use:
214//! ```bash
215//! (gdb) info threads
216//! ```
217//!
218//! This will display the state of each thread, including which CPU core it
219//! belongs to and its current stack frame. For example:
220//! ```text
221//! (gdb) info threads
222//! Id Target Id Frame
223//! * 1 Thread 1 (CPU#0 [running]) 0x000000000000fff0 in ?? ()
224//! 2 Thread 2 (CPU#1 [running]) 0x000000000000fff0 in ?? ()
225//! 3 Thread 3 (CPU#2 [running]) 0x000000000000fff0 in ?? ()
226//! 4 Thread 4 (CPU#3 [running]) 0x000000000000fff0 in ?? ()
227//! ```
228//!
229//! To switch to a specific **CPU core (thread)**, use:
230//! ```bash
231//! (gdb) thread {thread_id}
232//! ```
233//!
234//! This allows you to inspect registers, call stacks, and execution state
235//! per core.
236//!
237//! ---
238//!
239//! ## Analyzing Execution State
240//!
241//! ### Viewing the Call Stack (Backtrace)
242//!
243//! Use `backtrace` (or `bt`) to display the **call stack** of the current
244//! thread:
245//! ```bash
246//! (gdb) bt
247//! ```
248//!
249//! Each function call in the stack is represented as a **frame**.
250//! To switch to a specific frame, use:
251//! ```bash
252//! (gdb) frame {frame_id}
253//! ```
254//!
255//! Once inside a frame, you can inspect variables:
256//! ```bash
257//! (gdb) info args
258//! (gdb) info locals
259//! (gdb) i r
260//! ```
261//!
262//! **Debugging a Panic:**
263//! If you encounter a **kernel panic** during a test, use:
264//! 1. `info threads` to locate the crashing core
265//! 2. `bt` to examine the backtrace
266//! 3. `frame {frame_id}` to inspect function parameters
267//!
268//! ---
269//!
270//! ## Setting Breakpoints
271//!
272//! Breakpoints help stop execution at specific points. However, in **multi-core
273//! debugging**, regular breakpoints may not always work correctly.
274//! Instead, use **hardware breakpoints**:
275//! ```bash
276//! (gdb) hb * {address_of_breakpoint}
277//! ```
278//!
279//! To view the source code that the current CPU is executing, use:
280//! ```bash
281//! (gdb) layout asm
282//! (gdb) layout src
283//! ```
284//!
285//! ### Examples
286//!
287//! Here are some examples of how to set breakpoints in GDB:
288//! ```text
289//! (gdb) hbreak function_name # Example: hbreak keos::fs::Directory::open
290//! (gdb) hbreak *address # Example: hbreak *0x1000
291//! (gdb) hbreak (file:)line # Example: hbreak syscall.rs:164
292//! ```
293//!
294//! #### Example 1
295//!
296//! To debug the `syscall::read_normal` test case in project 1, and set a
297//! breakpoint at `syscall.rs:150`, use:
298//! ```bash
299//! (gdb) hbreak syscall.rs:150
300//! ```
301//!
302//! Alternatively, you can set a breakpoint by the test case's name:
303//! ```bash
304//! (gdb) hbreak project1_grader::syscall::read_normal
305//! ```
306//!
307//! You can even set a breakpoint on the closure entry, for instance, to set a
308//! on a closure of `sync::semaphore::sema_0` test case in project 3:
309//! ```bash
310//! (gdb) hbreak project3_grader::sync::semaphore::{{closure}}
311//! ```
312//!
313//! To limit debugging to one core, use `thread apply`:
314//! ```bash
315//! (gdb) thread apply 1 hbreak syscall.rs:150
316//! (gdb) c
317//! ```
318//!
319//! #### Example 2
320//!
321//! To stop at a breakpoint only when a specific condition is met (e.g., when a
322//! parameter is `0xcafe0000`), use:
323//! ```bash
324//! (gdb) hbreak walk if va.__0 == 0xcafe0000
325//! ```
326//!
327//! This approach allows you to focus on specific conditions and skip over
328//! unrelated calls.
329//!
330//!
331//! ---
332//!
333//! ## Stopping an execution
334//!
335//! When KeOS got stuck in deadlock or does not automatically shut down after
336//! it panicked, you may need to forcibly shut down the QEMU.
337//!
338//! For execution in `cargo grade` or `cargo run` without argument in project 5,
339//! press **Ctrl-C** to stop execution.
340//!
341//! Otherwise, such as running KeOS by `cargo run` in project 1-4, press
342//! **Ctrl-A**, then press **X** to stop execution.
343//!
344//! [`System Call`]: ../keos_project1
345//! [`Memory Management`]: ../keos_project2
346//! [`Advanced Memory Management`]: ../keos_project3
347//! [`Process Management`]: ../keos_project4
348//! [`File System`]: ../keos_project5
349
350#![no_std]
351#![allow(internal_features, static_mut_refs)]
352#![feature(
353 allocator_api,
354 alloc_error_handler,
355 alloc_layout_extra,
356 lang_items,
357 core_intrinsics,
358 pointer_is_aligned_to,
359 slice_as_array,
360 step_trait
361)]
362#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
363
364#[macro_use]
365extern crate abyss;
366extern crate alloc;
367
368mod interrupt;
369mod lang;
370
371pub mod channel;
372pub mod fs;
373pub mod mm;
374pub mod sync;
375pub mod syscall;
376pub mod task;
377pub mod teletype;
378pub mod thread;
379pub mod util;
380
381use abyss::spinlock;
382pub use abyss::{
383 x86_64::intrinsics,
384 {MAX_CPU, addressing, debug, info, print, println, warning},
385};
386use alloc::{boxed::Box, collections::btree_set::BTreeSet, ffi::CString, vec::Vec};
387use task::Task;
388use thread::scheduler::{BOOT_DONE, Scheduler, scheduler};
389
390/// Enum representing errors that can occur during a kernel operation.
391///
392/// This enum is used to categorize errors encountered by the kernel operation.
393/// Each variant corresponds to a specific type of error that might
394/// occur during the handling of a kernel operation. These errors can be
395/// returned to the user program to indicate the nature of the failure.
396#[derive(Debug, Eq, PartialEq)]
397pub enum KernelError {
398 /// Operation is not permitted. (EPERM)
399 OperationNotPermitted,
400 /// No such file or directory. (ENOENT)
401 NoSuchEntry,
402 /// IO Error. (EIO)
403 IOError,
404 /// Exec format error. (ENOEXEC)
405 NoExec,
406 /// BAD file descriptor. (EBADF)
407 BadFileDescriptor,
408 /// Out of memory. (ENOMEM)
409 NoMemory,
410 /// Permission denied. (EACCES)
411 InvalidAccess,
412 /// Bad address. (EFAULT)
413 BadAddress,
414 /// Device or resource busy. (EBUSY)
415 Busy,
416 /// File exists. (EEXIST)
417 FileExist,
418 /// Not a directory. (ENOTDIR)
419 NotDirectory,
420 /// Is a directory. (EISDIR)
421 IsDirectory,
422 /// Invalid arguement. (EINVAL)
423 InvalidArgument,
424 /// Too many open files. (EMFILE)
425 TooManyOpenFile,
426 /// No space left on device. (ENOSPC)
427 NoSpace,
428 /// Broken pipe. (EPIPE)
429 BrokenPipe,
430 /// File name too long. (ENAMETOOLONG)
431 NameTooLong,
432 /// Invalid system call number. (ENOSYS)
433 NoSuchSyscall,
434 /// Directory not empty (ENOTEMPTY)
435 DirectoryNotEmpty,
436 /// File system is corrupted. (EFSCORRUPTED)
437 FilesystemCorrupted(&'static str),
438 /// Operation is not supported. (ENOTSUPP)
439 NotSupportedOperation,
440}
441
442impl KernelError {
443 /// Converts the [`KernelError`] enum into a corresponding `usize` error
444 /// code. The result is cast to `usize` for use as a return value in
445 /// system calls.
446 pub fn into_usize(self) -> usize {
447 (match self {
448 KernelError::OperationNotPermitted => -1isize,
449 KernelError::NoSuchEntry => -2,
450 KernelError::IOError => -5,
451 KernelError::NoExec => -8,
452 KernelError::BadFileDescriptor => -9,
453 KernelError::NoMemory => -12,
454 KernelError::InvalidAccess => -13,
455 KernelError::BadAddress => -14,
456 KernelError::Busy => -16,
457 KernelError::FileExist => -17,
458 KernelError::NotDirectory => -20,
459 KernelError::IsDirectory => -21,
460 KernelError::InvalidArgument => -22,
461 KernelError::TooManyOpenFile => -24,
462 KernelError::NoSpace => -28,
463 KernelError::BrokenPipe => -32,
464 KernelError::NameTooLong => -36,
465 KernelError::NoSuchSyscall => -38,
466 KernelError::DirectoryNotEmpty => -39,
467 KernelError::FilesystemCorrupted(_) => -117,
468 KernelError::NotSupportedOperation => -524,
469 }) as usize
470 }
471}
472
473/// The given `isize` does not indicate an [`KernelError`].
474#[derive(Debug, Eq, PartialEq)]
475pub struct TryFromError {
476 e: isize,
477}
478
479impl TryFrom<isize> for KernelError {
480 type Error = TryFromError;
481
482 // Required method
483 fn try_from(value: isize) -> Result<Self, Self::Error> {
484 match value {
485 -1 => Ok(Self::OperationNotPermitted),
486 -2 => Ok(Self::NoSuchEntry),
487 -5 => Ok(Self::IOError),
488 -8 => Ok(Self::NoExec),
489 -9 => Ok(Self::BadFileDescriptor),
490 -12 => Ok(Self::NoMemory),
491 -13 => Ok(Self::InvalidAccess),
492 -14 => Ok(Self::BadAddress),
493 -16 => Ok(Self::Busy),
494 -17 => Ok(Self::FileExist),
495 -20 => Ok(Self::NotDirectory),
496 -21 => Ok(Self::IsDirectory),
497 -22 => Ok(Self::InvalidArgument),
498 -24 => Ok(Self::TooManyOpenFile),
499 -28 => Ok(Self::NoSpace),
500 -32 => Ok(Self::BrokenPipe),
501 -36 => Ok(Self::NameTooLong),
502 -38 => Ok(Self::NoSuchSyscall),
503 -39 => Ok(Self::DirectoryNotEmpty),
504 -117 => Ok(Self::FilesystemCorrupted("")),
505 -524 => Ok(Self::NotSupportedOperation),
506 e => Err(TryFromError { e }),
507 }
508 }
509}
510
511#[doc(hidden)]
512static mut KERNEL_CMDLINE: Option<CString> = None;
513
514/// Panic depth level.
515///
516/// Used for determining double panic, and notifying drop handlers that panic is
517/// in progress.
518pub static PANIC_DEPTH: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
519
520/// A builder for system configuration settings.
521///
522/// The [`SystemConfigurationBuilder`] struct provides an interface for
523/// configuring various system-wide settings before initialization.
524/// This builder is typically used during system setup to configure fundamental
525/// aspects like scheduling policies, memory management, and other system-wide
526/// parameters.
527pub struct SystemConfigurationBuilder {
528 _p: (), // Prevents external instantiation while allowing future extensions.
529}
530
531impl SystemConfigurationBuilder {
532 /// Sets the system-wide scheduler.
533 ///
534 /// This function configures the default scheduler with a custom scheduler
535 /// implementation. It is expected that the provided scheduler
536 /// implements the [`Scheduler`] trait and has a `'static` lifetime,
537 /// meaning it must outlive all references.
538 pub fn set_scheduler(self, scheduler: impl Scheduler + 'static) {
539 unsafe {
540 thread::scheduler::set_scheduler(scheduler);
541 }
542 }
543}
544
545/// The entry of the KeOS for bootstrap processor.
546#[unsafe(no_mangle)]
547#[allow(clippy::missing_safety_doc)]
548pub unsafe fn rust_main(core_id: usize, regions: abyss::boot::Regions, cmd: Option<&'static [u8]>) {
549 info!(
550 "\x1bc\n\
551__ __ ___ ____ \n\
552| |/ /___ / _ \\/ ___| \n\
553| ' // _ \\ | | \\___ \\ \n\
554| . \\ __/ |_| |___) |\n\
555|_|\\_\\___|\\___/|____/ \n\
556\n\
557KAIST educational Operating System\n\
558Copyright 2025 Computer Architecture and Systems Lab\n"
559 );
560 // Init memory.
561 unsafe {
562 info!("Memory: init memory.");
563 crate::mm::init_mm(regions);
564 if let Some(cmd) = cmd {
565 KERNEL_CMDLINE = CString::from_vec_with_nul(cmd.into()).ok();
566 }
567 }
568 info!("Devices: init pci devices.");
569 unsafe {
570 abyss::dev::pci::init();
571 }
572 // Load debug symbols
573 info!("Panicking: Load debug symbols.");
574 if !crate::lang::panicking::load_debug_infos() {
575 warning!("Failed to read kernel image. Disabling stack backtrace.");
576 }
577
578 unsafe extern "Rust" {
579 fn main(conf_builder: SystemConfigurationBuilder);
580 }
581 unsafe {
582 main(SystemConfigurationBuilder { _p: () });
583 abyss::boot::bootup_mps();
584 // Kill the kernel low address.
585 (abyss::addressing::Pa::new({
586 unsafe extern "C" {
587 static mut boot_pml4e: u64;
588 }
589 boot_pml4e as usize
590 })
591 .unwrap()
592 .into_kva()
593 .into_usize() as *mut mm::page_table::Pml4e)
594 .as_mut()
595 .unwrap()
596 .set_flags(mm::page_table::Pml4eFlags::empty());
597 println!(
598 "Command line: {}",
599 KERNEL_CMDLINE
600 .as_ref()
601 .and_then(|cmd| cmd.to_str().ok())
602 .unwrap_or("")
603 );
604 }
605
606 crate::interrupt::register(32, |_| scheduler().timer_tick());
607 crate::interrupt::register(126, mm::tlb::handler);
608 crate::interrupt::register(127, |_regs| { /* no-op */ });
609 BOOT_DONE.store(true, core::sync::atomic::Ordering::SeqCst);
610 // Now kernel is ready to serve task.
611 crate::thread::scheduler::idle(core_id);
612}
613
614/// The entry of the KeOS for application processor.
615#[unsafe(no_mangle)]
616#[allow(clippy::missing_safety_doc)]
617pub unsafe fn rust_ap_main(core_id: usize) {
618 unsafe extern "Rust" {
619 fn ap_main();
620 }
621 unsafe {
622 ap_main();
623 }
624 crate::thread::scheduler::idle(core_id);
625}
626
627// Test utilities
628#[doc(hidden)]
629pub trait TestCase
630where
631 Self: Sync + Send,
632{
633 fn name(&'static self) -> &'static str;
634 fn run(&'static self, task: Box<dyn Task>) -> bool;
635}
636
637impl<T> TestCase for T
638where
639 T: Fn() + Send + Sync + 'static,
640{
641 fn name(&'static self) -> &'static str {
642 core::any::type_name::<T>()
643 }
644 fn run(&'static self, task: Box<dyn Task>) -> bool {
645 print!("test {} ... ", core::any::type_name::<T>());
646 if crate::thread::ThreadBuilder::new(core::any::type_name::<T>())
647 .attach_task(task)
648 .spawn(self)
649 .join()
650 == 0
651 {
652 println!("ok");
653 true
654 } else {
655 println!("FAILED");
656 false
657 }
658 }
659}
660
661/// A driver for running tests.
662pub struct TestDriver<T: Task + Default + 'static> {
663 _t: core::marker::PhantomData<T>,
664}
665
666impl<T: Task + Default + 'static> TestDriver<T> {
667 /// Run the given tests.
668 pub fn start<const TC: usize>(tests: [&'static dyn TestCase; TC]) {
669 crate::thread::ThreadBuilder::new("test_main").spawn(move || {
670 let tests = match unsafe { KERNEL_CMDLINE.as_ref() } {
671 Some(cmd) if cmd.to_str() != Ok("") => {
672 let filter = cmd
673 .to_str()
674 .expect("Failed to parse cmd")
675 .split(" ")
676 .collect::<BTreeSet<_>>();
677 tests
678 .iter()
679 .filter(|test| {
680 let name = test.name();
681 let r = name.split("::").next().map(|n| n.len() + 2).unwrap_or(0);
682 filter.contains(&name[r..])
683 })
684 .collect::<Vec<_>>()
685 }
686 _ => tests.iter().collect::<Vec<_>>(),
687 };
688 let (total, mut succ) = (tests.len(), 0);
689 println!(
690 "Running {} test{}",
691 total,
692 if total == 1 { "" } else { "s" }
693 );
694
695 for test in tests {
696 if test.run(Box::new(T::default())) {
697 succ += 1;
698 }
699 }
700 println!(
701 "test result: {}. {} passed; {} failed",
702 if total == succ { "ok" } else { "FAILED" },
703 succ,
704 total - succ
705 );
706
707 unsafe {
708 abyss::x86_64::power_control::power_off();
709 }
710 });
711 }
712}