@@ -2,5 +2,6 @@
members = [
"libgpiod-sys",
- "libgpiod"
+ "libgpiod",
+ "gpiosim"
]
new file mode 100644
@@ -0,0 +1,18 @@
+[package]
+name = "gpiosim"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+libc = ">=0.2.39"
+libgpiod = { path = "../libgpiod" }
+vmm-sys-util = "=0.10.0"
+
+[features]
+generate = [ "bindgen" ]
+
+[build-dependencies]
+bindgen = { version = "0.59.1", optional = true }
+cc = "1.0.46"
new file mode 100644
@@ -0,0 +1,43 @@
+#[cfg(feature = "generate")]
+extern crate bindgen;
+#[cfg(feature = "generate")]
+use std::env;
+#[cfg(feature = "generate")]
+use std::path::PathBuf;
+
+#[cfg(feature = "generate")]
+fn generate_bindings() {
+ // Tell cargo to invalidate the built crate whenever following files change
+ println!("cargo:rerun-if-changed=../../../tests/gpiosim/gpiosim.h");
+
+ // The bindgen::Builder is the main entry point
+ // to bindgen, and lets you build up options for
+ // the resulting bindings.
+ let bindings = bindgen::Builder::default()
+ // The input header we would like to generate
+ // bindings for.
+ .header("../../../tests/gpiosim/gpiosim.h")
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files changed.
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+ // Finish the builder and generate the bindings.
+ .generate()
+ // Unwrap the Result and panic on failure.
+ .expect("Unable to generate bindings");
+
+ // Write the bindings to the $OUT_DIR/bindings.rs file.
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
+
+fn main() {
+ #[cfg(feature = "generate")]
+ generate_bindings();
+
+ println!("cargo:rustc-link-lib=kmod");
+ println!("cargo:rustc-link-lib=mount");
+ println!("cargo:rustc-link-search=./../../tests/gpiosim/.libs/");
+ println!("cargo:rustc-link-lib=static=gpiosim");
+}
new file mode 100644
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#[allow(non_camel_case_types, non_upper_case_globals)]
+#[cfg_attr(test, allow(deref_nullptr, non_snake_case))]
+#[allow(dead_code)]
+mod bindings_raw {
+ #[cfg(feature = "generate")]
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+ #[cfg(not(feature = "generate"))]
+ include!("bindings.rs");
+}
+
+use bindings_raw::*;
+
+pub use bindings_raw::{
+ GPIOSIM_HOG_DIR_INPUT, GPIOSIM_HOG_DIR_OUTPUT_HIGH, GPIOSIM_HOG_DIR_OUTPUT_LOW,
+ GPIOSIM_PULL_DOWN, GPIOSIM_PULL_UP, GPIOSIM_VALUE_ACTIVE, GPIOSIM_VALUE_INACTIVE,
+};
+
+#[allow(dead_code)]
+mod sim;
+
+#[allow(unused_imports)]
+pub use sim::*;
new file mode 100644
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+// Viresh Kumar <viresh.kumar@linaro.org>
+
+use libc::strlen;
+use std::os::raw::c_char;
+use std::path::PathBuf;
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as Errno;
+
+use libgpiod::{Error, Offset, OperationType, Result};
+
+use crate::*;
+
+/// Sim Ctx
+#[derive(Debug)]
+struct SimCtx {
+ ctx: *mut gpiosim_ctx,
+}
+
+unsafe impl Send for SimCtx {}
+unsafe impl Sync for SimCtx {}
+
+impl SimCtx {
+ fn new() -> Result<Self> {
+ let ctx = unsafe { gpiosim_ctx_new() };
+ if ctx.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::SimCtxNew,
+ Errno::last(),
+ ));
+ }
+
+ Ok(Self { ctx })
+ }
+
+ fn ctx(&self) -> *mut gpiosim_ctx {
+ self.ctx
+ }
+}
+
+impl Drop for SimCtx {
+ fn drop(&mut self) {
+ unsafe { gpiosim_ctx_unref(self.ctx) }
+ }
+}
+
+/// Sim Dev
+#[derive(Debug)]
+struct SimDev {
+ dev: *mut gpiosim_dev,
+}
+
+unsafe impl Send for SimDev {}
+unsafe impl Sync for SimDev {}
+
+impl SimDev {
+ fn new(ctx: &SimCtx) -> Result<Self> {
+ let dev = unsafe { gpiosim_dev_new(ctx.ctx()) };
+ if dev.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::SimDevNew,
+ Errno::last(),
+ ));
+ }
+
+ Ok(Self { dev })
+ }
+
+ fn dev(&self) -> *mut gpiosim_dev {
+ self.dev
+ }
+
+ fn enable(&self) -> Result<()> {
+ let ret = unsafe { gpiosim_dev_enable(self.dev) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimDevEnable,
+ Errno::last(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn disable(&self) -> Result<()> {
+ let ret = unsafe { gpiosim_dev_disable(self.dev) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimDevDisable,
+ Errno::last(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl Drop for SimDev {
+ fn drop(&mut self) {
+ unsafe { gpiosim_dev_unref(self.dev) }
+ }
+}
+
+/// Sim Bank
+#[derive(Debug)]
+struct SimBank {
+ bank: *mut gpiosim_bank,
+}
+
+unsafe impl Send for SimBank {}
+unsafe impl Sync for SimBank {}
+
+impl SimBank {
+ fn new(dev: &SimDev) -> Result<Self> {
+ let bank = unsafe { gpiosim_bank_new(dev.dev()) };
+ if bank.is_null() {
+ return Err(Error::OperationFailed(
+ OperationType::SimBankNew,
+ Errno::last(),
+ ));
+ }
+
+ Ok(Self { bank })
+ }
+
+ fn chip_name(&self) -> Result<&str> {
+ // SAFETY: The string returned by gpiosim is guaranteed to live as long
+ // as the `struct SimBank`.
+ let name = unsafe { gpiosim_bank_get_chip_name(self.bank) };
+
+ // SAFETY: The string is guaranteed to be valid here.
+ str::from_utf8(unsafe { slice::from_raw_parts(name as *const u8, strlen(name) as usize) })
+ .map_err(Error::StringNotUtf8)
+ }
+
+ fn dev_path(&self) -> Result<PathBuf> {
+ // SAFETY: The string returned by gpiosim is guaranteed to live as long
+ // as the `struct SimBank`.
+ let path = unsafe { gpiosim_bank_get_dev_path(self.bank) };
+
+ // SAFETY: The string is guaranteed to be valid here.
+ let path = str::from_utf8(unsafe {
+ slice::from_raw_parts(path as *const u8, strlen(path) as usize)
+ })
+ .map_err(Error::StringNotUtf8)?;
+
+ Ok(PathBuf::from(path))
+ }
+
+ fn val(&self, offset: Offset) -> Result<u32> {
+ let ret = unsafe { gpiosim_bank_get_value(self.bank, offset) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankGetVal,
+ Errno::last(),
+ ))
+ } else {
+ Ok(ret as u32)
+ }
+ }
+
+ fn set_label(&self, label: &str) -> Result<()> {
+ // Null-terminate the string
+ let label = label.to_owned() + "\0";
+
+ let ret = unsafe { gpiosim_bank_set_label(self.bank, label.as_ptr() as *const c_char) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetLabel,
+ Errno::last(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn set_num_lines(&self, num: u64) -> Result<()> {
+ let ret = unsafe { gpiosim_bank_set_num_lines(self.bank, num) };
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetNumLines,
+ Errno::last(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn set_line_name(&self, offset: Offset, name: &str) -> Result<()> {
+ // Null-terminate the string
+ let name = name.to_owned() + "\0";
+
+ let ret = unsafe {
+ gpiosim_bank_set_line_name(self.bank, offset, name.as_ptr() as *const c_char)
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetLineName,
+ Errno::last(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn set_pull(&self, offset: Offset, pull: i32) -> Result<()> {
+ let ret = unsafe { gpiosim_bank_set_pull(self.bank, offset, pull) };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankSetPull,
+ Errno::last(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn hog_line(&self, offset: Offset, name: &str, dir: i32) -> Result<()> {
+ // Null-terminate the string
+ let name = name.to_owned() + "\0";
+
+ let ret = unsafe {
+ gpiosim_bank_hog_line(self.bank, offset, name.as_ptr() as *const c_char, dir)
+ };
+
+ if ret == -1 {
+ Err(Error::OperationFailed(
+ OperationType::SimBankHogLine,
+ Errno::last(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl Drop for SimBank {
+ fn drop(&mut self) {
+ unsafe { gpiosim_bank_unref(self.bank) }
+ }
+}
+
+/// GPIO SIM
+#[derive(Debug)]
+pub struct Sim {
+ ctx: SimCtx,
+ dev: SimDev,
+ bank: SimBank,
+}
+
+unsafe impl Send for Sim {}
+unsafe impl Sync for Sim {}
+
+impl Sim {
+ pub fn new(ngpio: Option<u64>, label: Option<&str>, enable: bool) -> Result<Self> {
+ let ctx = SimCtx::new()?;
+ let dev = SimDev::new(&ctx)?;
+ let bank = SimBank::new(&dev)?;
+
+ if let Some(ngpio) = ngpio {
+ bank.set_num_lines(ngpio)?;
+ }
+
+ if let Some(label) = label {
+ bank.set_label(label)?;
+ }
+
+ if enable {
+ dev.enable()?;
+ }
+
+ Ok(Self { ctx, dev, bank })
+ }
+
+ pub fn chip_name(&self) -> &str {
+ self.bank.chip_name().unwrap()
+ }
+
+ pub fn dev_path(&self) -> PathBuf {
+ self.bank.dev_path().unwrap()
+ }
+
+ pub fn val(&self, offset: Offset) -> Result<u32> {
+ self.bank.val(offset)
+ }
+
+ pub fn set_label(&self, label: &str) -> Result<()> {
+ self.bank.set_label(label)
+ }
+
+ pub fn set_num_lines(&self, num: u64) -> Result<()> {
+ self.bank.set_num_lines(num)
+ }
+
+ pub fn set_line_name(&self, offset: Offset, name: &str) -> Result<()> {
+ self.bank.set_line_name(offset, name)
+ }
+
+ pub fn set_pull(&self, offset: Offset, pull: i32) -> Result<()> {
+ self.bank.set_pull(offset, pull)
+ }
+
+ pub fn hog_line(&self, offset: Offset, name: &str, dir: i32) -> Result<()> {
+ self.bank.hog_line(offset, name, dir)
+ }
+
+ pub fn enable(&self) -> Result<()> {
+ self.dev.enable()
+ }
+
+ pub fn disable(&self) -> Result<()> {
+ self.dev.disable()
+ }
+}
This adds gpiosim rust crate, which provides helpers to emulate GPIO chips. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> --- bindings/rust/Cargo.toml | 3 +- bindings/rust/gpiosim/Cargo.toml | 18 ++ bindings/rust/gpiosim/build.rs | 43 ++++ bindings/rust/gpiosim/src/lib.rs | 25 +++ bindings/rust/gpiosim/src/sim.rs | 323 +++++++++++++++++++++++++++++++ 5 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 bindings/rust/gpiosim/Cargo.toml create mode 100644 bindings/rust/gpiosim/build.rs create mode 100644 bindings/rust/gpiosim/src/lib.rs create mode 100644 bindings/rust/gpiosim/src/sim.rs