├── kernel
├── .gitignore
├── src
│ ├── file
│ │ ├── mod.rs
│ │ └── load.rs
│ ├── sync
│ │ ├── mod.rs
│ │ ├── mutex.rs
│ │ └── event_bus.rs
│ ├── asm
│ │ ├── boot.asm
│ │ └── linkage.asm
│ ├── syscall
│ │ ├── timer.rs
│ │ ├── fs.rs
│ │ ├── mod.rs
│ │ └── process.rs
│ ├── mem
│ │ ├── address
│ │ │ ├── mod.rs
│ │ │ ├── physical_address.rs
│ │ │ ├── virtual_address.rs
│ │ │ ├── page_number.rs
│ │ │ └── frame_number.rs
│ │ ├── mod.rs
│ │ ├── heap_allocator.rs
│ │ ├── user_ptr.rs
│ │ ├── frame_allocator.rs
│ │ ├── page_table.rs
│ │ └── segment.rs
│ ├── task
│ │ ├── mod.rs
│ │ ├── tid.rs
│ │ ├── pid.rs
│ │ ├── thread.rs
│ │ └── process.rs
│ ├── timer.rs
│ ├── executor
│ │ ├── scheduler.rs
│ │ ├── context.rs
│ │ ├── mod.rs
│ │ └── future.rs
│ ├── lang_items.rs
│ ├── constant.rs
│ ├── console.rs
│ ├── linker.ld
│ ├── logging.rs
│ ├── sbi.rs
│ └── main.rs
└── Cargo.toml
├── kernel-lib
├── src
│ ├── constant.rs
│ ├── bin
│ │ ├── hello_world.rs
│ │ ├── privileged_instruction.rs
│ │ ├── page_fault.rs
│ │ ├── sleep.rs
│ │ ├── init.rs
│ │ ├── fork.rs
│ │ └── shell.rs
│ ├── linker.ld
│ ├── lang_items.rs
│ ├── heap_allocator.rs
│ ├── console.rs
│ ├── logging.rs
│ ├── lib.rs
│ └── syscall.rs
└── Cargo.toml
├── Cargo.toml
├── bootloader
└── opensbi-jump.bin
├── doc
└── index.html
├── rust-toolchain.toml
├── .gitignore
├── rustfmt.toml
├── .cargo
└── config.toml
├── Makefile
├── LICENSE
├── .github
└── workflows
│ └── cargo.yml
└── README.md
/kernel/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/kernel-lib/src/constant.rs:
--------------------------------------------------------------------------------
1 | pub const USER_HEAP_SIZE: usize = 4096 * 8;
2 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "kernel",
4 | "kernel-lib"
5 | ]
6 |
--------------------------------------------------------------------------------
/kernel/src/file/mod.rs:
--------------------------------------------------------------------------------
1 | mod load;
2 |
3 | pub use load::{get_bin, get_bin_count, get_bin_data, print_bin_name};
4 |
--------------------------------------------------------------------------------
/bootloader/opensbi-jump.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoyang-sde/rust-kernel-riscv/HEAD/bootloader/opensbi-jump.bin
--------------------------------------------------------------------------------
/doc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | profile = "minimal"
3 | channel = "nightly"
4 | components = ["rust-src", "llvm-tools-preview", "rustfmt", "clippy"]
5 |
--------------------------------------------------------------------------------
/kernel-lib/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kernel-lib"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | linked_list_allocator = "0.10.5"
8 | log = "0.4.17"
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Rust Toolchain
2 | debug/
3 | target/
4 | Cargo.lock
5 | **/*.rs.bk
6 | *.pdb
7 | .gdb_history
8 |
9 | # Configuration
10 | .vscode
11 |
12 | # macOS
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | comment_width = 100
2 | format_code_in_doc_comments = true
3 | group_imports = "StdExternalCrate"
4 | imports_granularity = "Crate"
5 | imports_layout = "HorizontalVertical"
6 | wrap_comments = true
7 |
--------------------------------------------------------------------------------
/kernel-lib/src/bin/hello_world.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 |
4 | use log::info;
5 |
6 | extern crate kernel_lib;
7 |
8 | #[no_mangle]
9 | fn main() -> i32 {
10 | info!("hello, world!");
11 | 0
12 | }
13 |
--------------------------------------------------------------------------------
/kernel/src/sync/mod.rs:
--------------------------------------------------------------------------------
1 | //! The `sync` module provides synchronization primitives for concurrent programming.
2 |
3 | mod event_bus;
4 | mod mutex;
5 |
6 | pub use event_bus::{wait_for_event, Event, EventBus};
7 | pub use mutex::{Mutex, MutexGuard};
8 |
--------------------------------------------------------------------------------
/kernel/src/asm/boot.asm:
--------------------------------------------------------------------------------
1 | .section .text.boot
2 | .globl _start
3 | _start:
4 | la sp, boot_stack_top
5 | call rust_main
6 |
7 | .section .bss.stack
8 | boot_stack_bottom:
9 | .space 4096 * 16
10 | .globl boot_stack_top
11 | boot_stack_top:
12 |
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [unstable]
2 | profile-rustflags = true
3 |
4 | [build]
5 | target = "riscv64gc-unknown-none-elf"
6 |
7 | [profile.dev.package.kernel]
8 | rustflags = ["-C", "link-arg=-Tkernel/src/linker.ld"]
9 |
10 | [profile.dev.package.kernel-lib]
11 | rustflags = ["-C", "link-arg=-Tkernel-lib/src/linker.ld"]
12 |
--------------------------------------------------------------------------------
/kernel-lib/src/bin/privileged_instruction.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 |
4 | extern crate kernel_lib;
5 |
6 | use core::arch::asm;
7 |
8 | use log::warn;
9 |
10 | #[no_mangle]
11 | fn main() -> i32 {
12 | warn!("attempt to execute a privileged instruction");
13 | unsafe {
14 | asm!("sret");
15 | }
16 | 0
17 | }
18 |
--------------------------------------------------------------------------------
/kernel-lib/src/bin/page_fault.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 |
4 | extern crate kernel_lib;
5 |
6 | use core::ptr::null_mut;
7 |
8 | use log::warn;
9 |
10 | #[no_mangle]
11 | fn main() -> i32 {
12 | warn!("attempt to write to an invalid location");
13 | unsafe {
14 | null_mut::().write_volatile(0);
15 | }
16 | 0
17 | }
18 |
--------------------------------------------------------------------------------
/kernel/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "kernel"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | async-task = { version = "4.3.0", default-features = false }
8 | bitflags = "2.0.2"
9 | lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
10 | linked_list_allocator = "0.10.5"
11 | log = "0.4.17"
12 | riscv = "0.10.1"
13 | xmas-elf = "0.9.0"
14 |
--------------------------------------------------------------------------------
/kernel-lib/src/bin/sleep.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 |
4 | extern crate kernel_lib;
5 |
6 | use kernel_lib::{get_time, sched_yield};
7 | use log::info;
8 |
9 | #[no_mangle]
10 | fn main() -> i32 {
11 | let start_time = get_time();
12 | while get_time() < start_time + 1000 {
13 | sched_yield();
14 | }
15 | info!("slept for 1 second");
16 | 0
17 | }
18 |
--------------------------------------------------------------------------------
/kernel/src/syscall/timer.rs:
--------------------------------------------------------------------------------
1 | //! The `timer` module provides time-related system calls.
2 |
3 | use crate::{executor::ControlFlow, syscall::SystemCall, timer};
4 |
5 | impl SystemCall<'_> {
6 | /// Returns the current system time in milliseconds.
7 | pub fn sys_get_time(&self) -> (isize, ControlFlow) {
8 | (timer::get_time() as isize, ControlFlow::Continue)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/kernel/src/mem/address/mod.rs:
--------------------------------------------------------------------------------
1 | //! The `address` module defines various structs for the Sv39 page table specification.
2 |
3 | mod frame_number;
4 | mod page_number;
5 | mod physical_address;
6 | mod virtual_address;
7 |
8 | pub use frame_number::FrameNumber;
9 | pub use page_number::{PageNumber, PageRange};
10 | pub use physical_address::PhysicalAddress;
11 | pub use virtual_address::VirtualAddress;
12 |
--------------------------------------------------------------------------------
/kernel/src/mem/mod.rs:
--------------------------------------------------------------------------------
1 | mod address;
2 | mod frame_allocator;
3 | mod heap_allocator;
4 | mod page_table;
5 | mod segment;
6 | mod user_ptr;
7 |
8 | pub use address::{FrameNumber, PageNumber, PhysicalAddress, VirtualAddress};
9 | pub use frame_allocator::deallocate_frame;
10 | pub use segment::{MapPermission, PageSet, KERNEL_SPACE};
11 | pub use user_ptr::UserPtr;
12 |
13 | pub fn init() {
14 | heap_allocator::init();
15 | frame_allocator::init();
16 | KERNEL_SPACE.lock().init();
17 | }
18 |
--------------------------------------------------------------------------------
/kernel/src/task/mod.rs:
--------------------------------------------------------------------------------
1 | //! The `task` module provides types for representing processes and threads.
2 |
3 | mod pid;
4 | mod process;
5 | mod thread;
6 | mod tid;
7 |
8 | use alloc::sync::Arc;
9 |
10 | use lazy_static::{initialize, lazy_static};
11 | pub use process::{Process, Status};
12 | pub use thread::Thread;
13 |
14 | lazy_static! {
15 | static ref INIT_PROCESS: Arc = Process::new("init");
16 | }
17 |
18 | /// Spawns the init process.
19 | pub fn init() {
20 | initialize(&INIT_PROCESS);
21 | }
22 |
--------------------------------------------------------------------------------
/kernel-lib/src/bin/init.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 |
4 | use kernel_lib::{exec, fork, wait};
5 | use log::info;
6 |
7 | extern crate kernel_lib;
8 |
9 | #[no_mangle]
10 | fn main() -> i32 {
11 | if fork() == 0 {
12 | exec("shell\0");
13 | } else {
14 | loop {
15 | let mut exit_code = 0;
16 | let pid = wait(&mut exit_code);
17 | info!(
18 | "released a zombie process (pid: {}, exit_code: {})",
19 | pid, exit_code
20 | );
21 | }
22 | }
23 | 0
24 | }
25 |
--------------------------------------------------------------------------------
/kernel-lib/src/linker.ld:
--------------------------------------------------------------------------------
1 | OUTPUT_ARCH(riscv)
2 | ENTRY(_start)
3 | BASE_ADDRESS = 0x10000;
4 |
5 | SECTIONS
6 | {
7 | . = BASE_ADDRESS;
8 | .text : {
9 | *(.text.init)
10 | *(.text .text.*)
11 | }
12 | . = ALIGN(4K);
13 |
14 | .rodata : {
15 | *(.rodata .rodata.*)
16 | *(.srodata .srodata.*)
17 | }
18 | . = ALIGN(4K);
19 |
20 | .data : {
21 | *(.data .data.*)
22 | *(.sdata .sdata.*)
23 | }
24 |
25 | .bss : {
26 | *(.bss .bss.*)
27 | *(.sbss .sbss.*)
28 | }
29 |
30 | /DISCARD/ : {
31 | *(.eh_frame)
32 | *(.debug*)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/kernel-lib/src/bin/fork.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 |
4 | use kernel_lib::{exit, fork, wait};
5 | use log::info;
6 |
7 | extern crate kernel_lib;
8 |
9 | #[no_mangle]
10 | fn main() -> i32 {
11 | const MAX_CHILD: i32 = 8;
12 | for i in 0..MAX_CHILD {
13 | let pid = fork();
14 | if pid == 0 {
15 | info!("the child process {} has been spawned", i);
16 | exit(0);
17 | } else {
18 | info!("forked a child process with PID = {}", pid);
19 | }
20 | assert!(pid > 0);
21 | }
22 |
23 | let mut exit_code: usize = 0;
24 | for _ in 0..MAX_CHILD {
25 | wait(&mut exit_code);
26 | }
27 | 0
28 | }
29 |
--------------------------------------------------------------------------------
/kernel/src/task/tid.rs:
--------------------------------------------------------------------------------
1 | use alloc::vec::Vec;
2 |
3 | pub type Tid = usize;
4 |
5 | pub struct TidAllocator {
6 | state: Tid,
7 | deallocated_tid: Vec,
8 | }
9 |
10 | impl TidAllocator {
11 | pub fn new() -> Self {
12 | TidAllocator {
13 | state: 0,
14 | deallocated_tid: Vec::new(),
15 | }
16 | }
17 |
18 | pub fn allocate(&mut self) -> Tid {
19 | if let Some(tid) = self.deallocated_tid.pop() {
20 | tid
21 | } else {
22 | let tid = self.state;
23 | self.state += 1;
24 | tid
25 | }
26 | }
27 |
28 | pub fn deallocate(&mut self, tid: Tid) {
29 | self.deallocated_tid.push(tid);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/kernel/src/timer.rs:
--------------------------------------------------------------------------------
1 | //! The `timer` module provides functions to configure the timer interrupt.
2 |
3 | use riscv::register::{sie, time};
4 |
5 | use crate::{constant::CLOCK_FREQ, sbi};
6 |
7 | /// The number of timer ticks per second.
8 | const TICK_PER_SEC: usize = 100;
9 |
10 | /// Returns the current system time in milliseconds.
11 | pub fn get_time() -> usize {
12 | time::read() / (CLOCK_FREQ / 1000)
13 | }
14 |
15 | /// Sets the timer interrupt trigger for the next timer tick.
16 | pub fn set_trigger() {
17 | sbi::set_timer(time::read() + CLOCK_FREQ / TICK_PER_SEC);
18 | }
19 |
20 | /// Enables the system timer interrupt.
21 | pub fn enable_timer_interrupt() {
22 | unsafe {
23 | sie::set_stimer();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/kernel/src/executor/scheduler.rs:
--------------------------------------------------------------------------------
1 | use alloc::collections::VecDeque;
2 |
3 | use async_task::Runnable;
4 |
5 | pub trait Scheduler {
6 | fn schedule(&mut self, runnable: Runnable);
7 |
8 | fn task(&mut self) -> Option;
9 | }
10 |
11 | /// The `TaskQueue` struct represents a queue of [Runnable] tasks, which are either kernel threads
12 | /// or user threads.
13 | pub struct TaskQueue {
14 | queue: VecDeque,
15 | }
16 |
17 | impl TaskQueue {
18 | pub fn new() -> Self {
19 | Self {
20 | queue: VecDeque::new(),
21 | }
22 | }
23 | }
24 |
25 | impl Scheduler for TaskQueue {
26 | fn schedule(&mut self, runnable: Runnable) {
27 | self.queue.push_back(runnable)
28 | }
29 |
30 | fn task(&mut self) -> Option {
31 | self.queue.pop_front()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/kernel-lib/src/lang_items.rs:
--------------------------------------------------------------------------------
1 | //! The `lang_items` module contains Rust lang items.
2 | //! Rust lang items are functionalities that isn't hard-coded into the language,
3 | //! but is implemented in libraries, with a special marker to tell the compiler it exists.
4 | //! Since the kernel doesn't depend on the `std` crate, it has to implement some
5 | //! lang items, such as the `panic_handler`.
6 |
7 | use core::panic::PanicInfo;
8 |
9 | use log::error;
10 |
11 | #[panic_handler]
12 | fn panic(info: &PanicInfo) -> ! {
13 | if let Some(location) = info.location() {
14 | error!(
15 | "panic at {}:{}: {}",
16 | location.file(),
17 | location.line(),
18 | info.message().unwrap()
19 | );
20 | } else {
21 | error!("panic: {}", info.message().unwrap());
22 | }
23 | loop {}
24 | }
25 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build fmt doc qemu qemu-gdb gdb clean
2 |
3 | build:
4 | cargo build
5 |
6 | fmt:
7 | cargo fmt
8 |
9 | doc:
10 | cargo doc --no-deps --bin kernel --lib
11 |
12 | qemu: build
13 | qemu-system-riscv64 \
14 | -machine virt \
15 | -nographic \
16 | -bios bootloader/opensbi-jump.bin \
17 | -device loader,file=target/riscv64gc-unknown-none-elf/debug/kernel,addr=0x80200000
18 |
19 | qemu-gdb: build
20 | qemu-system-riscv64 \
21 | -machine virt \
22 | -nographic \
23 | -bios bootloader/opensbi-jump.bin \
24 | -device loader,file=target/riscv64gc-unknown-none-elf/debug/kernel,addr=0x80200000 \
25 | -s -S
26 |
27 | gdb:
28 | riscv64-unknown-elf-gdb \
29 | -ex 'file target/riscv64gc-unknown-none-elf/debug/kernel' \
30 | -ex 'set arch riscv:rv64' \
31 | -ex 'target remote localhost:1234'
32 |
33 | clean:
34 | cargo clean
35 |
--------------------------------------------------------------------------------
/kernel/src/lang_items.rs:
--------------------------------------------------------------------------------
1 | //! The `lang_items` module contains Rust lang items.
2 | //! Rust lang items are functionalities that isn't hard-coded into the language,
3 | //! but is implemented in libraries, with a special marker to tell the compiler it exists.
4 | //! Since the kernel doesn't depend on the `std` crate, it has to implement some
5 | //! lang items, such as the `panic_handler`.
6 |
7 | use core::panic::PanicInfo;
8 |
9 | use log::error;
10 |
11 | use crate::sbi;
12 |
13 | #[panic_handler]
14 | fn panic(info: &PanicInfo) -> ! {
15 | if let Some(location) = info.location() {
16 | error!(
17 | "panic at {}:{}: {}",
18 | location.file(),
19 | location.line(),
20 | info.message().unwrap()
21 | );
22 | } else {
23 | error!("panic: {}", info.message().unwrap());
24 | }
25 | sbi::shutdown();
26 | }
27 |
--------------------------------------------------------------------------------
/kernel/src/constant.rs:
--------------------------------------------------------------------------------
1 | //! The `constant` module defines several parameters for the kernel.
2 |
3 | /// The size of the kernel heap, in bytes.
4 | pub const KERNEL_HEAP_SIZE: usize = 4096 * 768;
5 |
6 | /// The size of the user stack, in bytes.
7 | pub const USER_STACK_SIZE: usize = 4096 * 2;
8 |
9 | /// The size of a page in memory, in bytes.
10 | pub const PAGE_SIZE: usize = 4096;
11 |
12 | /// The number of bits needed to represent a page size.
13 | pub const PAGE_SIZE_BIT: usize = 12;
14 |
15 | /// The memory limit for the kernel, in bytes.
16 | pub const MEM_LIMIT: usize = 0x81000000;
17 |
18 | /// The address of the trampoline page, which is used for switching between user and kernel space.
19 | pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1;
20 |
21 | /// The base address of the trap context.
22 | pub const TRAP_CONTEXT_BASE: usize = usize::MAX - 256 * PAGE_SIZE + 1;
23 |
24 | /// The clock frequency of the system, in Hz.
25 | pub const CLOCK_FREQ: usize = 12500000;
26 |
--------------------------------------------------------------------------------
/kernel-lib/src/heap_allocator.rs:
--------------------------------------------------------------------------------
1 | //! The `heap_allocator` module provides a heap allocator for the user.
2 | //! The heap is initialized with a fixed size as the [USER_HEAP_SIZE] constant.
3 |
4 | use core::alloc::Layout;
5 |
6 | use linked_list_allocator::LockedHeap;
7 |
8 | use crate::constant::USER_HEAP_SIZE;
9 |
10 | #[global_allocator]
11 | static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
12 |
13 | static mut USER_HEAP: [u8; USER_HEAP_SIZE] = [0; USER_HEAP_SIZE];
14 |
15 | /// Initializes the user heap with a fixed size as the [KERNEL_HEAP_SIZE]
16 | /// constant. This function must be called before the heap can be used.
17 | pub fn init() {
18 | unsafe {
19 | HEAP_ALLOCATOR
20 | .lock()
21 | .init(USER_HEAP.as_mut_ptr(), USER_HEAP_SIZE);
22 | }
23 | }
24 |
25 | /// Panics when heap allocation fails.
26 | #[alloc_error_handler]
27 | pub fn handle_alloc_error(layout: Layout) -> ! {
28 | panic!("failed to allocate the desired layout {:?}", layout);
29 | }
30 |
--------------------------------------------------------------------------------
/kernel/src/mem/heap_allocator.rs:
--------------------------------------------------------------------------------
1 | //! The `heap_allocator` module provides a heap allocator for the kernel.
2 | //! The heap is initialized with a fixed size as the [KERNEL_HEAP_SIZE] constant.
3 |
4 | use core::alloc::Layout;
5 |
6 | use linked_list_allocator::LockedHeap;
7 |
8 | use crate::constant::KERNEL_HEAP_SIZE;
9 |
10 | #[global_allocator]
11 | static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
12 |
13 | static mut KERNEL_HEAP: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];
14 |
15 | /// Initializes the kernel heap with a fixed size as the [KERNEL_HEAP_SIZE]
16 | /// constant. This function must be called before the heap can be used.
17 | pub fn init() {
18 | unsafe {
19 | HEAP_ALLOCATOR
20 | .lock()
21 | .init(KERNEL_HEAP.as_mut_ptr(), KERNEL_HEAP_SIZE);
22 | }
23 | }
24 |
25 | /// Panics when heap allocation fails.
26 | #[alloc_error_handler]
27 | pub fn handle_alloc_error(layout: Layout) -> ! {
28 | panic!("failed to allocate the desired layout {:?}", layout);
29 | }
30 |
--------------------------------------------------------------------------------
/kernel/src/console.rs:
--------------------------------------------------------------------------------
1 | //! The `console` module contains functions that interacts with the debug console.
2 | //! It exports useful macros such as `print!` and `println!`.
3 |
4 | use core::fmt::{self, Write};
5 |
6 | use crate::sbi;
7 |
8 | /// The `Console` struct implements the [Write] trait, which invokes the [sbi::console_putchar]
9 | /// function.
10 | struct Console;
11 |
12 | impl Write for Console {
13 | fn write_str(&mut self, string: &str) -> fmt::Result {
14 | for char in string.bytes() {
15 | sbi::console_putchar(char.into());
16 | }
17 | Ok(())
18 | }
19 | }
20 |
21 | pub fn print(args: fmt::Arguments) {
22 | Console.write_fmt(args).unwrap();
23 | }
24 |
25 | /// Print to the debug console.
26 | #[macro_export]
27 | macro_rules! print {
28 | ($($arg:tt)*) => ($crate::console::print(format_args!($($arg)*)));
29 | }
30 |
31 | /// Print to the debug console, with a newline.
32 | #[macro_export]
33 | macro_rules! println {
34 | () => ($crate::print!("\n"));
35 | ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
36 | }
37 |
--------------------------------------------------------------------------------
/kernel-lib/src/console.rs:
--------------------------------------------------------------------------------
1 | //! The `console` module contains functions that interacts with the debug console.
2 | //! It exports useful macros such as `print!` and `println!`.
3 |
4 | use core::fmt::{self, Write};
5 |
6 | use crate::{read, write};
7 |
8 | const STDIN: usize = 0;
9 | const STDOUT: usize = 1;
10 |
11 | struct Console;
12 |
13 | impl Write for Console {
14 | fn write_str(&mut self, string: &str) -> fmt::Result {
15 | write(STDOUT, string.as_bytes());
16 | Ok(())
17 | }
18 | }
19 |
20 | pub fn getchar() -> u8 {
21 | let mut char = [0; 1];
22 | read(STDIN, &mut char);
23 | char[0]
24 | }
25 |
26 | pub fn print(args: fmt::Arguments) {
27 | Console.write_fmt(args).unwrap();
28 | }
29 |
30 | /// Print to the debug console.
31 | #[macro_export]
32 | macro_rules! print {
33 | ($($arg:tt)*) => ($crate::console::print(format_args!($($arg)*)));
34 | }
35 |
36 | /// Print to the debug console, with a newline.
37 | #[macro_export]
38 | macro_rules! println {
39 | () => ($crate::print!("\n"));
40 | ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Xiaoyang Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/cargo.yml:
--------------------------------------------------------------------------------
1 | name: cargo
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | build:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v3
9 | - uses: dtolnay/rust-toolchain@master
10 | with:
11 | toolchain: nightly
12 | targets: riscv64gc-unknown-none-elf
13 | - run: make build
14 |
15 | fmt:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v3
19 | - uses: dtolnay/rust-toolchain@master
20 | with:
21 | toolchain: nightly
22 | targets: riscv64gc-unknown-none-elf
23 | - run: make fmt
24 |
25 | doc:
26 | runs-on: ubuntu-latest
27 | steps:
28 | - uses: actions/checkout@v3
29 | - uses: dtolnay/rust-toolchain@master
30 | with:
31 | toolchain: nightly
32 | targets: riscv64gc-unknown-none-elf
33 | - run: make doc
34 | - run: mv target/riscv64gc-unknown-none-elf/doc/* doc
35 | - uses: peaceiris/actions-gh-pages@v3
36 | if: github.event_name == 'push' && github.ref == 'refs/heads/master'
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_dir: doc
40 | publish_branch: gh-pages
41 | cname: rust-kernel-riscv.com
42 |
--------------------------------------------------------------------------------
/kernel/src/task/pid.rs:
--------------------------------------------------------------------------------
1 | use alloc::vec::Vec;
2 |
3 | use lazy_static::lazy_static;
4 |
5 | use crate::sync::Mutex;
6 |
7 | pub type Pid = usize;
8 |
9 | pub struct PidHandle {
10 | pid: Pid,
11 | }
12 |
13 | impl PidHandle {
14 | pub fn new(pid: Pid) -> Self {
15 | Self { pid }
16 | }
17 |
18 | pub fn pid(&self) -> Pid {
19 | self.pid
20 | }
21 | }
22 |
23 | impl Drop for PidHandle {
24 | fn drop(&mut self) {
25 | PID_ALLOCATOR.lock().deallocate(self.pid);
26 | }
27 | }
28 |
29 | pub struct PidAllocator {
30 | state: Pid,
31 | deallocated_pid: Vec,
32 | }
33 |
34 | impl PidAllocator {
35 | pub fn new() -> Self {
36 | PidAllocator {
37 | state: 0,
38 | deallocated_pid: Vec::new(),
39 | }
40 | }
41 |
42 | pub fn allocate(&mut self) -> PidHandle {
43 | if let Some(pid) = self.deallocated_pid.pop() {
44 | PidHandle::new(pid)
45 | } else {
46 | let pid_handle = PidHandle::new(self.state);
47 | self.state += 1;
48 | pid_handle
49 | }
50 | }
51 |
52 | pub fn deallocate(&mut self, pid: Pid) {
53 | self.deallocated_pid.push(pid);
54 | }
55 | }
56 |
57 | lazy_static! {
58 | static ref PID_ALLOCATOR: Mutex = Mutex::new(PidAllocator::new());
59 | }
60 |
61 | pub fn allocate_pid() -> PidHandle {
62 | PID_ALLOCATOR.lock().allocate()
63 | }
64 |
--------------------------------------------------------------------------------
/kernel/src/linker.ld:
--------------------------------------------------------------------------------
1 | /*
2 | The linker script determines the layout of the output file,
3 | which moves the `.text.boot` section defined in `src/boot.asm`
4 | to the base address of the kernel.
5 | It defines various symbols that contains the start and the end
6 | address of each section.
7 | */
8 |
9 | OUTPUT_ARCH(riscv)
10 | ENTRY(_start)
11 | BASE_ADDRESS = 0x80200000;
12 |
13 | SECTIONS
14 | {
15 | . = BASE_ADDRESS;
16 | kernel_start = .;
17 |
18 | text_start = .;
19 | .text : {
20 | *(.text.boot)
21 | . = ALIGN(4k);
22 |
23 | trampoline_start = .;
24 | *(.text.trampoline)
25 | . = ALIGN(4k);
26 | trampoline_end = .;
27 |
28 | *(.text .text.*)
29 | }
30 | . = ALIGN(4K);
31 | text_end = .;
32 |
33 | rodata_start = .;
34 | .rodata : {
35 | *(.rodata .rodata.*)
36 | *(.srodata .srodata.*)
37 | }
38 | . = ALIGN(4K);
39 | rodata_end = .;
40 |
41 | data_start = .;
42 | .data : {
43 | *(.data .data.*)
44 | *(.sdata .sdata.*)
45 | }
46 | . = ALIGN(4K);
47 | data_end = .;
48 |
49 | bss_stack_start = .;
50 | .bss : {
51 | *(.bss.stack)
52 | bss_stack_end = .;
53 |
54 | bss_start = .;
55 | *(.bss .bss.*)
56 | *(.sbss .sbss.*)
57 | }
58 | . = ALIGN(4K);
59 | bss_end = .;
60 |
61 | kernel_end = .;
62 |
63 | /DISCARD/ : {
64 | *(.eh_frame)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/kernel/src/executor/context.rs:
--------------------------------------------------------------------------------
1 | //! The `context` module provides a `TrapContext` struct that save and restore
2 | //! the context of a thread when an exception or interrupt occurs.
3 |
4 | use riscv::register::sstatus::Sstatus;
5 |
6 | /// The `TrapContext` struct is used to save and restore the context of a thread when an exception
7 | /// or interrupt occurs. It contains the values of all the general-purpose registers of the thread,
8 | /// the `sstatus` register, the `sepc` register, the address of the kernel stack, and the `satp`
9 | /// register value that refers to the kernel page table.
10 | #[repr(C)]
11 | pub struct TrapContext {
12 | user_register: [usize; 32],
13 | user_sstatus: Sstatus,
14 | user_sepc: usize,
15 |
16 | kernel_stack: usize,
17 | kernel_satp: usize,
18 | }
19 |
20 | impl TrapContext {
21 | pub fn user_status(&self) -> Sstatus {
22 | self.user_sstatus
23 | }
24 |
25 | pub fn set_user_status(&mut self, user_sstatus: Sstatus) {
26 | self.user_sstatus = user_sstatus;
27 | }
28 |
29 | pub fn user_sepc(&self) -> usize {
30 | self.user_sepc
31 | }
32 |
33 | pub fn set_user_sepc(&mut self, user_sepc: usize) {
34 | self.user_sepc = user_sepc;
35 | }
36 |
37 | pub fn user_register(&self, index: usize) -> usize {
38 | self.user_register[index]
39 | }
40 |
41 | pub fn set_user_register(&mut self, index: usize, value: usize) {
42 | self.user_register[index] = value;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/kernel/src/logging.rs:
--------------------------------------------------------------------------------
1 | //! The `logging` module implements the [log::Log] trait.
2 |
3 | use log::{self, Level, LevelFilter, Metadata, Record};
4 |
5 | use crate::println;
6 |
7 | struct KernelLogger;
8 |
9 | impl log::Log for KernelLogger {
10 | fn enabled(&self, _: &Metadata) -> bool {
11 | true
12 | }
13 |
14 | fn log(&self, record: &Record) {
15 | if !self.enabled(record.metadata()) {
16 | return;
17 | }
18 |
19 | let color: u8 = match record.level() {
20 | Level::Error => 31,
21 | Level::Warn => 93,
22 | Level::Info => 34,
23 | Level::Debug => 32,
24 | Level::Trace => 90,
25 | };
26 | println!(
27 | "\u{1B}[{}m[S][{}] {}\u{1B}[0m",
28 | color,
29 | record.level(),
30 | record.args()
31 | );
32 | }
33 |
34 | fn flush(&self) {}
35 | }
36 |
37 | /// Initialize the kernel logger with the specified log level,
38 | /// or defaults to LevelFilter::Info
39 | pub fn init() {
40 | static LOGGER: KernelLogger = KernelLogger;
41 | log::set_logger(&LOGGER).unwrap();
42 | log::set_max_level(match option_env!("LOG_LEVEL") {
43 | Some("error") => LevelFilter::Error,
44 | Some("warn") => LevelFilter::Warn,
45 | Some("info") => LevelFilter::Info,
46 | Some("debug") => LevelFilter::Debug,
47 | Some("trace") => LevelFilter::Trace,
48 | _ => LevelFilter::Info,
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/kernel-lib/src/logging.rs:
--------------------------------------------------------------------------------
1 | //! The `logging` module implements the [log::Log] trait.
2 |
3 | use log::{self, Level, LevelFilter, Metadata, Record};
4 |
5 | use crate::println;
6 |
7 | struct KernelLogger;
8 |
9 | impl log::Log for KernelLogger {
10 | fn enabled(&self, _: &Metadata) -> bool {
11 | true
12 | }
13 |
14 | fn log(&self, record: &Record) {
15 | if !self.enabled(record.metadata()) {
16 | return;
17 | }
18 |
19 | let color: u8 = match record.level() {
20 | Level::Error => 31,
21 | Level::Warn => 93,
22 | Level::Info => 34,
23 | Level::Debug => 32,
24 | Level::Trace => 90,
25 | };
26 | println!(
27 | "\u{1B}[{}m[U][{}] {}\u{1B}[0m",
28 | color,
29 | record.level(),
30 | record.args()
31 | );
32 | }
33 |
34 | fn flush(&self) {}
35 | }
36 |
37 | /// Initialize the kernel logger with the specified log level,
38 | /// or defaults to LevelFilter::Info
39 | pub fn init() {
40 | static LOGGER: KernelLogger = KernelLogger;
41 | log::set_logger(&LOGGER).unwrap();
42 | log::set_max_level(match option_env!("LOG_LEVEL") {
43 | Some("error") => LevelFilter::Error,
44 | Some("warn") => LevelFilter::Warn,
45 | Some("info") => LevelFilter::Info,
46 | Some("debug") => LevelFilter::Debug,
47 | Some("trace") => LevelFilter::Trace,
48 | _ => LevelFilter::Info,
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/kernel-lib/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 | #![feature(linkage)]
4 | #![feature(panic_info_message)]
5 | #![feature(alloc_error_handler)]
6 |
7 | pub mod console;
8 | mod constant;
9 | mod heap_allocator;
10 | mod lang_items;
11 | mod logging;
12 | mod syscall;
13 |
14 | use syscall::{
15 | sys_exec,
16 | sys_exit,
17 | sys_fork,
18 | sys_get_time,
19 | sys_read,
20 | sys_sched_yield,
21 | sys_waitpid,
22 | sys_write,
23 | };
24 |
25 | #[no_mangle]
26 | #[link_section = ".text.init"]
27 | pub extern "C" fn _start() -> ! {
28 | logging::init();
29 | heap_allocator::init();
30 |
31 | exit(main());
32 | panic!("failed to invoke `exit`")
33 | }
34 |
35 | #[linkage = "weak"]
36 | #[no_mangle]
37 | fn main() -> i32 {
38 | panic!("failed to find the `main` function");
39 | }
40 |
41 | pub fn read(fd: usize, buffer: &mut [u8]) -> isize {
42 | sys_read(fd, buffer)
43 | }
44 |
45 | pub fn write(fd: usize, buffer: &[u8]) -> isize {
46 | sys_write(fd, buffer)
47 | }
48 |
49 | pub fn exit(exit_code: i32) -> isize {
50 | sys_exit(exit_code)
51 | }
52 |
53 | pub fn sched_yield() -> isize {
54 | sys_sched_yield()
55 | }
56 |
57 | pub fn get_time() -> isize {
58 | sys_get_time()
59 | }
60 |
61 | pub fn fork() -> isize {
62 | sys_fork()
63 | }
64 |
65 | pub fn exec(path: &str) -> isize {
66 | sys_exec(path)
67 | }
68 |
69 | pub fn wait(exit_code: &mut usize) -> isize {
70 | sys_waitpid(-1, exit_code as *mut usize)
71 | }
72 |
73 | pub fn waitpid(pid: usize, exit_code: &mut usize) -> isize {
74 | sys_waitpid(pid as isize, exit_code as *mut usize)
75 | }
76 |
--------------------------------------------------------------------------------
/kernel/src/executor/mod.rs:
--------------------------------------------------------------------------------
1 | //! The `executor` module provides an executor that schedules and runs both the kernel threads and
2 | //! the user threads.
3 |
4 | use alloc::boxed::Box;
5 | use core::future::Future;
6 |
7 | use async_task::{Runnable, Task};
8 | use lazy_static::lazy_static;
9 | use riscv::register::{stvec, utvec::TrapMode};
10 |
11 | use crate::{
12 | constant::TRAMPOLINE,
13 | executor::scheduler::{Scheduler, TaskQueue},
14 | sync::Mutex,
15 | };
16 |
17 | mod context;
18 | mod future;
19 | mod scheduler;
20 |
21 | pub use context::TrapContext;
22 | pub use future::{spawn_thread, yield_now, ControlFlow};
23 |
24 | /// Initializes the `stvec` to the address of the `_enter_kernel_space` function, which is located
25 | /// at the beginning of the [TRAMPOLINE] page.
26 | pub fn init() {
27 | unsafe {
28 | stvec::write(TRAMPOLINE, TrapMode::Direct);
29 | }
30 | }
31 |
32 | lazy_static! {
33 | static ref SCHEDULER: Mutex> = Mutex::new(Box::new(TaskQueue::new()));
34 | }
35 |
36 | fn spawn(future: F) -> (Runnable, Task)
37 | where
38 | F: Future + Send + 'static,
39 | F::Output: Send + 'static,
40 | {
41 | async_task::spawn(future, |runnable| {
42 | SCHEDULER.lock().schedule(runnable);
43 | })
44 | }
45 |
46 | /// Runs an event loop that executes all the tasks in the `TASK_QUEUE` until there are no more task
47 | /// left.
48 | pub fn run_until_complete() {
49 | loop {
50 | let task = SCHEDULER.lock().task();
51 | if let Some(task) = task {
52 | task.run();
53 | } else {
54 | break;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/kernel-lib/src/syscall.rs:
--------------------------------------------------------------------------------
1 | use core::arch::asm;
2 |
3 | const SYSCALL_READ: usize = 63;
4 | const SYSCALL_WRITE: usize = 64;
5 | const SYSCALL_EXIT: usize = 93;
6 | const SYSCALL_SCHED_YIELD: usize = 128;
7 | const SYSCALL_GET_TIME: usize = 169;
8 | const SYSCALL_FORK: usize = 220;
9 | const SYSCALL_EXEC: usize = 221;
10 | const SYSCALL_WAITPID: usize = 260;
11 |
12 | fn syscall(id: usize, args: [usize; 3]) -> isize {
13 | let mut result: isize;
14 | unsafe {
15 | asm!(
16 | "ecall",
17 | inlateout("a0") args[0] => result,
18 | in("a1") args[1],
19 | in("a2") args[2],
20 | in("a7") id,
21 | );
22 | }
23 | result
24 | }
25 |
26 | pub fn sys_read(fd: usize, buffer: &mut [u8]) -> isize {
27 | syscall(
28 | SYSCALL_READ,
29 | [fd, buffer.as_mut_ptr() as usize, buffer.len()],
30 | )
31 | }
32 |
33 | pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
34 | syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
35 | }
36 |
37 | pub fn sys_exit(exit_code: i32) -> isize {
38 | syscall(SYSCALL_EXIT, [exit_code as usize, 0, 0])
39 | }
40 |
41 | pub fn sys_sched_yield() -> isize {
42 | syscall(SYSCALL_SCHED_YIELD, [0, 0, 0])
43 | }
44 |
45 | pub fn sys_get_time() -> isize {
46 | syscall(SYSCALL_GET_TIME, [0, 0, 0])
47 | }
48 |
49 | pub fn sys_fork() -> isize {
50 | syscall(SYSCALL_FORK, [0, 0, 0])
51 | }
52 |
53 | pub fn sys_waitpid(pid: isize, exit_code: *mut usize) -> isize {
54 | syscall(SYSCALL_WAITPID, [pid as usize, exit_code as usize, 0])
55 | }
56 |
57 | pub fn sys_exec(path: &str) -> isize {
58 | syscall(SYSCALL_EXEC, [path.as_ptr() as usize, 0, 0])
59 | }
60 |
--------------------------------------------------------------------------------
/kernel-lib/src/bin/shell.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 | #![no_main]
3 |
4 | use alloc::string::String;
5 |
6 | use kernel_lib::{console::getchar, exec, exit, fork, waitpid};
7 |
8 | extern crate alloc;
9 | #[macro_use]
10 | extern crate kernel_lib;
11 |
12 | const LF: u8 = 0x0au8;
13 | const CR: u8 = 0x0du8;
14 | const DL: u8 = 0x7fu8;
15 | const BS: u8 = 0x08u8;
16 |
17 | #[no_mangle]
18 | fn main() -> i32 {
19 | print!("$ ");
20 |
21 | let mut line = String::new();
22 | loop {
23 | let char = getchar();
24 | match char {
25 | LF | CR => {
26 | println!("");
27 | if line.is_empty() {
28 | continue;
29 | } else if line == "exit" {
30 | exit(0);
31 | } else {
32 | let pid = fork() as usize;
33 | if pid == 0 {
34 | line.push('\0');
35 | if exec(line.as_str()) == -1 {
36 | return -4;
37 | }
38 | } else {
39 | let mut exit_code = 0;
40 | waitpid(pid, &mut exit_code);
41 | }
42 | line.clear();
43 | print!("$ ");
44 | }
45 | }
46 | BS | DL => {
47 | if line.is_empty() {
48 | continue;
49 | }
50 | print!("{0} {0}", BS as char);
51 | line.pop();
52 | }
53 | _ => {
54 | print!("{}", char as char);
55 | line.push(char as char);
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/kernel/src/sbi.rs:
--------------------------------------------------------------------------------
1 | //! The `sbi` module contains functions that invokes the RISC-V interface.
2 | //! SBI is an interface between the Supervisor Execution Environment and the supervisor.
3 | //! It allows the supervisor to execute some privileged operations with the `ecall` instruction.
4 | //! For more details, please refer to the
5 | //! [RISC-V SBI Specification](https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/riscv-sbi.adoc).
6 |
7 | use core::arch::asm;
8 |
9 | use log::info;
10 |
11 | const CONSOLE_PUTCHAR_EXTENSION: usize = 0x01;
12 | const CONSOLE_GETCHAR_EXTENSION: usize = 0x02;
13 | const SYSTEM_RESET_EXTENSION: usize = 0x53525354;
14 | const TIMER_EXTENSION: usize = 0x54494D45;
15 |
16 | #[inline]
17 | fn sbi_call(extension: usize, function: usize, arg0: usize, arg1: usize) -> (isize, isize) {
18 | let (error, value);
19 | unsafe {
20 | asm!(
21 | "ecall",
22 | inlateout("a0") arg0 => error,
23 | inlateout("a1") arg1 => value,
24 | in("a6") function,
25 | in("a7") extension,
26 | )
27 | }
28 | (error, value)
29 | }
30 |
31 | #[inline]
32 | pub fn set_timer(stime_value: usize) {
33 | sbi_call(TIMER_EXTENSION, 0, stime_value, 0);
34 | }
35 |
36 | /// Write data present in `char` to debug console.
37 | #[inline]
38 | pub fn console_putchar(char: usize) {
39 | sbi_call(CONSOLE_PUTCHAR_EXTENSION, 0, char, 0);
40 | }
41 |
42 | #[inline]
43 | pub fn console_getchar() -> usize {
44 | let (value, _) = sbi_call(CONSOLE_GETCHAR_EXTENSION, 0, 0, 0);
45 | value.max(0) as usize
46 | }
47 |
48 | /// Put all the harts to shutdown state.
49 | #[inline]
50 | pub fn shutdown() -> ! {
51 | info!("shutdown");
52 | sbi_call(SYSTEM_RESET_EXTENSION, 0, 0, 0);
53 | panic!("failed to shutdown");
54 | }
55 |
--------------------------------------------------------------------------------
/kernel/src/syscall/fs.rs:
--------------------------------------------------------------------------------
1 | //! The `fs` module provides system calls to interact with the file system.
2 |
3 | use core::str;
4 |
5 | use log::error;
6 |
7 | use crate::{
8 | executor::{yield_now, ControlFlow},
9 | mem::UserPtr,
10 | print,
11 | sbi,
12 | syscall::SystemCall,
13 | };
14 |
15 | const STDIN: usize = 0;
16 | const STDOUT: usize = 1;
17 |
18 | impl SystemCall<'_> {
19 | /// Reads the content from a file descriptor and writes them to a buffer.
20 | pub async fn sys_read(
21 | &self,
22 | fd: usize,
23 | mut buffer: UserPtr,
24 | _length: usize,
25 | ) -> (isize, ControlFlow) {
26 | match fd {
27 | STDIN => {
28 | let mut char;
29 | loop {
30 | char = sbi::console_getchar();
31 | if char == 0 {
32 | yield_now().await;
33 | } else {
34 | break;
35 | }
36 | }
37 | *buffer = char as u8;
38 | (1, ControlFlow::Continue)
39 | }
40 | _ => {
41 | error!("the file descriptor {} is not supported in 'sys_write'", fd);
42 | (-1, ControlFlow::Continue)
43 | }
44 | }
45 | }
46 |
47 | /// Writes the contents of a buffer to a file descriptor.
48 | pub fn sys_write(&self, fd: usize, buffer: UserPtr, length: usize) -> (isize, ControlFlow) {
49 | match fd {
50 | STDOUT => {
51 | for buffer in buffer.as_buffer(length) {
52 | print!("{}", str::from_utf8(buffer).unwrap());
53 | }
54 | (length as isize, ControlFlow::Continue)
55 | }
56 | _ => {
57 | error!("the file descriptor {} is not supported in 'sys_write'", fd);
58 | (-1, ControlFlow::Continue)
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/kernel/src/asm/linkage.asm:
--------------------------------------------------------------------------------
1 | .align 3
2 | .section .data
3 | .global _bin_count
4 | .global _bin_address
5 | .global _bin_name
6 |
7 | _bin_count:
8 | .quad 7
9 |
10 | _bin_address:
11 | .quad bin_0_start
12 | .quad bin_0_end
13 | .quad bin_1_start
14 | .quad bin_1_end
15 | .quad bin_2_start
16 | .quad bin_2_end
17 | .quad bin_3_start
18 | .quad bin_3_end
19 | .quad bin_4_start
20 | .quad bin_4_end
21 | .quad bin_5_start
22 | .quad bin_5_end
23 | .quad bin_6_start
24 | .quad bin_6_end
25 |
26 | _bin_name:
27 | .string "fork"
28 | .string "hello_world"
29 | .string "init"
30 | .string "page_fault"
31 | .string "privileged_instruction"
32 | .string "shell"
33 | .string "sleep"
34 |
35 | .section .data
36 | .global bin_0_start
37 | .global bin_0_end
38 | .align 3
39 | bin_0_start:
40 | .incbin "target/riscv64gc-unknown-none-elf/debug/fork"
41 | bin_0_end:
42 |
43 | .section .data
44 | .global bin_1_start
45 | .global bin_1_end
46 | .align 3
47 | bin_1_start:
48 | .incbin "target/riscv64gc-unknown-none-elf/debug/hello_world"
49 | bin_1_end:
50 |
51 | .section .data
52 | .global bin_2_start
53 | .global bin_2_end
54 | .align 3
55 | bin_2_start:
56 | .incbin "target/riscv64gc-unknown-none-elf/debug/init"
57 | bin_2_end:
58 |
59 | .section .data
60 | .global bin_3_start
61 | .global bin_3_end
62 | .align 3
63 | bin_3_start:
64 | .incbin "target/riscv64gc-unknown-none-elf/debug/page_fault"
65 | bin_3_end:
66 |
67 | .section .data
68 | .global bin_4_start
69 | .global bin_4_end
70 | .align 3
71 | bin_4_start:
72 | .incbin "target/riscv64gc-unknown-none-elf/debug/privileged_instruction"
73 | bin_4_end:
74 |
75 | .section .data
76 | .global bin_5_start
77 | .global bin_5_end
78 | .align 3
79 | bin_5_start:
80 | .incbin "target/riscv64gc-unknown-none-elf/debug/shell"
81 | bin_5_end:
82 |
83 | .section .data
84 | .global bin_6_start
85 | .global bin_6_end
86 | .align 3
87 | bin_6_start:
88 | .incbin "target/riscv64gc-unknown-none-elf/debug/sleep"
89 | bin_6_end:
90 |
--------------------------------------------------------------------------------
/kernel/src/file/load.rs:
--------------------------------------------------------------------------------
1 | use alloc::vec::Vec;
2 | use core::{slice, str};
3 |
4 | use lazy_static::lazy_static;
5 | use log::info;
6 |
7 | extern "C" {
8 | fn _bin_count();
9 | fn _bin_address();
10 | fn _bin_name();
11 | }
12 |
13 | lazy_static! {
14 | static ref BIN_NAME_LIST: Vec<&'static str> = {
15 | let mut bin_name_list = Vec::new();
16 | let mut bin_name_pointer = _bin_name as usize as *const u8;
17 | let bin_count = get_bin_count();
18 | unsafe {
19 | for _ in 0..bin_count {
20 | let mut bin_name_length = 0;
21 | while bin_name_pointer.add(bin_name_length).read_volatile() != b'\0' {
22 | bin_name_length += 1;
23 | }
24 | let bin_name_slice = slice::from_raw_parts(bin_name_pointer, bin_name_length);
25 | bin_name_list.push(str::from_utf8(bin_name_slice).unwrap());
26 | bin_name_pointer = bin_name_pointer.add(bin_name_length).add(1);
27 | }
28 | }
29 | bin_name_list
30 | };
31 | }
32 |
33 | pub fn get_bin_count() -> usize {
34 | unsafe { (_bin_count as *const usize).read_volatile() }
35 | }
36 |
37 | pub fn get_bin(name: &str) -> Option<&'static [u8]> {
38 | let bin_count = get_bin_count();
39 | (0..bin_count)
40 | .find(|&bin_index| BIN_NAME_LIST[bin_index] == name)
41 | .map(get_bin_data)
42 | }
43 |
44 | pub fn get_bin_data(bin_index: usize) -> &'static [u8] {
45 | let bin_address_pointer = _bin_address as *const usize;
46 |
47 | unsafe {
48 | let bin_address_start = bin_address_pointer.add(bin_index * 2).read_volatile();
49 | let bin_address_end = bin_address_pointer.add(bin_index * 2 + 1).read_volatile();
50 | slice::from_raw_parts(
51 | bin_address_start as *const u8,
52 | bin_address_end - bin_address_start,
53 | )
54 | }
55 | }
56 |
57 | pub fn print_bin_name() {
58 | let bin_count = get_bin_count();
59 | let mut bin_name_list = Vec::new();
60 | for bin_index in 0..bin_count {
61 | let bin_name = BIN_NAME_LIST[bin_index];
62 | if bin_name == "init" {
63 | continue;
64 | }
65 | bin_name_list.push(bin_name);
66 | }
67 | info!("built-in binaries: {}", bin_name_list.join(", "));
68 | }
69 |
--------------------------------------------------------------------------------
/kernel/src/main.rs:
--------------------------------------------------------------------------------
1 | //! `rust-kernel-riscv` is an open-source project that implements an operating system kernel on RISC-V architecture with Rust programming language. The project draws inspiration from several open-source implementations, such as [xv6-riscv](https://github.com/mit-pdos/xv6-riscv) and [zCore](https://github.com/rcore-os/zCore).
2 | //!
3 | //! - The kernel leverages Rust's asynchronous programming model to schedule threads in both the
4 | //! kernel and user space, which makes context switching more efficient and eliminates the need of
5 | //! allocating a separate kernel stack for each user process.
6 | //!
7 | //! - The kernel implements the kernel page-table isolation, which prevents the kernel space and the
8 | //! user space to share a same page table and mitigates potential Meltdown attacks.
9 |
10 | #![no_std]
11 | #![no_main]
12 | #![feature(alloc_error_handler)]
13 | #![feature(panic_info_message)]
14 | #![feature(naked_functions)]
15 |
16 | extern crate alloc;
17 |
18 | #[macro_use]
19 | mod console;
20 | mod constant;
21 | mod executor;
22 | mod file;
23 | mod lang_items;
24 | mod logging;
25 | mod mem;
26 | mod sbi;
27 | mod sync;
28 | mod syscall;
29 | mod task;
30 | mod timer;
31 |
32 | use core::arch::global_asm;
33 |
34 | use log::info;
35 |
36 | global_asm!(include_str!("asm/boot.asm"));
37 | global_asm!(include_str!("asm/linkage.asm"));
38 |
39 | /// Initializes the thread executor and spawns the `INIT_PROCESS`.
40 | #[no_mangle]
41 | pub fn rust_main() {
42 | clear_bss();
43 | logging::init();
44 |
45 | mem::init();
46 |
47 | timer::enable_timer_interrupt();
48 | timer::set_trigger();
49 |
50 | info!("rust-kernel has booted");
51 | file::print_bin_name();
52 |
53 | task::init();
54 | executor::init();
55 | executor::run_until_complete();
56 |
57 | sbi::shutdown();
58 | }
59 |
60 | /// Initializes the `.bss` section with zeros.
61 | fn clear_bss() {
62 | extern "C" {
63 | /// The `bss_start` is a symbol declared in the `src/linker.ld`,
64 | /// which represent the start address of the `.bss` section.
65 | fn bss_start();
66 | /// The `bss_end` is a symbol declared in the `src/linker.ld`,
67 | /// which represent the end address of the `.bss` section.
68 | fn bss_end();
69 | }
70 |
71 | (bss_start as usize..bss_end as usize)
72 | .for_each(|address| unsafe { (address as *mut u8).write_volatile(0) })
73 | }
74 |
--------------------------------------------------------------------------------
/kernel/src/sync/mutex.rs:
--------------------------------------------------------------------------------
1 | //! The `mutex` module provides a mutual exclusion primitive useful for protecting shared data.
2 |
3 | use core::{
4 | cell::UnsafeCell,
5 | hint,
6 | marker::PhantomData,
7 | ops::{Deref, DerefMut},
8 | sync::atomic::{AtomicBool, Ordering},
9 | };
10 |
11 | /// The `Mutex` struct is a mutual exclusion primitive useful for protecting shared data, which
12 | /// implements the [Send] and [Sync] traits.
13 | pub struct Mutex {
14 | lock: AtomicBool,
15 | cell: UnsafeCell,
16 | phantom: PhantomData,
17 | }
18 |
19 | impl Mutex {
20 | /// Creates a new `Mutex` with the given initial value.
21 | pub fn new(value: T) -> Self {
22 | Self {
23 | lock: AtomicBool::new(false),
24 | cell: UnsafeCell::new(value),
25 | phantom: PhantomData,
26 | }
27 | }
28 |
29 | /// Acquires a lock on the `Mutex` and returns a [MutexGuard] that provides exclusive access to
30 | /// the shared resource.
31 | pub fn lock(&self) -> MutexGuard {
32 | while self
33 | .lock
34 | .compare_exchange(false, true, Ordering::Acquire, Ordering::Acquire)
35 | .is_err()
36 | {
37 | while self.lock.load(Ordering::Relaxed) {
38 | hint::spin_loop();
39 | }
40 | }
41 |
42 | MutexGuard::new(self)
43 | }
44 |
45 | /// Releases the lock on the `Mutex`.
46 | pub fn unlock(&self) {
47 | self.lock.store(false, Ordering::Release);
48 | }
49 | }
50 |
51 | unsafe impl Sync for Mutex {}
52 |
53 | unsafe impl Send for Mutex {}
54 |
55 | /// The `MutexGuard` struct is an RAII guard to allow scoped unlock of the lock. When the guard goes
56 | /// out of scope, the [Mutex] it guards will be unlocked.
57 | pub struct MutexGuard<'a, T> {
58 | mutex: &'a Mutex,
59 | }
60 |
61 | impl<'a, T> MutexGuard<'a, T> {
62 | /// Creates a new `MutexGuard` for the given [Mutex].
63 | pub fn new(mutex: &'a Mutex) -> Self {
64 | Self { mutex }
65 | }
66 | }
67 |
68 | impl<'a, T> Deref for MutexGuard<'a, T> {
69 | type Target = T;
70 |
71 | fn deref(&self) -> &T {
72 | unsafe { &*self.mutex.cell.get() }
73 | }
74 | }
75 |
76 | impl<'a, T> DerefMut for MutexGuard<'a, T> {
77 | fn deref_mut(&mut self) -> &mut T {
78 | unsafe { &mut *self.mutex.cell.get() }
79 | }
80 | }
81 |
82 | impl<'a, T> Drop for MutexGuard<'a, T> {
83 | fn drop(&mut self) {
84 | self.mutex.unlock();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/kernel/src/mem/address/physical_address.rs:
--------------------------------------------------------------------------------
1 | use core::ops::{Add, Sub};
2 |
3 | use crate::{
4 | constant::{PAGE_SIZE, PAGE_SIZE_BIT},
5 | mem::FrameNumber,
6 | };
7 |
8 | const PHYSICAL_ADDRESS_SIZE: usize = 56;
9 |
10 | /// The `PhysicalAddress` struct represents a 56-bit physical address defined in the Sv39
11 | /// page table format.
12 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
13 | pub struct PhysicalAddress {
14 | bits: usize,
15 | }
16 |
17 | impl PhysicalAddress {
18 | /// Returns the [FrameNumber] that represents the frame that contains the physical address.
19 | pub fn floor(&self) -> FrameNumber {
20 | FrameNumber {
21 | bits: self.bits / PAGE_SIZE,
22 | }
23 | }
24 |
25 | /// Returns the [FrameNumber] that represents the frame that contains the physical address,
26 | /// rounding up to the next frame if the physical address is not aligned to a frame.
27 | pub fn ceil(&self) -> FrameNumber {
28 | FrameNumber {
29 | bits: (self.bits + PAGE_SIZE - 1) / PAGE_SIZE,
30 | }
31 | }
32 |
33 | /// Returns the byte offset of the physical address within its containing frame.
34 | pub fn page_offset(&self) -> usize {
35 | self.bits & (PAGE_SIZE - 1)
36 | }
37 |
38 | /// Returns `true` if the physical address is aligned to a frame.
39 | pub fn is_aligned(&self) -> bool {
40 | self.page_offset() == 0
41 | }
42 |
43 | /// Returns a raw pointer to the physical address.
44 | pub fn as_ptr(&self) -> *const u8 {
45 | self.bits as *const u8
46 | }
47 |
48 | /// Returns a mutable raw pointer to the physical address.
49 | pub fn as_ptr_mut(&self) -> *mut u8 {
50 | self.bits as *mut u8
51 | }
52 |
53 | pub fn as_ref(&self) -> &'static T {
54 | unsafe { (self.bits as *const T).as_ref().unwrap() }
55 | }
56 |
57 | pub fn as_mut(&self) -> &'static mut T {
58 | unsafe { (self.bits as *mut T).as_mut().unwrap() }
59 | }
60 | }
61 |
62 | impl Add for PhysicalAddress {
63 | type Output = Self;
64 |
65 | fn add(self, rhs: usize) -> Self {
66 | Self::from(self.bits + rhs)
67 | }
68 | }
69 |
70 | impl Sub for PhysicalAddress {
71 | type Output = Self;
72 |
73 | fn sub(self, rhs: usize) -> Self {
74 | Self::from(self.bits - rhs)
75 | }
76 | }
77 |
78 | impl From for PhysicalAddress {
79 | fn from(value: usize) -> Self {
80 | Self {
81 | bits: value & ((1 << PHYSICAL_ADDRESS_SIZE) - 1),
82 | }
83 | }
84 | }
85 |
86 | impl From for usize {
87 | fn from(value: PhysicalAddress) -> Self {
88 | value.bits
89 | }
90 | }
91 |
92 | impl From for PhysicalAddress {
93 | fn from(value: FrameNumber) -> Self {
94 | Self {
95 | bits: usize::from(value) << PAGE_SIZE_BIT,
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/kernel/src/syscall/mod.rs:
--------------------------------------------------------------------------------
1 | //! The `syscall` module provides system calls for interacting with the operating system.
2 |
3 | use crate::{executor::ControlFlow, mem::UserPtr, task::Thread};
4 |
5 | mod fs;
6 | mod process;
7 | mod timer;
8 |
9 | const SYSCALL_READ: usize = 63;
10 | const SYSCALL_WRITE: usize = 64;
11 | const SYSCALL_EXIT: usize = 93;
12 | const SYSCALL_SCHED_YIELD: usize = 128;
13 | const SYSCALL_GET_TIME: usize = 169;
14 | const SYSCALL_FORK: usize = 220;
15 | const SYSCALL_EXEC: usize = 221;
16 | const SYSCALL_WAITPID: usize = 260;
17 |
18 | /// The `SystemCall` struct provides an interface for invoking system calls on a given thread.
19 | pub struct SystemCall<'a> {
20 | thread: &'a Thread,
21 | }
22 |
23 | impl<'a> SystemCall<'a> {
24 | /// Constructs a new `SystemCall` instance with the given thread.
25 | pub fn new(thread: &'a Thread) -> Self {
26 | Self { thread }
27 | }
28 |
29 | /// Invokes a system call with the given arguments.
30 | pub async fn execute(&mut self) -> ControlFlow {
31 | let trap_context = self.thread.state().lock().kernel_trap_context_mut();
32 |
33 | // Increments the program counter to skip the `ecall` instruction
34 | trap_context.set_user_sepc(trap_context.user_sepc() + 4);
35 |
36 | let system_call_id = trap_context.user_register(17);
37 | let argument_0 = trap_context.user_register(10);
38 | let argument_1 = trap_context.user_register(11);
39 | let argument_2 = trap_context.user_register(12);
40 |
41 | let (exit_code, control_flow) = match system_call_id {
42 | SYSCALL_READ => {
43 | self.sys_read(
44 | argument_0,
45 | UserPtr::new(self.thread.satp(), argument_1),
46 | argument_2,
47 | )
48 | .await
49 | }
50 | SYSCALL_WRITE => self.sys_write(
51 | argument_0,
52 | UserPtr::new(self.thread.satp(), argument_1),
53 | argument_2,
54 | ),
55 | SYSCALL_EXIT => self.sys_exit(argument_0),
56 | SYSCALL_SCHED_YIELD => self.sys_sched_yield(),
57 | SYSCALL_GET_TIME => self.sys_get_time(),
58 | SYSCALL_FORK => self.sys_fork(),
59 | SYSCALL_EXEC => self.sys_exec(UserPtr::new(self.thread.satp(), argument_0)),
60 | SYSCALL_WAITPID => {
61 | self.sys_waitpid(
62 | argument_0 as isize,
63 | UserPtr::new(self.thread.satp(), argument_1),
64 | )
65 | .await
66 | }
67 | _ => panic!("unsupported syscall {}", system_call_id),
68 | };
69 |
70 | if control_flow == ControlFlow::Continue || control_flow == ControlFlow::Yield {
71 | trap_context.set_user_register(10, exit_code as usize);
72 | }
73 | control_flow
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/kernel/src/mem/address/virtual_address.rs:
--------------------------------------------------------------------------------
1 | use core::ops::{Add, AddAssign, Sub};
2 |
3 | use crate::{
4 | constant::{PAGE_SIZE, PAGE_SIZE_BIT},
5 | mem::PageNumber,
6 | };
7 |
8 | const VIRTUAL_ADDRESS_SIZE: usize = 39;
9 |
10 | /// The `VirtualAddress` struct represents a 39-bit virtual address defined in the Sv39
11 | /// page table format.
12 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
13 | pub struct VirtualAddress {
14 | bits: usize,
15 | }
16 |
17 | impl VirtualAddress {
18 | /// Returns the [PageNumber] that represents the page that contains the virtual address.
19 | pub fn floor(&self) -> PageNumber {
20 | PageNumber::from(self.bits / PAGE_SIZE)
21 | }
22 |
23 | /// Returns the [PageNumber] that represents the page that contains the virtual address,
24 | /// rounding up to the next frame if the physical address is not aligned to a frame.
25 | pub fn ceil(&self) -> PageNumber {
26 | PageNumber::from((self.bits - 1 + PAGE_SIZE) / PAGE_SIZE)
27 | }
28 |
29 | /// Returns the byte offset of the virtual address within its containing page.
30 | pub fn page_offset(&self) -> usize {
31 | self.bits & (PAGE_SIZE - 1)
32 | }
33 |
34 | /// Returns `true` if the virtual address is aligned to a page.
35 | pub fn is_aligned(&self) -> bool {
36 | self.page_offset() == 0
37 | }
38 |
39 | /// Returns a raw pointer to the virtual address.
40 | pub fn as_ptr(&self) -> *const u8 {
41 | self.bits as *const u8
42 | }
43 |
44 | /// Returns a mutable raw pointer to the virtual address.
45 | pub fn as_ptr_mut(&self) -> *mut u8 {
46 | self.bits as *mut u8
47 | }
48 | }
49 |
50 | impl Add for VirtualAddress {
51 | type Output = Self;
52 |
53 | fn add(self, rhs: usize) -> Self {
54 | Self::from(self.bits + rhs)
55 | }
56 | }
57 |
58 | impl AddAssign for VirtualAddress {
59 | fn add_assign(&mut self, rhs: usize) {
60 | self.bits += rhs;
61 | }
62 | }
63 |
64 | impl Sub for VirtualAddress {
65 | type Output = Self;
66 |
67 | fn sub(self, rhs: usize) -> Self {
68 | Self::from(self.bits - rhs)
69 | }
70 | }
71 |
72 | impl From for VirtualAddress {
73 | fn from(value: usize) -> Self {
74 | assert!(
75 | (value >> VIRTUAL_ADDRESS_SIZE) == 0
76 | || (value >> VIRTUAL_ADDRESS_SIZE) == (1 << 25) - 1
77 | );
78 | Self { bits: value }
79 | }
80 | }
81 |
82 | impl From for usize {
83 | fn from(value: VirtualAddress) -> Self {
84 | value.bits
85 | }
86 | }
87 |
88 | impl From for VirtualAddress {
89 | fn from(value: PageNumber) -> Self {
90 | let mut bits = usize::from(value) << PAGE_SIZE_BIT;
91 |
92 | if (bits >> (VIRTUAL_ADDRESS_SIZE - 1)) == 1 {
93 | bits |= !((1 << VIRTUAL_ADDRESS_SIZE) - 1);
94 | }
95 | Self { bits }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/kernel/src/sync/event_bus.rs:
--------------------------------------------------------------------------------
1 | //! The `event_bus` module provides an [EventBus] that supports publish-subscribe-style
2 | //! communication between different tasks.
3 |
4 | use alloc::{boxed::Box, sync::Arc, vec::Vec};
5 | use core::{
6 | future::Future,
7 | pin::Pin,
8 | task::{Context, Poll},
9 | };
10 |
11 | use bitflags::bitflags;
12 |
13 | use crate::sync::Mutex;
14 |
15 | bitflags! {
16 | #[derive(Default, Copy, Clone)]
17 | /// The `Event` struct represents events that can be subscribed to on [EventBus].
18 | pub struct Event: u32 {
19 | /// Indicates that a child process has quit.
20 | const CHILD_PROCESS_QUIT = 1 << 0;
21 | }
22 | }
23 |
24 | type EventCallback = Box bool + Send>;
25 |
26 | /// The `EventBus` structsupports publish-subscribe-style communication between different tasks.
27 | #[derive(Default)]
28 | pub struct EventBus {
29 | event: Event,
30 | callback_list: Vec,
31 | }
32 |
33 | impl EventBus {
34 | /// Creates a new event bus.
35 | pub fn new() -> Arc> {
36 | Arc::new(Mutex::new(Self::default()))
37 | }
38 |
39 | /// Publishes an event on the event bus.
40 | pub fn push(&mut self, event: Event) {
41 | self.event.set(event, true);
42 | for callback in &self.callback_list {
43 | callback(event);
44 | }
45 | }
46 |
47 | /// Clears an event from the event bus.
48 | pub fn clear(&mut self, event: Event) {
49 | self.event.remove(event);
50 | }
51 |
52 | /// Subscribes to events on the event bus and executes the given callback function when an event
53 | /// is published.
54 | pub fn subscribe(&mut self, callback: EventCallback) {
55 | self.callback_list.push(callback);
56 | }
57 | }
58 |
59 | /// The `EventBusFuture` struct is a future that completes when a specified event is published on
60 | /// an [EventBus].
61 | struct EventBusFuture {
62 | event_bus: Arc>,
63 | subscribed_event: Event,
64 | }
65 |
66 | impl Future for EventBusFuture {
67 | type Output = Event;
68 |
69 | fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll {
70 | let mut event_bus = self.event_bus.lock();
71 | if event_bus.event.contains(self.subscribed_event) {
72 | return Poll::Ready(event_bus.event);
73 | }
74 |
75 | let subscribed_event = self.subscribed_event;
76 | let waker = context.waker().clone();
77 | event_bus.subscribe(Box::new(move |event| {
78 | if event.contains(subscribed_event) {
79 | waker.wake_by_ref();
80 | true
81 | } else {
82 | false
83 | }
84 | }));
85 | Poll::Pending
86 | }
87 | }
88 |
89 | /// Returns a future that completes when a specified event is published on an [EventBus].
90 | pub fn wait_for_event(
91 | event_bus: Arc>,
92 | subscribed_event: Event,
93 | ) -> impl Future