Use crossbeam-queue and AtomicWaker for async keypress handling

This commit is contained in:
Philipp Oppermann
2020-02-28 17:46:29 +01:00
parent 6329274f02
commit 786a7a6922
8 changed files with 163 additions and 22 deletions

46
Cargo.lock generated
View File

@@ -32,7 +32,9 @@ name = "blog_os"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bootloader", "bootloader",
"conquer-once",
"crossbeam-queue", "crossbeam-queue",
"futures-util",
"lazy_static", "lazy_static",
"linked_list_allocator", "linked_list_allocator",
"pc-keyboard", "pc-keyboard",
@@ -64,6 +66,21 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "conquer-once"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f7644600a548ecad74e4a918392af1798f7dd045be610be3203b9e129b4f98f"
dependencies = [
"conquer-util",
]
[[package]]
name = "conquer-util"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a"
[[package]] [[package]]
name = "cpuio" name = "cpuio"
version = "0.2.0" version = "0.2.0"
@@ -90,6 +107,29 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "futures-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
[[package]]
name = "futures-task"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
[[package]]
name = "futures-util"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
dependencies = [
"futures-core",
"futures-task",
"pin-utils",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@@ -129,6 +169,12 @@ dependencies = [
"cpuio", "cpuio",
] ]
[[package]]
name = "pin-utils"
version = "0.1.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"

View File

@@ -31,6 +31,15 @@ version = "0.2.1"
default-features = false default-features = false
features = ["alloc"] features = ["alloc"]
[dependencies.futures-util]
version = "0.3.4"
default-features = false
features = ["alloc", "async-await"]
[dependencies.conquer-once]
version = "0.2.0"
default-features = false
[package.metadata.bootimage] [package.metadata.bootimage]
test-args = [ test-args = [
"-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio",

48
src/driver/keyboard.rs Normal file
View File

@@ -0,0 +1,48 @@
use crate::{interrupts, print};
use core::future::Future;
use core::{
pin::Pin,
task::{Context, Poll},
};
use pc_keyboard::{layouts, DecodedKey, Keyboard, ScancodeSet1};
fn next_scancode() -> impl Future<Output = u8> {
NextScancode
}
struct NextScancode;
impl Future for NextScancode {
type Output = u8;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<u8> {
let scancodes = interrupts::SCANCODE_QUEUE
.try_get()
.expect("scancode queue not initialized");
// fast path
if let Ok(scancode) = scancodes.pop() {
return Poll::Ready(scancode);
}
interrupts::KEYBOARD_INTERRUPT_WAKER.register(&cx.waker());
match scancodes.pop() {
Ok(scancode) => Poll::Ready(scancode),
Err(crossbeam_queue::PopError) => Poll::Pending,
}
}
}
pub async fn print_keypresses() {
let mut keyboard = Keyboard::new(layouts::Us104Key, ScancodeSet1);
loop {
if let Ok(Some(key_event)) = keyboard.add_byte(next_scancode().await) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => print!("{}", character),
DecodedKey::RawKey(key) => print!("{:?}", key),
}
}
}
}
}

1
src/driver/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod keyboard;

View File

@@ -1,4 +1,8 @@
use crate::{gdt, hlt_loop, print, println}; use crate::{gdt, hlt_loop, print, println};
use conquer_once::spin::OnceCell;
use core::task::Waker;
use crossbeam_queue::ArrayQueue;
use futures_util::task::AtomicWaker;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use pic8259_simple::ChainedPics; use pic8259_simple::ChainedPics;
use spin; use spin;
@@ -47,6 +51,23 @@ pub fn init_idt() {
IDT.load(); IDT.load();
} }
pub fn init_queues() {
INTERRUPT_WAKEUPS
.try_init_once(|| ArrayQueue::new(10))
.expect("failed to init interrupt wakeup queue");
SCANCODE_QUEUE
.try_init_once(|| ArrayQueue::new(10))
.expect("failed to init scancode queue");
}
static INTERRUPT_WAKEUPS: OnceCell<ArrayQueue<Waker>> = OnceCell::uninit();
pub(crate) fn interrupt_wakeups() -> &'static ArrayQueue<Waker> {
INTERRUPT_WAKEUPS
.try_get()
.expect("interrupt wakeup queue not initialized")
}
extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) { extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
} }
@@ -79,26 +100,24 @@ extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptSt
} }
} }
pub(crate) static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
pub(crate) static KEYBOARD_INTERRUPT_WAKER: AtomicWaker = AtomicWaker::new();
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) { extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) {
use pc_keyboard::{layouts, DecodedKey, Keyboard, ScancodeSet1};
use spin::Mutex;
use x86_64::instructions::port::Port; use x86_64::instructions::port::Port;
lazy_static! {
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1));
}
let mut keyboard = KEYBOARD.lock();
let mut port = Port::new(0x60); let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() }; 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) { let scancode_queue = SCANCODE_QUEUE
match key { .try_get()
DecodedKey::Unicode(character) => print!("{}", character), .expect("scancode queue not initialized");
DecodedKey::RawKey(key) => print!("{:?}", key), if let Err(_) = scancode_queue.push(scancode) {
} println!("WARNING: dropping keyboard input");
}
if let Some(waker) = KEYBOARD_INTERRUPT_WAKER.take() {
if let Err(_) = interrupt_wakeups().push(waker) {
println!("WARNING: dropping interrupt wakeup");
} }
} }

