1use alloc::format;
14use alloc::string::String;
15
16use crate::core::error::{Error, Result};
17use crate::error;
18
19pub const WINDOW_MAX: usize = 16;
21const PARK_US_MAX: u64 = 10_000;
23const BACKSTOP_US_MAX: u64 = 1_000_000;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct WaitPlan {
29 pub predict: Option<DeadlinePlan>,
31 pub park: Park,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum Park {
38 Block,
40 QueryPoll,
42 FlagWait {
44 park_ns: u64,
46 backstop_ns: u64,
48 },
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct DeadlinePlan {
54 pub margin_ns: u64,
56 pub min_sleep_ns: u64,
58 pub max_sleep_ns: u64,
60 pub window: u8,
62}
63
64fn refused(text: &str, why: &str) -> Error {
66 error!(InvalidPlan, "completion wait plan", format!("{text:?}: {why}"))
67}
68
69fn fields<const N: usize>(text: &str, body: &str, keys: [&str; N]) -> Result<[u64; N]> {
71 let mut got: [Option<u64>; N] = [None; N];
72 for kv in body.split(',') {
73 let (k, v) = kv
74 .split_once('=')
75 .ok_or_else(|| refused(text, "a field is not key=<n>"))?;
76 let i = keys
77 .iter()
78 .position(|x| *x == k)
79 .ok_or_else(|| refused(text, &format!("unknown field {k:?}; expected {keys:?}")))?;
80 if got[i].is_some() {
81 return Err(refused(text, &format!("field {k:?} stated twice")));
82 }
83 got[i] = Some(
84 v.parse()
85 .map_err(|_| refused(text, &format!("field {k:?}: {v:?} is not a u64")))?,
86 );
87 }
88 let mut out = [0u64; N];
89 for ((o, g), k) in out.iter_mut().zip(got).zip(keys) {
90 *o = g.ok_or_else(|| {
91 refused(
92 text,
93 &format!("field {k:?} missing (every field is required)"),
94 )
95 })?;
96 }
97 Ok(out)
98}
99
100fn ns(text: &str, us: u64) -> Result<u64> {
102 us.checked_mul(1_000)
103 .ok_or_else(|| refused(text, "a duration overflows u64 nanoseconds"))
104}
105
106impl DeadlinePlan {
107 fn parse(text: &str, body: &str) -> Result<Self> {
108 let [m, lo, hi, w] = fields(
109 text,
110 body,
111 ["margin_us", "min_sleep_us", "max_sleep_us", "window"],
112 )?;
113 let window = u8::try_from(w)
114 .ok()
115 .filter(|w| (1..=WINDOW_MAX).contains(&usize::from(*w)));
116 let window = window
117 .ok_or_else(|| refused(text, &format!("window {w}: expected 1..={WINDOW_MAX}")))?;
118 if lo > hi {
119 return Err(refused(text, "min_sleep_us is above max_sleep_us"));
120 }
121 Ok(Self {
122 margin_ns: ns(text, m)?,
123 min_sleep_ns: ns(text, lo)?,
124 max_sleep_ns: ns(text, hi)?,
125 window,
126 })
127 }
128}
129
130impl Park {
131 fn parse(text: &str, park: &str) -> Result<Self> {
132 match park {
133 "block" => Ok(Self::Block),
134 "poll" => Ok(Self::QueryPoll),
135 p => {
136 let body = p
137 .strip_prefix("flag:")
138 .ok_or_else(|| refused(text, "park: expected block, poll or flag:<fields>"))?;
139 let [park_us, backstop_us] = fields(text, body, ["park_us", "backstop_us"])?;
140 if !(1..=PARK_US_MAX).contains(&park_us) {
141 return Err(refused(
142 text,
143 &format!("park_us {park_us}: expected 1..={PARK_US_MAX}"),
144 ));
145 }
146 if !(1..=BACKSTOP_US_MAX).contains(&backstop_us) {
147 return Err(refused(
148 text,
149 &format!("backstop_us {backstop_us}: expected 1..={BACKSTOP_US_MAX}"),
150 ));
151 }
152 Ok(Self::FlagWait {
153 park_ns: ns(text, park_us)?,
154 backstop_ns: ns(text, backstop_us)?,
155 })
156 }
157 }
158 }
159}
160
161impl WaitPlan {
162 pub fn parse(text: &str) -> Result<Self> {
167 let (park, predict) = match text.split_once('+') {
168 None => (text, None),
169 Some((park, rest)) => {
170 let body = rest
171 .strip_prefix("predict:")
172 .ok_or_else(|| refused(text, "after '+': expected predict:<fields>"))?;
173 (park, Some(DeadlinePlan::parse(text, body)?))
174 }
175 };
176 let park = Park::parse(text, park)?;
177 if park == Park::Block && predict.is_some() {
178 return Err(refused(text, "block takes no predict"));
179 }
180 Ok(Self { predict, park })
181 }
182
183 #[must_use]
185 pub fn label(&self) -> String {
186 let park = match self.park {
187 Park::Block => String::from("block"),
188 Park::QueryPoll => String::from("poll"),
189 Park::FlagWait {
190 park_ns,
191 backstop_ns,
192 } => {
193 format!(
194 "flag:park_us={},backstop_us={}",
195 park_ns / 1_000,
196 backstop_ns / 1_000
197 )
198 }
199 };
200 match self.predict {
201 None => park,
202 Some(d) => format!(
203 "{park}+predict:margin_us={},min_sleep_us={},max_sleep_us={},window={}",
204 d.margin_ns / 1_000,
205 d.min_sleep_ns / 1_000,
206 d.max_sleep_ns / 1_000,
207 d.window
208 ),
209 }
210 }
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
215pub enum Capability {
216 DeviceWordWait,
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq)]
223pub struct Fallback {
224 pub from: ParkKind,
226 pub needs: Capability,
228 pub to: ParkKind,
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq)]
234pub enum ParkKind {
235 Block,
237 QueryPoll,
239 FlagWait,
241}
242
243pub const FALLBACKS: [Fallback; 1] = [Fallback {
249 from: ParkKind::FlagWait,
250 needs: Capability::DeviceWordWait,
251 to: ParkKind::QueryPoll,
252}];
253
254#[derive(Clone, Debug, PartialEq, Eq)]
256pub struct HostCaps {
257 pub device_word_wait: core::result::Result<(), String>,
259}
260
261impl HostCaps {
262 fn lacks(&self, c: Capability) -> Option<&str> {
263 match c {
264 Capability::DeviceWordWait => self.device_word_wait.as_ref().err().map(String::as_str),
265 }
266 }
267}
268
269#[derive(Clone, Debug)]
271pub struct Resolved {
272 pub requested: WaitPlan,
274 pub plan: WaitPlan,
276 pub warning: Option<Error>,
278}
279
280impl Resolved {
281 #[must_use]
284 pub fn label(&self) -> String {
285 if self.warning.is_some() {
286 format!(
287 "{} (fallback from {})",
288 self.plan.label(),
289 self.requested.label()
290 )
291 } else {
292 self.plan.label()
293 }
294 }
295}
296
297impl Park {
298 #[must_use]
300 pub fn kind(&self) -> ParkKind {
301 match self {
302 Self::Block => ParkKind::Block,
303 Self::QueryPoll => ParkKind::QueryPoll,
304 Self::FlagWait { .. } => ParkKind::FlagWait,
305 }
306 }
307}
308
309impl WaitPlan {
310 #[must_use]
312 pub fn resolve(&self, caps: &HostCaps) -> Resolved {
313 let mut plan = *self;
314 let mut warning = None;
315 let kind = plan.park.kind();
316 for link in FALLBACKS.iter().filter(|l| l.from == kind) {
317 if let Some(why) = caps.lacks(link.needs) {
318 let from = plan.park;
319 plan.park = match link.to {
320 ParkKind::Block => Park::Block,
321 ParkKind::QueryPoll => Park::QueryPoll,
322 ParkKind::FlagWait => from,
323 };
324 warning = Some(error!(
325 ParkFallback,
326 self.label(),
327 format!("{:?} ({:?})", link.from, link.needs),
328 String::from(why),
329 plan.label()
330 ));
331 }
332 }
333 Resolved {
334 requested: *self,
335 plan,
336 warning,
337 }
338 }
339}
340
341#[cfg(test)]
342#[path = "wait_plan_tests.rs"]
343mod tests;