1use cu29::bundle_resources;
2use cu29::prelude::*;
3use cu29::resource::{ResourceBundle, ResourceManager};
4use embedded_io::{ErrorType as EmbeddedErrorType, Read as EmbeddedRead, Write as EmbeddedWrite};
5use serialport::{Parity as SerialParity, StopBits as SerialStopBits};
6use std::string::String;
7
8#[cfg(all(unix, feature = "serial-rts"))]
9mod serial_rts;
10#[cfg(all(unix, feature = "serial-rts"))]
11pub use serial_rts::{LinuxSerialRtsPin, SerialRtsError};
12
13pub const SERIAL0_DEV_KEY: &str = "serial0_dev";
14pub const SERIAL0_BAUDRATE_KEY: &str = "serial0_baudrate";
15pub const SERIAL0_PARITY_KEY: &str = "serial0_parity";
16pub const SERIAL0_STOPBITS_KEY: &str = "serial0_stopbits";
17pub const SERIAL0_TIMEOUT_MS_KEY: &str = "serial0_timeout_ms";
18pub const SERIAL0_NAME: &str = "serial0";
19
20pub const SERIAL1_DEV_KEY: &str = "serial1_dev";
21pub const SERIAL1_BAUDRATE_KEY: &str = "serial1_baudrate";
22pub const SERIAL1_PARITY_KEY: &str = "serial1_parity";
23pub const SERIAL1_STOPBITS_KEY: &str = "serial1_stopbits";
24pub const SERIAL1_TIMEOUT_MS_KEY: &str = "serial1_timeout_ms";
25pub const SERIAL1_NAME: &str = "serial1";
26
27pub const SERIAL2_DEV_KEY: &str = "serial2_dev";
28pub const SERIAL2_BAUDRATE_KEY: &str = "serial2_baudrate";
29pub const SERIAL2_PARITY_KEY: &str = "serial2_parity";
30pub const SERIAL2_STOPBITS_KEY: &str = "serial2_stopbits";
31pub const SERIAL2_TIMEOUT_MS_KEY: &str = "serial2_timeout_ms";
32pub const SERIAL2_NAME: &str = "serial2";
33
34pub const SERIAL3_DEV_KEY: &str = "serial3_dev";
35pub const SERIAL3_BAUDRATE_KEY: &str = "serial3_baudrate";
36pub const SERIAL3_PARITY_KEY: &str = "serial3_parity";
37pub const SERIAL3_STOPBITS_KEY: &str = "serial3_stopbits";
38pub const SERIAL3_TIMEOUT_MS_KEY: &str = "serial3_timeout_ms";
39pub const SERIAL3_NAME: &str = "serial3";
40
41pub const SERIAL4_DEV_KEY: &str = "serial4_dev";
42pub const SERIAL4_BAUDRATE_KEY: &str = "serial4_baudrate";
43pub const SERIAL4_PARITY_KEY: &str = "serial4_parity";
44pub const SERIAL4_STOPBITS_KEY: &str = "serial4_stopbits";
45pub const SERIAL4_TIMEOUT_MS_KEY: &str = "serial4_timeout_ms";
46pub const SERIAL4_NAME: &str = "serial4";
47
48pub const SERIAL5_DEV_KEY: &str = "serial5_dev";
49pub const SERIAL5_BAUDRATE_KEY: &str = "serial5_baudrate";
50pub const SERIAL5_PARITY_KEY: &str = "serial5_parity";
51pub const SERIAL5_STOPBITS_KEY: &str = "serial5_stopbits";
52pub const SERIAL5_TIMEOUT_MS_KEY: &str = "serial5_timeout_ms";
53pub const SERIAL5_NAME: &str = "serial5";
54
55pub const I2C0_DEV_KEY: &str = "i2c0_dev";
56pub const I2C1_DEV_KEY: &str = "i2c1_dev";
57pub const I2C2_DEV_KEY: &str = "i2c2_dev";
58pub const I2C0_NAME: &str = "i2c0";
59pub const I2C1_NAME: &str = "i2c1";
60pub const I2C2_NAME: &str = "i2c2";
61
62pub const GPIO0_NAME: &str = "gpio0";
63pub const GPIO1_NAME: &str = "gpio1";
64pub const GPIO2_NAME: &str = "gpio2";
65pub const GPIO3_NAME: &str = "gpio3";
66pub const GPIO4_NAME: &str = "gpio4";
67pub const GPIO5_NAME: &str = "gpio5";
68pub const GPIO0_PIN_KEY: &str = "gpio0_pin";
69pub const GPIO1_PIN_KEY: &str = "gpio1_pin";
70pub const GPIO2_PIN_KEY: &str = "gpio2_pin";
71pub const GPIO3_PIN_KEY: &str = "gpio3_pin";
72pub const GPIO4_PIN_KEY: &str = "gpio4_pin";
73pub const GPIO5_PIN_KEY: &str = "gpio5_pin";
74pub const GPIO0_DIRECTION_KEY: &str = "gpio0_direction";
75pub const GPIO1_DIRECTION_KEY: &str = "gpio1_direction";
76pub const GPIO2_DIRECTION_KEY: &str = "gpio2_direction";
77pub const GPIO3_DIRECTION_KEY: &str = "gpio3_direction";
78pub const GPIO4_DIRECTION_KEY: &str = "gpio4_direction";
79pub const GPIO5_DIRECTION_KEY: &str = "gpio5_direction";
80pub const GPIO0_BIAS_KEY: &str = "gpio0_bias";
81pub const GPIO1_BIAS_KEY: &str = "gpio1_bias";
82pub const GPIO2_BIAS_KEY: &str = "gpio2_bias";
83pub const GPIO3_BIAS_KEY: &str = "gpio3_bias";
84pub const GPIO4_BIAS_KEY: &str = "gpio4_bias";
85pub const GPIO5_BIAS_KEY: &str = "gpio5_bias";
86pub const GPIO0_INITIAL_LEVEL_KEY: &str = "gpio0_initial_level";
87pub const GPIO1_INITIAL_LEVEL_KEY: &str = "gpio1_initial_level";
88pub const GPIO2_INITIAL_LEVEL_KEY: &str = "gpio2_initial_level";
89pub const GPIO3_INITIAL_LEVEL_KEY: &str = "gpio3_initial_level";
90pub const GPIO4_INITIAL_LEVEL_KEY: &str = "gpio4_initial_level";
91pub const GPIO5_INITIAL_LEVEL_KEY: &str = "gpio5_initial_level";
92
93pub const DEFAULT_SERIAL_BAUDRATE: u32 = 115_200;
94pub const DEFAULT_SERIAL_TIMEOUT_MS: u64 = 50;
95pub const DEFAULT_SERIAL_PARITY: SerialParity = SerialParity::None;
96pub const DEFAULT_SERIAL_STOPBITS: SerialStopBits = SerialStopBits::One;
97
98pub struct Exclusive<T>(T);
104
105impl<T> Exclusive<T> {
106 pub const fn new(inner: T) -> Self {
107 Self(inner)
108 }
109
110 pub fn into_inner(self) -> T {
111 self.0
112 }
113
114 pub fn get_mut(&mut self) -> &mut T {
115 &mut self.0
116 }
117}
118
119unsafe impl<T: Send> Sync for Exclusive<T> {}
122
123impl<T: std::io::Read> std::io::Read for Exclusive<T> {
124 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
125 self.0.read(buf)
126 }
127}
128
129impl<T: std::io::Write> std::io::Write for Exclusive<T> {
130 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
131 self.0.write(buf)
132 }
133
134 fn flush(&mut self) -> std::io::Result<()> {
135 self.0.flush()
136 }
137}
138
139impl<T: EmbeddedErrorType> EmbeddedErrorType for Exclusive<T> {
140 type Error = T::Error;
141}
142
143impl<T: EmbeddedRead> EmbeddedRead for Exclusive<T> {
144 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
145 self.0.read(buf)
146 }
147}
148
149impl<T: EmbeddedWrite> EmbeddedWrite for Exclusive<T> {
150 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
151 self.0.write(buf)
152 }
153
154 fn flush(&mut self) -> Result<(), Self::Error> {
155 self.0.flush()
156 }
157}
158
159impl<T> embedded_hal::i2c::ErrorType for Exclusive<T>
160where
161 T: embedded_hal::i2c::ErrorType,
162{
163 type Error = T::Error;
164}
165
166impl<T> embedded_hal::i2c::I2c for Exclusive<T>
167where
168 T: embedded_hal::i2c::I2c,
169{
170 fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
171 self.0.read(address, read)
172 }
173
174 fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
175 self.0.write(address, write)
176 }
177
178 fn write_read(
179 &mut self,
180 address: u8,
181 write: &[u8],
182 read: &mut [u8],
183 ) -> Result<(), Self::Error> {
184 self.0.write_read(address, write, read)
185 }
186
187 fn transaction(
188 &mut self,
189 address: u8,
190 operations: &mut [embedded_hal::i2c::Operation<'_>],
191 ) -> Result<(), Self::Error> {
192 self.0.transaction(address, operations)
193 }
194}
195
196impl<T> embedded_hal::digital::ErrorType for Exclusive<T>
197where
198 T: embedded_hal::digital::ErrorType,
199{
200 type Error = T::Error;
201}
202
203impl<T> embedded_hal::digital::OutputPin for Exclusive<T>
204where
205 T: embedded_hal::digital::OutputPin,
206{
207 fn set_low(&mut self) -> Result<(), Self::Error> {
208 self.0.set_low()
209 }
210
211 fn set_high(&mut self) -> Result<(), Self::Error> {
212 self.0.set_high()
213 }
214}
215
216impl<T> embedded_hal::digital::StatefulOutputPin for Exclusive<T>
217where
218 T: embedded_hal::digital::StatefulOutputPin,
219{
220 fn is_set_high(&mut self) -> Result<bool, Self::Error> {
221 self.0.is_set_high()
222 }
223
224 fn is_set_low(&mut self) -> Result<bool, Self::Error> {
225 self.0.is_set_low()
226 }
227}
228
229impl<T> embedded_hal::digital::InputPin for Exclusive<T>
230where
231 T: embedded_hal::digital::InputPin,
232{
233 fn is_high(&mut self) -> Result<bool, Self::Error> {
234 self.0.is_high()
235 }
236
237 fn is_low(&mut self) -> Result<bool, Self::Error> {
238 self.0.is_low()
239 }
240}
241
242pub struct LinuxSerialPort {
243 inner: Exclusive<Box<dyn serialport::SerialPort>>,
244}
245
246impl LinuxSerialPort {
247 #[cfg(all(unix, feature = "serial-rts"))]
249 pub fn open_with_rts(config: &SerialSlotConfig) -> std::io::Result<(Self, LinuxSerialRtsPin)> {
250 let (port, rts) = serial_rts::open(config)?;
251 Ok((Self::new(Box::new(port)), rts))
252 }
253
254 pub fn new(inner: Box<dyn serialport::SerialPort>) -> Self {
255 Self {
256 inner: Exclusive::new(inner),
257 }
258 }
259
260 pub fn open(dev: &str, baudrate: u32, timeout_ms: u64) -> std::io::Result<Self> {
261 let config = SerialSlotConfig {
262 dev: dev.to_string(),
263 baudrate,
264 parity: DEFAULT_SERIAL_PARITY,
265 stop_bits: DEFAULT_SERIAL_STOPBITS,
266 timeout_ms,
267 };
268 Self::open_with_config(&config)
269 }
270
271 pub fn open_with_config(config: &SerialSlotConfig) -> std::io::Result<Self> {
272 let port = serialport::new(config.dev.as_str(), config.baudrate)
273 .parity(config.parity)
274 .stop_bits(config.stop_bits)
275 .timeout(std::time::Duration::from_millis(config.timeout_ms))
276 .open()?;
277 Ok(Self::new(port))
278 }
279}
280
281impl std::io::Read for LinuxSerialPort {
282 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
283 self.inner.read(buf)
284 }
285}
286
287impl std::io::Write for LinuxSerialPort {
288 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
289 self.inner.write(buf)
290 }
291
292 fn flush(&mut self) -> std::io::Result<()> {
293 self.inner.flush()
294 }
295}
296
297impl embedded_io::ErrorType for LinuxSerialPort {
298 type Error = std::io::Error;
299}
300
301impl EmbeddedRead for LinuxSerialPort {
302 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
303 std::io::Read::read(self, buf)
304 }
305}
306
307impl EmbeddedWrite for LinuxSerialPort {
308 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
309 std::io::Write::write(self, buf)
310 }
311
312 fn flush(&mut self) -> Result<(), Self::Error> {
313 std::io::Write::flush(self)
314 }
315}
316
317#[cfg(unix)]
320#[derive(Debug)]
321pub struct LinuxNonblockingSerialPort(std::fs::File);
322
323#[cfg(unix)]
324impl LinuxNonblockingSerialPort {
325 #[cfg(feature = "serial-rts")]
327 pub fn open_with_rts(config: &SerialSlotConfig) -> std::io::Result<(Self, LinuxSerialRtsPin)> {
328 let (port, rts) = serial_rts::open(config)?;
329 Ok((Self::from_native(port)?, rts))
330 }
331
332 pub fn open_with_config(config: &SerialSlotConfig) -> std::io::Result<Self> {
333 let port = serialport::new(&config.dev, config.baudrate)
334 .parity(config.parity)
335 .stop_bits(config.stop_bits)
336 .open_native()?;
337 Self::from_native(port)
338 }
339
340 pub fn from_native(port: serialport::TTYPort) -> std::io::Result<Self> {
342 use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd};
343 let fd = port.as_raw_fd();
344 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
346 if flags < 0 || unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
347 return Err(std::io::Error::last_os_error());
348 }
349 Ok(Self(unsafe {
351 std::fs::File::from_raw_fd(port.into_raw_fd())
352 }))
353 }
354
355 fn result(result: std::io::Result<usize>) -> std::io::Result<usize> {
356 match result {
357 Err(error)
358 if matches!(
359 error.kind(),
360 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::Interrupted
361 ) =>
362 {
363 Ok(0)
364 }
365 other => other,
366 }
367 }
368}
369#[cfg(unix)]
370impl embedded_io::ErrorType for LinuxNonblockingSerialPort {
371 type Error = std::io::Error;
372}
373#[cfg(unix)]
374impl cu_serial::SerialIo for LinuxNonblockingSerialPort {
375 fn try_read(&mut self, bytes: &mut [u8]) -> std::io::Result<usize> {
376 let result = std::io::Read::read(&mut self.0, bytes);
377 if !bytes.is_empty() && matches!(result, Ok(0)) {
378 return Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
379 }
380 Self::result(result)
381 }
382 fn try_write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
383 Self::result(std::io::Write::write(&mut self.0, bytes))
384 }
385}
386
387#[cfg(target_os = "linux")]
388pub type LinuxI2c = Exclusive<linux_embedded_hal::I2cdev>;
389#[cfg(target_os = "linux")]
390pub type LinuxOutputPin = Exclusive<rppal::gpio::IoPin>;
391#[cfg(target_os = "linux")]
392pub type LinuxInputPin = Exclusive<rppal::gpio::InputPin>;
393
394pub struct LinuxResources;
395
396bundle_resources!(
397 LinuxResources:
398 Serial0 = "serial0",
399 Serial1 = "serial1",
400 Serial2 = "serial2",
401 Serial3 = "serial3",
402 Serial4 = "serial4",
403 Serial5 = "serial5",
404 I2c0 = "i2c0",
405 I2c1 = "i2c1",
406 I2c2 = "i2c2",
407 Gpio0 = "gpio0",
408 Gpio1 = "gpio1",
409 Gpio2 = "gpio2",
410 Gpio3 = "gpio3",
411 Gpio4 = "gpio4",
412 Gpio5 = "gpio5",
413 Serial0Rts = "serial0_rts",
414 Serial1Rts = "serial1_rts",
415 Serial2Rts = "serial2_rts",
416 Serial3Rts = "serial3_rts",
417 Serial4Rts = "serial4_rts",
418 Serial5Rts = "serial5_rts"
419);
420
421const LINUX_RESOURCE_SLOT_NAMES: &[&str] = &[
422 SERIAL0_NAME,
423 SERIAL1_NAME,
424 SERIAL2_NAME,
425 SERIAL3_NAME,
426 SERIAL4_NAME,
427 SERIAL5_NAME,
428 I2C0_NAME,
429 I2C1_NAME,
430 I2C2_NAME,
431 GPIO0_NAME,
432 GPIO1_NAME,
433 GPIO2_NAME,
434 GPIO3_NAME,
435 GPIO4_NAME,
436 GPIO5_NAME,
437 "serial0_rts",
438 "serial1_rts",
439 "serial2_rts",
440 "serial3_rts",
441 "serial4_rts",
442 "serial5_rts",
443];
444
445struct SerialSlot {
446 id: LinuxResourcesId,
447 rts_id: LinuxResourcesId,
448 dev_key: &'static str,
449 baudrate_key: &'static str,
450 parity_key: &'static str,
451 stopbits_key: &'static str,
452 timeout_ms_key: &'static str,
453}
454
455#[derive(Clone, Debug)]
456pub struct SerialSlotConfig {
457 pub dev: String,
458 pub baudrate: u32,
459 pub parity: SerialParity,
460 pub stop_bits: SerialStopBits,
461 pub timeout_ms: u64,
462}
463
464const SERIAL_SLOTS: &[SerialSlot] = &[
465 SerialSlot {
466 id: LinuxResourcesId::Serial0,
467 rts_id: LinuxResourcesId::Serial0Rts,
468 dev_key: SERIAL0_DEV_KEY,
469 baudrate_key: SERIAL0_BAUDRATE_KEY,
470 parity_key: SERIAL0_PARITY_KEY,
471 stopbits_key: SERIAL0_STOPBITS_KEY,
472 timeout_ms_key: SERIAL0_TIMEOUT_MS_KEY,
473 },
474 SerialSlot {
475 id: LinuxResourcesId::Serial1,
476 rts_id: LinuxResourcesId::Serial1Rts,
477 dev_key: SERIAL1_DEV_KEY,
478 baudrate_key: SERIAL1_BAUDRATE_KEY,
479 parity_key: SERIAL1_PARITY_KEY,
480 stopbits_key: SERIAL1_STOPBITS_KEY,
481 timeout_ms_key: SERIAL1_TIMEOUT_MS_KEY,
482 },
483 SerialSlot {
484 id: LinuxResourcesId::Serial2,
485 rts_id: LinuxResourcesId::Serial2Rts,
486 dev_key: SERIAL2_DEV_KEY,
487 baudrate_key: SERIAL2_BAUDRATE_KEY,
488 parity_key: SERIAL2_PARITY_KEY,
489 stopbits_key: SERIAL2_STOPBITS_KEY,
490 timeout_ms_key: SERIAL2_TIMEOUT_MS_KEY,
491 },
492 SerialSlot {
493 id: LinuxResourcesId::Serial3,
494 rts_id: LinuxResourcesId::Serial3Rts,
495 dev_key: SERIAL3_DEV_KEY,
496 baudrate_key: SERIAL3_BAUDRATE_KEY,
497 parity_key: SERIAL3_PARITY_KEY,
498 stopbits_key: SERIAL3_STOPBITS_KEY,
499 timeout_ms_key: SERIAL3_TIMEOUT_MS_KEY,
500 },
501 SerialSlot {
502 id: LinuxResourcesId::Serial4,
503 rts_id: LinuxResourcesId::Serial4Rts,
504 dev_key: SERIAL4_DEV_KEY,
505 baudrate_key: SERIAL4_BAUDRATE_KEY,
506 parity_key: SERIAL4_PARITY_KEY,
507 stopbits_key: SERIAL4_STOPBITS_KEY,
508 timeout_ms_key: SERIAL4_TIMEOUT_MS_KEY,
509 },
510 SerialSlot {
511 id: LinuxResourcesId::Serial5,
512 rts_id: LinuxResourcesId::Serial5Rts,
513 dev_key: SERIAL5_DEV_KEY,
514 baudrate_key: SERIAL5_BAUDRATE_KEY,
515 parity_key: SERIAL5_PARITY_KEY,
516 stopbits_key: SERIAL5_STOPBITS_KEY,
517 timeout_ms_key: SERIAL5_TIMEOUT_MS_KEY,
518 },
519];
520
521#[cfg(target_os = "linux")]
522struct I2cSlot {
523 id: LinuxResourcesId,
524 dev_key: &'static str,
525}
526
527#[cfg(target_os = "linux")]
528const I2C_SLOTS: &[I2cSlot] = &[
529 I2cSlot {
530 id: LinuxResourcesId::I2c0,
531 dev_key: I2C0_DEV_KEY,
532 },
533 I2cSlot {
534 id: LinuxResourcesId::I2c1,
535 dev_key: I2C1_DEV_KEY,
536 },
537 I2cSlot {
538 id: LinuxResourcesId::I2c2,
539 dev_key: I2C2_DEV_KEY,
540 },
541];
542
543#[cfg_attr(not(any(target_os = "linux", test)), allow(dead_code))]
544struct GpioSlot {
545 id: LinuxResourcesId,
546 name: &'static str,
547 pin_key: &'static str,
548 direction_key: &'static str,
549 bias_key: &'static str,
550 initial_level_key: &'static str,
551}
552
553macro_rules! gpio_slot {
554 ($id:ident, $name:ident, $pin_key:ident, $direction_key:ident, $bias_key:ident, $initial_level_key:ident) => {
555 GpioSlot {
556 id: LinuxResourcesId::$id,
557 name: $name,
558 pin_key: $pin_key,
559 direction_key: $direction_key,
560 bias_key: $bias_key,
561 initial_level_key: $initial_level_key,
562 }
563 };
564}
565
566#[cfg(any(target_os = "linux", test))]
567#[derive(Copy, Clone, Debug, Eq, PartialEq)]
568enum GpioDirection {
569 Input,
570 Output,
571}
572
573#[cfg(any(target_os = "linux", test))]
574#[derive(Copy, Clone, Debug, Eq, PartialEq)]
575enum GpioBias {
576 Off,
577 PullDown,
578 PullUp,
579}
580
581#[cfg(any(target_os = "linux", test))]
582impl GpioBias {
583 #[cfg(target_os = "linux")]
584 const fn into_rppal(self) -> rppal::gpio::Bias {
585 match self {
586 GpioBias::Off => rppal::gpio::Bias::Off,
587 GpioBias::PullDown => rppal::gpio::Bias::PullDown,
588 GpioBias::PullUp => rppal::gpio::Bias::PullUp,
589 }
590 }
591}
592
593#[cfg(any(target_os = "linux", test))]
594#[derive(Copy, Clone, Debug, Eq, PartialEq)]
595enum GpioInitialLevel {
596 Low,
597 High,
598}
599
600#[cfg(any(target_os = "linux", test))]
601#[derive(Copy, Clone, Debug, Eq, PartialEq)]
602struct GpioSlotConfig {
603 pin: u8,
604 direction: GpioDirection,
605 bias: GpioBias,
606 initial_level: Option<GpioInitialLevel>,
607}
608
609const GPIO_SLOTS: &[GpioSlot] = &[
610 gpio_slot!(
611 Gpio0,
612 GPIO0_NAME,
613 GPIO0_PIN_KEY,
614 GPIO0_DIRECTION_KEY,
615 GPIO0_BIAS_KEY,
616 GPIO0_INITIAL_LEVEL_KEY
617 ),
618 gpio_slot!(
619 Gpio1,
620 GPIO1_NAME,
621 GPIO1_PIN_KEY,
622 GPIO1_DIRECTION_KEY,
623 GPIO1_BIAS_KEY,
624 GPIO1_INITIAL_LEVEL_KEY
625 ),
626 gpio_slot!(
627 Gpio2,
628 GPIO2_NAME,
629 GPIO2_PIN_KEY,
630 GPIO2_DIRECTION_KEY,
631 GPIO2_BIAS_KEY,
632 GPIO2_INITIAL_LEVEL_KEY
633 ),
634 gpio_slot!(
635 Gpio3,
636 GPIO3_NAME,
637 GPIO3_PIN_KEY,
638 GPIO3_DIRECTION_KEY,
639 GPIO3_BIAS_KEY,
640 GPIO3_INITIAL_LEVEL_KEY
641 ),
642 gpio_slot!(
643 Gpio4,
644 GPIO4_NAME,
645 GPIO4_PIN_KEY,
646 GPIO4_DIRECTION_KEY,
647 GPIO4_BIAS_KEY,
648 GPIO4_INITIAL_LEVEL_KEY
649 ),
650 gpio_slot!(
651 Gpio5,
652 GPIO5_NAME,
653 GPIO5_PIN_KEY,
654 GPIO5_DIRECTION_KEY,
655 GPIO5_BIAS_KEY,
656 GPIO5_INITIAL_LEVEL_KEY
657 ),
658];
659
660impl ResourceBundle for LinuxResources {
661 fn build(
662 bundle: cu29::resource::BundleContext<Self>,
663 config: Option<&ComponentConfig>,
664 manager: &mut ResourceManager,
665 ) -> CuResult<()> {
666 for slot in SERIAL_SLOTS {
667 let rts = read_serial_rts_config(config, slot)?;
668 let Some(serial_config) = read_serial_slot_config(config, slot)? else {
669 continue; };
671 let nonblocking_key = format!("{}_nonblocking", slot_name(slot.id));
672 let nonblocking = config
673 .and_then(|cfg| cfg.get::<bool>(&nonblocking_key).transpose())
674 .transpose()?
675 .unwrap_or(false);
676 if rts {
677 #[cfg(all(unix, feature = "serial-rts"))]
678 {
679 if nonblocking {
680 let (serial, pin) = LinuxNonblockingSerialPort::open_with_rts(
681 &serial_config,
682 )
683 .map_err(|err| CuError::new_with_cause("Open serial RTS resource", err))?;
684 manager.add_owned(bundle.key(slot.id), serial)?;
685 manager.add_owned(bundle.key(slot.rts_id), pin)?;
686 } else {
687 let (serial, pin) = LinuxSerialPort::open_with_rts(&serial_config)
688 .map_err(|err| {
689 CuError::new_with_cause("Open serial RTS resource", err)
690 })?;
691 manager.add_owned(bundle.key(slot.id), serial)?;
692 manager.add_owned(bundle.key(slot.rts_id), pin)?;
693 }
694 continue;
695 }
696 #[cfg(not(all(unix, feature = "serial-rts")))]
697 return Err(CuError::from(
698 "Serial RTS requires Unix and the cu-linux-resources/serial-rts feature",
699 ));
700 }
701 if nonblocking {
702 #[cfg(unix)]
703 {
704 let serial = LinuxNonblockingSerialPort::open_with_config(&serial_config)
705 .map_err(|err| {
706 CuError::new_with_cause("Open nonblocking serial resource", err)
707 })?;
708 manager.add_owned(bundle.key(slot.id), serial)?;
709 continue;
710 }
711 #[cfg(not(unix))]
712 return Err(CuError::from("Nonblocking serial resources require Unix"));
713 }
714 match LinuxSerialPort::open_with_config(&serial_config) {
715 Ok(serial) => {
716 manager.add_owned(bundle.key(slot.id), serial)?;
717 }
718 Err(err) => {
719 warning!(
720 "LinuxResources: skipping serial slot {} (dev {}): {}",
721 slot_name(slot.id),
722 serial_config.dev,
723 err.to_string()
724 );
725 }
726 }
727 }
728
729 #[cfg(target_os = "linux")]
730 for slot in I2C_SLOTS {
731 let Some(dev) = get_string(config, slot.dev_key)? else {
732 continue; };
734 match linux_embedded_hal::I2cdev::new(&dev) {
735 Ok(i2c) => {
736 manager.add_owned(bundle.key(slot.id), Exclusive::new(i2c))?;
737 }
738 Err(err) => {
739 warning!(
740 "LinuxResources: skipping i2c slot {} (dev {}): {}",
741 slot_name(slot.id),
742 dev,
743 err.to_string()
744 );
745 }
746 }
747 }
748
749 #[cfg(target_os = "linux")]
750 {
751 let mut configured_gpio_slots: std::vec::Vec<(
752 LinuxResourcesId,
753 &'static str,
754 GpioSlotConfig,
755 )> = std::vec::Vec::new();
756
757 for slot in GPIO_SLOTS {
758 let Some(slot_config) = read_gpio_slot_config(config, slot)? else {
759 continue;
760 };
761 configured_gpio_slots.push((slot.id, slot.name, slot_config));
762 }
763
764 if !configured_gpio_slots.is_empty() {
765 let gpio = rppal::gpio::Gpio::new().map_err(|err| {
766 CuError::new_with_cause("Failed to initialize GPIO subsystem", err)
767 })?;
768
769 for (slot_id, slot_name, slot_config) in configured_gpio_slots {
770 let pin = match gpio.get(slot_config.pin) {
771 Ok(pin) => pin,
772 Err(err) => {
773 warning!(
774 "LinuxResources: skipping gpio slot {} (pin {}): {}",
775 slot_name,
776 slot_config.pin,
777 err.to_string()
778 );
779 continue;
780 }
781 };
782 match slot_config.direction {
783 GpioDirection::Input => {
784 let mut pin = pin.into_input();
785 pin.set_bias(slot_config.bias.into_rppal());
786 manager.add_owned(bundle.key(slot_id), Exclusive::new(pin))?;
787 }
788 GpioDirection::Output => {
789 let mut pin = pin.into_io(rppal::gpio::Mode::Output);
790 pin.set_bias(slot_config.bias.into_rppal());
791 if let Some(initial_level) = slot_config.initial_level {
792 match initial_level {
793 GpioInitialLevel::Low => pin.set_low(),
794 GpioInitialLevel::High => pin.set_high(),
795 }
796 }
797 manager.add_owned(bundle.key(slot_id), Exclusive::new(pin))?;
798 }
799 }
800 }
801 }
802 }
803
804 #[cfg(not(target_os = "linux"))]
805 {
806 for slot in GPIO_SLOTS {
807 if let Some(pin) = get_u8(config, slot.pin_key)? {
808 warning!(
809 "LinuxResources: requested gpio slot {} on pin {} but GPIO is only supported on Linux",
810 slot_name(slot.id),
811 pin
812 );
813 }
814 }
815 }
816
817 Ok(())
818 }
819}
820
821fn read_serial_slot_config(
822 config: Option<&ComponentConfig>,
823 slot: &SerialSlot,
824) -> CuResult<Option<SerialSlotConfig>> {
825 let Some(dev) = get_string(config, slot.dev_key)? else {
826 return Ok(None); };
828 let baudrate = get_u32(config, slot.baudrate_key)?.unwrap_or(DEFAULT_SERIAL_BAUDRATE);
829 let parity = get_serial_parity(config, slot.parity_key)?.unwrap_or(DEFAULT_SERIAL_PARITY);
830 let stop_bits =
831 get_serial_stop_bits(config, slot.stopbits_key)?.unwrap_or(DEFAULT_SERIAL_STOPBITS);
832 let timeout_ms = get_u64(config, slot.timeout_ms_key)?.unwrap_or(DEFAULT_SERIAL_TIMEOUT_MS);
833
834 Ok(Some(SerialSlotConfig {
835 dev,
836 baudrate,
837 parity,
838 stop_bits,
839 timeout_ms,
840 }))
841}
842
843#[cfg(any(target_os = "linux", test))]
844fn read_gpio_slot_config(
845 config: Option<&ComponentConfig>,
846 slot: &GpioSlot,
847) -> CuResult<Option<GpioSlotConfig>> {
848 let pin = get_u8(config, slot.pin_key)?;
849 let direction = get_string(config, slot.direction_key)?;
850 let bias = get_string(config, slot.bias_key)?;
851 let initial_level = get_string(config, slot.initial_level_key)?;
852
853 let Some(pin) = pin else {
854 if direction.is_some() || bias.is_some() || initial_level.is_some() {
855 return Err(CuError::from(format!(
856 "Config key '{}' is required when configuring {}",
857 slot.pin_key, slot.name
858 )));
859 }
860 return Ok(None);
861 };
862
863 let direction_raw = direction.ok_or_else(|| {
864 CuError::from(format!(
865 "Config key '{}' is required when '{}' is set",
866 slot.direction_key, slot.pin_key
867 ))
868 })?;
869 let direction = parse_gpio_direction_value(direction_raw.as_str())?;
870
871 let bias = match bias {
872 Some(raw) => parse_gpio_bias_value(raw.as_str())?,
873 None => GpioBias::Off,
874 };
875
876 let initial_level = match initial_level {
877 Some(raw) => Some(parse_gpio_initial_level_value(raw.as_str())?),
878 None => None,
879 };
880
881 if matches!(direction, GpioDirection::Input) && initial_level.is_some() {
882 return Err(CuError::from(format!(
883 "Config key '{}' is only valid when '{}' is 'output'",
884 slot.initial_level_key, slot.direction_key
885 )));
886 }
887
888 Ok(Some(GpioSlotConfig {
889 pin,
890 direction,
891 bias,
892 initial_level,
893 }))
894}
895
896fn get_serial_parity(
897 config: Option<&ComponentConfig>,
898 key: &str,
899) -> CuResult<Option<SerialParity>> {
900 let Some(raw) = get_string(config, key)? else {
901 return Ok(None);
902 };
903 Ok(Some(parse_serial_parity_value(raw.as_str())?))
904}
905
906fn get_serial_stop_bits(
907 config: Option<&ComponentConfig>,
908 key: &str,
909) -> CuResult<Option<SerialStopBits>> {
910 let Some(raw) = get_u8(config, key)? else {
911 return Ok(None);
912 };
913 Ok(Some(parse_serial_stop_bits_value(raw)?))
914}
915
916fn parse_serial_parity_value(raw: &str) -> CuResult<SerialParity> {
917 let normalized = raw.trim().to_ascii_lowercase();
918 match normalized.as_str() {
919 "none" => Ok(SerialParity::None),
920 "odd" => Ok(SerialParity::Odd),
921 "even" => Ok(SerialParity::Even),
922 _ => Err(CuError::from(format!(
923 "Invalid parity '{raw}'. Expected one of: none, odd, even"
924 ))),
925 }
926}
927
928fn parse_serial_stop_bits_value(raw: u8) -> CuResult<SerialStopBits> {
929 match raw {
930 1 => Ok(SerialStopBits::One),
931 2 => Ok(SerialStopBits::Two),
932 _ => Err(CuError::from(format!(
933 "Invalid stopbits value '{raw}'. Expected 1 or 2"
934 ))),
935 }
936}
937
938#[cfg(any(target_os = "linux", test))]
939fn parse_gpio_direction_value(raw: &str) -> CuResult<GpioDirection> {
940 let normalized = raw.trim().to_ascii_lowercase();
941 match normalized.as_str() {
942 "input" => Ok(GpioDirection::Input),
943 "output" => Ok(GpioDirection::Output),
944 _ => Err(CuError::from(format!(
945 "Invalid GPIO direction '{raw}'. Expected one of: input, output"
946 ))),
947 }
948}
949
950#[cfg(any(target_os = "linux", test))]
951fn parse_gpio_bias_value(raw: &str) -> CuResult<GpioBias> {
952 let normalized = raw.trim().to_ascii_lowercase();
953 match normalized.as_str() {
954 "off" | "none" => Ok(GpioBias::Off),
955 "pull_down" | "pulldown" => Ok(GpioBias::PullDown),
956 "pull_up" | "pullup" => Ok(GpioBias::PullUp),
957 _ => Err(CuError::from(format!(
958 "Invalid GPIO bias '{raw}'. Expected one of: off, pull_up, pull_down"
959 ))),
960 }
961}
962
963#[cfg(any(target_os = "linux", test))]
964fn parse_gpio_initial_level_value(raw: &str) -> CuResult<GpioInitialLevel> {
965 let normalized = raw.trim().to_ascii_lowercase();
966 match normalized.as_str() {
967 "low" => Ok(GpioInitialLevel::Low),
968 "high" => Ok(GpioInitialLevel::High),
969 _ => Err(CuError::from(format!(
970 "Invalid GPIO initial level '{raw}'. Expected one of: low, high"
971 ))),
972 }
973}
974
975fn read_serial_rts_config(config: Option<&ComponentConfig>, slot: &SerialSlot) -> CuResult<bool> {
976 let key = slot_name(slot.rts_id);
977 let enabled = config
978 .and_then(|cfg| cfg.get::<bool>(key).transpose())
979 .transpose()?
980 .unwrap_or(false);
981 if enabled && get_string(config, slot.dev_key)?.is_none() {
982 return Err(CuError::from(format!("{key} requires {}", slot.dev_key)));
983 }
984 Ok(enabled)
985}
986
987fn slot_name(id: LinuxResourcesId) -> &'static str {
988 LINUX_RESOURCE_SLOT_NAMES[id as usize]
989}
990
991fn get_string(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<String>> {
992 match config {
993 Some(cfg) => Ok(cfg.get::<String>(key)?.filter(|value| !value.is_empty())),
994 None => Ok(None),
995 }
996}
997
998fn get_u8(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<u8>> {
999 match config {
1000 Some(cfg) => Ok(cfg.get::<u8>(key)?),
1001 None => Ok(None),
1002 }
1003}
1004
1005fn get_u32(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<u32>> {
1006 match config {
1007 Some(cfg) => Ok(cfg.get::<u32>(key)?),
1008 None => Ok(None),
1009 }
1010}
1011
1012fn get_u64(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<u64>> {
1013 match config {
1014 Some(cfg) => Ok(cfg.get::<u64>(key)?),
1015 None => Ok(None),
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022 use cu29::resource::{NamedResourceBundleDecl, resource_index_by_name};
1023
1024 #[test]
1025 fn named_resource_bundle_decl_matches_public_linux_slot_names() {
1026 let declared = <LinuxResources as NamedResourceBundleDecl>::NAMES;
1027 assert_eq!(declared, LINUX_RESOURCE_SLOT_NAMES);
1028
1029 for (idx, name) in declared.iter().enumerate() {
1030 assert_eq!(resource_index_by_name::<LinuxResources>(name), idx);
1031 }
1032 }
1033
1034 fn rts_config(value: &str) -> ComponentConfig {
1035 let ron = format!(
1036 r#"(resources: [(id: "board", provider: "cu_linux_resources::LinuxResources",
1037 config: {{ "serial0_rts": {value} }})], tasks: [], cnx: [])"#,
1038 );
1039 cu29::config::CuConfig::deserialize_ron(&ron)
1040 .unwrap()
1041 .resources
1042 .remove(0)
1043 .config
1044 .unwrap()
1045 }
1046
1047 #[test]
1048 fn serial_rts_is_opt_in_and_requires_a_device() {
1049 let slot = &SERIAL_SLOTS[0];
1050 assert!(!read_serial_rts_config(None, slot).unwrap());
1051 let mut config = rts_config("false");
1052 assert!(!read_serial_rts_config(Some(&config), slot).unwrap());
1053 config = rts_config("true");
1054 assert!(read_serial_rts_config(Some(&config), slot).is_err());
1055 config.set("serial0_dev", "/dev/ttyUSB0".to_string());
1056 assert!(read_serial_rts_config(Some(&config), slot).unwrap());
1057 assert!(!read_serial_rts_config(Some(&config), &SERIAL_SLOTS[1]).unwrap());
1058 config.set("serial0_rts", "invalid".to_string());
1059 assert!(read_serial_rts_config(Some(&config), slot).is_err());
1060 }
1061
1062 #[cfg(not(feature = "serial-rts"))]
1063 #[test]
1064 fn serial_rts_request_without_feature_fails_before_opening_the_device() {
1065 let mut config = rts_config("true");
1066 config.set("serial0_dev", "/not/a/serial/device".to_string());
1067 let mut manager = ResourceManager::new(&[LINUX_RESOURCE_SLOT_NAMES.len()]);
1068 let error = LinuxResources::build(
1069 cu29::resource::BundleContext::new(cu29::resource::BundleIndex::new(0), "board"),
1070 Some(&config),
1071 &mut manager,
1072 )
1073 .unwrap_err();
1074 assert!(error.to_string().contains("serial-rts feature"));
1075 }
1076
1077 #[test]
1078 fn parse_serial_parity_value_accepts_expected_inputs() {
1079 assert!(matches!(
1080 parse_serial_parity_value("none").unwrap(),
1081 SerialParity::None
1082 ));
1083 assert!(matches!(
1084 parse_serial_parity_value("Odd").unwrap(),
1085 SerialParity::Odd
1086 ));
1087 assert!(matches!(
1088 parse_serial_parity_value("EVEN").unwrap(),
1089 SerialParity::Even
1090 ));
1091 }
1092
1093 #[test]
1094 fn parse_serial_parity_value_rejects_invalid_input() {
1095 assert!(parse_serial_parity_value("mark").is_err());
1096 }
1097
1098 #[test]
1099 fn parse_serial_stop_bits_value_accepts_expected_inputs() {
1100 assert!(matches!(
1101 parse_serial_stop_bits_value(1).unwrap(),
1102 SerialStopBits::One
1103 ));
1104 assert!(matches!(
1105 parse_serial_stop_bits_value(2).unwrap(),
1106 SerialStopBits::Two
1107 ));
1108 }
1109
1110 #[test]
1111 fn parse_serial_stop_bits_value_rejects_invalid_input() {
1112 assert!(parse_serial_stop_bits_value(0).is_err());
1113 assert!(parse_serial_stop_bits_value(3).is_err());
1114 }
1115
1116 #[test]
1117 fn parse_gpio_direction_value_accepts_expected_inputs() {
1118 assert!(matches!(
1119 parse_gpio_direction_value("input").unwrap(),
1120 GpioDirection::Input
1121 ));
1122 assert!(matches!(
1123 parse_gpio_direction_value("Output").unwrap(),
1124 GpioDirection::Output
1125 ));
1126 }
1127
1128 #[test]
1129 fn parse_gpio_direction_value_rejects_invalid_input() {
1130 assert!(parse_gpio_direction_value("io").is_err());
1131 }
1132
1133 #[test]
1134 fn parse_gpio_bias_value_accepts_expected_inputs() {
1135 assert!(matches!(
1136 parse_gpio_bias_value("off").unwrap(),
1137 GpioBias::Off
1138 ));
1139 assert!(matches!(
1140 parse_gpio_bias_value("pull_down").unwrap(),
1141 GpioBias::PullDown
1142 ));
1143 assert!(matches!(
1144 parse_gpio_bias_value("PullUp").unwrap(),
1145 GpioBias::PullUp
1146 ));
1147 }
1148
1149 #[test]
1150 fn parse_gpio_bias_value_rejects_invalid_input() {
1151 assert!(parse_gpio_bias_value("hold").is_err());
1152 }
1153
1154 #[test]
1155 fn parse_gpio_initial_level_value_accepts_expected_inputs() {
1156 assert!(matches!(
1157 parse_gpio_initial_level_value("low").unwrap(),
1158 GpioInitialLevel::Low
1159 ));
1160 assert!(matches!(
1161 parse_gpio_initial_level_value("HIGH").unwrap(),
1162 GpioInitialLevel::High
1163 ));
1164 }
1165
1166 #[test]
1167 fn parse_gpio_initial_level_value_rejects_invalid_input() {
1168 assert!(parse_gpio_initial_level_value("toggle").is_err());
1169 }
1170
1171 #[test]
1172 fn read_gpio_slot_config_requires_pin_when_aux_keys_are_present() {
1173 let mut cfg = ComponentConfig::new();
1174 cfg.set(GPIO0_DIRECTION_KEY, "input".to_string());
1175 assert!(read_gpio_slot_config(Some(&cfg), &GPIO_SLOTS[0]).is_err());
1176 }
1177
1178 #[test]
1179 fn read_gpio_slot_config_requires_direction_when_pin_is_set() {
1180 let mut cfg = ComponentConfig::new();
1181 cfg.set(GPIO0_PIN_KEY, 23_u8);
1182 assert!(read_gpio_slot_config(Some(&cfg), &GPIO_SLOTS[0]).is_err());
1183 }
1184
1185 #[test]
1186 fn read_gpio_slot_config_defaults_bias_and_preserves_level_for_output() {
1187 let mut cfg = ComponentConfig::new();
1188 cfg.set(GPIO0_PIN_KEY, 23_u8);
1189 cfg.set(GPIO0_DIRECTION_KEY, "output".to_string());
1190
1191 let slot_config = read_gpio_slot_config(Some(&cfg), &GPIO_SLOTS[0])
1192 .unwrap()
1193 .expect("slot should be configured");
1194 assert_eq!(
1195 slot_config,
1196 GpioSlotConfig {
1197 pin: 23,
1198 direction: GpioDirection::Output,
1199 bias: GpioBias::Off,
1200 initial_level: None,
1201 }
1202 );
1203 }
1204
1205 #[test]
1206 fn read_gpio_slot_config_parses_explicit_initial_level_for_output() {
1207 let mut cfg = ComponentConfig::new();
1208 cfg.set(GPIO0_PIN_KEY, 23_u8);
1209 cfg.set(GPIO0_DIRECTION_KEY, "output".to_string());
1210 cfg.set(GPIO0_INITIAL_LEVEL_KEY, "high".to_string());
1211
1212 let slot_config = read_gpio_slot_config(Some(&cfg), &GPIO_SLOTS[0])
1213 .unwrap()
1214 .expect("slot should be configured");
1215 assert_eq!(
1216 slot_config,
1217 GpioSlotConfig {
1218 pin: 23,
1219 direction: GpioDirection::Output,
1220 bias: GpioBias::Off,
1221 initial_level: Some(GpioInitialLevel::High),
1222 }
1223 );
1224 }
1225
1226 #[test]
1227 fn read_gpio_slot_config_rejects_initial_level_for_input() {
1228 let mut cfg = ComponentConfig::new();
1229 cfg.set(GPIO0_PIN_KEY, 23_u8);
1230 cfg.set(GPIO0_DIRECTION_KEY, "input".to_string());
1231 cfg.set(GPIO0_INITIAL_LEVEL_KEY, "high".to_string());
1232 assert!(read_gpio_slot_config(Some(&cfg), &GPIO_SLOTS[0]).is_err());
1233 }
1234
1235 struct MockIo {
1236 rx: [u8; 4],
1237 rx_len: usize,
1238 tx: [u8; 4],
1239 tx_len: usize,
1240 }
1241
1242 impl MockIo {
1243 fn new(rx: &[u8]) -> Self {
1244 let mut buf = [0_u8; 4];
1245 buf[..rx.len()].copy_from_slice(rx);
1246 Self {
1247 rx: buf,
1248 rx_len: rx.len(),
1249 tx: [0; 4],
1250 tx_len: 0,
1251 }
1252 }
1253 }
1254
1255 impl embedded_io::ErrorType for MockIo {
1256 type Error = core::convert::Infallible;
1257 }
1258
1259 impl embedded_io::Read for MockIo {
1260 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
1261 let n = core::cmp::min(buf.len(), self.rx_len);
1262 buf[..n].copy_from_slice(&self.rx[..n]);
1263 Ok(n)
1264 }
1265 }
1266
1267 impl embedded_io::Write for MockIo {
1268 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
1269 let n = core::cmp::min(buf.len(), self.tx.len());
1270 self.tx[..n].copy_from_slice(&buf[..n]);
1271 self.tx_len = n;
1272 Ok(n)
1273 }
1274
1275 fn flush(&mut self) -> Result<(), Self::Error> {
1276 Ok(())
1277 }
1278 }
1279
1280 #[test]
1281 fn exclusive_forwards_embedded_io_traits() {
1282 let mut wrapped = Exclusive::new(MockIo::new(&[1, 2, 3]));
1283
1284 let mut rx = [0_u8; 4];
1285 let read = embedded_io::Read::read(&mut wrapped, &mut rx).unwrap();
1286 assert_eq!(read, 3);
1287 assert_eq!(&rx[..3], &[1, 2, 3]);
1288
1289 let written = embedded_io::Write::write(&mut wrapped, &[9, 8]).unwrap();
1290 assert_eq!(written, 2);
1291 embedded_io::Write::flush(&mut wrapped).unwrap();
1292
1293 let inner = wrapped.into_inner();
1294 assert_eq!(&inner.tx[..inner.tx_len], &[9, 8]);
1295 }
1296}
1297
1298#[cfg(all(test, unix))]
1299mod nonblocking_tests {
1300 use super::*;
1301 use cu_serial::SerialIo;
1302 #[test]
1303 fn native_uart_reports_backpressure_and_preserves_binary_bytes() {
1304 let (a, b) = serialport::TTYPort::pair().unwrap();
1305 let mut tx = LinuxNonblockingSerialPort::from_native(a).unwrap();
1306 let mut rx = LinuxNonblockingSerialPort::from_native(b).unwrap();
1307 let mut buf = [0; 16];
1308 assert_eq!(rx.try_read(&mut buf).unwrap(), 0);
1309 assert_eq!(tx.try_write(&[0, 0x7e, 0x7d, 255]).unwrap(), 4);
1310 let mut received = 0;
1311 for _ in 0..1000 {
1312 received += rx.try_read(&mut buf[received..4]).unwrap();
1313 if received == 4 {
1314 break;
1315 }
1316 std::thread::yield_now();
1317 }
1318 assert_eq!(&buf[..received], &[0, 0x7e, 0x7d, 255]);
1319 let data = [42; 65536];
1320 let written = tx.try_write(&data).unwrap();
1321 assert!(written > 0 && written < data.len());
1322 let mut blocked = false;
1323 for _ in 0..128 {
1324 if tx.try_write(&data).unwrap() == 0 {
1325 blocked = true;
1326 break;
1327 }
1328 }
1329 assert!(blocked);
1330 }
1331}