View File

@@ -7,6 +7,8 @@
#![feature(alloc_layout_extra)] #![feature(alloc_layout_extra)]
#![feature(wake_trait)] #![feature(wake_trait)]
#![feature(const_in_array_repeat_expressions)] #![feature(const_in_array_repeat_expressions)]
#![feature(type_alias_impl_trait)]
#![feature(asm)]
#![test_runner(crate::test_runner)] #![test_runner(crate::test_runner)]
#![reexport_test_harness_main = "test_main"] #![reexport_test_harness_main = "test_main"]
@@ -15,6 +17,7 @@ extern crate alloc;
use core::panic::PanicInfo; use core::panic::PanicInfo;
pub mod allocator; pub mod allocator;
pub mod driver;
pub mod gdt; pub mod gdt;
pub mod interrupts; pub mod interrupts;
pub mod memory; pub mod memory;
@@ -26,7 +29,6 @@ pub fn init() {
gdt::init(); gdt::init();
interrupts::init_idt(); interrupts::init_idt();
unsafe { interrupts::PICS.lock().initialize() }; unsafe { interrupts::PICS.lock().initialize() };
x86_64::instructions::interrupts::enable();
} }
pub fn test_runner(tests: &[&dyn Fn()]) { pub fn test_runner(tests: &[&dyn Fn()]) {

View File

@@ -26,6 +26,8 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) }; let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) };
allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed");
blog_os::interrupts::init_queues();
x86_64::instructions::interrupts::enable();
// allocate a number on the heap // allocate a number on the heap
let heap_value = Box::new(41); let heap_value = Box::new(41);
@@ -66,6 +68,8 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
println!("It did not crash!"); println!("It did not crash!");
}); });
spawner.spawn(blog_os::driver::keyboard::print_keypresses());
executor.run(); executor.run();
} }

View File

@@ -1,3 +1,4 @@
use crate::{interrupts, println};
use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, task::Wake}; use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, task::Wake};
use core::{ use core::{
future::Future, future::Future,
@@ -34,13 +35,19 @@ impl Executor {
pub fn run(&mut self) -> ! { pub fn run(&mut self) -> ! {
loop { loop {
// perform wakeups caused by interrupts
// the interrupt handlers can't do it themselves since wakers might execute
// arbitrary code, e.g. allocate
while let Ok(waker) = interrupts::interrupt_wakeups().pop() {
waker.wake();
}
// wakeup waiting tasks // wakeup waiting tasks
while let Ok(task_id) = self.wake_queue.pop() { while let Ok(task_id) = self.wake_queue.pop() {
let task = self if let Some(task) = self.pending_tasks.remove(&task_id) {
.pending_tasks self.task_queue.push(task);
.remove(&task_id) } else {
.expect("woken task not found in pending_tasks"); println!("WARNING: woken task not found in pending_tasks");
self.task_queue.push(task); }
} }
// run ready tasks // run ready tasks
while let Ok(mut task) = self.task_queue.pop() { while let Ok(mut task) = self.task_queue.pop() {
@@ -57,7 +64,12 @@ impl Executor {
} }
// wait for next interrupt if there is nothing left to do // wait for next interrupt if there is nothing left to do
if self.wake_queue.is_empty() { if self.wake_queue.is_empty() {
crate::hlt_loop(); unsafe { asm!("cli") };
if self.wake_queue.is_empty() {
unsafe { asm!("sti; hlt") };
} else {
unsafe { asm!("sti") };
}
} }
} }
} }