Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
07d280a
refactor(runtime): add state-driven IRQ event runtime
ZR233 Jun 30, 2026
04a9833
feat(ax-task): add hard IRQ wake path
ZR233 Jun 30, 2026
b75e49e
fix(nvme-driver): defer irq completion draining
ZR233 Jun 30, 2026
1ba34d6
fix(runtime): harden IRQ-driven completion wakes
ZR233 Jul 1, 2026
75af8fa
refactor(ax-task): make IRQ runtime mandatory
ZR233 Jul 1, 2026
9b5a497
fix(axtask): harden irq wake handles
ZR233 Jul 1, 2026
41e3fcc
fix(axtask): keep irq poll bridge lockless
ZR233 Jul 1, 2026
0a8e434
fix(axtask): close irq notify sleep race
ZR233 Jul 1, 2026
4df8e0e
fix(axtask): stabilize IRQ wake scheduling
ZR233 Jul 1, 2026
fedf7f1
fix(axtask): separate task waker from hard IRQ wake
ZR233 Jul 1, 2026
6a5a1c7
refactor(axtask): split hard IRQ and task wake paths
ZR233 Jul 2, 2026
2110e3c
refactor(axtask): move task core protocols to bare-task
ZR233 Jul 2, 2026
11b7ae1
refactor(axtask): converge runtime wake core into bare-task
ZR233 Jul 2, 2026
4159e53
test(starry): drain build-script pipes after waitpid
ZR233 Jul 2, 2026
14c6a72
fix(axtask): address irq runtime review regressions
ZR233 Jul 3, 2026
1000c12
fix(ax-task): defer timer wakeups through irq queue
ZR233 Jul 3, 2026
a4e1e5b
fix(riscv-vcpu): route host software interrupts
ZR233 Jul 3, 2026
a49a123
fix(axtask): preserve wake target invariants
ZR233 Jul 3, 2026
2cd33f8
fix(build): drop stale platform feature references
ZR233 Jul 3, 2026
609ad59
fix(axtask): remove aborted waiters before returning
ZR233 Jul 3, 2026
6d01aa8
fix(axvisor): avoid delayed vmx smoke command injection
ZR233 Jul 3, 2026
0a18511
fix(axtask): key wait queue membership
ZR233 Jul 3, 2026
fb2bea9
fix(axtask): clear wait queue membership on forced wake
ZR233 Jul 3, 2026
b9d5eb0
fix(axtask): ignore stale ready waiters on notify
ZR233 Jul 3, 2026
fe13f95
fix(axtask): skip stale waiters in notify callbacks
ZR233 Jul 3, 2026
beaf9ec
fix(starry-kernel): restore wait child event polling
ZR233 Jul 3, 2026
39e622e
fix(starry-kernel): terminate task after exit syscall
ZR233 Jul 3, 2026
d6bff54
fix(starry-kernel): avoid yielding before exit is published
ZR233 Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ members = [
"components/axtest/axtest_macros",
"components/ax-lazyinit",
"components/axbacktrace",
"components/bare-task",
"components/axcpu",
"components/axerrno",
"components/axfs-ng-vfs",
Expand Down Expand Up @@ -187,6 +188,7 @@ ax-timer-list = { version = "0.3.5", path = "components/timer_list" }
axaddrspace = { version = "0.5.15", path = "virtualization/axaddrspace" }
axbacktrace = { version = "0.4.4", path = "components/axbacktrace" }
axbuild = { version = "0.4.16", path = "scripts/axbuild" }
bare-task = { version = "0.1.0", path = "components/bare-task" }
axdevice = { version = "0.5.1", path = "virtualization/axdevice" }
axdevice_base = { version = "0.6.0", path = "virtualization/axdevice_base" }
axfs-ng-vfs = { version = "0.5.3", path = "components/axfs-ng-vfs" }
Expand Down
2 changes: 1 addition & 1 deletion apps/arceos/thread_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false

[features]
default = []
arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"]
arceos = ["dep:ax-std", "ax-std/multitask"]

[dependencies]
ax-std = { workspace = true, optional = true }
Expand Down
2 changes: 1 addition & 1 deletion apps/arceos/tokio_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ publish = false

[features]
default = []
arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"]
arceos = ["dep:ax-std", "ax-std/multitask"]

[dependencies]
ax-std = { workspace = true, optional = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Build-time config for the Orange Pi 5 Plus UVC + RKNN Starry example.
target = "aarch64-unknown-none-softfloat"
features = [
"ax-driver/irq",
"ax-driver/list-pci-devices",
"ax-driver/rk3588-pcie",
"ax-driver/realtek-rtl8125",
Expand Down
139 changes: 62 additions & 77 deletions components/axpoll/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

extern crate alloc;

use alloc::{boxed::Box, vec::Vec};
use alloc::vec::Vec;
use core::{
mem::MaybeUninit,
task::{Context, Waker},
Expand Down Expand Up @@ -64,7 +64,7 @@ pub trait Pollable {
fn register(&self, context: &mut Context<'_>, events: IoEvents);
}

const POLL_SET_CAPACITY: usize = 64;
const IRQ_WAKE_BATCH: usize = 64;

struct Entry {
waker: Waker,
Expand All @@ -78,75 +78,73 @@ impl Entry {
}

struct Inner {
entries: Box<[MaybeUninit<Entry>]>,
cursor: usize,
entries: Vec<Entry>,
}

impl Inner {
fn new() -> Self {
Self {
entries: Box::new_uninit_slice(POLL_SET_CAPACITY),
cursor: 0,
entries: Vec::new(),
}
}

fn len(&self) -> usize {
self.cursor.min(POLL_SET_CAPACITY)
}

fn is_empty(&self) -> bool {
self.cursor == 0
self.entries.is_empty()
}

fn push_entry(&mut self, entry: Entry) {
debug_assert!(self.cursor < POLL_SET_CAPACITY);
let slot = self.cursor;
self.cursor += 1;
self.entries[slot].write(entry);
}
fn register(&mut self, waker: &Waker, interests: IoEvents) {
for entry in &mut self.entries {
if entry.waker.will_wake(waker) {
entry.waker = waker.clone();
entry.interests |= interests;
return;
}
}

fn register(&mut self, waker: &Waker, interests: IoEvents) -> Option<Entry> {
let slot = self.cursor % POLL_SET_CAPACITY;
let replaced = if self.cursor >= POLL_SET_CAPACITY {
let old = unsafe { self.entries[slot].assume_init_read() };
let replaced = (!old.waker.will_wake(waker)).then_some(old);
self.cursor = ((slot + 1) % POLL_SET_CAPACITY) + POLL_SET_CAPACITY;
replaced
} else {
self.cursor += 1;
None
};
self.entries[slot].write(Entry {
self.entries.push(Entry {
waker: waker.clone(),
interests,
});
replaced
}

fn drain_ready(&mut self, ready: IoEvents, ready_entries: &mut Vec<Entry>) {
if self.is_empty() {
return;
}

let mut old = Self::new();
core::mem::swap(&mut old, self);
let mut index = 0;
while index < self.entries.len() {
if self.entries[index].interests.intersects(ready) {
ready_entries.push(self.entries.swap_remove(index));
} else {
index += 1;
}
}
}

for i in 0..old.len() {
let entry = unsafe { old.entries[i].assume_init_read() };
if entry.interests.intersects(ready) {
ready_entries.push(entry);
fn drain_ready_batch(
&mut self,
ready: IoEvents,
ready_entries: &mut [MaybeUninit<Entry>],
) -> usize {
let mut ready_len = 0;
let mut index = 0;
while index < self.entries.len() && ready_len < ready_entries.len() {
if self.entries[index].interests.intersects(ready) {
ready_entries[ready_len].write(self.entries.swap_remove(index));
ready_len += 1;
} else {
self.push_entry(entry);
index += 1;
}
}
old.cursor = 0;
ready_len
}
}

impl Drop for Inner {
fn drop(&mut self) {
for i in 0..self.len() {
unsafe { self.entries[i].assume_init_read() }.wake();
for entry in self.entries.drain(..) {
entry.wake();
}
}
}
Expand Down Expand Up @@ -174,15 +172,10 @@ impl PollSet {
/// from hard IRQ, NMI, or trap callbacks, and must not hold locks that may
/// be re-entered by the registered waker or by poll wakeup paths.
pub unsafe fn register(&self, waker: &Waker, interests: IoEvents) {
let replaced = {
self.0
.call_once(|| SpinNoIrq::new(Inner::new()))
.lock()
.register(waker, interests)
};
if let Some(entry) = replaced {
entry.wake();
}
self.0
.call_once(|| SpinNoIrq::new(Inner::new()))
.lock()
.register(waker, interests);
}

/// Wakes up registered wakers whose interests intersect `ready`.
Expand All @@ -198,7 +191,7 @@ impl PollSet {
let Some(inner) = self.0.get() else {
return 0;
};
let mut ready_entries = Vec::with_capacity(POLL_SET_CAPACITY);
let mut ready_entries = Vec::new();
{
inner.lock().drain_ready(ready, &mut ready_entries);
}
Expand All @@ -211,42 +204,34 @@ impl PollSet {

/// Wakes up registered wakers whose interests intersect `ready` from IRQ context.
///
/// Unlike [`wake`](Self::wake), this does not allocate a replacement
/// waiter buffer. It drains the already-initialized entries in place, so
/// device IRQ handlers can acknowledge the device and then wake matching
/// poll waiters without allocating in hard IRQ context.
/// This method is kept for legacy users only. New hard IRQ code should
/// publish state into the device/backend and wake a deferred worker via an
/// IRQ-safe task waker instead. Unlike [`wake`](Self::wake), this method
/// avoids allocation by draining matching entries in fixed-size batches.
pub fn wake_from_irq(&self, ready: IoEvents) -> usize {
let Some(inner) = self.0.get() else {
return 0;
};
let mut ready_entries = [const { MaybeUninit::<Entry>::uninit() }; POLL_SET_CAPACITY];
let ready_len = {
let mut inner = inner.lock();
let len = inner.len();
if len == 0 {
return 0;
}

let mut ready_len = 0;
let mut keep_len = 0;
for i in 0..len {
let entry = unsafe { inner.entries[i].assume_init_read() };
if entry.interests.intersects(ready) {
ready_entries[ready_len].write(entry);
ready_len += 1;
} else {
inner.entries[keep_len].write(entry);
keep_len += 1;
let mut woke = 0;
loop {
let mut ready_entries = [const { MaybeUninit::<Entry>::uninit() }; IRQ_WAKE_BATCH];
let ready_len = {
let mut inner = inner.lock();
if inner.is_empty() {
return woke;
}
inner.drain_ready_batch(ready, &mut ready_entries)
};
if ready_len == 0 {
return woke;
}
inner.cursor = keep_len;
ready_len
};

for entry in ready_entries.iter_mut().take(ready_len) {
unsafe { entry.assume_init_read() }.wake();
woke += ready_len;
for entry in ready_entries.iter_mut().take(ready_len) {
unsafe { entry.assume_init_read() }.wake();
}
}
ready_len
}
}

Expand Down
Loading
Loading