use alloc::format;
use alloc::string::String;
use crate::core::error::{Error, Result};
use crate::error;
pub const WINDOW_MAX: usize = 16;
const PARK_US_MAX: u64 = 10_000;
const BACKSTOP_US_MAX: u64 = 1_000_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WaitPlan {
pub predict: Option<DeadlinePlan>,
pub park: Park,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Park {
Block,
QueryPoll,
FlagWait {
park_ns: u64,
backstop_ns: u64,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DeadlinePlan {
pub margin_ns: u64,
pub min_sleep_ns: u64,
pub max_sleep_ns: u64,
pub window: u8,
}
fn refused(text: &str, why: &str) -> Error {
error!(InvalidPlan, "completion wait plan", format!("{text:?}: {why}"))
}
fn fields<const N: usize>(text: &str, body: &str, keys: [&str; N]) -> Result<[u64; N]> {
let mut got: [Option<u64>; N] = [None; N];
for kv in body.split(',') {
let (k, v) = kv
.split_once('=')
.ok_or_else(|| refused(text, "a field is not key=<n>"))?;
let i = keys
.iter()
.position(|x| *x == k)
.ok_or_else(|| refused(text, &format!("unknown field {k:?}; expected {keys:?}")))?;
if got[i].is_some() {
return Err(refused(text, &format!("field {k:?} stated twice")));
}
got[i] = Some(
v.parse()
.map_err(|_| refused(text, &format!("field {k:?}: {v:?} is not a u64")))?,
);
}
let mut out = [0u64; N];
for ((o, g), k) in out.iter_mut().zip(got).zip(keys) {
*o = g.ok_or_else(|| {
refused(
text,
&format!("field {k:?} missing (every field is required)"),
)
})?;
}
Ok(out)
}
fn ns(text: &str, us: u64) -> Result<u64> {
us.checked_mul(1_000)
.ok_or_else(|| refused(text, "a duration overflows u64 nanoseconds"))
}
impl DeadlinePlan {
fn parse(text: &str, body: &str) -> Result<Self> {
let [m, lo, hi, w] = fields(
text,
body,
["margin_us", "min_sleep_us", "max_sleep_us", "window"],
)?;
let window = u8::try_from(w)
.ok()
.filter(|w| (1..=WINDOW_MAX).contains(&usize::from(*w)));
let window = window
.ok_or_else(|| refused(text, &format!("window {w}: expected 1..={WINDOW_MAX}")))?;
if lo > hi {
return Err(refused(text, "min_sleep_us is above max_sleep_us"));
}
Ok(Self {
margin_ns: ns(text, m)?,
min_sleep_ns: ns(text, lo)?,
max_sleep_ns: ns(text, hi)?,
window,
})
}
}
impl Park {
fn parse(text: &str, park: &str) -> Result<Self> {
match park {
"block" => Ok(Self::Block),
"poll" => Ok(Self::QueryPoll),
p => {
let body = p
.strip_prefix("flag:")
.ok_or_else(|| refused(text, "park: expected block, poll or flag:<fields>"))?;
let [park_us, backstop_us] = fields(text, body, ["park_us", "backstop_us"])?;
if !(1..=PARK_US_MAX).contains(&park_us) {
return Err(refused(
text,
&format!("park_us {park_us}: expected 1..={PARK_US_MAX}"),
));
}
if !(1..=BACKSTOP_US_MAX).contains(&backstop_us) {
return Err(refused(
text,
&format!("backstop_us {backstop_us}: expected 1..={BACKSTOP_US_MAX}"),
));
}
Ok(Self::FlagWait {
park_ns: ns(text, park_us)?,
backstop_ns: ns(text, backstop_us)?,
})
}
}
}
}
impl WaitPlan {
pub fn parse(text: &str) -> Result<Self> {
let (park, predict) = match text.split_once('+') {
None => (text, None),
Some((park, rest)) => {
let body = rest
.strip_prefix("predict:")
.ok_or_else(|| refused(text, "after '+': expected predict:<fields>"))?;
(park, Some(DeadlinePlan::parse(text, body)?))
}
};
let park = Park::parse(text, park)?;
if park == Park::Block && predict.is_some() {
return Err(refused(text, "block takes no predict"));
}
Ok(Self { predict, park })
}
#[must_use]
pub fn label(&self) -> String {
let park = match self.park {
Park::Block => String::from("block"),
Park::QueryPoll => String::from("poll"),
Park::FlagWait {
park_ns,
backstop_ns,
} => {
format!(
"flag:park_us={},backstop_us={}",
park_ns / 1_000,
backstop_ns / 1_000
)
}
};
match self.predict {
None => park,
Some(d) => format!(
"{park}+predict:margin_us={},min_sleep_us={},max_sleep_us={},window={}",
d.margin_ns / 1_000,
d.min_sleep_ns / 1_000,
d.max_sleep_ns / 1_000,
d.window
),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Capability {
DeviceWordWait,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Fallback {
pub from: ParkKind,
pub needs: Capability,
pub to: ParkKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParkKind {
Block,
QueryPoll,
FlagWait,
}
pub const FALLBACKS: [Fallback; 1] = [Fallback {
from: ParkKind::FlagWait,
needs: Capability::DeviceWordWait,
to: ParkKind::QueryPoll,
}];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HostCaps {
pub device_word_wait: core::result::Result<(), String>,
}
impl HostCaps {
fn lacks(&self, c: Capability) -> Option<&str> {
match c {
Capability::DeviceWordWait => self.device_word_wait.as_ref().err().map(String::as_str),
}
}
}
#[derive(Clone, Debug)]
pub struct Resolved {
pub requested: WaitPlan,
pub plan: WaitPlan,
pub warning: Option<Error>,
}
impl Resolved {
#[must_use]
pub fn label(&self) -> String {
if self.warning.is_some() {
format!(
"{} (fallback from {})",
self.plan.label(),
self.requested.label()
)
} else {
self.plan.label()
}
}
}
impl Park {
#[must_use]
pub fn kind(&self) -> ParkKind {
match self {
Self::Block => ParkKind::Block,
Self::QueryPoll => ParkKind::QueryPoll,
Self::FlagWait { .. } => ParkKind::FlagWait,
}
}
}
impl WaitPlan {
#[must_use]
pub fn resolve(&self, caps: &HostCaps) -> Resolved {
let mut plan = *self;
let mut warning = None;
let kind = plan.park.kind();
for link in FALLBACKS.iter().filter(|l| l.from == kind) {
if let Some(why) = caps.lacks(link.needs) {
let from = plan.park;
plan.park = match link.to {
ParkKind::Block => Park::Block,
ParkKind::QueryPoll => Park::QueryPoll,
ParkKind::FlagWait => from,
};
warning = Some(error!(
ParkFallback,
self.label(),
format!("{:?} ({:?})", link.from, link.needs),
String::from(why),
plan.label()
));
}
}
Resolved {
requested: *self,
plan,
warning,
}
}
}
#[cfg(test)]
#[path = "wait_plan_tests.rs"]
mod tests;