Skip to content

Commit bdbdb4a

Browse files
committed
Implement an actual scheduler.
As disk operations are still blocking, it doesn't quite work as well as one would want, but it's a start.
1 parent bf05565 commit bdbdb4a

11 files changed

Lines changed: 239 additions & 85 deletions

File tree

pixie-uefi/src/flash.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use core::cell::{Cell, RefCell};
55
use core::fmt::Write;
66
use core::mem;
77
use core::net::SocketAddrV4;
8+
use core::time::Duration;
89

910
use futures::future::{select, Either};
1011
use log::info;
@@ -120,7 +121,7 @@ pub async fn flash(server_addr: SocketAddrV4) -> Result<()> {
120121
let draw_task = async {
121122
let mut last_stats = stats.borrow().clone();
122123
while !done.get() {
123-
Executor::sleep_us(100_000).await;
124+
Executor::sleep(Duration::from_millis(100)).await;
124125
let stats = stats.borrow().clone();
125126
if stats == last_stats {
126127
continue;
@@ -176,7 +177,7 @@ pub async fn flash(server_addr: SocketAddrV4) -> Result<()> {
176177
);
177178
while !chunks_info.is_empty() {
178179
let recv = Box::pin(socket.recv_from(&mut buf));
179-
let sleep = Box::pin(Executor::sleep_us(100_000));
180+
let sleep = Box::pin(Executor::sleep(Duration::from_millis(100)));
180181
match select(recv, sleep).await {
181182
Either::Left(((buf, _addr), _)) => {
182183
stats.borrow_mut().pack_recv += 1;

pixie-uefi/src/main.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extern crate alloc;
77

88
use alloc::boxed::Box;
99
use core::net::{Ipv4Addr, SocketAddrV4};
10+
use core::time::Duration;
1011

1112
use futures::future::{self, Either};
1213
use pixie_shared::{Action, TcpRequest, UdpRequest, ACTION_PORT, PING_PORT};
@@ -43,7 +44,7 @@ async fn server_discover() -> Result<SocketAddrV4> {
4344
socket
4445
.send_to(SocketAddrV4::new(Ipv4Addr::BROADCAST, ACTION_PORT), &msg)
4546
.await?;
46-
Executor::sleep_us(1_000_000).await;
47+
Executor::sleep(Duration::from_secs(1)).await;
4748
})
4849
};
4950

@@ -70,7 +71,7 @@ async fn shutdown() -> ! {
7071
export_cov::export().await;
7172

7273
log::info!("Shutting down...");
73-
Executor::sleep_us(1_000_000).await;
74+
Executor::sleep(Duration::from_secs(1)).await;
7475
power_control::shutdown()
7576
}
7677

@@ -108,7 +109,7 @@ async fn run() -> Result<()> {
108109
.send_to(SocketAddrV4::new(*server.ip(), PING_PORT), b"pixie")
109110
.await
110111
.unwrap();
111-
Executor::sleep_us(10_000_000).await;
112+
Executor::sleep(Duration::from_secs(10)).await;
112113
}
113114
});
114115

@@ -132,11 +133,7 @@ async fn run() -> Result<()> {
132133
log::warn!("Started waiting for another command...");
133134
}
134135
last_was_wait = true;
135-
const WAIT_10MSECS: u64 = 50;
136-
for _ in 0..WAIT_10MSECS {
137-
Executor::deep_sleep_us(10_000);
138-
Executor::sched_yield().await;
139-
}
136+
Executor::sleep(Duration::from_millis(500)).await;
140137
} else {
141138
last_was_wait = false;
142139
log::info!("Command: {command:?}");

pixie-uefi/src/os/executor.rs

Lines changed: 153 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use alloc::boxed::Box;
2+
use alloc::collections::binary_heap::BinaryHeap;
23
use alloc::collections::VecDeque;
34
use alloc::sync::Arc;
45
use alloc::task::Wake;
@@ -8,10 +9,10 @@ use core::future::{poll_fn, Future};
89
use core::pin::Pin;
910
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
1011
use core::task::{Context, Poll, Waker};
11-
use futures::channel::oneshot;
12+
use core::time::Duration;
1213

14+
use futures::channel::oneshot;
1315
use spin::Mutex;
14-
use uefi::boot::{EventType, TimerTrigger, Tpl};
1516
use uefi::proto::console::text::Color;
1617

1718
use crate::os::send_wrapper::SendWrapper;
@@ -53,10 +54,58 @@ impl Wake for Task {
5354
}
5455
}
5556

56-
static EXECUTOR: Mutex<Executor> = Mutex::new(Executor {
57-
ready_tasks: VecDeque::new(),
58-
tasks: vec![],
59-
});
57+
#[derive(Clone)]
58+
pub(super) struct WrappedWaker(Arc<Mutex<Option<Waker>>>);
59+
60+
impl WrappedWaker {
61+
pub(super) fn empty() -> Self {
62+
Self(Arc::new(Mutex::new(None)))
63+
}
64+
65+
pub(super) fn clear(&self) {
66+
*self.0.lock() = None;
67+
}
68+
69+
fn wake(self) {
70+
let mut w = self.0.lock();
71+
if let Some(w) = w.take() {
72+
w.wake();
73+
}
74+
}
75+
76+
fn replace(&self, waker: Waker) -> bool {
77+
let mut w = self.0.lock();
78+
let was_present = w.is_some();
79+
*w = Some(waker);
80+
was_present
81+
}
82+
}
83+
84+
struct TimedWait {
85+
wake_at: i64,
86+
waker: WrappedWaker,
87+
}
88+
89+
impl PartialEq for TimedWait {
90+
fn eq(&self, other: &Self) -> bool {
91+
self.wake_at == other.wake_at
92+
}
93+
}
94+
95+
impl Eq for TimedWait {}
96+
97+
impl PartialOrd for TimedWait {
98+
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
99+
Some(self.cmp(other))
100+
}
101+
}
102+
103+
impl Ord for TimedWait {
104+
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
105+
// Reversed order: min-heap.
106+
other.wake_at.cmp(&self.wake_at)
107+
}
108+
}
60109

