diff --git a/pixie-uefi/src/export_cov.rs b/pixie-uefi/src/export_cov.rs index 51da6f1e..4ef1dc28 100644 --- a/pixie-uefi/src/export_cov.rs +++ b/pixie-uefi/src/export_cov.rs @@ -1,8 +1,7 @@ use crate::os::disk::Disk; -use crate::os::UefiOS; -pub async fn export(os: UefiOS) { - let mut disk = Disk::open_with_size(os, 500 << 20); +pub async fn export() { + let mut disk = Disk::open_with_size(500 << 20); let mut coverage = vec![]; // SAFETY: we never create threads anyway. diff --git a/pixie-uefi/src/flash.rs b/pixie-uefi/src/flash.rs index a2de03ca..fae377d4 100644 --- a/pixie-uefi/src/flash.rs +++ b/pixie-uefi/src/flash.rs @@ -16,8 +16,9 @@ use uefi::proto::console::text::Color; use crate::os::boot_options::BootOptions; use crate::os::error::{Error, Result}; +use crate::os::executor::Executor; use crate::os::net::{TcpStream, UdpSocket, ETH_PACKET_SIZE}; -use crate::os::{memory, UefiOS}; +use crate::os::{disk, memory, UefiOS}; use crate::MIN_MEMORY; async fn fetch_image(stream: &TcpStream) -> Result { @@ -135,7 +136,7 @@ pub async fn flash(os: UefiOS, server_addr: SocketAddrV4) -> Result<()> { ); }); - let mut disk = os.open_first_disk(); + let mut disk = disk::Disk::largest(); for (hash, (size, csize, pos)) in mem::take(&mut chunks_info) { let mut found = None; @@ -179,7 +180,7 @@ pub async fn flash(os: UefiOS, server_addr: SocketAddrV4) -> Result<()> { ); while !chunks_info.is_empty() { let recv = Box::pin(socket.recv_from(&mut buf)); - let sleep = Box::pin(os.sleep_us(100_000)); + let sleep = Box::pin(Executor::sleep_us(100_000)); match select(recv, sleep).await { Either::Left(((buf, _addr), _)) => { stats.borrow_mut().pack_recv += 1; diff --git a/pixie-uefi/src/main.rs b/pixie-uefi/src/main.rs index d8d878f7..fe0e66a3 100644 --- a/pixie-uefi/src/main.rs +++ b/pixie-uefi/src/main.rs @@ -14,16 +14,16 @@ use uefi::{entry, Status}; use crate::flash::flash; use crate::os::error::{Error, Result}; +use crate::os::executor::Executor; use crate::os::net::{TcpStream, UdpSocket, ETH_PACKET_SIZE}; use crate::os::UefiOS; -use crate::reboot_to_os::reboot_to_os; use crate::register::register; use crate::store::store; mod flash; mod os; mod parse_disk; -mod reboot_to_os; +mod power_control; mod register; mod store; @@ -33,7 +33,7 @@ mod export_cov; // Memory to keep free for non-chunk storage. const MIN_MEMORY: u64 = 32 << 20; -async fn server_discover(os: UefiOS) -> Result { +async fn server_discover() -> Result { let socket = UdpSocket::bind(None).await?; let task1 = async { @@ -43,7 +43,7 @@ async fn server_discover(os: UefiOS) -> Result { socket .send_to(SocketAddrV4::new(Ipv4Addr::BROADCAST, ACTION_PORT), &msg) .await?; - os.sleep_us(1_000_000).await; + Executor::sleep_us(1_000_000).await; }) }; @@ -65,13 +65,13 @@ async fn server_discover(os: UefiOS) -> Result { Ok(server) } -async fn shutdown(os: UefiOS) -> ! { +async fn shutdown() -> ! { #[cfg(feature = "coverage")] - export_cov::export(os).await; + export_cov::export().await; log::info!("Shutting down..."); - os.sleep_us(1_000_000).await; - os.shutdown() + Executor::sleep_us(1_000_000).await; + power_control::shutdown() } async fn get_action(stream: &TcpStream) -> Result { @@ -97,18 +97,18 @@ async fn complete_action(stream: &TcpStream) -> Result<()> { } async fn run(os: UefiOS) -> Result<()> { - let server = server_discover(os).await?; + let server = server_discover().await?; let mut last_was_wait = false; - os.spawn("ping", async move { + Executor::spawn("ping", async move { let udp_socket = UdpSocket::bind(None).await.unwrap(); loop { udp_socket .send_to(SocketAddrV4::new(*server.ip(), PING_PORT), b"pixie") .await .unwrap(); - os.sleep_us(10_000_000).await; + Executor::sleep_us(10_000_000).await; } }); @@ -136,17 +136,17 @@ async fn run(os: UefiOS) -> Result<()> { last_was_wait = true; const WAIT_10MSECS: u64 = 50; for _ in 0..WAIT_10MSECS { - os.deep_sleep_us(10_000); - os.schedule().await; + Executor::deep_sleep_us(10_000); + Executor::sched_yield().await; } } else { last_was_wait = false; log::info!("Command: {command:?}"); match command { Action::Wait => unreachable!(), - Action::Boot => reboot_to_os(os).await, + Action::Boot => power_control::reboot_to_os().await, Action::Restart => {} - Action::Shutdown => shutdown(os).await, + Action::Shutdown => shutdown().await, Action::Register => register(os, server).await?, Action::Store => store(os, server).await?, Action::Flash => flash(os, server).await?, @@ -158,7 +158,7 @@ async fn run(os: UefiOS) -> Result<()> { tcp.force_close().await; if command == Action::Restart { - os.reset(); + power_control::reset() } } } diff --git a/pixie-uefi/src/os/disk.rs b/pixie-uefi/src/os/disk.rs index 9d8c2516..5aad1c80 100644 --- a/pixie-uefi/src/os/disk.rs +++ b/pixie-uefi/src/os/disk.rs @@ -7,8 +7,9 @@ use uefi::boot::{OpenProtocolParams, ScopedProtocol}; use uefi::proto::media::block::BlockIO; use uefi::Handle; +use crate::os::executor::Executor; + use super::error::Result; -use super::UefiOS; fn open_disk(handle: Handle) -> Result> { let image_handle = uefi::boot::image_handle(); @@ -35,13 +36,12 @@ pub struct DiskPartition { pub struct Disk { block: ScopedProtocol, - os: UefiOS, } // TODO(veluca): consider making parts of this actually async, i.e. by using DiskIo2/BlockIO2 if // available; support having more than one disk. impl Disk { - pub fn new(os: UefiOS) -> Disk { + pub fn largest() -> Disk { let (_size, handle) = uefi::boot::find_handles::() .unwrap() .into_iter() @@ -60,11 +60,11 @@ impl Disk { .expect("Disk not found"); let block = open_disk(handle).unwrap(); - Disk { block, os } + Disk { block } } #[cfg(feature = "coverage")] - pub fn open_with_size(os: UefiOS, base_size: i64) -> Disk { + pub fn open_with_size(base_size: i64) -> Disk { let (_size, handle) = uefi::boot::find_handles::() .unwrap() .into_iter() @@ -83,7 +83,7 @@ impl Disk { .expect("Disk not found"); let block = open_disk(handle).unwrap(); - Disk { block, os } + Disk { block } } pub fn size(&self) -> u64 { @@ -124,7 +124,7 @@ impl Disk { } pub async fn read(&self, offset: u64, buf: &mut [u8]) -> Result<()> { - self.os.schedule().await; + Executor::sched_yield().await; self.read_sync(offset, buf) } @@ -158,7 +158,7 @@ impl Disk { } pub async fn write(&mut self, offset: u64, buf: &[u8]) -> Result<()> { - self.os.schedule().await; + Executor::sched_yield().await; self.write_sync(offset, buf) } diff --git a/pixie-uefi/src/os/executor.rs b/pixie-uefi/src/os/executor.rs index 6b4a5afc..e5fc0ef8 100644 --- a/pixie-uefi/src/os/executor.rs +++ b/pixie-uefi/src/os/executor.rs @@ -2,25 +2,26 @@ use alloc::boxed::Box; use alloc::collections::VecDeque; use alloc::sync::Arc; use alloc::task::Wake; -use core::cell::RefCell; -use core::future::Future; +use alloc::vec::Vec; +use core::future::{poll_fn, Future}; use core::pin::Pin; -use core::task::{Context, Waker}; +use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use core::task::{Context, Poll, Waker}; +use spin::Mutex; +use uefi::boot::{EventType, TimerTrigger, Tpl}; -use super::sync::SyncRefCell; use crate::os::timer::Timer; -pub(super) type BoxFuture = Pin + 'static>>; +struct BoxFuture(Pin + 'static>>); -struct TaskInner { - pub in_queue: bool, - pub future: Option, - pub micros: i64, -} +// SAFETY: there are no threads. +unsafe impl Send for BoxFuture {} -pub(super) struct Task { - pub name: &'static str, - inner: RefCell, +struct Task { + name: &'static str, + in_queue: AtomicBool, + future: Mutex, + micros: AtomicU64, } impl Task { @@ -30,65 +31,111 @@ impl Task { { Arc::new(Task { name, - inner: RefCell::new(TaskInner { - in_queue: false, - future: Some(Box::pin(future)), - micros: 0, - }), + future: Mutex::new(BoxFuture(Box::pin(future))), + micros: AtomicU64::new(0), + in_queue: AtomicBool::new(false), }) } - - pub(super) fn micros(&self) -> i64 { - self.inner.borrow().micros - } } -// SAFETY: we never create threads anyway. -unsafe impl Send for Task {} -unsafe impl Sync for Task {} - impl Wake for Task { fn wake(self: Arc) { - if !self.inner.borrow().in_queue { - self.inner.borrow_mut().in_queue = true; - EXECUTOR.borrow_mut().ready_tasks.push_back(self); + if !self.in_queue.swap(true, Ordering::Relaxed) { + EXECUTOR.lock().ready_tasks.push_back(self); } } } -static EXECUTOR: SyncRefCell = SyncRefCell::new(Executor { +static EXECUTOR: Mutex = Mutex::new(Executor { ready_tasks: VecDeque::new(), + tasks: vec![], }); pub struct Executor { // TODO(veluca): scheduling. ready_tasks: VecDeque>, + tasks: Vec>, } impl Executor { pub fn run() -> ! { loop { let task = EXECUTOR - .borrow_mut() + .lock() .ready_tasks .pop_front() .expect("Executor should never run out of ready tasks"); - task.inner.borrow_mut().in_queue = false; + task.in_queue.store(false, Ordering::Relaxed); let waker = Waker::from(task.clone()); let mut context = Context::from_waker(&waker); - let mut fut = task.inner.borrow_mut().future.take().unwrap(); + let mut fut = task.future.try_lock().unwrap(); let begin = Timer::micros(); - let status = fut.as_mut().poll(&mut context); + let _ = fut.0.as_mut().poll(&mut context); let end = Timer::micros(); - task.inner.borrow_mut().micros += end - begin; - if status.is_pending() { - task.inner.borrow_mut().future = Some(fut); - } + task.micros + .fetch_add((end - begin) as u64, Ordering::Relaxed); } } - pub(super) fn spawn(task: Arc) { - EXECUTOR.borrow_mut().ready_tasks.push_back(task); + /// Interrupt task execution. + /// This is useful to yield the CPU to other tasks. + pub fn sched_yield() -> impl Future { + let mut ready = false; + poll_fn(move |cx| { + if ready { + Poll::Ready(()) + } else { + ready = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + } + + pub fn sleep_us(us: u64) -> impl Future { + let tgt = Timer::micros() as u64 + us; + poll_fn(move |cx| { + let now = Timer::micros() as u64; + if now >= tgt { + Poll::Ready(()) + } else { + // TODO(veluca): actually suspend the task. + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + } + + /// **WARNING**: this function halts all tasks + pub fn deep_sleep_us(us: u64) { + // SAFETY: we are not using a callback + let e = + unsafe { uefi::boot::create_event(EventType::TIMER, Tpl::NOTIFY, None, None).unwrap() }; + uefi::boot::set_timer(&e, TimerTrigger::Relative(10 * us)).unwrap(); + uefi::boot::wait_for_event(&mut [e]).unwrap(); + } + + /// Spawn a new task. + pub fn spawn(name: &'static str, f: Fut) + where + Fut: Future + 'static, + { + let task = Task::new(name, f); + let mut executor = EXECUTOR.lock(); + executor.tasks.push(task.clone()); + executor.ready_tasks.push_back(task); + } + + pub fn top_tasks(n: usize) -> Vec<(u64, &'static str)> { + let mut tasks: Vec<_> = EXECUTOR + .lock() + .tasks + .iter() + .map(|x| (x.micros.load(Ordering::Relaxed), x.name)) + .collect(); + tasks.sort_by_key(|t| -(t.0 as i64)); + tasks.truncate(n); + tasks } } diff --git a/pixie-uefi/src/os/mod.rs b/pixie-uefi/src/os/mod.rs index 8dd2cd18..c9df0964 100644 --- a/pixie-uefi/src/os/mod.rs +++ b/pixie-uefi/src/os/mod.rs @@ -1,7 +1,6 @@ use alloc::boxed::Box; use alloc::collections::VecDeque; use alloc::string::{String, ToString}; -use alloc::sync::Arc; use alloc::vec::Vec; use core::cell::RefMut; use core::ffi::c_void; @@ -11,28 +10,26 @@ use core::ptr::NonNull; use core::task::Poll; use pixie_shared::util::BytesFmt; -use uefi::boot::{EventType, ScopedProtocol, TimerTrigger, Tpl}; +use uefi::boot::{EventType, ScopedProtocol, Tpl}; use uefi::proto::console::serial::Serial; use uefi::proto::console::text::{Color, Input, Key, Output}; use uefi::{Event, Status}; -use self::disk::Disk; use self::error::Result; -use self::executor::{Executor, Task}; +use self::executor::Executor; use self::sync::SyncRefCell; use self::timer::Timer; pub mod boot_options; pub mod disk; pub mod error; -mod executor; +pub mod executor; pub mod memory; pub mod net; -mod sync; +pub mod sync; mod timer; struct UefiOSImpl { - tasks: Vec>, input: ScopedProtocol, vga: ScopedProtocol, serial: Option>, @@ -150,7 +147,6 @@ impl UefiOS { vga.clear().unwrap(); *OS.borrow_mut() = Some(UefiOSImpl { - tasks: Vec::new(), input, vga, serial, @@ -167,7 +163,7 @@ impl UefiOS { net::init(); - os.spawn("init", async move { + Executor::spawn("init", async move { loop { if let Err(err) = f(os).await { log::error!("Error: {err:?}"); @@ -175,7 +171,7 @@ impl UefiOS { } }); - os.spawn("[watchdog]", async move { + Executor::spawn("[watchdog]", async move { loop { let err = uefi::boot::set_watchdog_timer(300, 0x10000, None); @@ -187,14 +183,14 @@ impl UefiOS { break; } - os.sleep_us(30_000_000).await; + Executor::sleep_us(30_000_000).await; } }); - os.spawn("[draw_ui]", async move { + Executor::spawn("[draw_ui]", async move { loop { os.draw_ui(); - os.sleep_us(1_000_000).await; + Executor::sleep_us(1_000_000).await; } }); @@ -205,52 +201,6 @@ impl UefiOS { RefMut::map(OS.borrow_mut(), |f| f.as_mut().unwrap()) } - fn tasks(&self) -> RefMut<'static, Vec>> { - RefMut::map(self.borrow_mut(), |f| &mut f.tasks) - } - - /// Interrupt task execution. - /// This is useful to yield the CPU to other tasks. - pub fn schedule(&self) -> impl Future { - let mut ready = false; - poll_fn(move |cx| { - if ready { - Poll::Ready(()) - } else { - ready = true; - cx.waker().wake_by_ref(); - Poll::Pending - } - }) - } - - pub fn sleep_us(self, us: u64) -> impl Future { - let tgt = Timer::micros() as u64 + us; - poll_fn(move |cx| { - let now = Timer::micros() as u64; - if now >= tgt { - Poll::Ready(()) - } else { - // TODO(veluca): actually suspend the task. - cx.waker().wake_by_ref(); - Poll::Pending - } - }) - } - - /// **WARNING**: this function halts all tasks - pub fn deep_sleep_us(&self, us: u64) { - // SAFETY: we are not using a callback - let e = - unsafe { uefi::boot::create_event(EventType::TIMER, Tpl::NOTIFY, None, None).unwrap() }; - uefi::boot::set_timer(&e, TimerTrigger::Relative(10 * us)).unwrap(); - uefi::boot::wait_for_event(&mut [e]).unwrap(); - } - - pub fn open_first_disk(&self) -> Disk { - Disk::new(*self) - } - pub fn read_key(&self) -> impl Future> + '_ { poll_fn(move |cx| { let key = self.borrow_mut().input.read_key(); @@ -298,13 +248,11 @@ impl UefiOS { Color::Black, ); - os.tasks.sort_by_key(|t| -t.micros()); - let tasks: Vec<_> = os.tasks.iter().take(7).cloned().collect(); - for task in tasks { - os.write_with_color(task.name, Color::White, Color::Black); + for (micros, name) in Executor::top_tasks(7) { + os.write_with_color(name, Color::White, Color::Black); os.maybe_advance_to_col(cols / 4); os.write_with_color( - &format!("{:7.3}s\n", task.micros() as f64 * 0.000_001), + &format!("{:7.3}s\n", micros as f64 * 0.000_001), Color::White, Color::Black, ); @@ -375,24 +323,6 @@ impl UefiOS { } self.force_ui_redraw(); } - - /// Spawn a new task. - pub fn spawn(&self, name: &'static str, f: Fut) - where - Fut: Future + 'static, - { - let task = executor::Task::new(name, f); - self.tasks().push(task.clone()); - Executor::spawn(task); - } - - pub fn reset(&self) -> ! { - uefi::runtime::reset(uefi::runtime::ResetType::WARM, Status::SUCCESS, None) - } - - pub fn shutdown(&self) -> ! { - uefi::runtime::reset(uefi::runtime::ResetType::SHUTDOWN, Status::SUCCESS, None) - } } impl log::Log for UefiOS { diff --git a/pixie-uefi/src/os/net/mod.rs b/pixie-uefi/src/os/net/mod.rs index 9f3035dd..a790414e 100644 --- a/pixie-uefi/src/os/net/mod.rs +++ b/pixie-uefi/src/os/net/mod.rs @@ -17,12 +17,12 @@ use uefi::Handle; use super::timer::Timer; use crate::os::boot_options::BootOptions; +use crate::os::executor::Executor; use crate::os::net::interface::SnpDevice; use crate::os::net::speed::{RX_SPEED, TX_SPEED}; pub use crate::os::net::tcp::TcpStream; pub use crate::os::net::udp::UdpSocket; use crate::os::timer::rdtsc; -use crate::os::UefiOS; mod interface; mod speed; @@ -78,9 +78,6 @@ fn handle_on_device(device: &DevicePath) -> Option { } pub(super) fn init() { - // TODO(veluca): remove the use of `os` once we move spawning to the scheduler. - let os = UefiOS { cant_build: () }; - let curopt = BootOptions::get(BootOptions::current()); let (descr, device) = BootOptions::boot_entry_info(&curopt[..]); log::info!( @@ -123,7 +120,7 @@ pub(super) fn init() { dhcp_socket_handle, }); - os.spawn( + Executor::spawn( "[net_poll]", poll_fn(move |cx| { poll(); @@ -133,7 +130,7 @@ pub(super) fn init() { }), ); - speed::spawn_update_network_speed_task(os); + speed::spawn_update_network_speed_task(); } pub fn wait_for_ip() -> impl Future { diff --git a/pixie-uefi/src/os/net/speed.rs b/pixie-uefi/src/os/net/speed.rs index 79555288..30765ad2 100644 --- a/pixie-uefi/src/os/net/speed.rs +++ b/pixie-uefi/src/os/net/speed.rs @@ -1,7 +1,7 @@ use core::sync::atomic::{AtomicU64, Ordering}; +use crate::os::executor::Executor; use crate::os::timer::Timer; -use crate::os::UefiOS; pub struct NetSpeed { total: AtomicU64, @@ -44,12 +44,12 @@ impl NetSpeed { pub(super) static TX_SPEED: NetSpeed = NetSpeed::new(); pub(super) static RX_SPEED: NetSpeed = NetSpeed::new(); -pub(super) fn spawn_update_network_speed_task(os: UefiOS) { - os.spawn("[net_speed]", async move { +pub(super) fn spawn_update_network_speed_task() { + Executor::spawn("[net_speed]", async move { loop { TX_SPEED.update_speed(); RX_SPEED.update_speed(); - os.sleep_us(1_000_000).await; + Executor::sleep_us(1_000_000).await; } }); } diff --git a/pixie-uefi/src/reboot_to_os.rs b/pixie-uefi/src/power_control.rs similarity index 52% rename from pixie-uefi/src/reboot_to_os.rs rename to pixie-uefi/src/power_control.rs index 4b3e8ea1..ca88c3a8 100644 --- a/pixie-uefi/src/reboot_to_os.rs +++ b/pixie-uefi/src/power_control.rs @@ -1,7 +1,9 @@ +use uefi::Status; + use crate::os::boot_options::BootOptions; -use crate::os::UefiOS; +use crate::os::executor::Executor; -pub async fn reboot_to_os(os: UefiOS) -> ! { +pub async fn reboot_to_os() -> ! { let next = BootOptions::reboot_target(); if let Some(next) = next { // Reboot to next boot option. @@ -12,7 +14,15 @@ pub async fn reboot_to_os(os: UefiOS) -> ! { BootOptions::current() ); log::warn!("{:?}", BootOptions::order()); - os.sleep_us(100_000_000).await; + Executor::sleep_us(100_000_000).await; } - os.reset(); + reset(); +} + +pub fn reset() -> ! { + uefi::runtime::reset(uefi::runtime::ResetType::WARM, Status::SUCCESS, None) +} + +pub fn shutdown() -> ! { + uefi::runtime::reset(uefi::runtime::ResetType::SHUTDOWN, Status::SUCCESS, None) } diff --git a/pixie-uefi/src/store.rs b/pixie-uefi/src/store.rs index 432207e1..2d17e8f5 100644 --- a/pixie-uefi/src/store.rs +++ b/pixie-uefi/src/store.rs @@ -12,7 +12,7 @@ use uefi::proto::console::text::Color; use crate::os::boot_options::BootOptions; use crate::os::error::{Error, Result}; use crate::os::net::{TcpStream, UdpSocket}; -use crate::os::{memory, UefiOS}; +use crate::os::{disk, memory, UefiOS}; use crate::{parse_disk, MIN_MEMORY}; #[derive(Debug)] @@ -70,7 +70,7 @@ pub async fn store(os: UefiOS, server_address: SocketAddrV4) -> Result<()> { let boid = BootOptions::reboot_target().expect("Could not find reboot target"); let bo_command = BootOptions::get(boid); - let mut disk = os.open_first_disk(); + let mut disk = disk::Disk::largest(); let chunks = parse_disk::parse_disk(&mut disk).await?; info!( "Total size of chunks: {}",