use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, AtomicUsize, Ordering};
use super::{
QueueNotification, STATE_DISABLED, STATE_IDLE, STATE_MASK, STATE_MISSED, STATE_POLLING,
STATE_SCHEDULED,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NetQueueStats {
pub irq: u64,
pub schedule: u64,
pub missed: u64,
pub poll_batches: u64,
pub budget_exhaustion: u64,
pub spurious: u64,
pub probe_deferred: u64,
pub rearm_race: u64,
pub owner_cpu: usize,
pub last_irq_cpu: Option<usize>,
pub last_poll_cpu: Option<usize>,
pub irq_to_poll_remote_wake: u64,
}
pub(super) struct QueueStatsAtomic {
pub(super) irq: AtomicU64,
pub(super) schedule: AtomicU64,
pub(super) missed: AtomicU64,
pub(super) poll_batches: AtomicU64,
pub(super) budget_exhaustion: AtomicU64,
pub(super) spurious: AtomicU64,
pub(super) probe_deferred: AtomicU64,
pub(super) rearm_race: AtomicU64,
pub(super) last_irq_cpu: AtomicUsize,
pub(super) last_poll_cpu: AtomicUsize,
pub(super) irq_to_poll_remote_wake: AtomicU64,
}
impl QueueStatsAtomic {
const fn new() -> Self {
Self {
irq: AtomicU64::new(0),
schedule: AtomicU64::new(0),
missed: AtomicU64::new(0),
poll_batches: AtomicU64::new(0),
budget_exhaustion: AtomicU64::new(0),
spurious: AtomicU64::new(0),
probe_deferred: AtomicU64::new(0),
rearm_race: AtomicU64::new(0),
last_irq_cpu: AtomicUsize::new(usize::MAX),
last_poll_cpu: AtomicUsize::new(usize::MAX),
irq_to_poll_remote_wake: AtomicU64::new(0),
}
}
pub(super) fn snapshot(&self, owner_cpu: usize) -> NetQueueStats {
let optional_cpu = |cpu| (cpu != usize::MAX).then_some(cpu);
NetQueueStats {
irq: self.irq.load(Ordering::Relaxed),
schedule: self.schedule.load(Ordering::Relaxed),
missed: self.missed.load(Ordering::Relaxed),
poll_batches: self.poll_batches.load(Ordering::Relaxed),
budget_exhaustion: self.budget_exhaustion.load(Ordering::Relaxed),
spurious: self.spurious.load(Ordering::Relaxed),
probe_deferred: self.probe_deferred.load(Ordering::Relaxed),
rearm_race: self.rearm_race.load(Ordering::Relaxed),
owner_cpu,
last_irq_cpu: optional_cpu(self.last_irq_cpu.load(Ordering::Acquire)),
last_poll_cpu: optional_cpu(self.last_poll_cpu.load(Ordering::Acquire)),
irq_to_poll_remote_wake: self.irq_to_poll_remote_wake.load(Ordering::Relaxed),
}
}
}
pub(super) struct PollGroupState {
pub(super) state: AtomicU8,
startup_absent: AtomicBool,
pub(super) owner_cpu: usize,
notify: Arc<QueueNotification>,
pub(super) stats: QueueStatsAtomic,
rx_drops: AtomicU64,
}
impl PollGroupState {
pub(super) fn new(owner_cpu: usize, notify: Arc<QueueNotification>) -> Self {
Self {
state: AtomicU8::new(STATE_DISABLED),
startup_absent: AtomicBool::new(false),
owner_cpu,
notify,
stats: QueueStatsAtomic::new(),
rx_drops: AtomicU64::new(0),
}
}
pub(super) fn mark_startup_absent(&self) {
self.startup_absent.store(true, Ordering::Release);
}
pub(super) fn startup_absent(&self) -> bool {
self.startup_absent.load(Ordering::Acquire)
}
pub(super) fn record_rx_drop(&self) {
self.rx_drops.fetch_add(1, Ordering::Relaxed);
}
pub(super) fn take_rx_drops(&self) -> u64 {
self.rx_drops.swap(0, Ordering::Relaxed)
}
pub(super) fn activate(&self, pending: bool) {
self.state.store(STATE_IDLE, Ordering::Release);
if pending {
self.schedule_task();
}
}
pub(super) fn schedule_irq(&self) {
let cpu = ax_hal::percpu::this_cpu_id();
self.stats.irq.fetch_add(1, Ordering::Relaxed);
self.stats.last_irq_cpu.store(cpu, Ordering::Release);
if self.startup_absent() {
return;
}
if cpu != self.owner_cpu {
self.stats
.irq_to_poll_remote_wake
.fetch_add(1, Ordering::Relaxed);
self.disable();
return;
}
if self.is_disabled() {
self.notify.notify();
} else if self.publish_schedule() {
self.notify.notify();
}
}
pub(super) fn wait_startup_irq(&self, waiter: &ax_task::sync::irq::IrqWorkerWaiter) {
self.notify.wait(waiter);
}
pub(super) fn wait_startup_deadline(
&self,
waiter: &ax_task::sync::irq::IrqWorkerWaiter,
deadline_nanos: u64,
) {
let now = ax_hal::time::monotonic_time_nanos();
if deadline_nanos > now {
let duration = core::time::Duration::from_nanos(deadline_nanos - now);
self.notify.wait_timeout(waiter, duration);
}
}
pub(super) fn schedule_task(&self) {
self.publish_schedule();
if !self.is_disabled() {
self.notify.notify();
}
}
fn publish_schedule(&self) -> bool {
loop {
let old = self.state.load(Ordering::Acquire);
match old & STATE_MASK {
STATE_DISABLED => return false,
STATE_IDLE => {
if self
.state
.compare_exchange(old, STATE_SCHEDULED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
self.stats.schedule.fetch_add(1, Ordering::Relaxed);
return true;
}
}
STATE_SCHEDULED | STATE_POLLING => {
if old & STATE_MISSED != 0 {
return false;
}
if self
.state
.compare_exchange(
old,
old | STATE_MISSED,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
self.stats.missed.fetch_add(1, Ordering::Relaxed);
return false;
}
}
_ => return false,
}
}
}
pub(super) fn claim(&self) -> bool {
let current_cpu = ax_hal::percpu::this_cpu_id();
if current_cpu != self.owner_cpu {
self.disable();
return false;
}
loop {
let old = self.state.load(Ordering::Acquire);
let claimable = (old & STATE_MASK == STATE_SCHEDULED)
|| (old & STATE_MASK == STATE_POLLING && old & STATE_MISSED != 0);
if !claimable {
return false;
}
if self
.state
.compare_exchange(old, STATE_POLLING, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
self.stats
.last_poll_cpu
.store(current_cpu, Ordering::Release);
self.stats.poll_batches.fetch_add(1, Ordering::Relaxed);
return true;
}
}
}
pub(super) fn finish_more(&self) {
loop {
let old = self.state.load(Ordering::Acquire);
if old & STATE_MASK != STATE_POLLING {
return;
}
if self
.state
.compare_exchange(old, STATE_SCHEDULED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return;
}
}
}
pub(super) fn begin_rearm(&self) -> bool {
loop {
let old = self.state.load(Ordering::Acquire);
if old & STATE_MASK != STATE_POLLING {
return false;
}
if old & STATE_MISSED != 0 {
if self
.state
.compare_exchange(old, STATE_SCHEDULED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return false;
}
continue;
}
if self
.state
.compare_exchange(old, STATE_IDLE, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return true;
}
}
}
pub(super) fn disable(&self) {
self.state.store(STATE_DISABLED, Ordering::Release);
self.notify.notify();
}
pub(super) fn is_disabled(&self) -> bool {
self.state.load(Ordering::Acquire) & STATE_MASK == STATE_DISABLED
}
}