61110
pub struct JoinHandle<T>(oneshot::Receiver<T>);
62111

@@ -66,8 +115,16 @@ impl<T> JoinHandle<T> {
66115
}
67116
}
68117

118+
static EXECUTOR: Mutex<Executor> = Mutex::new(Executor {
119+
wake_on_interrupt: vec![],
120+
timed_wait: BinaryHeap::new(),
121+
ready_tasks: VecDeque::new(),
122+
tasks: vec![],
123+
});
124+
69125
pub struct Executor {
70-
// TODO(veluca): scheduling.
126+
wake_on_interrupt: Vec<WrappedWaker>,
127+
timed_wait: BinaryHeap<TimedWait>,
71128
ready_tasks: VecDeque<Arc<Task>>,
72129
tasks: Vec<Arc<Task>>,
73130
}
@@ -81,7 +138,7 @@ impl Executor {
81138
assert!((w - 1).is_multiple_of(TASK_LEN + 1));
82139
let num_w = (w - 1) / (TASK_LEN + 1);
83140
let mut last = Timer::micros() as u64;
84-
Self::sleep_us(100_000).await;
141+
Self::sleep(Duration::from_millis(100)).await;
85142
loop {
86143
draw_area.clear();
87144
let cur = Timer::micros() as u64;
@@ -161,30 +218,76 @@ impl Executor {
161218
tasks.retain(|t| !t.done.load(Ordering::Relaxed));
162219
}
163220
last = Timer::micros() as u64;
164-
Self::sleep_us(1_000_000).await;
221+
Self::sleep(Duration::from_secs(1)).await
165222
}
166223
}
167224

