diff --git a/Cargo.lock b/Cargo.lock index b6697ea..6c8243f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "bit_field" version = "0.9.0" @@ -20,6 +26,73 @@ version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3c1ceed1cd9e61c7998100cc18c13d413aa40d018992b871ab8e7435ce6372" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "conquer-once" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96eb12fb69466716fbae9009d389e6a30830ae8975e170eff2d2cff579f9efa3" +dependencies = [ + "conquer-util", +] + +[[package]] +name = "conquer-util" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a" + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", +] + +[[package]] +name = "futures-core" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" + +[[package]] +name = "futures-task" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" + +[[package]] +name = "futures-util" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +dependencies = [ + "autocfg", + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -47,6 +120,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "pc-keyboard" version = "0.5.1" @@ -62,11 +141,26 @@ dependencies = [ "x86_64", ] +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "rust_os" version = "0.1.0" dependencies = [ "bootloader", + "conquer-once", + "crossbeam-queue", + "futures-util", "lazy_static", "linked_list_allocator", "pc-keyboard", diff --git a/Cargo.toml b/Cargo.toml index b2ed612..9e25038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,20 @@ pic8259 = "0.10.1" pc-keyboard = "0.5.0" linked_list_allocator = "0.9.0" +[dependencies.crossbeam-queue] +version = "0.2.1" +default-features = false +features = ["alloc"] + +[dependencies.futures-util] +version = "0.3.4" +default-features = false +features = ["alloc"] + +[dependencies.conquer-once] +version = "0.2.0" +default-features = false + [dependencies.lazy_static] version = "1.0" features = ["spin_no_std"] diff --git a/src/interrupts.rs b/src/interrupts.rs index ae33856..9094ce1 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -66,31 +66,13 @@ extern "x86-interrupt" fn breakpoint_handler( } extern "x86-interrupt" fn keyboard_interrupt_handler( - _stack_frame: InterruptStackFrame) -{ - use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; - use spin::Mutex; + _stack_frame: InterruptStackFrame +) { use x86_64::instructions::port::Port; - lazy_static! { - static ref KEYBOARD: Mutex> = - Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1, - HandleControl::Ignore) - ); - } - - let mut keyboard = KEYBOARD.lock(); let mut port = Port::new(0x60); - let scancode: u8 = unsafe { port.read() }; - if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { - if let Some(key) = keyboard.process_keyevent(key_event) { - match key { - DecodedKey::Unicode(character) => print!("{}", character), - DecodedKey::RawKey(key) => print!("{:?}", key), - } - } - } + crate::task::keyboard::add_scancode(scancode); unsafe { PICS.lock() diff --git a/src/main.rs b/src/main.rs index 8f207f4..d607a89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,18 +6,17 @@ extern crate alloc; -use alloc::{boxed::Box, vec, vec::Vec, rc::Rc}; -use core::panic::PanicInfo; use rust_os::println; -use rust_os::task::{Task, simple_executor::SimpleExecutor}; -use bootloader::{BootInfo, entry_point}; -use x86_64::VirtAddr; +use rust_os::task::{executor::Executor, keyboard, Task}; +use bootloader::{entry_point, BootInfo}; +use core::panic::PanicInfo; entry_point!(kernel_main); fn kernel_main(boot_info: &'static BootInfo) -> ! { use rust_os::allocator; use rust_os::memory::{self, BootInfoFrameAllocator}; + use x86_64::VirtAddr; println!("Hello World{}", "!"); rust_os::init(); @@ -31,8 +30,9 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { allocator::init_heap(&mut mapper, &mut frame_allocator) .expect("heap initialization failed"); - let mut executor = SimpleExecutor::new(); + let mut executor = Executor::new(); executor.spawn(Task::new(example_task())); + executor.spawn(Task::new(keyboard::print_keypresses())); executor.run(); #[cfg(test)] @@ -42,15 +42,6 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { rust_os::hlt_loop(); } -async fn async_number() -> u32 { - 42 -} - -async fn example_task() { - let number = async_number().await; - println!("async number: {}", number); -} - #[cfg(not(test))] #[panic_handler] fn panic(info: &PanicInfo) -> ! { @@ -62,4 +53,13 @@ fn panic(info: &PanicInfo) -> ! { #[panic_handler] fn panic(info: &PanicInfo) -> ! { rust_os::test_panic_handler(info) +} + +async fn async_number() -> u32 { + 42 +} + +async fn example_task() { + let number = async_number().await; + println!("async number: {}", number); } \ No newline at end of file diff --git a/src/task/executor.rs b/src/task/executor.rs new file mode 100644 index 0000000..8396794 --- /dev/null +++ b/src/task/executor.rs @@ -0,0 +1,104 @@ +use super::{Task, TaskId}; +use alloc::{collections::BTreeMap, sync::Arc}; +use core::task::Waker; +use crossbeam_queue::ArrayQueue; +use core::task::{Context, Poll}; +use alloc::task::Wake; + +pub struct Executor { + tasks: BTreeMap, + task_queue: Arc>, + waker_cache: BTreeMap, +} + +struct TaskWaker { + task_id: TaskId, + task_queue: Arc>, +} + +impl Executor { + pub fn new() -> Self { + Executor { + tasks: BTreeMap::new(), + task_queue: Arc::new(ArrayQueue::new(100)), + waker_cache: BTreeMap::new(), + } + } + + pub fn spawn(&mut self, task: Task) { + let task_id = task.id; + if self.tasks.insert(task.id, task).is_some() { + panic!("task with same ID already in tasks"); + } + self.task_queue.push(task_id).expect("queue full"); + } + + fn run_ready_tasks(&mut self) { + // destructure `self` to avoid borrow checker errors + let Self { + tasks, + task_queue, + waker_cache, + } = self; + + while let Ok(task_id) = task_queue.pop() { + let task = match tasks.get_mut(&task_id) { + Some(task) => task, + None => continue, // task no longer exists + }; + let waker = waker_cache + .entry(task_id) + .or_insert_with(|| TaskWaker::new(task_id, task_queue.clone())); + let mut context = Context::from_waker(waker); + match task.poll(&mut context) { + Poll::Ready(()) => { + // task done -> remove it and its cached waker + tasks.remove(&task_id); + waker_cache.remove(&task_id); + } + Poll::Pending => {} + } + } + } + + pub fn run(&mut self) -> ! { + loop { + self.run_ready_tasks(); + self.sleep_if_idle(); + } + } + + fn sleep_if_idle(&self) { + use x86_64::instructions::interrupts::{self, enable_and_hlt}; + + interrupts::disable(); + if self.task_queue.is_empty() { + enable_and_hlt(); + } else { + interrupts::enable(); + } + } +} + +impl TaskWaker { + fn wake_task(&self) { + self.task_queue.push(self.task_id).expect("task_queue full"); + } + + fn new(task_id: TaskId, task_queue: Arc>) -> Waker { + Waker::from(Arc::new(TaskWaker { + task_id, + task_queue, + })) + } +} + +impl Wake for TaskWaker { + fn wake(self: Arc) { + self.wake_task(); + } + + fn wake_by_ref(self: &Arc) { + self.wake_task(); + } +} \ No newline at end of file diff --git a/src/task/keyboard.rs b/src/task/keyboard.rs new file mode 100644 index 0000000..2219125 --- /dev/null +++ b/src/task/keyboard.rs @@ -0,0 +1,77 @@ +use conquer_once::spin::OnceCell; +use crossbeam_queue::ArrayQueue; +use crate::println; +use core::{pin::Pin, task::{Poll, Context}}; +use futures_util::stream::Stream; +use futures_util::task::AtomicWaker; +use futures_util::stream::StreamExt; +use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; +use crate::print; + +static WAKER: AtomicWaker = AtomicWaker::new(); +static SCANCODE_QUEUE: OnceCell> = OnceCell::uninit(); + +pub async fn print_keypresses() { + let mut scancodes = ScancodeStream::new(); + let mut keyboard = Keyboard::new(layouts::Us104Key, ScancodeSet1, + HandleControl::Ignore); + + while let Some(scancode) = scancodes.next().await { + if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { + if let Some(key) = keyboard.process_keyevent(key_event) { + match key { + DecodedKey::Unicode(character) => print!("{}", character), + DecodedKey::RawKey(key) => print!("{:?}", key), + } + } + } + } +} + +pub(crate) fn add_scancode(scancode: u8) { + if let Ok(queue) = SCANCODE_QUEUE.try_get() { + if let Err(_) = queue.push(scancode) { + println!("WARNING: scancode queue full; dropping keyboard input"); + } else { + WAKER.wake(); // new + } + } else { + println!("WARNING: scancode queue uninitialized"); + } +} + +pub struct ScancodeStream { + _private: (), +} + +impl ScancodeStream { + pub fn new() -> Self { + SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100)) + .expect("ScancodeStream::new should only be called once"); + ScancodeStream { _private: () } + } +} + +impl Stream for ScancodeStream { + type Item = u8; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let queue = SCANCODE_QUEUE + .try_get() + .expect("scancode queue not initialized"); + + // fast path + if let Ok(scancode) = queue.pop() { + return Poll::Ready(Some(scancode)); + } + + WAKER.register(&cx.waker()); + match queue.pop() { + Ok(scancode) => { + WAKER.take(); + Poll::Ready(Some(scancode)) + } + Err(crossbeam_queue::PopError) => Poll::Pending, + } + } +} \ No newline at end of file diff --git a/src/task/mod.rs b/src/task/mod.rs index 5844ae9..09658eb 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,16 +1,21 @@ use core::{future::Future, pin::Pin}; use alloc::boxed::Box; use core::task::{Context, Poll}; +use core::sync::atomic::{AtomicU64, Ordering}; pub mod simple_executor; +pub mod keyboard; +pub mod executor; pub struct Task { + id: TaskId, future: Pin>>, } impl Task { pub fn new(future: impl Future + 'static) -> Task { Task { + id: TaskId::new(), future: Box::pin(future), } } @@ -18,4 +23,14 @@ impl Task { fn poll(&mut self, context: &mut Context) -> Poll<()> { self.future.as_mut().poll(context) } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct TaskId(u64); + +impl TaskId { + fn new() -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + TaskId(NEXT_ID.fetch_add(1, Ordering::Relaxed)) + } } \ No newline at end of file