Skip to content

Commit 7b71bd9

Browse files
committed
adc: iio: make the iio thread less panicy
Previously the iio thread code contained a lot of .unwrap() calls and explicit panics. Now it contains a few less and gained some comments where it still .unwrap()s. Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
1 parent ca98cbf commit 7b71bd9

File tree

6 files changed

+104
-82
lines changed

6 files changed

+104
-82
lines changed

src/adc.rs

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -195,50 +195,36 @@ impl Adc {
195195
time: bb.topic_ro("/v1/tac/time/now", None),
196196
};
197197

198-
let adc_clone = adc.clone();
198+
let channels = [
199+
adc.usb_host_curr.clone(),
200+
adc.usb_host1_curr.clone(),
201+
adc.usb_host2_curr.clone(),
202+
adc.usb_host3_curr.clone(),
203+
adc.out0_volt.clone(),
204+
adc.out1_volt.clone(),
205+
adc.iobus_curr.clone(),
206+
adc.iobus_volt.clone(),
207+
adc.pwr_volt.clone(),
208+
adc.pwr_curr.clone(),
209+
];
210+
211+
let time = adc.time.clone();
199212

200213
// Spawn an async task to transfer values from the Atomic value based
201214
// "fast" interface to the broker based "slow" interface.
202215
spawn(async move {
203216
loop {
204217
sleep(SLOW_INTERVAL).await;
205218

206-
adc_clone
207-
.usb_host_curr
208-
.topic
209-
.set(adc_clone.usb_host_curr.fast.get());
210-
adc_clone
211-
.usb_host1_curr
212-
.topic
213-
.set(adc_clone.usb_host1_curr.fast.get());
214-
adc_clone
215-
.usb_host2_curr
216-
.topic
217-
.set(adc_clone.usb_host2_curr.fast.get());
218-
adc_clone
219-
.usb_host3_curr
220-
.topic
221-
.set(adc_clone.usb_host3_curr.fast.get());
222-
adc_clone
223-
.out0_volt
224-
.topic
225-
.set(adc_clone.out0_volt.fast.get());
226-
adc_clone
227-
.out1_volt
228-
.topic
229-
.set(adc_clone.out1_volt.fast.get());
230-
adc_clone
231-
.iobus_curr
232-
.topic
233-
.set(adc_clone.iobus_curr.fast.get());
234-
adc_clone
235-
.iobus_volt
236-
.topic
237-
.set(adc_clone.iobus_volt.fast.get());
238-
adc_clone.pwr_volt.topic.set(adc_clone.pwr_volt.fast.get());
239-
adc_clone.pwr_curr.topic.set(adc_clone.pwr_curr.fast.get());
240-
241-
adc_clone.time.set(Timestamp::now());
219+
for channel in &channels {
220+
if let Ok(val) = channel.fast.get() {
221+
// The adc channel topic should likely be wrapped in a Result
222+
// or otherwise be able to contain an error state.
223+
channel.topic.set(val)
224+
}
225+
}
226+
227+
time.set(Timestamp::now());
242228
}
243229
});
244230