168225
pub fn run() -> ! {
169226
Self::spawn("[show_tasks]", Self::draw_tasks());
227+
228+
// Maximum amount of microseconds between wakeups of interrupt-based wakers.
229+
const INTERRUPT_MICROS: i64 = 500;
230+
231+
let mut last_interrupt_wakeup = Timer::micros();
232+
233+
let mut do_wake = |force_interrupt_wake| {
234+
// Wake timed-waiting tasks.
235+
loop {
236+
let waker = {
237+
let mut ex = EXECUTOR.lock();
238+
let Some(w) = ex.timed_wait.peek() else {
239+
break;
240+
};
241+
if w.wake_at > Timer::micros() {
242+
break;
243+
}
244+
let w = ex.timed_wait.pop().unwrap();
245+
w.waker
246+
};
247+
waker.wake();
248+
}
249+
// Since we don't notice interrupts that happened while we are not hlt-ing,
250+
// make sure that we wake up all the interrupt-based waiting tasks every at
251+
// most INTERRUPT_MICROS micros to make it unlikely to miss interrupts.
252+
if last_interrupt_wakeup + INTERRUPT_MICROS <= Timer::micros() || force_interrupt_wake {
253+
last_interrupt_wakeup = Timer::micros();
254+
let to_wake = core::mem::take(&mut EXECUTOR.lock().wake_on_interrupt);
255+
for w in to_wake {
256+
w.wake();
257+
}
258+
}
259+
};
260+
170261
loop {
171-
let task = EXECUTOR
172-
.lock()
173-
.ready_tasks
174-
.pop_front()
175-
.expect("Executor should never run out of ready tasks");
176-
177-
task.in_queue.store(false, Ordering::Relaxed);
178-
let waker = Waker::from(task.clone());
179-
let mut context = Context::from_waker(&waker);
180-
let mut fut = task.future.try_lock().unwrap();
181-
let begin = Timer::micros();
182-
let done = fut.0.as_mut().poll(&mut context);
183-
let end = Timer::micros();
184-
task.micros
185-
.fetch_add((end - begin) as u64, Ordering::Relaxed);
186-
if done.is_ready() {
187-
task.done.swap(true, Ordering::Relaxed);
262+
let task = EXECUTOR.lock().ready_tasks.pop_front();
263+
if let Some(task) = task {
264+
{
265+
task.in_queue.store(false, Ordering::Relaxed);
266+
let waker = Waker::from(task.clone());
267+
let mut context = Context::from_waker(&waker);
268+
let mut fut = task.future.try_lock().unwrap();
269+
let begin = Timer::micros();
270+
let done = fut.0.as_mut().poll(&mut context);
271+
let end = Timer::micros();
272+
task.micros
273+
.fetch_add((end - begin) as u64, Ordering::Relaxed);
274+
if done.is_ready() {
275+
task.done.swap(true, Ordering::Relaxed);
276+
}
277+
}
278+
do_wake(false);
279+
} else {
280+
do_wake(false);
281+
if !EXECUTOR.lock().ready_tasks.is_empty() {
282+
continue;
283+
}
284+
// If we still don't have anything ready, sleep until the next interrupt.
285+
// SAFETY: hlt is available on all reasonable x86 processors and has no safety
286+
// requirements.
287+
unsafe {
288+
core::arch::asm!("hlt");
289+
}
290+
do_wake(true);
188291
}
189292
}
190293
}
@@ -204,27 +307,39 @@ impl Executor {
204307
})
205308
}
206309

