1use std::collections::HashMap;
83use std::env;
84use std::ffi::OsString;
85use std::fs::{self, File};
86use std::hash::{BuildHasherDefault, Hasher};
87use std::io::prelude::*;
88use std::io::BufReader;
89use std::os::unix::ffi::OsStringExt;
90use std::path::{Path, PathBuf};
91use std::process;
92use std::sync::atomic::{AtomicU32, Ordering};
93use std::thread::sleep;
94use std::time::Duration;
95
96#[derive(Debug, Eq, PartialEq)]
98pub struct Sim {
99 name: String,
101
102 chips: Vec<Chip>,
104
105 dir: PathBuf,
107}
108
109impl Sim {
110 pub fn chips(&self) -> &[Chip] {
112 self.chips.as_slice()
113 }
114
115 pub fn name(&self) -> &str {
117 &self.name
118 }
119
120 fn live(&mut self) -> Result<()> {
121 self.setup_configfs()?;
122 write_attr(&self.dir, "live", "1")?;
123 self.read_attrs()
124 }
125
126 fn cleanup_configfs(&mut self) {
127 if !self.dir.exists() {
128 return;
129 }
130 let _ = write_attr(&self.dir, "live", "0");
131 for (i, c) in self.chips.iter().enumerate() {
132 let bank = format!("bank{i}");
133 let bank_dir = self.dir.join(bank);
134 if !bank_dir.exists() {
135 continue;
136 }
137 for offset in c.cfg.hogs.keys() {
138 let line_dir = bank_dir.join(format!("line{offset}"));
139 let hog_dir = line_dir.join("hog");
140 let _ = fs::remove_dir(hog_dir);
141 let _ = fs::remove_dir(line_dir);
142 }
143 for offset in c.cfg.names.keys() {
144 let line_dir = bank_dir.join(format!("line{offset}"));
145 let _ = fs::remove_dir(line_dir);
146 }
147 let _ = fs::remove_dir(bank_dir);
148 }
149 let _ = fs::remove_dir(&self.dir);
150 while self.dir.exists() {}
151 }
152
153 fn setup_configfs(&mut self) -> Result<()> {
154 for (i, c) in self.chips.iter().enumerate() {
155 let bank_dir = self.dir.join(format!("bank{i}"));
156 fs::create_dir(&bank_dir)?;
157 write_attr(&bank_dir, "label", c.cfg.label.as_bytes())?;
158 write_attr(&bank_dir, "num_lines", format!("{}", c.cfg.num_lines))?;
159
160 for (offset, name) in &c.cfg.names {
161 let line_dir = bank_dir.join(format!("line{offset}"));
162 fs::create_dir(&line_dir)?;
163 write_attr(&line_dir, "name", name.as_bytes())?;
164 }
165 for (offset, hog) in &c.cfg.hogs {
166 let line_dir = bank_dir.join(format!("line{offset}"));
167 if !line_dir.exists() {
168 fs::create_dir(&line_dir)?;
169 }
170 let hog_dir = line_dir.join("hog");
171 fs::create_dir(&hog_dir)?;
172 write_attr(&hog_dir, "name", hog.consumer.as_bytes())?;
173 write_attr(&hog_dir, "direction", hog.direction.as_str())?;
174 }
175 }
176 Ok(())
177 }
178
179 fn read_attrs(&mut self) -> Result<()> {
180 let dev_name = read_attr(&self.dir, "dev_name")?;
181 for (i, c) in self.chips.iter_mut().enumerate() {
182 let bank_dir = self.dir.join(format!("bank{i}"));
183 let chip_name = read_attr(&bank_dir, "chip_name")?;
184 c.dev_path = "/dev".into();
185 c.dev_path.push(&chip_name);
186 let metadata = fs::symlink_metadata(&c.dev_path)?;
191 assert!(
192 !metadata.is_symlink(),
193 "A symlink ({}) is masking device {}. Please remove the symlink.",
194 &c.dev_path.display(),
195 &chip_name,
196 );
197 let mut sysfs_path = PathBuf::from("/sys/devices/platform");
198 sysfs_path.push(&dev_name);
199 sysfs_path.push(&chip_name);
200 c.sysfs_path = sysfs_path;
201 c.chip_name = chip_name;
202 c.dev_name.clone_from(&dev_name);
203 }
204 Ok(())
205 }
206}
207
208impl Drop for Sim {
209 fn drop(&mut self) {
210 self.cleanup_configfs();
211 }
212}
213
214#[derive(Debug)]
216pub struct Chip {
217 dev_path: PathBuf,
221
222 pub chip_name: String,
226
227 pub dev_name: String,
231
232 sysfs_path: PathBuf,
236
237 cfg: Bank,
239}
240
241impl Chip {
242 pub fn config(&self) -> &Bank {
244 &self.cfg
245 }
246
247 pub fn dev_path(&self) -> &PathBuf {
251 &self.dev_path
252 }
253
254 pub fn set_pull(&self, offset: Offset, pull: Level) -> Result<()> {
256 let value = match pull {
257 Level::Low => "pull-down",
258 Level::High => "pull-up",
259 };
260 let path = format!("sim_gpio{offset}/pull");
261 fs::write(self.sysfs_path.join(path), value).map_err(Error::IoError)
262 }
263
264 pub fn pullup(&self, offset: Offset) -> Result<()> {
266 self.set_pull(offset, Level::High)
267 }
268
269 pub fn pulldown(&self, offset: Offset) -> Result<()> {
271 self.set_pull(offset, Level::Low)
272 }
273
274 pub fn toggle(&self, offset: Offset) -> Result<Level> {
276 let value = match self.get_pull(offset)? {
277 Level::High => Level::Low,
278 Level::Low => Level::High,
279 };
280 self.set_pull(offset, value)?;
281 Ok(value)
282 }
283
284 fn get_attr(&self, offset: Offset, attr: &str) -> Result<String> {
285 let path = format!("sim_gpio{offset}/{attr}");
286 fs::read_to_string(self.sysfs_path.join(path))
287 .map(|s| s.trim().to_string())
288 .map_err(Error::IoError)
289 }
290
291 pub fn get_pull(&self, offset: Offset) -> Result<Level> {
293 let pull = self.get_attr(offset, "pull")?;
294 match pull.as_str() {
295 "pull-down" => Ok(Level::Low),
296 "pull-up" => Ok(Level::High),
297 _ => Err(Error::UnexpectedValue(pull)),
298 }
299 }
300
301 pub fn get_level(&self, offset: Offset) -> Result<Level> {
303 let val = self.get_attr(offset, "value")?;
304 match val.as_str() {
305 "0" => Ok(Level::Low),
306 "1" => Ok(Level::High),
307 _ => Err(Error::UnexpectedValue(val)),
308 }
309 }
310}
311impl PartialEq for Chip {
312 fn eq(&self, other: &Self) -> bool {
313 self.dev_path == other.dev_path
314 }
315}
316impl Eq for Chip {}
317
318pub fn builder() -> Builder {
320 Builder::default()
321}
322
323pub struct Simpleton {
328 pub sim: Sim,
329}
330
331impl Simpleton {
332 pub fn new(num_lines: u32) -> Simpleton {
339 Simpleton {
340 sim: builder()
341 .with_bank(&Bank::new(num_lines, "simpleton"))
342 .live()
343 .unwrap(),
344 }
345 }
346
347 pub fn chip(&self) -> &Chip {
349 &self.sim.chips[0]
350 }
351
352 pub fn config(&self) -> &Bank {
354 &self.sim.chips[0].cfg
355 }
356
357 pub fn dev_path(&self) -> &PathBuf {
359 &self.sim.chips[0].dev_path
360 }
361
362 pub fn set_pull(&self, offset: Offset, pull: Level) -> Result<()> {
364 self.sim.chips[0].set_pull(offset, pull)
365 }
366
367 pub fn pullup(&self, offset: Offset) -> Result<()> {
369 self.set_pull(offset, Level::High)
370 }
371
372 pub fn pulldown(&self, offset: Offset) -> Result<()> {
374 self.set_pull(offset, Level::Low)
375 }
376
377 pub fn toggle(&self, offset: Offset) -> Result<Level> {
379 self.sim.chips[0].toggle(offset)
380 }
381
382 pub fn get_pull(&self, offset: Offset) -> Result<Level> {
384 self.sim.chips[0].get_pull(offset)
385 }
386
387 pub fn get_level(&self, offset: Offset) -> Result<Level> {
389 self.sim.chips[0].get_level(offset)
390 }
391}
392
393#[derive(Clone, Debug, Default, Eq, PartialEq)]
398pub struct Builder {
399 pub name: Option<String>,
405
406 pub banks: Vec<Bank>,
410}
411
412impl Builder {
413 pub fn with_bank(&mut self, bank: &Bank) -> &mut Self {
415 self.banks.push(bank.clone());
416 self
417 }
418
419 pub fn with_name<N: Into<String>>(&mut self, name: N) -> &mut Self {
423 self.name = Some(name.into());
424 self
425 }
426
427 pub fn live(&mut self) -> Result<Sim> {
435 let name = match &self.name {
436 Some(n) => n.clone(),
437 None => default_name(),
438 };
439 let sim_dir = find_configfs()?.join(&name);
440 if sim_dir.exists() {
441 return Err(Error::SimulatorExists(name));
442 }
443 fs::create_dir(&sim_dir)?;
444
445 let mut sim = Sim {
446 name,
447 chips: Vec::new(),
448 dir: sim_dir,
449 };
450 for b in &self.banks {
451 sim.chips.push(Chip {
452 cfg: b.clone(),
453 dev_path: PathBuf::default(),
454 chip_name: String::default(),
455 dev_name: String::default(),
456 sysfs_path: PathBuf::default(),
457 })
458 }
459 sim.live()?;
460
461 Ok(sim)
462 }
463}
464
465pub type Offset = u32;
467
468pub type OffsetMap<T> = HashMap<Offset, T, BuildHasherDefault<OffsetHasher>>;
470
471#[derive(Default)]
473pub struct OffsetHasher(u64);
474
475impl Hasher for OffsetHasher {
476 fn finish(&self) -> u64 {
477 self.0
478 }
479
480 fn write(&mut self, _: &[u8]) {
481 panic!("OffsetHasher key must be u32")
482 }
483
484 fn write_u32(&mut self, n: u32) {
485 self.0 = n.into()
486 }
487}
488
489#[derive(Clone, Debug, Default, Eq, PartialEq)]
491pub struct Bank {
492 pub num_lines: u32,
494
495 pub label: String,
497
498 pub names: OffsetMap<String>,
500
501 pub hogs: OffsetMap<Hog>,
503}
504
505impl Bank {
506 pub fn new<N: Into<String>>(num_lines: u32, label: N) -> Bank {
508 Bank {
509 num_lines,
510 label: label.into(),
511 names: OffsetMap::default(),
512 hogs: OffsetMap::default(),
513 }
514 }
515
516 pub fn name<N: Into<String>>(&mut self, offset: Offset, name: N) -> &mut Self {
518 self.names.insert(offset, name.into());
519 self
520 }
521
522 pub fn unname(&mut self, offset: Offset) -> &mut Self {
524 self.names.remove(&offset);
525 self
526 }
527
528 pub fn hog<N: Into<String>>(
534 &mut self,
535 offset: Offset,
536 consumer: N,
537 direction: Direction,
538 ) -> &mut Self {
539 self.hogs.insert(
540 offset,
541 Hog {
542 direction,
543 consumer: consumer.into(),
544 },
545 );
546 self
547 }
548
549 pub fn unhog(&mut self, offset: Offset) -> &mut Self {
551 self.hogs.remove(&offset);
552 self
553 }
554}
555
556#[derive(Clone, Debug, Eq, PartialEq)]
560pub struct Hog {
561 pub consumer: String,
563
564 pub direction: Direction,
567}
568
569#[derive(Clone, Copy, Debug, Eq, PartialEq)]
571pub enum Direction {
572 Input,
574
575 OutputLow,
577
578 OutputHigh,
580}
581
582impl Direction {
583 fn as_str(&self) -> &'static str {
584 match self {
585 Direction::Input => "input",
586 Direction::OutputHigh => "output-high",
587 Direction::OutputLow => "output-low",
588 }
589 }
590}
591
592#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594pub enum Level {
595 High,
597
598 Low,
600}
601
602impl Level {
603 pub fn toggle(&self) -> Level {
605 match self {
606 Level::High => Level::Low,
607 Level::Low => Level::High,
608 }
609 }
610}
611
612pub fn unique_name(app: &str, instance: Option<&str>) -> String {
621 static NAME_COUNT: AtomicU32 = AtomicU32::new(0);
622
623 let mut name = format!(
624 "{}-p{}-{}",
625 app,
626 process::id(),
627 NAME_COUNT.fetch_add(1, Ordering::Relaxed)
628 );
629 if let Some(i) = instance {
630 name += "-";
631 name += i;
632 }
633 name
634}
635
636fn write_attr<D: AsRef<[u8]>>(p: &Path, file: &str, data: D) -> Result<()> {
638 let path = p.join(file);
639 fs::write(path, data).map_err(Error::IoError)
640}
641
642fn read_attr(p: &Path, file: &str) -> Result<String> {
644 let path = p.join(file);
645 fs::read_to_string(path)
646 .map(|s| s.trim().to_string())
647 .map_err(Error::IoError)
648}
649
650fn app_name() -> String {
651 if let Some(app) = env::args_os().next() {
652 if let Some(path) = Path::new(app.as_os_str()).file_name() {
653 if let Some(app) = path.to_str() {
654 return app.into();
655 }
656 }
657 }
658 "gpiosim".into()
659}
660
661fn default_name() -> String {
662 unique_name(&app_name(), None)
663}
664
665fn configfs_mountpoint() -> Option<PathBuf> {
666 if let Ok(f) = File::open("/proc/mounts") {
667 let r = BufReader::new(f);
668 for line in r.lines().map_while(|x| x.ok()) {
669 let words: Vec<&str> = line.split_ascii_whitespace().collect();
670 if words.len() >= 6 && words[2] == "configfs" {
671 return Some(PathBuf::from(words[1]));
672 }
673 }
674 }
675 None
676}
677
678fn find_configfs() -> Result<PathBuf> {
680 let path: PathBuf = "/sys/kernel/config/gpio-sim".into();
682 if path.exists() {
683 return Ok(path);
684 }
685 let output = process::Command::new("modprobe")
687 .arg("gpio-sim")
688 .output()
689 .map_err(|e| Error::CommandError("modprobe".into(), Box::new(e)))?;
690 if !output.status.success() {
691 return Err(Error::ModuleLoadError(OsString::from_vec(output.stderr)));
692 }
693 for _ in 0..10 {
694 if path.exists() {
695 return Ok(path);
696 }
697 if let Some(mut cfgfs) = configfs_mountpoint() {
700 cfgfs.push("gpio-sim");
701 if path.exists() {
702 return Ok(cfgfs);
703 }
704 }
705 sleep(Duration::from_millis(100));
706 }
707 Err(Error::ConfigfsNotFound)
708}
709
710pub type Result<T> = std::result::Result<T, Error>;
714
715#[derive(Debug, thiserror::Error)]
719pub enum Error {
720 #[error("Could not find configsfs")]
722 ConfigfsNotFound,
723
724 #[error("Could not load gpio-sim module: {0:?}")]
726 ModuleLoadError(OsString),
727
728 #[error("Simulator with name {0:?} already exists")]
730 SimulatorExists(String),
731
732 #[error("Read unexpected attr value {0:?}")]
734 UnexpectedValue(String),
735
736 #[error(transparent)]
738 IoError(#[from] std::io::Error),
739
740 #[error("Command {0} returned error {1}")]
742 CommandError(String, Box<dyn std::error::Error>),
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use Direction::*;
749
750 #[test]
751 fn unique_name_default() {
752 let name = unique_name("my_app", None);
753 assert!(name.starts_with("my_app-p"));
754 }
755
756 #[test]
757 fn unique_name_explicit() {
758 let name = unique_name("my_app", Some("test2"));
759 assert!(name.starts_with("my_app-p"));
760 assert!(name.ends_with("-test2"));
761 }
762
763 #[test]
764 fn bank_constructor_default() {
765 let c = Bank::default();
766 assert_eq!(c.num_lines, 0);
767 assert!(c.label.is_empty());
768 assert_eq!(c.names.len(), 0);
769 assert_eq!(c.hogs.len(), 0);
770 }
771
772 #[test]
773 fn bank_name() {
774 let mut c = Bank::default();
775 c.name(3, "pinata");
776 assert_eq!(c.names.len(), 1);
777 assert_eq!(c.names[&3], "pinata");
778 c.name(3, "pineapple");
779 assert_eq!(c.names.len(), 1);
780 assert_eq!(c.names[&3], "pineapple");
781 c.name(0, "nada");
782 assert_eq!(c.names.len(), 2);
783 assert_eq!(c.names[&0], "nada");
784 }
785
786 #[test]
787 fn bank_unname() {
788 let mut c = Bank::default();
789 c.name(3, "pinata");
790 c.name(0, "nada");
791 assert_eq!(c.names.len(), 2);
792 c.unname(3);
793 assert!(!c.names.contains_key(&3));
794 assert_eq!(c.names.len(), 1);
795 assert_eq!(c.names[&0], "nada");
796 }
797
798 #[test]
799 fn bank_hog() {
800 let mut c = Bank::default();
801 c.hog(3, "pinata", Direction::Input);
802 assert_eq!(c.hogs.len(), 1);
803 c.hog(2, "piggly", Direction::OutputLow);
804 assert_eq!(c.hogs.len(), 2);
805 c.hog(1, "wiggly", Direction::OutputHigh);
806 assert_eq!(c.hogs.len(), 3);
807 assert_eq!(c.hogs[&3].consumer, "pinata");
808 assert_eq!(c.hogs[&2].consumer, "piggly");
809 assert_eq!(c.hogs[&1].consumer, "wiggly");
810 assert_eq!(c.hogs[&3].direction, Direction::Input);
811 assert_eq!(c.hogs[&2].direction, Direction::OutputLow);
812 assert_eq!(c.hogs[&1].direction, Direction::OutputHigh);
813 c.hog(2, "wiggly", Direction::OutputHigh);
815 assert_eq!(c.hogs[&2].consumer, "wiggly");
816 assert_eq!(c.hogs[&2].direction, Direction::OutputHigh);
817 assert_eq!(c.hogs.len(), 3);
818 }
819
820 #[test]
821 fn bank_unhog() {
822 let mut c = Bank::default();
823 c.hog(3, "pinata", Direction::Input);
824 c.hog(2, "piggly", Direction::OutputLow);
825 c.hog(1, "wiggly", Direction::OutputHigh);
826 assert_eq!(c.hogs.len(), 3);
827 c.unhog(2);
828 assert_eq!(c.hogs.len(), 2);
829 assert!(!c.hogs.contains_key(&2));
830 assert_eq!(c.hogs[&3].consumer, "pinata");
831 assert_eq!(c.hogs[&1].consumer, "wiggly");
832 assert_eq!(c.hogs[&3].direction, Direction::Input);
833 assert_eq!(c.hogs[&1].direction, Direction::OutputHigh);
834 }
835
836 #[test]
837 fn builder_with_bank() {
838 let mut builder = builder();
839 builder
840 .with_bank(
841 Bank::new(8, "fish")
842 .name(3, "banana")
843 .name(5, "apple")
844 .hog(5, "breath", Input),
845 )
846 .with_bank(
847 Bank::new(42, "babel")
848 .name(3, "piƱata")
849 .hog(2, "hogster", OutputHigh)
850 .hog(7, "hogster", OutputLow),
851 );
852 assert_eq!(builder.banks.len(), 2);
853 assert_eq!(builder.banks[0].num_lines, 8);
854 assert_eq!(builder.banks[0].names.len(), 2);
855 assert_eq!(builder.banks[0].hogs.len(), 1);
856 assert_eq!(builder.banks[1].num_lines, 42);
857 assert_eq!(builder.banks[1].names.len(), 1);
858 assert_eq!(builder.banks[1].hogs.len(), 2);
859 }
860
861 #[test]
862 fn builder_with_name() {
863 let mut builder = builder();
864 assert!(builder.name.is_none());
865 builder.with_name("banana");
866 assert!(builder.name.is_some());
867 assert_eq!(builder.name.unwrap(), "banana");
868 }
869}