src/adc/iio/demo_mode.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,18 @@ impl CalibratedChannel {
9898
pub fn try_get_multiple<const N: usize>(
9999
&self,
100100
channels: [&Self; N],
101-
) -> Option<[Measurement; N]> {
101+
) -> Result<[Measurement; N]> {
102102
let ts = Timestamp::now();
103103
let mut results = [Measurement { ts, value: 0.0 }; N];
104104

105105
for i in 0..N {
106-
results[i].value = channels[i].get().value;
106+
results[i].value = channels[i].get().unwrap().value;
107107
}
108108

109-
Some(results)
109+
Ok(results)
110110
}
111111

112-
pub fn get(&self) -> Measurement {
112+
pub fn get(&self) -> Result<Measurement> {
113113
let ts = Timestamp::now();
114114

115115
let dt = {
@@ -135,13 +135,13 @@ impl CalibratedChannel {
135135
.inner
136136
.parents
137137
.iter()
138-
.map(|p| p.get().value)
138+
.map(|p| p.get().unwrap().value)
139139
.sum::<f32>();
140140
value += nominal;
141141

142142
self.inner.value.store(value.to_bits(), Ordering::Relaxed);
143143

144-
Measurement { ts, value }
144+
Ok(Measurement { ts, value })
145145
}
146146

147147
pub fn set(&self, state: bool) {

src/adc/iio/hardware.rs

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ use std::time::{Duration, Instant};
2727

2828
use anyhow::{anyhow, Context, Error, Result};
2929
use async_std::channel::bounded;
30-
use async_std::stream::StreamExt;
3130
use async_std::sync::Arc;
3231

3332
use industrial_io::{Buffer, Channel};
@@ -106,8 +105,20 @@ const CHANNELS_PWR: &[ChannelDesc] = &[
106105

107106
const TRIGGER_HR_PWR_DIR: &str = "/sys/kernel/config/iio/triggers/hrtimer/tacd-pwr";
108107

108+
// Timestamps are stored in a 64 Bit atomic variable containing the
109+
// time in nanoseconds passed since the tacd was started.
110+
// To reach u64::MAX the tacd would need to run for 2^64ns which is
111+
// about 584 years.
109112
const TIMESTAMP_ERROR: u64 = u64::MAX;
110113

114+
#[derive(Debug)]
115+
pub enum AdcReadError {
116+
Again,
117+
MismatchedChannels,
118+
AquisitionError,
119+
TimeStampError,
120+
}
121+
111122
#[derive(Clone, Copy)]
112123
struct Calibration {
113124
scale: f32,
@@ -185,54 +196,56 @@ impl CalibratedChannel {
185196
pub fn try_get_multiple<const N: usize>(
186197
&self,
187198
channels: [&Self; N],
188-
) -> Option<[Measurement; N]> {
199+
) -> Result<[Measurement; N], AdcReadError> {
189200
let ts_before = self.iio_thread.timestamp.load(Ordering::Acquire);
190201

191202
let mut values_raw = [0; N];
192203
for (d, ch) in values_raw.iter_mut().zip(channels.iter()) {
193-
assert!(
194-
Arc::ptr_eq(&self.iio_thread, &ch.iio_thread),
195-
"Can only get synchronized adc values for the same thread"
196-
);
204+
// Can only get time-aligned values for channels of the same ADC
205+
if !Arc::ptr_eq(&self.iio_thread, &ch.iio_thread) {
206+
return Err(AdcReadError::MismatchedChannels);
207+
}
208+
197209
*d = self.iio_thread.values[ch.index].load(Ordering::Relaxed);
198210
}
199211

200212
let ts_after = self.iio_thread.timestamp.load(Ordering::Acquire);
201213

202214
if ts_before == TIMESTAMP_ERROR || ts_after == TIMESTAMP_ERROR {
203-
panic!("Failed to read from ADC");
215+
return Err(AdcReadError::AquisitionError);
204216
}
205217

206218
if ts_before == ts_after {
207219
let ts = self
208220
.iio_thread
209221
.ref_instant
210222
.checked_add(Duration::from_nanos(ts_before))
211-
.unwrap();
223+
.ok_or(AdcReadError::TimeStampError)?;
212224
let ts = Timestamp::new(ts);
213225

214226
let mut values = [Measurement { ts, value: 0.0 }; N];
215227
for i in 0..N {
216228
values[i].value = channels[i].calibration.apply(values_raw[i] as f32);
217229
}
218230

219-
Some(values)
231+
Ok(values)
220232
} else {
221-
None
233+
Err(AdcReadError::Again)
222234
}
223235
}
224236

225237
/// Get the value of the channel, or None if the timestamp changed while
226238
/// reading the value (which should be extremely rare)
227-
pub fn try_get(&self) -> Option<Measurement> {
239+
pub fn try_get(&self) -> Result<Measurement, AdcReadError> {
228240
self.try_get_multiple([self]).map(|res| res[0])
229241
}
230242

231243
// Get the current value of the channel
232-
pub fn get(&self) -> Measurement {
244+
pub fn get(&self) -> Result<Measurement, AdcReadError> {
233245
loop {
234-
if let Some(r) = self.try_get() {
235-
break r;
246+
match self.try_get() {
247+
Err(AdcReadError::Again) => {}
248+
res => break res,
236249
}
237250
}
238251
}
@@ -269,18 +282,23 @@ impl IioThread {
269282
warn!("Failed to disable {} ADC buffer: {}", adc_name, err);
270283
}
271284

272-
let channels: Vec<Channel> = channel_descs
285+
let channels: Result<Vec<Channel>> = channel_descs
273286
.iter()
274287
.map(|ChannelDesc { kernel_name, .. }| {
275288
let ch = adc
276289
.find_channel(kernel_name, false)
277-
.unwrap_or_else(|| panic!("Failed to open kernel channel {}", kernel_name));
290+
.ok_or_else(|| anyhow!("Failed to open iio channel {}", kernel_name));
291+
292+
if let Ok(ch) = ch.as_ref() {
293+
ch.enable();
294+
}
278295

279-
ch.enable();
280296
ch
281297
})
282298
.collect();
283299

300+
let channels = channels?;
301+
284302
let trig = ctx
285303
.find_device(trigger_name)
286304
.ok_or(anyhow!("Could not find IIO trigger: {}", trigger_name))?;
@@ -292,9 +310,13 @@ impl IioThread {
292310

293311
let buf = adc.create_buffer(buffer_len, false)?;
294312

313+
let prio = ThreadPriorityValue::try_from(10).map_err(|e| {
314+
anyhow!("Failed to set thread priority to 10 as you OS does not support it: {e:?}")
315+
})?;
316+
295317
set_thread_priority_and_policy(
296318
thread_native_id(),
297-
ThreadPriority::Crossplatform(ThreadPriorityValue::try_from(10).unwrap()),
319+
ThreadPriority::Crossplatform(prio),
298320
ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo),
299321
)
300322
.map_err(|e| anyhow!("Failed to set realtime thread priority: {e:?}"))?;
@@ -317,7 +339,7 @@ impl IioThread {
317339
// setup was sucessful.
318340
// This is why we create Self inside the thread and send it back
319341
// to the calling thread via a queue.
320-
let (thread_res_tx, mut thread_res_rx) = bounded(1);
342+
let (thread_res_tx, thread_res_rx) = bounded(1);
321343

322344
// Spawn a high priority thread that updates the atomic values in `thread`.
323345
let join = thread::Builder::new()
@@ -343,6 +365,8 @@ impl IioThread {
343365
(thread, channels, buf)
344366
}
345367
Err(e) => {
368+
// Can not fail in practice as the queue is known to be empty
369+
// at this point.
346370
thread_res_tx.try_send(Err(e)).unwrap();
347371
return;
348372
}
@@ -364,6 +388,8 @@ impl IioThread {
364388
// This gives us a chance to return an Err from new().
365389
// If the queue was already used just print an error instead.
366390
if let Some((_, tx)) = signal_ready.take() {
391+
// Can not fail in practice as the queue is only .take()n
392+
// once and thus known to be empty.
367393
tx.try_send(Err(Error::new(e))).unwrap();
368394
}
369395

@@ -379,24 +405,31 @@ impl IioThread {
379405
d.store(s, Ordering::Relaxed)
380406
}
381407

408+
// These should only fail if
409+
// a) The monotonic time started running backward
410+
// b) The tacd has been running for more than 2**64ns (584 years).
382411
let ts: u64 = Instant::now()
383412
.checked_duration_since(thread.ref_instant)
384-
.unwrap()
385-
.as_nanos()
386-
.try_into()
387-
.unwrap();
413+
.and_then(|d| d.as_nanos().try_into().ok())
414+
.unwrap_or(TIMESTAMP_ERROR);
388415

389416
thread.timestamp.store(ts, Ordering::Release);
390417

391418
// Now that we know that the ADC actually works and we have
392419
// initial values: return a handle to it.
393420
if let Some((content, tx)) = signal_ready.take() {
421+
// Can not fail in practice as the queue is only .take()n
422+
// once and thus known to be empty.
394423
tx.try_send(Ok(content)).unwrap();
395424
}
396425
}
397426
})?;
398427

399-
let thread = thread_res_rx.next().await.unwrap()?;
428+
let thread = thread_res_rx.recv().await??;
429+
430+
// Locking the Mutex could only fail if the Mutex was poisoned by
431+
// a thread that held the lock and panicked.
432+
// At this point the Mutex has not yet been locked in another thread.
400433
*thread.join.lock().unwrap() = Some(join);
401434

402435
Ok(thread)
@@ -418,7 +451,7 @@ impl IioThread {
418451
let hr_trigger_path = Path::new(TRIGGER_HR_PWR_DIR);
419452

420453
if !hr_trigger_path.is_dir() {
421-
create_dir(hr_trigger_path).unwrap();
454+
create_dir(hr_trigger_path)?;
422455
}
423456

424457
Self::new("powerboard", "lmp92064", "tacd-pwr", 20, CHANNELS_PWR, 1).await

src/adc/iio/test.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl CalibratedChannel {
5757
pub fn try_get_multiple<const N: usize>(
5858
&self,
5959
channels: [&Self; N],
60-
) -> Option<[Measurement; N]> {
60+
) -> Result<[Measurement; N]> {
6161
let mut ts = Timestamp::now();
6262

6363
if self.stall.load(Ordering::Relaxed) {
@@ -78,19 +78,15 @@ impl CalibratedChannel {
7878
results[i].value = f32::from_bits(val_u32);
7979
}
8080

81-
Some(results)
81+
Ok(results)
8282
}
8383

84-
pub fn try_get(&self) -> Option<Measurement> {
84+
pub fn try_get(&self) -> Result<Measurement> {
8585
self.try_get_multiple([self]).map(|res| res[0])
8686
}
8787

88-
pub fn get(&self) -> Measurement {
89-
loop {
90-
if let Some(r) = self.try_get() {
91-
break r;
92-
}
93-
}
88+
pub fn get(&self) -> Result<Measurement> {
89+
self.try_get()
9490
}
9591

9692
pub fn set(&self, val: f32) {

src/dut_power.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,12 @@ impl DutPwrThread {
353353
.fast
354354
.try_get_multiple([&pwr_volt.fast, &pwr_curr.fast]);
355355

356-
if let Some(m) = feedback {
356+
// We do not care too much about _why_ we could not get
357+
// a new value from the ADC.
358+
// If we get a new valid value before the timeout we
359+
// are fine.
360+
// If not we are not.
361+
if let Ok(m) = feedback {
357362
last_ts = Some(m[0].ts.as_instant());
358363
}
359364

@@ -374,7 +379,7 @@ impl DutPwrThread {
374379
tick.fetch_add(1, Ordering::Relaxed);
375380
}
376381

377-
if let Some(m) = feedback {
382+
if let Ok(m) = feedback {
378383
break (m[0].value, m[1].value);
379384
}
380385
};

src/iobus.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,12 @@ impl IoBus {
142142
let current = iobus_curr.get();
143143
let voltage = iobus_volt.get();
144144

145-
let undervolt = pwr_en && (voltage.value < VOLTAGE_MIN);
146-
let overcurrent = current.value > CURRENT_MAX;
145+
if let (Ok(current), Ok(voltage)) = (current, voltage) {
146+
let undervolt = pwr_en && (voltage.value < VOLTAGE_MIN);
147+
let overcurrent = current.value > CURRENT_MAX;
147148

148-
supply_fault_task.set_if_changed(undervolt || overcurrent);
149+
supply_fault_task.set_if_changed(undervolt || overcurrent);
150+
}
149151

150152
sleep(Duration::from_secs(1)).await;
151153
}

0 commit comments

Comments
 (0)