207-
pub fn sleep_us(us: u64) -> impl Future<Output = ()> {
208-
let tgt = Timer::micros() as u64 + us;
310+
// Note: there are no guarantees on whether the amount of time we will sleep for
311+
// will be exceeded.
312+
pub fn sleep(time: Duration) -> impl Future<Output = ()> {
313+
let tgt = Timer::micros() + time.as_micros() as i64;
314+
let ww = WrappedWaker::empty();
209315
poll_fn(move |cx| {
210-
let now = Timer::micros() as u64;
316+
let now = Timer::micros();
211317
if now >= tgt {
212318
Poll::Ready(())
213319
} else {
214-
// TODO(veluca): actually suspend the task.
215-
cx.waker().wake_by_ref();
320+
Self::wake_at_micros(tgt, cx.waker().clone(), &ww);
216321
Poll::Pending
217322
}
218323
})
219324
}
220325

221-
/// **WARNING**: this function halts all tasks
222-
pub fn deep_sleep_us(us: u64) {
223-
// SAFETY: we are not using a callback
224-
let e =
225-
unsafe { uefi::boot::create_event(EventType::TIMER, Tpl::NOTIFY, None, None).unwrap() };
226-
uefi::boot::set_timer(&e, TimerTrigger::Relative(10 * us)).unwrap();
227-
uefi::boot::wait_for_event(&mut [e]).unwrap();
326+
// Wakes a task as soon as *any* interrupt is received.
327+
pub(super) fn wake_on_interrupt(waker: Waker, previous_waker: &WrappedWaker) {
328+
if !previous_waker.replace(waker) {
329+
EXECUTOR
330+
.lock()
331+
.wake_on_interrupt
332+
.push(previous_waker.clone());
333+
}
334+
}
335+
336+
pub(super) fn wake_at_micros(micros: i64, waker: Waker, previous_waker: &WrappedWaker) {
337+
if !previous_waker.replace(waker) {
338+
EXECUTOR.lock().timed_wait.push(TimedWait {
339+
wake_at: micros,
340+
waker: previous_waker.clone(),
341+
});
342+
}
228343
}
229344

230345
/// Spawn a new task.

pixie-uefi/src/os/input.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use uefi::boot::ScopedProtocol;
77
use uefi::proto::console::text::{Input, Key};
88

99
use crate::os::error::Result;
10+
use crate::os::executor::{Executor, WrappedWaker};
1011
use crate::os::send_wrapper::SendWrapper;
1112

1213
static INPUT: Lazy<Mutex<SendWrapper<ScopedProtocol<Input>>>> = Lazy::new(|| {
@@ -16,13 +17,20 @@ static INPUT: Lazy<Mutex<SendWrapper<ScopedProtocol<Input>>>> = Lazy::new(|| {
1617
});
1718

1819
pub fn read_key() -> impl Future<Output = Result<Key>> {
20+
let ww = WrappedWaker::empty();
1921
poll_fn(move |cx| {
2022
let key = INPUT.lock().read_key();
2123
match key {
22-
Err(e) => Poll::Ready(Err(e.into())),
23-
Ok(Some(key)) => Poll::Ready(Ok(key)),
24+
Err(e) => {
25+
ww.clear();
26+
Poll::Ready(Err(e.into()))
27+
}
28+
Ok(Some(key)) => {
29+
ww.clear();
30+
Poll::Ready(Ok(key))
31+
}
2432
Ok(None) => {
25-
cx.waker().wake_by_ref();
33+
Executor::wake_on_interrupt(cx.waker().clone(), &ww);
2634
Poll::Pending
2735
}
2836
}

pixie-uefi/src/os/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use core::ffi::c_void;
22
use core::future::Future;
33
use core::ptr::NonNull;
44
use core::sync::atomic::{AtomicBool, Ordering};
5+
use core::time::Duration;
56

67
use uefi::boot::{EventType, Tpl};
78
use uefi::{Event, Status};
@@ -72,7 +73,7 @@ where
7273
break;
7374
}
7475

75-
Executor::sleep_us(30_000_000).await;
76+
Executor::sleep(Duration::from_secs(30)).await;
7677
}
7778
});
7879

pixie-uefi/src/os/net/interface.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use uefi::proto::network::snp::{ReceiveFlags, SimpleNetwork};
55
use uefi::Status;
66

77
use super::ETH_PACKET_SIZE;
8+
use crate::os::send_wrapper::SendWrapper;
89

9-
type Snp = ScopedProtocol<SimpleNetwork>;
10+
type Snp = SendWrapper<ScopedProtocol<SimpleNetwork>>;
1011

1112
pub struct SnpDevice {
1213
snp: Snp,
@@ -15,9 +16,6 @@ pub struct SnpDevice {
1516
rx_buf: [u8; ETH_PACKET_SIZE + 4],
1617
}
1718

18-
// SAFETY: we never create threads anyway.
19-
unsafe impl Send for SnpDevice {}
20-
2119
impl SnpDevice {
2220
pub fn new(snp: Snp) -> SnpDevice {
2321
// Shut down the SNP protocol if needed.

0 commit comments

Comments
 (0)