@@ -11,3 +11,6 @@ libc = ">=0.2.39"
libgpiod-sys = { path = "../libgpiod-sys" }
thiserror = "1.0"
vmm-sys-util = "=0.10.0"
+
+[dev-dependencies]
+gpiosim = { path = "../gpiosim" }
new file mode 100644
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod chip {
+ use libc::{ENODEV, ENOENT, ENOTTY};
+ use std::path::PathBuf;
+
+ use vmm_sys_util::errno::Error as Errno;
+
+ use gpiosim::Sim;
+ use libgpiod::{chip::Chip, Error as ChipError, OperationType};
+
+ mod open {
+ use super::*;
+
+ #[test]
+ fn nonexistent_file() {
+ assert_eq!(
+ Chip::open(&PathBuf::from("/dev/nonexistent")).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipOpen, Errno::new(ENOENT))
+ );
+ }
+
+ #[test]
+ fn no_dev_file() {
+ assert_eq!(
+ Chip::open(&PathBuf::from("/tmp")).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipOpen, Errno::new(ENOTTY))
+ );
+ }
+
+ #[test]
+ fn non_gpio_char_dev_file() {
+ assert_eq!(
+ Chip::open(&PathBuf::from("/dev/null")).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipOpen, Errno::new(ENODEV))
+ );
+ }
+
+ #[test]
+ fn gpiosim_file() {
+ let sim = Sim::new(None, None, true).unwrap();
+ assert!(Chip::open(&sim.dev_path()).is_ok());
+ }
+ }
+
+ mod verify {
+ use super::*;
+ const NGPIO: u64 = 16;
+ const LABEL: &str = "foobar";
+
+ #[test]
+ fn basic_helpers() {
+ let sim = Sim::new(Some(NGPIO), Some(LABEL), true).unwrap();
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ assert_eq!(chip.label().unwrap(), LABEL);
+ assert_eq!(chip.name().unwrap(), sim.chip_name());
+ assert_eq!(chip.path().unwrap(), sim.dev_path().to_str().unwrap());
+ assert_eq!(chip.num_lines(), NGPIO as usize);
+ assert!(chip.fd().is_ok());
+ }
+
+ #[test]
+ fn find_line() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.set_line_name(0, "zero").unwrap();
+ sim.set_line_name(2, "two").unwrap();
+ sim.set_line_name(3, "three").unwrap();
+ sim.set_line_name(5, "five").unwrap();
+ sim.set_line_name(10, "ten").unwrap();
+ sim.set_line_name(11, "ten").unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ // Success case
+ assert_eq!(chip.line_offset_from_name("zero").unwrap(), 0);
+ assert_eq!(chip.line_offset_from_name("two").unwrap(), 2);
+ assert_eq!(chip.line_offset_from_name("three").unwrap(), 3);
+ assert_eq!(chip.line_offset_from_name("five").unwrap(), 5);
+
+ // Success with duplicate names, should return first entry
+ assert_eq!(chip.line_offset_from_name("ten").unwrap(), 10);
+
+ // Failure
+ assert_eq!(
+ chip.line_offset_from_name("nonexistent").unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipGetLine, Errno::new(ENOENT))
+ );
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+use std::sync::Arc;
+use std::time::Duration;
+
+use gpiosim::Sim;
+use libgpiod::{
+ chip::Chip, line, request, Bias, Direction, Drive, Edge, EventClock, Offset, Result,
+ SettingVal, Value,
+};
+
+pub(crate) struct TestConfig {
+ sim: Arc<Sim>,
+ chip: Option<Chip>,
+ request: Option<line::Request>,
+ rconfig: request::Config,
+ lconfig: line::Config,
+ lsettings: Option<line::Settings>,
+}
+
+impl TestConfig {
+ pub(crate) fn new(ngpio: u64) -> Result<Self> {
+ Ok(Self {
+ sim: Arc::new(Sim::new(Some(ngpio), None, true)?),
+ chip: None,
+ request: None,
+ rconfig: request::Config::new().unwrap(),
+ lconfig: line::Config::new().unwrap(),
+ lsettings: Some(line::Settings::new().unwrap()),
+ })
+ }
+
+ pub(crate) fn set_pull(&self, offsets: &[Offset], pulls: &[u32]) {
+ for i in 0..pulls.len() {
+ self.sim.set_pull(offsets[i], pulls[i] as i32).unwrap();
+ }
+ }
+
+ pub(crate) fn rconfig_set_consumer(&self, consumer: &str) {
+ self.rconfig.set_consumer(consumer);
+ }
+
+ pub(crate) fn lconfig_val(&mut self, dir: Option<Direction>, val: Option<Value>) {
+ let mut settings = Vec::new();
+
+ if let Some(dir) = dir {
+ settings.push(SettingVal::Direction(dir));
+ }
+
+ if let Some(val) = val {
+ settings.push(SettingVal::OutputValue(val));
+ }
+
+ if !settings.is_empty() {
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+ }
+
+ pub(crate) fn lconfig_bias(&mut self, dir: Direction, bias: Option<Bias>) {
+ let settings = vec![SettingVal::Direction(dir), SettingVal::Bias(bias)];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_clock(&mut self, clock: EventClock) {
+ let settings = vec![SettingVal::EventClock(clock)];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_debounce(&mut self, duration: Duration) {
+ let settings = vec![
+ SettingVal::Direction(Direction::Input),
+ SettingVal::DebouncePeriod(duration),
+ ];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_drive(&mut self, dir: Direction, drive: Drive) {
+ let settings = vec![SettingVal::Direction(dir), SettingVal::Drive(drive)];
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_edge(&mut self, dir: Option<Direction>, edge: Option<Edge>) {
+ let mut settings = Vec::new();
+
+ if let Some(dir) = dir {
+ settings.push(SettingVal::Direction(dir));
+ }
+
+ settings.push(SettingVal::EdgeDetection(edge));
+ self.lsettings().set_prop(&settings).unwrap();
+ }
+
+ pub(crate) fn lconfig_add_settings(&mut self, offsets: &[Offset]) {
+ self.lconfig
+ .add_line_settings(offsets, self.lsettings.take().unwrap())
+ .unwrap()
+ }
+
+ pub(crate) fn request_lines(&mut self) -> Result<()> {
+ let chip = Chip::open(&self.sim.dev_path())?;
+
+ self.request = Some(chip.request_lines(&self.rconfig, &self.lconfig)?);
+ self.chip = Some(chip);
+
+ Ok(())
+ }
+
+ pub(crate) fn sim(&self) -> Arc<Sim> {
+ self.sim.clone()
+ }
+
+ pub(crate) fn chip(&self) -> &Chip {
+ self.chip.as_ref().unwrap()
+ }
+
+ pub(crate) fn lsettings(&mut self) -> &mut line::Settings {
+ self.lsettings.as_mut().unwrap()
+ }
+
+ pub(crate) fn request(&self) -> &line::Request {
+ self.request.as_ref().unwrap()
+ }
+}
+
+impl Drop for TestConfig {
+ fn drop(&mut self) {
+ // Explicit freeing is important to make sure "request" get freed
+ // before "sim" and "chip".
+ self.request = None;
+ }
+}
new file mode 100644
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+#[allow(dead_code)]
+mod config;
+
+#[allow(unused_imports)]
+pub(crate) use config::*;
new file mode 100644
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod edge_event {
+ use std::sync::Arc;
+ use std::thread::{sleep, spawn};
+ use std::time::Duration;
+
+ use crate::common::*;
+ use gpiosim::Sim;
+ use gpiosim::{GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP};
+ use libgpiod::{edge, Edge, EdgeKind, Offset};
+
+ const NGPIO: u64 = 8;
+
+ mod buffer_capacity {
+ use super::*;
+
+ #[test]
+ fn default_capacity() {
+ assert_eq!(edge::event::Buffer::new(0).unwrap().capacity(), 64);
+ }
+
+ #[test]
+ fn user_defined_capacity() {
+ assert_eq!(edge::event::Buffer::new(123).unwrap().capacity(), 123);
+ }
+
+ #[test]
+ fn max_capacity() {
+ assert_eq!(edge::event::Buffer::new(1024 * 2).unwrap().capacity(), 1024);
+ }
+ }
+
+ mod trigger {
+ use super::*;
+
+ // Helpers to generate events
+ fn trigger_falling_and_rising_edge(sim: Arc<Sim>, offset: Offset) {
+ spawn(move || {
+ sleep(Duration::from_millis(30));
+ sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+
+ sleep(Duration::from_millis(30));
+ sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
+ });
+ }
+
+ fn trigger_rising_edge_events_on_two_offsets(sim: Arc<Sim>, offset: [Offset; 2]) {
+ spawn(move || {
+ sleep(Duration::from_millis(30));
+ sim.set_pull(offset[0], GPIOSIM_PULL_UP as i32).unwrap();
+
+ sleep(Duration::from_millis(30));
+ sim.set_pull(offset[1], GPIOSIM_PULL_UP as i32).unwrap();
+ });
+ }
+
+ fn trigger_multiple_events(sim: Arc<Sim>, offset: Offset) {
+ sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+ sleep(Duration::from_millis(10));
+
+ sim.set_pull(offset, GPIOSIM_PULL_DOWN as i32).unwrap();
+ sleep(Duration::from_millis(10));
+
+ sim.set_pull(offset, GPIOSIM_PULL_UP as i32).unwrap();
+ sleep(Duration::from_millis(10));
+ }
+
+ #[test]
+ fn both_edges() {
+ const GPIO: Offset = 2;
+ let buf = edge::event::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+ // Rising event
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 1);
+ assert_eq!(buf.len(), 1);
+
+ let event = buf.event(0).unwrap();
+ let ts_rising = event.timestamp();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // Falling event
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 1);
+ assert_eq!(buf.len(), 1);
+
+ let event = buf.event(0).unwrap();
+ let ts_falling = event.timestamp();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Falling);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_event(Some(Duration::from_millis(100)))
+ .unwrap());
+
+ assert!(ts_falling > ts_rising);
+ }
+
+ #[test]
+ fn rising_edge() {
+ const GPIO: Offset = 6;
+ let buf = edge::event::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Rising));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+ // Rising event
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 1);
+ assert_eq!(buf.len(), 1);
+
+ let event = buf.event(0).unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_event(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn falling_edge() {
+ const GPIO: Offset = 7;
+ let buf = edge::event::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Falling));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_falling_and_rising_edge(config.sim(), GPIO);
+
+ // Falling event
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 1);
+ assert_eq!(buf.len(), 1);
+
+ let event = buf.event(0).unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Falling);
+ assert_eq!(event.line_offset(), GPIO);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_event(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn edge_sequence() {
+ const GPIO: [u32; 2] = [0, 1];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&GPIO);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_rising_edge_events_on_two_offsets(config.sim(), GPIO);
+
+ // Rising event GPIO 0
+ let buf = edge::event::Buffer::new(0).unwrap();
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 1);
+ assert_eq!(buf.len(), 1);
+
+ let event = buf.event(0).unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO[0]);
+ assert_eq!(event.global_seqno(), 1);
+ assert_eq!(event.line_seqno(), 1);
+
+ // Rising event GPIO 1
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 1);
+ assert_eq!(buf.len(), 1);
+
+ let event = buf.event(0).unwrap();
+ assert_eq!(event.event_type().unwrap(), EdgeKind::Rising);
+ assert_eq!(event.line_offset(), GPIO[1]);
+ assert_eq!(event.global_seqno(), 2);
+ assert_eq!(event.line_seqno(), 1);
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_event(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn multiple_events() {
+ const GPIO: Offset = 1;
+ let buf = edge::event::Buffer::new(0).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_multiple_events(config.sim(), GPIO);
+
+ // Read multiple events
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 3);
+ assert_eq!(buf.len(), 3);
+
+ let mut global_seqno = 1;
+ let mut line_seqno = 1;
+
+ // Verify sequence number of events
+ for i in 0..3 {
+ let event = buf.event(i).unwrap();
+ assert_eq!(event.line_offset(), GPIO);
+ assert_eq!(event.global_seqno(), global_seqno);
+ assert_eq!(event.line_seqno(), line_seqno);
+
+ global_seqno += 1;
+ line_seqno += 1;
+ }
+ }
+
+ #[test]
+ fn over_capacity() {
+ const GPIO: Offset = 2;
+ let buf = edge::event::Buffer::new(2).unwrap();
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ // Generate events
+ trigger_multiple_events(config.sim(), GPIO);
+
+ // Read multiple events
+ assert!(config
+ .request()
+ .wait_edge_event(Some(Duration::from_secs(1)))
+ .unwrap());
+
+ assert_eq!(config.request().read_edge_events(&buf).unwrap(), 2);
+ assert_eq!(buf.len(), 2);
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod info_event {
+ use libc::EINVAL;
+ use std::sync::Arc;
+ use std::thread::{sleep, spawn};
+ use std::time::Duration;
+
+ use vmm_sys_util::errno::Error as Errno;
+
+ use gpiosim::Sim;
+ use libgpiod::{
+ chip::Chip, line, request, Direction, Error as ChipError, InfoChangeKind, Offset,
+ OperationType, SettingVal,
+ };
+
+ fn request_reconfigure_line(chip: Arc<Chip>) {
+ spawn(move || {
+ sleep(Duration::from_millis(10));
+
+ let lconfig1 = line::Config::new().unwrap();
+ let lsettings = line::Settings::new().unwrap();
+ lconfig1.add_line_settings(&[7], lsettings).unwrap();
+ let rconfig = request::Config::new().unwrap();
+
+ let request = chip.request_lines(&rconfig, &lconfig1).unwrap();
+
+ sleep(Duration::from_millis(10));
+
+ let lconfig2 = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[SettingVal::Direction(Direction::Output)])
+ .unwrap();
+ lconfig2.add_line_settings(&[7], lsettings).unwrap();
+
+ request.reconfigure_lines(&lconfig2).unwrap();
+
+ sleep(Duration::from_millis(10));
+ });
+ }
+
+ mod watch {
+ use super::*;
+ const NGPIO: u64 = 8;
+ const GPIO: Offset = 7;
+
+ #[test]
+ fn line_info() {
+ let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ assert_eq!(
+ chip.watch_line_info(NGPIO as u32).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipWatchLineInfo, Errno::new(EINVAL))
+ );
+
+ let info = chip.watch_line_info(GPIO).unwrap();
+ assert_eq!(info.offset(), GPIO);
+
+ // No events available
+ assert!(!chip
+ .wait_info_event(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+
+ #[test]
+ fn reconfigure() {
+ let sim = Sim::new(Some(NGPIO), None, true).unwrap();
+ let chip = Arc::new(Chip::open(&sim.dev_path()).unwrap());
+ let info = chip.watch_line_info(GPIO).unwrap();
+
+ assert_eq!(info.direction().unwrap(), Direction::Input);
+
+ // Generate events
+ request_reconfigure_line(chip.clone());
+
+ // Line requested event
+ assert!(chip.wait_info_event(Some(Duration::from_secs(1))).unwrap());
+ let event = chip.read_info_event().unwrap();
+ let ts_req = event.timestamp();
+
+ assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineRequested);
+ assert_eq!(
+ event.line_info().unwrap().direction().unwrap(),
+ Direction::Input
+ );
+
+ // Line changed event
+ assert!(chip.wait_info_event(Some(Duration::from_secs(1))).unwrap());
+ let event = chip.read_info_event().unwrap();
+ let ts_rec = event.timestamp();
+
+ assert_eq!(
+ event.event_type().unwrap(),
+ InfoChangeKind::LineConfigChanged
+ );
+ assert_eq!(
+ event.line_info().unwrap().direction().unwrap(),
+ Direction::Output
+ );
+
+ // Line released event
+ assert!(chip.wait_info_event(Some(Duration::from_secs(1))).unwrap());
+ let event = chip.read_info_event().unwrap();
+ let ts_rel = event.timestamp();
+
+ assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineReleased);
+
+ // No events available
+ assert!(!chip
+ .wait_info_event(Some(Duration::from_millis(100)))
+ .unwrap());
+
+ // Check timestamps are really monotonic.
+ assert!(ts_rel > ts_rec);
+ assert!(ts_rec > ts_req);
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_config {
+ use libgpiod::{
+ line, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value,
+ };
+
+ #[test]
+ fn settings() {
+ let mut lsettings1 = line::Settings::new().unwrap();
+ lsettings1
+ .set_prop(&[SettingVal::Direction(Direction::Input)])
+ .unwrap();
+ lsettings1
+ .set_prop(&[SettingVal::EdgeDetection(Some(Edge::Both))])
+ .unwrap();
+ lsettings1
+ .set_prop(&[SettingVal::Bias(Some(Bias::PullDown))])
+ .unwrap();
+ lsettings1
+ .set_prop(&[SettingVal::Drive(Drive::PushPull)])
+ .unwrap();
+
+ let mut lsettings2 = line::Settings::new().unwrap();
+ lsettings2
+ .set_prop(&[SettingVal::Direction(Direction::Output)])
+ .unwrap();
+ lsettings2.set_prop(&[SettingVal::ActiveLow(true)]).unwrap();
+ lsettings2
+ .set_prop(&[SettingVal::EventClock(EventClock::Realtime)])
+ .unwrap();
+ lsettings2
+ .set_prop(&[SettingVal::OutputValue(Value::Active)])
+ .unwrap();
+
+ // Add settings for multiple lines
+ let lconfig = line::Config::new().unwrap();
+ lconfig.add_line_settings(&[0, 1, 2], lsettings1).unwrap();
+ lconfig.add_line_settings(&[4, 5], lsettings2).unwrap();
+
+ // Retrieve settings
+ let lsettings = lconfig.line_settings(1).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Input)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Both))
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(Some(Bias::PullDown))
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::PushPull)
+ );
+
+ let lsettings = lconfig.line_settings(5).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Output)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(true)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Realtime)
+ );
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::Active)
+ );
+ }
+
+ #[test]
+ fn offsets() {
+ let mut lsettings1 = line::Settings::new().unwrap();
+ lsettings1
+ .set_prop(&[SettingVal::Direction(Direction::Input)])
+ .unwrap();
+
+ let mut lsettings2 = line::Settings::new().unwrap();
+ lsettings2
+ .set_prop(&[SettingVal::EventClock(EventClock::Realtime)])
+ .unwrap();
+
+ // Add settings for multiple lines
+ let lconfig = line::Config::new().unwrap();
+ lconfig.add_line_settings(&[0, 1, 2], lsettings1).unwrap();
+ lconfig.add_line_settings(&[4, 5], lsettings2).unwrap();
+
+ // Verify offsets
+ let offsets = lconfig.offsets().unwrap();
+ assert_eq!(offsets, [0, 1, 2, 4, 5]);
+ }
+}
new file mode 100644
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_info {
+ use libc::EINVAL;
+ use std::time::Duration;
+
+ use vmm_sys_util::errno::Error as Errno;
+
+ use crate::common::*;
+ use gpiosim::Sim;
+ use gpiosim::{GPIOSIM_HOG_DIR_INPUT, GPIOSIM_HOG_DIR_OUTPUT_HIGH, GPIOSIM_HOG_DIR_OUTPUT_LOW};
+ use libgpiod::{
+ chip::Chip, Bias, Direction, Drive, Edge, Error as ChipError, EventClock, OperationType,
+ };
+
+ const NGPIO: u64 = 8;
+
+ mod properties {
+ use super::*;
+
+ #[test]
+ fn default() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.set_line_name(4, "four").unwrap();
+ sim.hog_line(4, "hog4", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
+ .unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info4 = chip.line_info(4).unwrap();
+ assert_eq!(info4.offset(), 4);
+ assert_eq!(info4.name().unwrap(), "four");
+ assert!(info4.is_used());
+ assert_eq!(info4.consumer().unwrap(), "hog4");
+ assert_eq!(info4.direction().unwrap(), Direction::Output);
+ assert!(!info4.is_active_low());
+ assert_eq!(info4.bias().unwrap(), None);
+ assert_eq!(info4.drive().unwrap(), Drive::PushPull);
+ assert_eq!(info4.edge_detection().unwrap(), None);
+ assert_eq!(info4.event_clock().unwrap(), EventClock::Monotonic);
+ assert!(!info4.is_debounced());
+ assert_eq!(info4.debounce_period(), Duration::from_millis(0));
+
+ assert_eq!(
+ chip.line_info(NGPIO as u32).unwrap_err(),
+ ChipError::OperationFailed(OperationType::ChipGetLineInfo, Errno::new(EINVAL))
+ );
+ }
+
+ #[test]
+ fn name_and_offset() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+
+ // Line 0 has no name
+ for i in 1..NGPIO {
+ sim.set_line_name(i as u32, &i.to_string()).unwrap();
+ }
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+ let info = chip.line_info(0).unwrap();
+
+ assert_eq!(info.offset(), 0);
+ assert_eq!(
+ info.name().unwrap_err(),
+ ChipError::NullString("GPIO line's name")
+ );
+
+ for i in 1..NGPIO {
+ let info = chip.line_info(i as u32).unwrap();
+
+ assert_eq!(info.offset(), i as u32);
+ assert_eq!(info.name().unwrap(), &i.to_string());
+ }
+ }
+
+ #[test]
+ fn is_used() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.hog_line(0, "hog", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+ .unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info = chip.line_info(0).unwrap();
+ assert!(info.is_used());
+
+ let info = chip.line_info(1).unwrap();
+ assert!(!info.is_used());
+ }
+
+ #[test]
+ fn consumer() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.hog_line(0, "hog", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+ .unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info = chip.line_info(0).unwrap();
+ assert_eq!(info.consumer().unwrap(), "hog");
+
+ let info = chip.line_info(1).unwrap();
+ assert_eq!(
+ info.consumer().unwrap_err(),
+ ChipError::NullString("GPIO line's consumer name")
+ );
+ }
+
+ #[test]
+ fn direction() {
+ let sim = Sim::new(Some(NGPIO), None, false).unwrap();
+ sim.hog_line(0, "hog", GPIOSIM_HOG_DIR_INPUT as i32)
+ .unwrap();
+ sim.hog_line(1, "hog", GPIOSIM_HOG_DIR_OUTPUT_HIGH as i32)
+ .unwrap();
+ sim.hog_line(2, "hog", GPIOSIM_HOG_DIR_OUTPUT_LOW as i32)
+ .unwrap();
+ sim.enable().unwrap();
+
+ let chip = Chip::open(&sim.dev_path()).unwrap();
+
+ let info = chip.line_info(0).unwrap();
+ assert_eq!(info.direction().unwrap(), Direction::Input);
+
+ let info = chip.line_info(1).unwrap();
+ assert_eq!(info.direction().unwrap(), Direction::Output);
+
+ let info = chip.line_info(2).unwrap();
+ assert_eq!(info.direction().unwrap(), Direction::Output);
+ }
+
+ #[test]
+ fn bias() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), None);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::PullUp));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullUp));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::PullDown));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullDown));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::Disabled));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::Disabled));
+ }
+
+ #[test]
+ fn drive() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_drive(Direction::Input, Drive::PushPull);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_drive(Direction::Output, Drive::OpenDrain);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenDrain);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_drive(Direction::Output, Drive::OpenSource);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenSource);
+ }
+
+ #[test]
+ fn edge() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), None);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Input), Some(Edge::Both));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Input), Some(Edge::Rising));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Input), Some(Edge::Falling));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling));
+ }
+
+ #[test]
+ fn clock() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_clock(EventClock::Monotonic);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_clock(EventClock::Realtime);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Realtime);
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_clock(EventClock::HTE);
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::HTE);
+ }
+
+ #[test]
+ fn debounce() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(!info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(0));
+
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_debounce(Duration::from_millis(100));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(100));
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_request {
+ use libc::{E2BIG, EINVAL};
+ use std::time::Duration;
+
+ use vmm_sys_util::errno::Error as Errno;
+
+ use crate::common::*;
+ use gpiosim::{
+ GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP, GPIOSIM_VALUE_ACTIVE, GPIOSIM_VALUE_INACTIVE,
+ };
+ use libgpiod::{
+ line, Bias, Direction, Drive, Edge, Error as ChipError, EventClock, Offset, OperationType,
+ SettingVal, Value, ValueMap,
+ };
+
+ const NGPIO: u64 = 8;
+
+ mod invalid_arguments {
+ use super::*;
+
+ #[test]
+ fn no_offsets() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+
+ assert_eq!(
+ config.request_lines().unwrap_err(),
+ ChipError::OperationFailed(OperationType::LineRequest, Errno::new(EINVAL))
+ );
+ }
+
+ #[test]
+ fn out_of_bound_offsets() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[2, 0, 8, 4]);
+
+ assert_eq!(
+ config.request_lines().unwrap_err(),
+ ChipError::OperationFailed(OperationType::LineRequest, Errno::new(EINVAL))
+ );
+ }
+
+ #[test]
+ fn dir_out_edge_failure() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(Some(Direction::Output), Some(Edge::Both));
+ config.lconfig_add_settings(&[0]);
+
+ assert_eq!(
+ config.request_lines().unwrap_err(),
+ ChipError::OperationFailed(OperationType::LineRequest, Errno::new(EINVAL))
+ );
+ }
+ }
+
+ mod verify {
+ use super::*;
+
+ #[test]
+ fn custom_consumer() {
+ const GPIO: Offset = 2;
+ const CONSUMER: &str = "foobar";
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.rconfig_set_consumer(CONSUMER);
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ let info = config.chip().line_info(GPIO).unwrap();
+
+ assert!(info.is_used());
+ assert_eq!(info.consumer().unwrap(), CONSUMER);
+ }
+
+ #[test]
+ fn empty_consumer() {
+ const GPIO: Offset = 2;
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[GPIO]);
+ config.request_lines().unwrap();
+
+ let info = config.chip().line_info(GPIO).unwrap();
+
+ assert!(info.is_used());
+ assert_eq!(info.consumer().unwrap(), "?");
+ }
+
+ #[test]
+ fn read_values() {
+ let offsets = [7, 1, 0, 6, 2];
+ let pulls = [
+ GPIOSIM_PULL_UP,
+ GPIOSIM_PULL_UP,
+ GPIOSIM_PULL_DOWN,
+ GPIOSIM_PULL_UP,
+ GPIOSIM_PULL_DOWN,
+ ];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.set_pull(&offsets, &pulls);
+ config.lconfig_val(Some(Direction::Input), None);
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+
+ let request = config.request();
+
+ // Single values read properly
+ assert_eq!(request.value(7).unwrap(), Value::Active);
+
+ // Values read properly
+ let map = request.values().unwrap();
+ for i in 0..offsets.len() {
+ assert_eq!(
+ *map.get(offsets[i].into()).unwrap(),
+ match pulls[i] {
+ GPIOSIM_PULL_DOWN => Value::InActive,
+ _ => Value::Active,
+ }
+ );
+ }
+
+ // Subset of values read properly
+ let map = request.values_subset(&[2, 0, 6]).unwrap();
+ assert_eq!(*map.get(2).unwrap(), Value::InActive);
+ assert_eq!(*map.get(0).unwrap(), Value::InActive);
+ assert_eq!(*map.get(6).unwrap(), Value::Active);
+
+ // Value read properly after reconfigure
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings.set_prop(&[SettingVal::ActiveLow(true)]).unwrap();
+ let lconfig = line::Config::new().unwrap();
+ lconfig.add_line_settings(&offsets, lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ assert_eq!(request.value(7).unwrap(), Value::InActive);
+ }
+
+ #[test]
+ fn set_output_values() {
+ let offsets = [0, 1, 3, 4];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_val(Some(Direction::Output), Some(Value::Active));
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+
+ assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_ACTIVE);
+
+ // Default
+ assert_eq!(config.sim().val(2).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ }
+
+ #[test]
+ fn update_output_values() {
+ let offsets = [0, 1, 3, 4];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_val(Some(Direction::Output), Some(Value::InActive));
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+ let request = config.request();
+
+ // Set single value
+ request.set_value(1, Value::Active).unwrap();
+ assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ request.set_value(1, Value::InActive).unwrap();
+ assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+ // Set values of subset
+ let mut map = ValueMap::new();
+ map.insert(4, Value::Active);
+ map.insert(3, Value::Active);
+ request.set_values_subset(map).unwrap();
+ assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_ACTIVE);
+
+ let mut map = ValueMap::new();
+ map.insert(4, Value::InActive);
+ map.insert(3, Value::InActive);
+ request.set_values_subset(map).unwrap();
+ assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+
+ // Set all values
+ request
+ .set_values(&[
+ Value::Active,
+ Value::InActive,
+ Value::Active,
+ Value::InActive,
+ ])
+ .unwrap();
+ assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ request
+ .set_values(&[
+ Value::InActive,
+ Value::InActive,
+ Value::InActive,
+ Value::InActive,
+ ])
+ .unwrap();
+ assert_eq!(config.sim().val(0).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(1).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ assert_eq!(config.sim().val(4).unwrap(), GPIOSIM_VALUE_INACTIVE);
+ }
+
+ #[test]
+ fn set_bias() {
+ let offsets = [3];
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_bias(Direction::Input, Some(Bias::PullUp));
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+ config.request();
+
+ // Set single value
+ assert_eq!(config.sim().val(3).unwrap(), GPIOSIM_VALUE_ACTIVE);
+ }
+
+ #[test]
+ fn no_events() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_edge(None, Some(Edge::Both));
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+
+ // No events available
+ assert!(!config
+ .request()
+ .wait_edge_event(Some(Duration::from_millis(100)))
+ .unwrap());
+ }
+ }
+
+ mod reconfigure {
+ use super::*;
+
+ #[test]
+ fn e2big() {
+ let offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ let mut config = TestConfig::new(16).unwrap();
+ config.lconfig_add_settings(&offsets);
+ config.request_lines().unwrap();
+
+ let request = config.request();
+
+ // Reconfigure
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[SettingVal::Direction(Direction::Input)])
+ .unwrap();
+ let lconfig = line::Config::new().unwrap();
+
+ // The uAPI config has only 10 attribute slots, this should pass.
+ for offset in offsets {
+ lsettings
+ .set_prop(&[SettingVal::DebouncePeriod(Duration::from_millis(
+ (100 + offset).into(),
+ ))])
+ .unwrap();
+ lconfig
+ .add_line_settings(&[offset as Offset], lsettings.settings_clone().unwrap())
+ .unwrap();
+ }
+
+ assert!(request.reconfigure_lines(&lconfig).is_ok());
+
+ // The uAPI config has only 10 attribute slots, and this is the 11th entry.
+ // This should fail with E2BIG.
+ lsettings
+ .set_prop(&[SettingVal::DebouncePeriod(Duration::from_millis(100 + 11))])
+ .unwrap();
+ lconfig.add_line_settings(&[11], lsettings).unwrap();
+
+ assert_eq!(
+ request.reconfigure_lines(&lconfig).unwrap_err(),
+ ChipError::OperationFailed(
+ OperationType::LineRequestReconfigLines,
+ Errno::new(E2BIG),
+ )
+ );
+ }
+
+ #[test]
+ fn bias() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), None);
+
+ let request = config.request();
+
+ // Reconfigure
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Bias(Some(Bias::PullUp)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullUp));
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Bias(Some(Bias::PullDown)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::PullDown));
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Bias(Some(Bias::Disabled)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.bias().unwrap(), Some(Bias::Disabled));
+ }
+
+ #[test]
+ fn drive() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ let request = config.request();
+
+ // Reconfigure
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::Drive(Drive::PushPull),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::PushPull);
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Output),
+ SettingVal::Drive(Drive::OpenDrain),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenDrain);
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Output),
+ SettingVal::Drive(Drive::OpenSource),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.drive().unwrap(), Drive::OpenSource);
+ }
+
+ #[test]
+ fn edge() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), None);
+
+ let request = config.request();
+
+ // Reconfigure
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::EdgeDetection(Some(Edge::Both)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Both));
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::EdgeDetection(Some(Edge::Rising)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Rising));
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::EdgeDetection(Some(Edge::Falling)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.edge_detection().unwrap(), Some(Edge::Falling));
+ }
+
+ #[test]
+ fn event_clock() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let request = config.request();
+
+ // Reconfigure
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[SettingVal::EventClock(EventClock::Monotonic)])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Monotonic);
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[SettingVal::EventClock(EventClock::Realtime)])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::Realtime);
+
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[SettingVal::EventClock(EventClock::HTE)])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert_eq!(info.event_clock().unwrap(), EventClock::HTE);
+ }
+
+ #[test]
+ fn debounce() {
+ let mut config = TestConfig::new(NGPIO).unwrap();
+ config.lconfig_add_settings(&[0]);
+ config.request_lines().unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(!info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(0));
+
+ let request = config.request();
+
+ // Reconfigure
+ let lconfig = line::Config::new().unwrap();
+ let mut lsettings = line::Settings::new().unwrap();
+ lsettings
+ .set_prop(&[
+ SettingVal::Direction(Direction::Input),
+ SettingVal::DebouncePeriod(Duration::from_millis(100)),
+ ])
+ .unwrap();
+ lconfig.add_line_settings(&[0], lsettings).unwrap();
+ request.reconfigure_lines(&lconfig).unwrap();
+ let info = config.chip().line_info(0).unwrap();
+ assert!(info.is_debounced());
+ assert_eq!(info.debounce_period(), Duration::from_millis(100));
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod line_settings {
+ use std::time::Duration;
+
+ use libgpiod::{
+ line, Bias, Direction, Drive, Edge, EventClock, SettingKind, SettingVal, Value,
+ };
+
+ #[test]
+ fn direction() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::AsIs)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::Direction(Direction::Input)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Input)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::Direction(Direction::Output)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Direction).unwrap(),
+ SettingVal::Direction(Direction::Output)
+ );
+ }
+
+ #[test]
+ fn edge_detection() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(None)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::EdgeDetection(Some(Edge::Both))])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Both))
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::EdgeDetection(Some(Edge::Rising))])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Rising))
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::EdgeDetection(Some(Edge::Falling))])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EdgeDetection).unwrap(),
+ SettingVal::EdgeDetection(Some(Edge::Falling))
+ );
+ }
+
+ #[test]
+ fn bias() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(None)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::Bias(Some(Bias::PullDown))])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(Some(Bias::PullDown))
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::Bias(Some(Bias::PullUp))])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Bias).unwrap(),
+ SettingVal::Bias(Some(Bias::PullUp))
+ );
+ }
+
+ #[test]
+ fn drive() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::PushPull)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::Drive(Drive::PushPull)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::PushPull)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::Drive(Drive::OpenDrain)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::OpenDrain)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::Drive(Drive::OpenSource)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::Drive).unwrap(),
+ SettingVal::Drive(Drive::OpenSource)
+ );
+ }
+
+ #[test]
+ fn active_low() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(false)
+ );
+
+ lsettings.set_prop(&[SettingVal::ActiveLow(true)]).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(true)
+ );
+
+ lsettings.set_prop(&[SettingVal::ActiveLow(false)]).unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::ActiveLow).unwrap(),
+ SettingVal::ActiveLow(false)
+ );
+ }
+
+ #[test]
+ fn debounce_period() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::DebouncePeriod).unwrap(),
+ SettingVal::DebouncePeriod(Duration::from_millis(0))
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::DebouncePeriod(Duration::from_millis(5))])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::DebouncePeriod).unwrap(),
+ SettingVal::DebouncePeriod(Duration::from_millis(5))
+ );
+ }
+
+ #[test]
+ fn event_clock() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Monotonic)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::EventClock(EventClock::Realtime)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Realtime)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::EventClock(EventClock::HTE)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::HTE)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::EventClock(EventClock::Monotonic)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::EventClock).unwrap(),
+ SettingVal::EventClock(EventClock::Monotonic)
+ );
+ }
+
+ #[test]
+ fn output_value() {
+ let mut lsettings = line::Settings::new().unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::InActive)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::OutputValue(Value::Active)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::Active)
+ );
+
+ lsettings
+ .set_prop(&[SettingVal::OutputValue(Value::InActive)])
+ .unwrap();
+ assert_eq!(
+ lsettings.prop(SettingKind::OutputValue).unwrap(),
+ SettingVal::OutputValue(Value::InActive)
+ );
+ }
+}
new file mode 100644
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+mod common;
+
+mod request_config {
+ use vmm_sys_util::errno::Error as Errno;
+
+ use libgpiod::{request, Error as ChipError, OperationType};
+
+ mod verify {
+ use super::*;
+
+ #[test]
+ fn default() {
+ let rconfig = request::Config::new().unwrap();
+
+ assert_eq!(rconfig.event_buffer_size(), 0);
+ assert_eq!(
+ rconfig.consumer().unwrap_err(),
+ ChipError::OperationFailed(OperationType::RequestConfigGetConsumer, Errno::new(0))
+ );
+ }
+
+ #[test]
+ fn initialized() {
+ const CONSUMER: &str = "foobar";
+ let rconfig = request::Config::new().unwrap();
+ rconfig.set_consumer(CONSUMER);
+ rconfig.set_event_buffer_size(64);
+
+ assert_eq!(rconfig.event_buffer_size(), 64);
+ assert_eq!(rconfig.consumer().unwrap(), CONSUMER);
+ }
+ }
+}
Add tests for the rust bindings, quite similar to the ones in cxx bindings. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> --- bindings/rust/libgpiod/Cargo.toml | 3 + bindings/rust/libgpiod/tests/chip.rs | 97 ++++ bindings/rust/libgpiod/tests/common/config.rs | 134 +++++ bindings/rust/libgpiod/tests/common/mod.rs | 10 + bindings/rust/libgpiod/tests/edge_event.rs | 294 ++++++++++ bindings/rust/libgpiod/tests/info_event.rs | 125 +++++ bindings/rust/libgpiod/tests/line_config.rs | 105 ++++ bindings/rust/libgpiod/tests/line_info.rs | 279 ++++++++++ bindings/rust/libgpiod/tests/line_request.rs | 519 ++++++++++++++++++ bindings/rust/libgpiod/tests/line_settings.rs | 226 ++++++++ .../rust/libgpiod/tests/request_config.rs | 38 ++ 11 files changed, 1830 insertions(+) create mode 100644 bindings/rust/libgpiod/tests/chip.rs create mode 100644 bindings/rust/libgpiod/tests/common/config.rs create mode 100644 bindings/rust/libgpiod/tests/common/mod.rs create mode 100644 bindings/rust/libgpiod/tests/edge_event.rs create mode 100644 bindings/rust/libgpiod/tests/info_event.rs create mode 100644 bindings/rust/libgpiod/tests/line_config.rs create mode 100644 bindings/rust/libgpiod/tests/line_info.rs create mode 100644 bindings/rust/libgpiod/tests/line_request.rs create mode 100644 bindings/rust/libgpiod/tests/line_settings.rs create mode 100644 bindings/rust/libgpiod/tests/request_config.rs