diff mbox series

[V4,4/8] libgpiod: Add rust wrapper crate

Message ID c3bdcaa85e1ee4a227d11a9e113f40d0c92b0542.1657279685.git.viresh.kumar@linaro.org
State New
Headers show
Series libgpiod: Add Rust bindings | expand

Commit Message

Viresh Kumar July 8, 2022, 11:34 a.m. UTC
Add rust wrapper crate, around the libpiod-sys crate added earlier, to
provide a convenient interface for the users.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
---
 bindings/rust/Cargo.toml            |  12 +
 bindings/rust/src/chip.rs           | 179 ++++++++++
 bindings/rust/src/chip_info.rs      |  69 ++++
 bindings/rust/src/edge_event.rs     | 105 ++++++
 bindings/rust/src/event_buffer.rs   |  88 +++++
 bindings/rust/src/info_event.rs     |  68 ++++
 bindings/rust/src/lib.rs            | 317 ++++++++++++++++++
 bindings/rust/src/line_config.rs    | 493 ++++++++++++++++++++++++++++
 bindings/rust/src/line_info.rs      | 189 +++++++++++
 bindings/rust/src/line_request.rs   | 248 ++++++++++++++
 bindings/rust/src/request_config.rs | 122 +++++++
 11 files changed, 1890 insertions(+)
 create mode 100644 bindings/rust/Cargo.toml
 create mode 100644 bindings/rust/src/chip.rs
 create mode 100644 bindings/rust/src/chip_info.rs
 create mode 100644 bindings/rust/src/edge_event.rs
 create mode 100644 bindings/rust/src/event_buffer.rs
 create mode 100644 bindings/rust/src/info_event.rs
 create mode 100644 bindings/rust/src/lib.rs
 create mode 100644 bindings/rust/src/line_config.rs
 create mode 100644 bindings/rust/src/line_info.rs
 create mode 100644 bindings/rust/src/line_request.rs
 create mode 100644 bindings/rust/src/request_config.rs

Comments

Kent Gibson July 27, 2022, 2:57 a.m. UTC | #1
On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
> Add rust wrapper crate, around the libpiod-sys crate added earlier, to
> provide a convenient interface for the users.
> 
> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
> ---
>  bindings/rust/Cargo.toml            |  12 +
>  bindings/rust/src/chip.rs           | 179 ++++++++++
>  bindings/rust/src/chip_info.rs      |  69 ++++
>  bindings/rust/src/edge_event.rs     | 105 ++++++
>  bindings/rust/src/event_buffer.rs   |  88 +++++
>  bindings/rust/src/info_event.rs     |  68 ++++
>  bindings/rust/src/lib.rs            | 317 ++++++++++++++++++
>  bindings/rust/src/line_config.rs    | 493 ++++++++++++++++++++++++++++
>  bindings/rust/src/line_info.rs      | 189 +++++++++++
>  bindings/rust/src/line_request.rs   | 248 ++++++++++++++
>  bindings/rust/src/request_config.rs | 122 +++++++
>  11 files changed, 1890 insertions(+)
>  create mode 100644 bindings/rust/Cargo.toml
>  create mode 100644 bindings/rust/src/chip.rs
>  create mode 100644 bindings/rust/src/chip_info.rs
>  create mode 100644 bindings/rust/src/edge_event.rs
>  create mode 100644 bindings/rust/src/event_buffer.rs
>  create mode 100644 bindings/rust/src/info_event.rs
>  create mode 100644 bindings/rust/src/lib.rs
>  create mode 100644 bindings/rust/src/line_config.rs
>  create mode 100644 bindings/rust/src/line_info.rs
>  create mode 100644 bindings/rust/src/line_request.rs
>  create mode 100644 bindings/rust/src/request_config.rs
> 
> diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml
> new file mode 100644
> index 000000000000..d5d81486fa2f
> --- /dev/null
> +++ b/bindings/rust/Cargo.toml
> @@ -0,0 +1,12 @@
> +[package]
> +name = "libgpiod"
> +version = "0.1.0"
> +edition = "2018"
> +

Make the top level a workspace, and move libgpiod, libgpiod-sys and
gpiosim into sub-directories (as libgpiod-sys already is).

> +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
> +
> +[dependencies]
> +libc = ">=0.2.39"
> +libgpiod-sys = { path = "libgpiod-sys" }
> +thiserror = "1.0"
> +vmm-sys-util = "=0.9.0"
> diff --git a/bindings/rust/src/chip.rs b/bindings/rust/src/chip.rs
> new file mode 100644
> index 000000000000..50b5d6102f96
> --- /dev/null
> +++ b/bindings/rust/src/chip.rs
> @@ -0,0 +1,179 @@
> +// 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::os::raw::c_char;
> +use std::sync::Arc;
> +use std::time::Duration;
> +use std::{slice, str};
> +
> +use vmm_sys_util::errno::Error as IoError;
> +
> +use super::{
> +    bindings, chip_info::ChipInfo, Error, InfoEvent, LineConfig, LineInfo, LineRequest,
> +    RequestConfig, Result,
> +};
> +
> +/// GPIO chip
> +///
> +/// A GPIO chip object is associated with an open file descriptor to the GPIO
> +/// character device. It exposes basic information about the chip and allows
> +/// callers to retrieve information about each line, watch lines for state
> +/// changes and make line requests.
> +pub(crate) struct ChipInternal {
> +    chip: *mut bindings::gpiod_chip,
> +}
> +
> +impl ChipInternal {
> +    /// Find a chip by path.
> +    pub(crate) fn open(path: &str) -> Result<Self> {
> +        // Null-terminate the string
> +        let path = path.to_owned() + "\0";
> +
> +        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };
> +        if chip.is_null() {
> +            return Err(Error::OperationFailed("Gpio Chip open", IoError::last()));
> +        }
> +
> +        Ok(Self { chip })
> +    }
> +
> +    /// Private helper, Returns gpiod_chip
> +    pub(crate) fn chip(&self) -> *mut bindings::gpiod_chip {
> +        self.chip
> +    }
> +}
> +
> +impl Drop for ChipInternal {
> +    /// Close the chip and release all associated resources.
> +    fn drop(&mut self) {
> +        unsafe { bindings::gpiod_chip_close(self.chip) }
> +    }
> +}
> +
> +pub struct Chip {
> +    ichip: Arc<ChipInternal>,
> +    info: ChipInfo,
> +}
> +

Chip is undocumented. (check the generated docs)

> +impl Chip {
> +    /// Find a chip by path.
> +    pub fn open(path: &str) -> Result<Self> {
> +        let ichip = Arc::new(ChipInternal::open(path)?);
> +        let info = ChipInfo::new(ichip.clone())?;
> +
> +        Ok(Self { ichip, info })
> +    }
> +
> +    /// Get the chip name as represented in the kernel.
> +    pub fn get_name(&self) -> Result<&str> {
> +        self.info.name()
> +    }
> +

Drop the "get_" prefix from accessor methods (mutators are
still prefixed with "set_")[1]

[1] https://rust-lang.github.io/api-guidelines/naming.html

> +    /// Get the chip label as represented in the kernel.
> +    pub fn get_label(&self) -> Result<&str> {
> +        self.info.label()
> +    }
> +
> +    /// Get the number of GPIO lines exposed by the chip.
> +    pub fn get_num_lines(&self) -> u32 {
> +        self.info.num_lines()
> +    }
> +
> +    /// Get the path used to find the chip.
> +    pub fn get_path(&self) -> Result<&str> {

It seems absurd that a method that simply returns the path provided to
open() requires a Result, but that is a consequence of wrapping.

I was considering suggesting caching a copy in struct Chip, but all the
other methods require Results so at least this is consistent :-(.

Yay, more unwrapping than Xmas past.

> +        // SAFETY: The string returned by libgpiod is guaranteed to live as long
> +        // as the `struct Chip`.
> +        let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) };

Trusting that it is never NULL?
Add a null check to be sure.

> +
> +        // SAFETY: The string is guaranteed to be valid here.
> +        str::from_utf8(unsafe {
> +            slice::from_raw_parts(path as *const u8, bindings::strlen(path) as usize)
> +        })
> +        .map_err(Error::InvalidString)
> +    }
> +
> +    /// Get information about the chip.
> +    pub fn info(&self) -> Result<ChipInfo> {
> +        ChipInfo::new(self.ichip.clone())
> +    }
> +
> +    /// Get a snapshot of information about the line.
> +    pub fn line_info(&self, offset: u32) -> Result<LineInfo> {
> +        LineInfo::new(self.ichip.clone(), offset, false)
> +    }
> +
> +    /// Get the current snapshot of information about the line at given offset
> +    /// and optionally start watching it for future changes.
> +    pub fn watch_line_info(&self, offset: u32) -> Result<LineInfo> {
> +        LineInfo::new(self.ichip.clone(), offset, true)
> +    }
> +
> +    /// Get the file descriptor associated with the chip.
> +    ///
> +    /// The returned file descriptor must not be closed by the caller, else other methods for the
> +    /// `struct Chip` may fail.
> +    pub fn get_fd(&self) -> Result<u32> {
> +        let fd = unsafe { bindings::gpiod_chip_get_fd(self.ichip.chip()) };
> +
> +        if fd < 0 {
> +            Err(Error::OperationFailed("Gpio Chip get-fd", IoError::last()))
> +        } else {
> +            Ok(fd as u32)
> +        }
> +    }
> +
> +    /// Wait for line status events on any of the watched lines on the chip.
> +    pub fn wait_info_event(&self, timeout: Duration) -> Result<()> {

Durations cannot be negative, so caller cannot make this block
indefinitely.  Make timeout an Option? (no timeout => block)

> +        let ret = unsafe {
> +            bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
> +        };
> +
> +        match ret {
> +            -1 => Err(Error::OperationFailed(
> +                "Gpio Chip info-event-wait",
> +                IoError::last(),
> +            )),
> +            0 => Err(Error::OperationTimedOut),
> +            _ => Ok(()),
> +        }
> +    }
> +

Better to return Result<bool> indicating if an event is present rather
than a timeout error?

> +    /// Read a single line status change event from the chip. If no events are
> +    /// pending, this function will block.
> +    pub fn read_info_event(&self) -> Result<InfoEvent> {
> +        InfoEvent::new(&self.ichip)
> +    }
> +
> +    /// Map a GPIO line's name to its offset within the chip.
> +    pub fn find_line(&self, name: &str) -> Result<u32> {

Rename to line_offset_from_name() to match the renaming in the C API.

> +        // Null-terminate the string
> +        let name = name.to_owned() + "\0";
> +
> +        let ret = unsafe {
> +            bindings::gpiod_chip_get_line_offset_from_name(
> +                self.ichip.chip(),
> +                name.as_ptr() as *const c_char,
> +            )
> +        };
> +
> +        if ret == -1 {
> +            Err(Error::OperationFailed(
> +                "Gpio Chip find-line",
> +                IoError::last(),
> +            ))
> +        } else {
> +            Ok(ret as u32)
> +        }
> +    }
> +
> +    /// Request a set of lines for exclusive usage.
> +    pub fn request_lines(
> +        &self,
> +        rconfig: &RequestConfig,
> +        lconfig: &LineConfig,
> +    ) -> Result<LineRequest> {
> +        LineRequest::new(&self.ichip, rconfig, lconfig)
> +    }
> +}
> diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs
> new file mode 100644
> index 000000000000..950368b54c6f
> --- /dev/null
> +++ b/bindings/rust/src/chip_info.rs
> @@ -0,0 +1,69 @@
> +// 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::{slice, str};
> +
> +use vmm_sys_util::errno::Error as IoError;
> +
> +use super::{bindings, ChipInternal, Error, Result};
> +
> +/// GPIO chip Information
> +pub struct ChipInfo {
> +    info: *mut bindings::gpiod_chip_info,
> +}
> +

Consider modules and namespaces rather than lumping everything in
the gpiod space.

e.g. gpiod::ChipInfo -> gpiod::chip::Info

> +impl ChipInfo {
> +    /// Find a GPIO chip by path.
> +    pub(crate) fn new(chip: Arc<ChipInternal>) -> Result<Self> {
> +        let info = unsafe { bindings::gpiod_chip_get_info(chip.chip()) };
> +        if info.is_null() {
> +            return Err(Error::OperationFailed(
> +                "Gpio Chip get info",
> +                IoError::last(),
> +            ));
> +        }
> +
> +        Ok(Self { info })
> +    }
> +
> +    /// Get the GPIO chip name as represented in the kernel.
> +    pub(crate) fn name(&self) -> Result<&str> {
> +        // SAFETY: The string returned by libgpiod is guaranteed to live as long
> +        // as the `struct Chip`.
> +        let name = unsafe { bindings::gpiod_chip_info_get_name(self.info) };
> +

Again, trusting the C not to return NULL.
Similarly elsewhere - don't trust the C library.

> +        // SAFETY: The string is guaranteed to be valid here.
> +        str::from_utf8(unsafe {
> +            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
> +        })
> +        .map_err(Error::InvalidString)
> +    }
> +
> +    /// Get the GPIO chip label as represented in the kernel.
> +    pub(crate) fn label(&self) -> Result<&str> {
> +        // SAFETY: The string returned by libgpiod is guaranteed to live as long
> +        // as the `struct Chip`.
> +        let label = unsafe { bindings::gpiod_chip_info_get_label(self.info) };
> +
> +        // SAFETY: The string is guaranteed to be valid here.
> +        str::from_utf8(unsafe {
> +            slice::from_raw_parts(label as *const u8, bindings::strlen(label) as usize)
> +        })
> +        .map_err(Error::InvalidString)
> +    }
> +
> +    /// Get the number of GPIO lines exposed by the chip.
> +    pub(crate) fn num_lines(&self) -> u32 {
> +        unsafe { bindings::gpiod_chip_info_get_num_lines(self.info) as u32 }
> +    }
> +}
> +
> +impl Drop for ChipInfo {
> +    /// Close the GPIO chip info and release all associated resources.
> +    fn drop(&mut self) {
> +        unsafe { bindings::gpiod_chip_info_free(self.info) }
> +    }
> +}
> diff --git a/bindings/rust/src/edge_event.rs b/bindings/rust/src/edge_event.rs
> new file mode 100644
> index 000000000000..89bda58709d2
> --- /dev/null
> +++ b/bindings/rust/src/edge_event.rs
> @@ -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>
> +
> +use std::sync::Arc;
> +use std::time::Duration;
> +
> +use vmm_sys_util::errno::Error as IoError;
> +
> +use super::{bindings, EdgeEventBufferInternal, Error, LineEdgeEvent, Result};
> +
> +/// Line edge events handling
> +///
> +/// An edge event object contains information about a single line edge event.
> +/// It contains the event type, timestamp and the offset of the line on which
> +/// the event occurred as well as two sequence numbers (global for all lines
> +/// in the associated request and local for this line only).
> +///
> +/// Edge events are stored into an edge-event buffer object to improve
> +/// performance and to limit the number of memory allocations when a large
> +/// number of events are being read.
> +
> +pub struct EdgeEvent {
> +    ibuffer: Option<Arc<EdgeEventBufferInternal>>,
> +    event: *mut bindings::gpiod_edge_event,
> +}
> +
> +impl EdgeEvent {
> +    /// Get an event stored in the buffer.
> +    pub(crate) fn new(
> +        ibuffer: &Arc<EdgeEventBufferInternal>,
> +        index: u64,
> +        copy: bool,

rename to copied or cloned, or flip the sense and call it buffered or
contained.

> +    ) -> Result<Self> {
> +        let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(ibuffer.buffer(), index) };
> +        if event.is_null() {
> +            return Err(Error::OperationFailed(
> +                "Gpio EdgeEvent buffer-get-event",
> +                IoError::last(),
> +            ));
> +        }
> +
> +        if copy {
> +            let event = unsafe { bindings::gpiod_edge_event_copy(event) };
> +            if event.is_null() {
> +                return Err(Error::OperationFailed(
> +                    "Gpio EdgeEvent copy",
> +                    IoError::last(),
> +                ));
> +            }
> +
> +            Ok(Self {
> +                ibuffer: None,
> +                event,
> +            })
> +        } else {
> +            Ok(Self {
> +                ibuffer: Some(ibuffer.clone()),
> +                event,
> +            })
> +        }
> +    }

This is both a new() and a clone(). Split them out.

> +
> +    /// Get the event type.
> +    pub fn get_event_type(&self) -> Result<LineEdgeEvent> {
> +        LineEdgeEvent::new(unsafe { bindings::gpiod_edge_event_get_event_type(self.event) } as u32)
> +    }
> +
> +    /// Get the timestamp of the event.
> +    pub fn get_timestamp(&self) -> Duration {
> +        Duration::from_nanos(unsafe { bindings::gpiod_edge_event_get_timestamp_ns(self.event) })
> +    }
> +
> +    /// Get the offset of the line on which the event was triggered.
> +    pub fn get_line_offset(&self) -> u32 {
> +        unsafe { bindings::gpiod_edge_event_get_line_offset(self.event) }
> +    }
> +
> +    /// Get the global sequence number of the event.
> +    ///
> +    /// Returns sequence number of the event relative to all lines in the
> +    /// associated line request.
> +    pub fn get_global_seqno(&self) -> u64 {
> +        unsafe { bindings::gpiod_edge_event_get_global_seqno(self.event) }
> +    }
> +
> +    /// Get the event sequence number specific to concerned line.
> +    ///
> +    /// Returns sequence number of the event relative to the line within the
> +    /// lifetime of the associated line request.
> +    pub fn get_line_seqno(&self) -> u64 {
> +        unsafe { bindings::gpiod_edge_event_get_line_seqno(self.event) }
> +    }
> +}
> +
> +impl Drop for EdgeEvent {
> +    /// Free the edge event.
> +    fn drop(&mut self) {
> +        // Free the event only if a copy is made
> +        if self.ibuffer.is_none() {
> +            unsafe { bindings::gpiod_edge_event_free(self.event) };
> +        }
> +    }
> +}
> diff --git a/bindings/rust/src/event_buffer.rs b/bindings/rust/src/event_buffer.rs
> new file mode 100644
> index 000000000000..bb568a80e09c
> --- /dev/null
> +++ b/bindings/rust/src/event_buffer.rs
> @@ -0,0 +1,88 @@
> +// 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::os::raw::c_ulong;
> +use std::sync::Arc;
> +
> +use vmm_sys_util::errno::Error as IoError;
> +
> +use super::{bindings, EdgeEvent, Error, Result};
> +
> +/// Line edge events buffer
> +pub(crate) struct EdgeEventBufferInternal {
> +    buffer: *mut bindings::gpiod_edge_event_buffer,
> +}
> +
> +impl EdgeEventBufferInternal {
> +    /// Create a new edge event buffer.
> +    ///
> +    /// If capacity equals 0, it will be set to a default value of 64. If
> +    /// capacity is larger than 1024, it will be limited to 1024.
> +    pub fn new(capacity: u32) -> Result<Self> {

Use usize not u32 for array sizes throughout.

> +        let buffer = unsafe { bindings::gpiod_edge_event_buffer_new(capacity as c_ulong) };
> +        if buffer.is_null() {
> +            return Err(Error::OperationFailed(
> +                "Gpio EdgeEventBuffer new",
> +                IoError::last(),
> +            ));
> +        }
> +
> +        Ok(Self { buffer })
> +    }
> +
> +    /// Private helper, Returns gpiod_edge_event_buffer
> +    pub(crate) fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer {
> +        self.buffer
> +    }
> +}
> +
> +impl Drop for EdgeEventBufferInternal {
> +    /// Free the edge event buffer and release all associated resources.
> +    fn drop(&mut self) {
> +        unsafe { bindings::gpiod_edge_event_buffer_free(self.buffer) };
> +    }
> +}
> +
> +/// Line edge events buffer
> +pub struct EdgeEventBuffer {
> +    ibuffer: Arc<EdgeEventBufferInternal>,
> +}
> +
> +impl EdgeEventBuffer {
> +    /// Create a new edge event buffer.
> +    ///
> +    /// If capacity equals 0, it will be set to a default value of 64. If
> +    /// capacity is larger than 1024, it will be limited to 1024.
> +    pub fn new(capacity: u32) -> Result<Self> {
> +        Ok(Self {
> +            ibuffer: Arc::new(EdgeEventBufferInternal::new(capacity)?),
> +        })
> +    }
> +
> +    /// Private helper, Returns gpiod_edge_event_buffer
> +    pub(crate) fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer {
> +        self.ibuffer.buffer()
> +    }
> +
> +    /// Get the capacity of the event buffer.
> +    pub fn get_capacity(&self) -> u32 {
> +        unsafe { bindings::gpiod_edge_event_buffer_get_capacity(self.buffer()) as u32 }
> +    }

Follow the Vec API and make this capacity().
num_events() becomes len().

> +
> +    /// Read an event stored in the buffer.
> +    pub fn get_event(&self, index: u64) -> Result<EdgeEvent> {
> +        EdgeEvent::new(&self.ibuffer, index, false)
> +    }
> +
> +    /// Make copy of an edge event stored in the buffer.
> +    pub fn get_event_copy(&self, index: u64) -> Result<EdgeEvent> {
> +        EdgeEvent::new(&self.ibuffer, index, true)
> +    }

impl Clone for EdgeEvent instead.

> +
> +    /// Get the number of events the buffers stores.

stores -> contains

The capacity indicates the number that can be stored.
This field indicates the number the buffer currently contains.

And, as mentioned above, rename to len(), and return value should be
usize.

Add a set_len() method to replace the max_events parameter in
LineRequest::read_edge_event() to handle the unusual case where the user
only wants to partially fill the buffer.

> +    pub fn get_num_events(&self) -> u32 {
> +        unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 }
> +    }
> +}
> diff --git a/bindings/rust/src/info_event.rs b/bindings/rust/src/info_event.rs
> new file mode 100644
> index 000000000000..ffba23f350be
> --- /dev/null
> +++ b/bindings/rust/src/info_event.rs
> @@ -0,0 +1,68 @@
> +// 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::convert::TryFrom;
> +use std::sync::Arc;
> +use std::time::Duration;
> +
> +use vmm_sys_util::errno::Error as IoError;
> +
> +use super::{bindings, ChipInternal, Error, Event, LineInfo, Result};
> +
> +/// Line status watch events
> +///
> +/// Accessors for the info event objects allowing to monitor changes in GPIO
> +/// line state.
> +///
> +/// Callers can be notified about changes in line's state using the interfaces
> +/// exposed by GPIO chips. Each info event contains information about the event
> +/// itself (timestamp, type) as well as a snapshot of line's state in the form
> +/// of a line-info object.
> +
> +pub struct InfoEvent {
> +    event: *mut bindings::gpiod_info_event,
> +}
> +
> +impl InfoEvent {
> +    /// Get a single chip's line's status change event.
> +    pub(crate) fn new(ichip: &Arc<ChipInternal>) -> Result<Self> {
> +        let event = unsafe { bindings::gpiod_chip_read_info_event(ichip.chip()) };
> +        if event.is_null() {
> +            return Err(Error::OperationFailed(
> +                "Gpio InfoEvent event-read",
> +                IoError::last(),
> +            ));
> +        }
> +
> +        Ok(Self { event })
> +    }
> +
> +    /// Private helper, Returns gpiod_info_event
> +    pub(crate) fn event(&self) -> *mut bindings::gpiod_info_event {
> +        self.event
> +    }
> +
> +    /// Get the event type of the status change event.
> +    pub fn get_event_type(&self) -> Result<Event> {
> +        Event::new(unsafe { bindings::gpiod_info_event_get_event_type(self.event) } as u32)
> +    }
> +
> +    /// Get the timestamp of the event, read from the monotonic clock.
> +    pub fn get_timestamp(&self) -> Duration {
> +        Duration::from_nanos(unsafe { bindings::gpiod_info_event_get_timestamp_ns(self.event) })
> +    }
> +
> +    /// Get the line-info object associated with the event.
> +    pub fn line_info(&self) -> Result<LineInfo> {
> +        LineInfo::try_from(self)
> +    }
> +}
> +
> +impl Drop for InfoEvent {
> +    /// Free the info event object and release all associated resources.
> +    fn drop(&mut self) {
> +        unsafe { bindings::gpiod_info_event_free(self.event) }
> +    }
> +}
> diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs
> new file mode 100644
> index 000000000000..2f2ac515d353
> --- /dev/null
> +++ b/bindings/rust/src/lib.rs
> @@ -0,0 +1,317 @@
> +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
> +//
> +// Rust wrappers for GPIOD APIs
> +//
> +// Copyright 2022 Linaro Ltd. All Rights Reserved.
> +//     Viresh Kumar <viresh.kumar@linaro.org>
> +
> +//! libgpiod public API
> +//!
> +//! This is the complete documentation of the public Rust API made available to
> +//! users of libgpiod.
> +//!
> +//! The API is logically split into several parts such as: GPIO chip & line
> +//! operators, GPIO events handling etc.
> +
> +mod chip;
> +mod chip_info;
> +mod edge_event;
> +mod event_buffer;
> +mod info_event;
> +mod line_config;
> +mod line_info;
> +mod line_request;
> +mod request_config;
> +
> +use libgpiod_sys as bindings;

I find the original name clearer.
Viresh Kumar July 27, 2022, 9:07 a.m. UTC | #2
On 27-07-22, 10:57, Kent Gibson wrote:
> On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:

> > +    /// Get the path used to find the chip.
> > +    pub fn get_path(&self) -> Result<&str> {
> 
> It seems absurd that a method that simply returns the path provided to
> open() requires a Result, but that is a consequence of wrapping.
> 
> I was considering suggesting caching a copy in struct Chip, but all the
> other methods require Results so at least this is consistent :-(.
> 
> Yay, more unwrapping than Xmas past.

:)

> > +        // SAFETY: The string returned by libgpiod is guaranteed to live as long
> > +        // as the `struct Chip`.
> > +        let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) };
> 
> Trusting that it is never NULL?

I believe we discussed that early on (few months back) and decided this will
never be NULL and since the Rust wrappers are pretty much part of the same
repository, we can take that as a guarantee. An out-of-this-repository user
can't really assume that.

> Add a null check to be sure.

But I am fine with this as well.

> > +    /// Wait for line status events on any of the watched lines on the chip.
> > +    pub fn wait_info_event(&self, timeout: Duration) -> Result<()> {
> 
> Durations cannot be negative, so caller cannot make this block
> indefinitely.  Make timeout an Option? (no timeout => block)

I didn't know we want the always blocking capability as well. Yeah, Option
sounds like the right approach.

So in that case we just pass -1 to gpiod_chip_wait_info_event() ?

> > +        let ret = unsafe {
> > +            bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
> > +        };

> > diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs
> > +/// GPIO chip Information
> > +pub struct ChipInfo {
> > +    info: *mut bindings::gpiod_chip_info,
> > +}
> > +
> 
> Consider modules and namespaces rather than lumping everything in
> the gpiod space.
> 
> e.g. gpiod::ChipInfo -> gpiod::chip::Info

The modules are already there, as file names. So what we really have is
gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked
tougher/complex/unnecessary for end users to know the internals of a dependency
and so I did this:

pub use crate::chip::*;
pub use crate::edge_event::*;
pub use crate::event_buffer::*;
pub use crate::info_event::*;
pub use crate::line_config::*;
pub use crate::line_info::*;
pub use crate::line_request::*;
pub use crate::request_config::*;

which puts everything under gpiod::*. I think it is easier for users that way.
The modules are fine for in-crate management, but for end user they shouldn't be
visible.

> > diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs
> > +/// Error codes for libgpiod operations
> > +#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
> > +pub enum Error {

> > +    #[error("Operation {0} Failed: {1}")]
> > +    OperationFailed(&'static str, IoError),
> 
> Use an enum for the operation rather than a string?

Not sure I understood this.

> And if it returns an IoError it must be an IoOperation?
> Else if the IoError is just an errno then call it that.
> 
> > +}

> > diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs
> > +/// Line info
> > +///
> > +/// Exposes functions for retrieving kernel information about both requested and
> > +/// free lines.  Line info object contains an immutable snapshot of a line's status.
> > +///
> > +/// The line info contains all the publicly available information about a
> > +/// line, which does not include the line value.  The line must be requested
> > +/// to access the line value.
> > +
> > +pub struct LineInfo {
> > +    info: *mut bindings::gpiod_line_info,
> > +    ichip: Option<Arc<ChipInternal>>,
> > +    free: bool,
> 
> This flag makes no sense - the info always needs to be freed no matter
> which path, watched or not, was taken to get it from the C API.

Not the one created with gpiod_info_event_get_line_info(), else it will result
in double free.

> > +    /// Stop watching the line
> > +    pub fn unwatch(&mut self) {
> > +        if let Some(ichip) = &self.ichip {
> > +            unsafe {
> > +                bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset());
> > +            }
> > +            self.ichip = None;
> > +        }
> > +    }
> > +
> 
> This should be a method of the chip, not the info.

I think there were some issues with my understanding of the whole watch thing.
Is there any existing example of gpiowatch somewhere ?

> > +impl TryFrom<&InfoEvent> for LineInfo {
> 
> Is try_from appropriate for getting a contained object?
> "from" normally refers to a type conversion.

Not sure, but as most of new() is going away here, I will likely move this to
info_event as well. No try-from with that.

> > +impl Drop for LineInfo {
> > +    fn drop(&mut self) {
> > +        // We must not free the Line info object created from `struct InfoEvent` by calling
> > +        // libgpiod API.
> > +        if self.free {
> > +            self.unwatch();
> 
> Why does dropping a LineInfo unwatch the line???

Because I thought it is related to the Line's info and we won't wanna watch once
line info is gone. Again, it is my wrong interpretation.

> > +    /// Get values of a subset of lines associated with the request.
> > +    pub fn get_values_subset(&self, offsets: &[u32], values: &mut Vec<i32>) -> Result<()> {
> > +        if offsets.len() != values.len() {
> > +            return Err(Error::OperationFailed(
> > +                "Gpio LineRequest array size mismatch",
> > +                IoError::new(EINVAL),
> > +            ));
> > +        }
> > +
> 
> Returned values are awkward to use as the user has to index into them
> using the index corresponding to the offset in offsets.
> Provide a Values type that maps offset to value, e.g. using an IntMap,
> and pass that in instead of separate offsets and values arrays.

We would need to separate out the offsets then as that's what
gpiod_line_request_get_values_subset() needs. Maybe take offsets, like now, as
an argument and return Result<IntMap> ?

> Same applies to set_values_subset().

That would be fine here though.

> > +    /// Set the size of the kernel event buffer for the request.
> > +    ///
> > +    /// The kernel may adjust the value if it's too high. If set to 0, the
> > +    /// default value will be used.
> > +    pub fn set_event_buffer_size(&self, size: u32) {
> > +        unsafe {
> > +            bindings::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong)
> > +        }
> > +    }
> 
> The kernel may adjust the value regardless - this value is a tentative
> suggestion to the kernel (the kernel buffers have to be sized in 2^N, so
> it generally rounds up to the next power of 2, within limits).
> 
> > +
> > +    /// Get the edge event buffer size for the request config.
> > +    pub fn get_event_buffer_size(&self) -> u32 {
> > +        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
> > +    }
> 
> You might want to note that this just reads the value from the config.
> The actual value used by the kernel is not made available to user space.

Do you want me to add these two comments for the above two routines ?

Other comments, which I removed, look acceptable to me. Will try to incorporate
all that in the next version.

Thanks a lot for the very thorough review.
Kent Gibson July 27, 2022, 10:08 a.m. UTC | #3
On Wed, Jul 27, 2022 at 02:37:01PM +0530, Viresh Kumar wrote:
> On 27-07-22, 10:57, Kent Gibson wrote:
> > On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
> 
> > > +    /// Get the path used to find the chip.
> > > +    pub fn get_path(&self) -> Result<&str> {
> > 
> > It seems absurd that a method that simply returns the path provided to
> > open() requires a Result, but that is a consequence of wrapping.
> > 
> > I was considering suggesting caching a copy in struct Chip, but all the
> > other methods require Results so at least this is consistent :-(.
> > 
> > Yay, more unwrapping than Xmas past.
> 
> :)
> 
> > > +        // SAFETY: The string returned by libgpiod is guaranteed to live as long
> > > +        // as the `struct Chip`.
> > > +        let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) };
> > 
> > Trusting that it is never NULL?
> 
> I believe we discussed that early on (few months back) and decided this will
> never be NULL and since the Rust wrappers are pretty much part of the same
> repository, we can take that as a guarantee. An out-of-this-repository user
> can't really assume that.
> 
> > Add a null check to be sure.
> 
> But I am fine with this as well.
> 

It should never return NULL.  At the moment.
I would prefer to have NULL checks for all cases, not assume anything
about the C implementation, and to be consistent with other places
where you do NULL checks.  As it stands when I see this I need to go check
the C to see if this is a reasonable exception or not.  And I'm lazy.

> > > +    /// Wait for line status events on any of the watched lines on the chip.
> > > +    pub fn wait_info_event(&self, timeout: Duration) -> Result<()> {
> > 
> > Durations cannot be negative, so caller cannot make this block
> > indefinitely.  Make timeout an Option? (no timeout => block)
> 
> I didn't know we want the always blocking capability as well. Yeah, Option
> sounds like the right approach.
> 
> So in that case we just pass -1 to gpiod_chip_wait_info_event() ?
> 
> > > +        let ret = unsafe {
> > > +            bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
> > > +        };
> 
> > > diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs
> > > +/// GPIO chip Information
> > > +pub struct ChipInfo {
> > > +    info: *mut bindings::gpiod_chip_info,
> > > +}
> > > +
> > 
> > Consider modules and namespaces rather than lumping everything in
> > the gpiod space.
> > 
> > e.g. gpiod::ChipInfo -> gpiod::chip::Info
> 
> The modules are already there, as file names. So what we really have is
> gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked
> tougher/complex/unnecessary for end users to know the internals of a dependency
> and so I did this:
> 
> pub use crate::chip::*;
> pub use crate::edge_event::*;
> pub use crate::event_buffer::*;
> pub use crate::info_event::*;
> pub use crate::line_config::*;
> pub use crate::line_info::*;
> pub use crate::line_request::*;
> pub use crate::request_config::*;
> 
> which puts everything under gpiod::*. I think it is easier for users that way.
> The modules are fine for in-crate management, but for end user they shouldn't be
> visible.
> 

The main problem I have with putting everything in the top level is the
generated docs.  You get everything dumped on you, so all structs, enums
and functions, and it isn't clear to the user what the logical starting
point is.
If things are tiered then you can introduce them more gradually, or keep
them out of their way if they are unlikely to use them (e.g. ChipInfo,
InfoEvent).

> > > diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs
> > > +/// Error codes for libgpiod operations
> > > +#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
> > > +pub enum Error {
> 
> > > +    #[error("Operation {0} Failed: {1}")]
> > > +    OperationFailed(&'static str, IoError),
> > 
> > Use an enum for the operation rather than a string?
> 
> Not sure I understood this.
> 

Rather than passing a static str, define an enum with a variant to
identify the operation.  Like std::io::ErrorKind (not sure that is the
best example).

> > And if it returns an IoError it must be an IoOperation?
> > Else if the IoError is just an errno then call it that.
> > 
> > > +}
> 
> > > diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs
> > > +/// Line info
> > > +///
> > > +/// Exposes functions for retrieving kernel information about both requested and
> > > +/// free lines.  Line info object contains an immutable snapshot of a line's status.
> > > +///
> > > +/// The line info contains all the publicly available information about a
> > > +/// line, which does not include the line value.  The line must be requested
> > > +/// to access the line value.
> > > +
> > > +pub struct LineInfo {
> > > +    info: *mut bindings::gpiod_line_info,
> > > +    ichip: Option<Arc<ChipInternal>>,
> > > +    free: bool,
> > 
> > This flag makes no sense - the info always needs to be freed no matter
> > which path, watched or not, was taken to get it from the C API.
> 
> Not the one created with gpiod_info_event_get_line_info(), else it will result
> in double free.
> 

Ah, down in the try_from.  Fair enough.  Though the other two cases both
need to be freed.  And I would prefer the field name changed to match
the EdgeEvent - which ever way you go with that.

> > > +    /// Stop watching the line
> > > +    pub fn unwatch(&mut self) {
> > > +        if let Some(ichip) = &self.ichip {
> > > +            unsafe {
> > > +                bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset());
> > > +            }
> > > +            self.ichip = None;
> > > +        }
> > > +    }
> > > +
> > 
> > This should be a method of the chip, not the info.
> 
> I think there were some issues with my understanding of the whole watch thing.
> Is there any existing example of gpiowatch somewhere ?
> 

There is one in my proposed tool changes for v2[1]

[1] https://lore.kernel.org/linux-gpio/20220708120626.89844-5-warthog618@gmail.com/

> > > +impl TryFrom<&InfoEvent> for LineInfo {
> > 
> > Is try_from appropriate for getting a contained object?
> > "from" normally refers to a type conversion.
> 
> Not sure, but as most of new() is going away here, I will likely move this to
> info_event as well. No try-from with that.
> 
> > > +impl Drop for LineInfo {
> > > +    fn drop(&mut self) {
> > > +        // We must not free the Line info object created from `struct InfoEvent` by calling
> > > +        // libgpiod API.
> > > +        if self.free {
> > > +            self.unwatch();
> > 
> > Why does dropping a LineInfo unwatch the line???
> 
> Because I thought it is related to the Line's info and we won't wanna watch once
> line info is gone. Again, it is my wrong interpretation.
> 

Yeah, no.  You can get LineInfoChange events from the chip similar to
the EdgeEvents from a line request.  unwatching when here is akin to
disabling edge detection when you free an edge event.

Watching and unwatching line info are at chip scope, and your bindings
should never unwatch themselves - only wrap the C and provide a method
on the Chip for the user to call.

> > > +    /// Get values of a subset of lines associated with the request.
> > > +    pub fn get_values_subset(&self, offsets: &[u32], values: &mut Vec<i32>) -> Result<()> {
> > > +        if offsets.len() != values.len() {
> > > +            return Err(Error::OperationFailed(
> > > +                "Gpio LineRequest array size mismatch",
> > > +                IoError::new(EINVAL),
> > > +            ));
> > > +        }
> > > +
> > 
> > Returned values are awkward to use as the user has to index into them
> > using the index corresponding to the offset in offsets.
> > Provide a Values type that maps offset to value, e.g. using an IntMap,
> > and pass that in instead of separate offsets and values arrays.
> 
> We would need to separate out the offsets then as that's what
> gpiod_line_request_get_values_subset() needs. Maybe take offsets, like now, as
> an argument and return Result<IntMap> ?
> 

Don't use IntMap raw, use it as the basis of a Values type.

You can pull the keys from the map to determine which lines to get.
If the map is empty get them all.
Hmmm, that is assuming you have the requested offsets on hand at that
point.

> > Same applies to set_values_subset().
> 
> That would be fine here though.
> 
> > > +    /// Set the size of the kernel event buffer for the request.
> > > +    ///
> > > +    /// The kernel may adjust the value if it's too high. If set to 0, the
> > > +    /// default value will be used.
> > > +    pub fn set_event_buffer_size(&self, size: u32) {
> > > +        unsafe {
> > > +            bindings::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong)
> > > +        }
> > > +    }
> > 
> > The kernel may adjust the value regardless - this value is a tentative
> > suggestion to the kernel (the kernel buffers have to be sized in 2^N, so
> > it generally rounds up to the next power of 2, within limits).
> > 
> > > +
> > > +    /// Get the edge event buffer size for the request config.
> > > +    pub fn get_event_buffer_size(&self) -> u32 {
> > > +        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
> > > +    }
> > 
> > You might want to note that this just reads the value from the config.
> > The actual value used by the kernel is not made available to user space.
> 
> Do you want me to add these two comments for the above two routines ?
> 

Not add verbatim.  Maybe work them in.
Probably should look at revising the C API comments and then just mirror that.

> Other comments, which I removed, look acceptable to me. Will try to incorporate
> all that in the next version.
> 
> Thanks a lot for the very thorough review.
> 

No problem.  Thanks for the effort you've put into them.

Cheers,
Kent.
Miguel Ojeda July 27, 2022, 11:06 a.m. UTC | #4
On Wed, Jul 27, 2022 at 12:08 PM Kent Gibson <warthog618@gmail.com> wrote:
>
> It should never return NULL.  At the moment.
> I would prefer to have NULL checks for all cases, not assume anything
> about the C implementation, and to be consistent with other places
> where you do NULL checks.  As it stands when I see this I need to go check
> the C to see if this is a reasonable exception or not.  And I'm lazy.

Ideally the C side would document the guarantees explicitly instead,
and then the Rust side can rely on them.

In any case, if a given C API never returned an invalid pointer and
suddenly it starts doing so in some cases, I would consider that a
breaking change in practice, which would likely break C users too.

A potential compromise meanwhile is `debug_assert!` to at least test
those assumptions.

Cheers,
Miguel
Kent Gibson July 27, 2022, 12:40 p.m. UTC | #5
On Wed, Jul 27, 2022 at 01:06:28PM +0200, Miguel Ojeda wrote:
> On Wed, Jul 27, 2022 at 12:08 PM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > It should never return NULL.  At the moment.
> > I would prefer to have NULL checks for all cases, not assume anything
> > about the C implementation, and to be consistent with other places
> > where you do NULL checks.  As it stands when I see this I need to go check
> > the C to see if this is a reasonable exception or not.  And I'm lazy.
> 
> Ideally the C side would document the guarantees explicitly instead,
> and then the Rust side can rely on them.
> 

Unfortunately the C header doesn't currently provide any guarantee -
except in the cases where it CAN return NULL.
But we can fix that.

> In any case, if a given C API never returned an invalid pointer and
> suddenly it starts doing so in some cases, I would consider that a
> breaking change in practice, which would likely break C users too.
> 

Not sure I'm onboard with that.  Unless the API has a contract not to
return a NULL then it is free to at a later date. The user should
always assume that NULL is a possibility, even if they have never seen
one.

But in practice you are probably right.

> A potential compromise meanwhile is `debug_assert!` to at least test
> those assumptions.
> 

I'd be fine with that.
I'd also be satisfied with a comment in the Rust that the C guarantees a
non-NULL where that is the case.  That would at least demonstrate that the
possibility has been duly considered.

Cheers,
Kent.
Miguel Ojeda July 27, 2022, 1:02 p.m. UTC | #6
On Wed, Jul 27, 2022 at 2:40 PM Kent Gibson <warthog618@gmail.com> wrote:
>
> Unfortunately the C header doesn't currently provide any guarantee -
> except in the cases where it CAN return NULL.
> But we can fix that.

Yeah, fixing that is what I was suggesting, since it is a possibility
here, and would improve things for C users too.

> Not sure I'm onboard with that.  Unless the API has a contract not to
> return a NULL then it is free to at a later date. The user should
> always assume that NULL is a possibility, even if they have never seen
> one.
>
> But in practice you are probably right.

I definitely agree that a client should aim to avoid assuming anything.

However, if we are strict, given C pointers are unconstrained, all
pointers would be useless unless told otherwise, because checking for
NULL is not a guarantee of validity either.

Also, if an C API just says "returns the name", for instance, it is
reasonable to assume it is a valid name because it is not said
otherwise (e.g. it does not say "returns the name, if available" nor
"returns an optional name").

And, of course, eventually consumers will end up relying on your
particular implementation no matter what, and returning invalid
pointers where there weren't before is a very dangerous idea for a C
library.

> I'd be fine with that.
> I'd also be satisfied with a comment in the Rust that the C guarantees a
> non-NULL where that is the case.  That would at least demonstrate that the
> possibility has been duly considered.

I think the current `SAFETY` comment already intends to imply that,
but yeah, it could be clarified.

In any case, I would say it always returns a valid pointer, not
"non-NULL", since the latter does not really show it is a valid
pointer (it could point to a non-NULL, bad address).

Cheers,
Miguel
Kent Gibson July 28, 2022, 3:10 a.m. UTC | #7
On Wed, Jul 27, 2022 at 06:08:09PM +0800, Kent Gibson wrote:
> > > > +
> > > > +    /// Get the edge event buffer size for the request config.
> > > > +    pub fn get_event_buffer_size(&self) -> u32 {
> > > > +        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
> > > > +    }
> > > 
> > > You might want to note that this just reads the value from the config.
> > > The actual value used by the kernel is not made available to user space.
> > 
> > Do you want me to add these two comments for the above two routines ?
> > 
> 
> Not add verbatim.  Maybe work them in.
> Probably should look at revising the C API comments and then just mirror that.
> 

Just to be clear, I don't think we want to get into the details as to how
the kernel interprets that setting, so that is just informational for
you in case you find the kernel doesn't buffer exactly the number you
expected.  The description for the set covers the case where you might
get less than you expected.  But in some cases you may get more.

The key difference is that the C API description says:
 @return Edge event buffer size setting from the request config.
                                ^^^^^^^
which indicates you are getting the setting value back, not whatever the
buffer size in the kernel actually is.
That is missing from the one-liner description, which is all you copied.
So if you are going to condense it to one line then add "setting" in
there.

Cheers,
Kent.
Kent Gibson July 28, 2022, 3:11 a.m. UTC | #8
On Wed, Jul 27, 2022 at 03:02:10PM +0200, Miguel Ojeda wrote:
> On Wed, Jul 27, 2022 at 2:40 PM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > Unfortunately the C header doesn't currently provide any guarantee -
> > except in the cases where it CAN return NULL.
> > But we can fix that.
> 
> Yeah, fixing that is what I was suggesting, since it is a possibility
> here, and would improve things for C users too.
> 
> > Not sure I'm onboard with that.  Unless the API has a contract not to
> > return a NULL then it is free to at a later date. The user should
> > always assume that NULL is a possibility, even if they have never seen
> > one.
> >
> > But in practice you are probably right.
> 
> I definitely agree that a client should aim to avoid assuming anything.
> 
> However, if we are strict, given C pointers are unconstrained, all
> pointers would be useless unless told otherwise, because checking for
> NULL is not a guarantee of validity either.
> 
> Also, if an C API just says "returns the name", for instance, it is
> reasonable to assume it is a valid name because it is not said
> otherwise (e.g. it does not say "returns the name, if available" nor
> "returns an optional name").
> 
> And, of course, eventually consumers will end up relying on your
> particular implementation no matter what, and returning invalid
> pointers where there weren't before is a very dangerous idea for a C
> library.
> 

All true, but practically speaking the only cases we need to be
concerned with here are NULL and valid.  NULL is the only one we can
detect for certain, and we have no alternative but to assume valid if not.

> > I'd be fine with that.
> > I'd also be satisfied with a comment in the Rust that the C guarantees a
> > non-NULL where that is the case.  That would at least demonstrate that the
> > possibility has been duly considered.
> 
> I think the current `SAFETY` comment already intends to imply that,
> but yeah, it could be clarified.
> 

The comment is:

    // SAFETY: The string is guaranteed to be valid here.

and that is whether there a NULL check or not, so it isn't clear what
the source of the guarantee is.
I would prefer:

    // SAFETY: The string is guaranteed to be valid by the C API.

and updating the C header to explicitly state it returns a valid pointer.
It currently says "Pointer to a human-readable string" which could be
taken to mean valid, but making it "Valid pointer to..." would more
clearly place the onus of it actually being valid on the C library.

Cheers,
Kent.

> In any case, I would say it always returns a valid pointer, not
> "non-NULL", since the latter does not really show it is a valid
> pointer (it could point to a non-NULL, bad address).
> 
> Cheers,
> Miguel
Viresh Kumar July 28, 2022, 8:52 a.m. UTC | #9
On 27-07-22, 10:57, Kent Gibson wrote:
> On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
 
> > +    /// Get the number of events the buffers stores.
> 
> stores -> contains
> 
> The capacity indicates the number that can be stored.
> This field indicates the number the buffer currently contains.
> 
> And, as mentioned above, rename to len(), and return value should be
> usize.
> 
> Add a set_len() method to replace the max_events parameter in
> LineRequest::read_edge_event() to handle the unusual case where the user
> only wants to partially fill the buffer.

So capacity is max that can be stored, len is what is currently present. What
read_edge_event() needs is how much we want it to read, which should be <=
capacity.

If we remove the parameter to read_edge_event(), then it must fetch the value
from the buffer itself, which should be buffer.capacity() if user hasn't called
set_len(), else it will be that special value the user sets.

What do you want to call the value (to be added as a field to struct
EdgeEventBuffer) and the helper routine to fetch it ?

Maybe we should name it "max_events" and call the helpers as max_events() and
set_max_events() instead of set_len()?

> > +    pub fn get_num_events(&self) -> u32 {
> > +        unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 }
> > +    }
> > +}
Kent Gibson July 28, 2022, 9:59 a.m. UTC | #10
On Thu, Jul 28, 2022 at 02:22:24PM +0530, Viresh Kumar wrote:
> On 27-07-22, 10:57, Kent Gibson wrote:
> > On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
>  
> > > +    /// Get the number of events the buffers stores.
> > 
> > stores -> contains
> > 
> > The capacity indicates the number that can be stored.
> > This field indicates the number the buffer currently contains.
> > 
> > And, as mentioned above, rename to len(), and return value should be
> > usize.
> > 
> > Add a set_len() method to replace the max_events parameter in
> > LineRequest::read_edge_event() to handle the unusual case where the user
> > only wants to partially fill the buffer.
> 
> So capacity is max that can be stored, len is what is currently present. What
> read_edge_event() needs is how much we want it to read, which should be <=
> capacity.
> 
> If we remove the parameter to read_edge_event(), then it must fetch the value
> from the buffer itself, which should be buffer.capacity() if user hasn't called
> set_len(), else it will be that special value the user sets.
> 

Ah, so the the problem there is after the read_edge_event() the len() is
supposed to indicate the number of events in the buffer, so if we use
that to size the read then the user will have to re-set_len() on each read
- so we are effectively back to providing the length to read on each call.

So not a good plan - my bad.

> What do you want to call the value (to be added as a field to struct
> EdgeEventBuffer) and the helper routine to fetch it ?
> 
> Maybe we should name it "max_events" and call the helpers as max_events() and
> set_max_events() instead of set_len()?
> 

I'm not even sure it makes sense to have an option to partially fill the
buffer.  It is there in the C as we have to provide the buffer size, and
so can always elect to provide a smaller size, but I'm not sure there is
any use case for it - certainly none that I am aware of.
Perhaps we should always fill the buffer to capacity.

Then we can stick with the Vec len/capacity pattern, so capacity() indicates
how many can be written during a read and len() indicates how many are
available in the buffer.  No set_len() required.

The goal was to provide an interface that is more idiomatic than
just wrapping the C API verbatim, so the idea was to provide something
with a Vec feel.

That is what I tried to do with mine[1], though that totally hides the
filling of the buffer from the user - filling it on demand when the user
requests an event (the buffer also being an iterator) when the buffer is
empty.
Something like that may be doable using the libpgiod API.

Kent.
[1]https://warthog618.github.io/gpiocdev-rs/gpiocdev/request/struct.EdgeEventBuffer.html
Viresh Kumar July 29, 2022, 4:40 a.m. UTC | #11
On 28-07-22, 11:11, Kent Gibson wrote:
> The comment is:
> 
>     // SAFETY: The string is guaranteed to be valid here.
> 
> and that is whether there a NULL check or not, so it isn't clear what
> the source of the guarantee is.
> I would prefer:
> 
>     // SAFETY: The string is guaranteed to be valid by the C API.

I believe this is what we settled with now. I will make updates accordingly.

> and updating the C header to explicitly state it returns a valid pointer.
> It currently says "Pointer to a human-readable string" which could be
> taken to mean valid, but making it "Valid pointer to..." would more
> clearly place the onus of it actually being valid on the C library.

I will let you guys handle the C API :)
Viresh Kumar Aug. 1, 2022, 12:05 p.m. UTC | #12
On 27-07-22, 18:08, Kent Gibson wrote:
> On Wed, Jul 27, 2022 at 02:37:01PM +0530, Viresh Kumar wrote:
> > On 27-07-22, 10:57, Kent Gibson wrote:
> > > On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
> > > Consider modules and namespaces rather than lumping everything in
> > > the gpiod space.
> > > 
> > > e.g. gpiod::ChipInfo -> gpiod::chip::Info
> > 
> > The modules are already there, as file names. So what we really have is
> > gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked
> > tougher/complex/unnecessary for end users to know the internals of a dependency
> > and so I did this:
> > 
> > pub use crate::chip::*;
> > pub use crate::edge_event::*;
> > pub use crate::event_buffer::*;
> > pub use crate::info_event::*;
> > pub use crate::line_config::*;
> > pub use crate::line_info::*;
> > pub use crate::line_request::*;
> > pub use crate::request_config::*;
> > 
> > which puts everything under gpiod::*. I think it is easier for users that way.
> > The modules are fine for in-crate management, but for end user they shouldn't be
> > visible.
> > 
> 
> The main problem I have with putting everything in the top level is the
> generated docs.  You get everything dumped on you, so all structs, enums
> and functions, and it isn't clear to the user what the logical starting
> point is.
> If things are tiered then you can introduce them more gradually, or keep
> them out of their way if they are unlikely to use them (e.g. ChipInfo,
> InfoEvent).

I am not sure what to do about this. I was suggested earlier to dump it all at
the top level namespace so it is easier/shorter for users, which also won't need
to know the internal namespaces/modules of libgpiod.

Looking at structures, there are just 8 of them which are exposed in docs now,
which isn't a lot really. And then we have 12 enums now.
Kent Gibson Aug. 1, 2022, 1:20 p.m. UTC | #13
On Mon, Aug 01, 2022 at 05:35:06PM +0530, Viresh Kumar wrote:
> On 27-07-22, 18:08, Kent Gibson wrote:
> > On Wed, Jul 27, 2022 at 02:37:01PM +0530, Viresh Kumar wrote:
> > > On 27-07-22, 10:57, Kent Gibson wrote:
> > > > On Fri, Jul 08, 2022 at 05:04:57PM +0530, Viresh Kumar wrote:
> > > > Consider modules and namespaces rather than lumping everything in
> > > > the gpiod space.
> > > > 
> > > > e.g. gpiod::ChipInfo -> gpiod::chip::Info
> > > 
> > > The modules are already there, as file names. So what we really have is
> > > gpiod::chip_info::ChipInfo (yeah it isn't great for sure). But then it looked
> > > tougher/complex/unnecessary for end users to know the internals of a dependency
> > > and so I did this:
> > > 
> > > pub use crate::chip::*;
> > > pub use crate::edge_event::*;
> > > pub use crate::event_buffer::*;
> > > pub use crate::info_event::*;
> > > pub use crate::line_config::*;
> > > pub use crate::line_info::*;
> > > pub use crate::line_request::*;
> > > pub use crate::request_config::*;
> > > 
> > > which puts everything under gpiod::*. I think it is easier for users that way.
> > > The modules are fine for in-crate management, but for end user they shouldn't be
> > > visible.
> > > 
> > 
> > The main problem I have with putting everything in the top level is the
> > generated docs.  You get everything dumped on you, so all structs, enums
> > and functions, and it isn't clear to the user what the logical starting
> > point is.
> > If things are tiered then you can introduce them more gradually, or keep
> > them out of their way if they are unlikely to use them (e.g. ChipInfo,
> > InfoEvent).
> 
> I am not sure what to do about this. I was suggested earlier to dump it all at
> the top level namespace so it is easier/shorter for users, which also won't need
> to know the internal namespaces/modules of libgpiod.
> 

libgpiod has no namespaces/modules - cos it is C.
The users do need to understand how things fit together, and the flat
namespace doesn't help there - everything is equal.

True, "_" is shorter than "::".  Can't argue with that ;-).

In Rust the user can rename the import whatever they want, so I'm not sure
the "easier/shorter" argument holds much water.

> Looking at structures, there are just 8 of them which are exposed in docs now,
> which isn't a lot really. And then we have 12 enums now.
> 

This isn't a question of numbers, it is a question of whether to
indicate structure, or to flatten everything.

I find the structured approach both clearer and more idiomatic in Rust,
so that gets my vote.

Cheers,
Kent.
Miguel Ojeda Aug. 1, 2022, 1:28 p.m. UTC | #14
On Mon, Aug 1, 2022 at 3:20 PM Kent Gibson <warthog618@gmail.com> wrote:
>
> True, "_" is shorter than "::".  Can't argue with that ;-).

Not sure about that in non-monospace fonts -- just kidding ;P

(Though I know an experienced C programmer that uses non-monospaced
fonts for code)

> I find the structured approach both clearer and more idiomatic in Rust,
> so that gets my vote.

Yeah, we are also following that approach in the kernel.

Cheers,
Miguel
diff mbox series

Patch

diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml
new file mode 100644
index 000000000000..d5d81486fa2f
--- /dev/null
+++ b/bindings/rust/Cargo.toml
@@ -0,0 +1,12 @@ 
+[package]
+name = "libgpiod"
+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-sys = { path = "libgpiod-sys" }
+thiserror = "1.0"
+vmm-sys-util = "=0.9.0"
diff --git a/bindings/rust/src/chip.rs b/bindings/rust/src/chip.rs
new file mode 100644
index 000000000000..50b5d6102f96
--- /dev/null
+++ b/bindings/rust/src/chip.rs
@@ -0,0 +1,179 @@ 
+// 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::os::raw::c_char;
+use std::sync::Arc;
+use std::time::Duration;
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{
+    bindings, chip_info::ChipInfo, Error, InfoEvent, LineConfig, LineInfo, LineRequest,
+    RequestConfig, Result,
+};
+
+/// GPIO chip
+///
+/// A GPIO chip object is associated with an open file descriptor to the GPIO
+/// character device. It exposes basic information about the chip and allows
+/// callers to retrieve information about each line, watch lines for state
+/// changes and make line requests.
+pub(crate) struct ChipInternal {
+    chip: *mut bindings::gpiod_chip,
+}
+
+impl ChipInternal {
+    /// Find a chip by path.
+    pub(crate) fn open(path: &str) -> Result<Self> {
+        // Null-terminate the string
+        let path = path.to_owned() + "\0";
+
+        let chip = unsafe { bindings::gpiod_chip_open(path.as_ptr() as *const c_char) };
+        if chip.is_null() {
+            return Err(Error::OperationFailed("Gpio Chip open", IoError::last()));
+        }
+
+        Ok(Self { chip })
+    }
+
+    /// Private helper, Returns gpiod_chip
+    pub(crate) fn chip(&self) -> *mut bindings::gpiod_chip {
+        self.chip
+    }
+}
+
+impl Drop for ChipInternal {
+    /// Close the chip and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_chip_close(self.chip) }
+    }
+}
+
+pub struct Chip {
+    ichip: Arc<ChipInternal>,
+    info: ChipInfo,
+}
+
+impl Chip {
+    /// Find a chip by path.
+    pub fn open(path: &str) -> Result<Self> {
+        let ichip = Arc::new(ChipInternal::open(path)?);
+        let info = ChipInfo::new(ichip.clone())?;
+
+        Ok(Self { ichip, info })
+    }
+
+    /// Get the chip name as represented in the kernel.
+    pub fn get_name(&self) -> Result<&str> {
+        self.info.name()
+    }
+
+    /// Get the chip label as represented in the kernel.
+    pub fn get_label(&self) -> Result<&str> {
+        self.info.label()
+    }
+
+    /// Get the number of GPIO lines exposed by the chip.
+    pub fn get_num_lines(&self) -> u32 {
+        self.info.num_lines()
+    }
+
+    /// Get the path used to find the chip.
+    pub fn get_path(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct Chip`.
+        let path = unsafe { bindings::gpiod_chip_get_path(self.ichip.chip()) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(path as *const u8, bindings::strlen(path) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Get information about the chip.
+    pub fn info(&self) -> Result<ChipInfo> {
+        ChipInfo::new(self.ichip.clone())
+    }
+
+    /// Get a snapshot of information about the line.
+    pub fn line_info(&self, offset: u32) -> Result<LineInfo> {
+        LineInfo::new(self.ichip.clone(), offset, false)
+    }
+
+    /// Get the current snapshot of information about the line at given offset
+    /// and optionally start watching it for future changes.
+    pub fn watch_line_info(&self, offset: u32) -> Result<LineInfo> {
+        LineInfo::new(self.ichip.clone(), offset, true)
+    }
+
+    /// Get the file descriptor associated with the chip.
+    ///
+    /// The returned file descriptor must not be closed by the caller, else other methods for the
+    /// `struct Chip` may fail.
+    pub fn get_fd(&self) -> Result<u32> {
+        let fd = unsafe { bindings::gpiod_chip_get_fd(self.ichip.chip()) };
+
+        if fd < 0 {
+            Err(Error::OperationFailed("Gpio Chip get-fd", IoError::last()))
+        } else {
+            Ok(fd as u32)
+        }
+    }
+
+    /// Wait for line status events on any of the watched lines on the chip.
+    pub fn wait_info_event(&self, timeout: Duration) -> Result<()> {
+        let ret = unsafe {
+            bindings::gpiod_chip_wait_info_event(self.ichip.chip(), timeout.as_nanos() as i64)
+        };
+
+        match ret {
+            -1 => Err(Error::OperationFailed(
+                "Gpio Chip info-event-wait",
+                IoError::last(),
+            )),
+            0 => Err(Error::OperationTimedOut),
+            _ => Ok(()),
+        }
+    }
+
+    /// Read a single line status change event from the chip. If no events are
+    /// pending, this function will block.
+    pub fn read_info_event(&self) -> Result<InfoEvent> {
+        InfoEvent::new(&self.ichip)
+    }
+
+    /// Map a GPIO line's name to its offset within the chip.
+    pub fn find_line(&self, name: &str) -> Result<u32> {
+        // Null-terminate the string
+        let name = name.to_owned() + "\0";
+
+        let ret = unsafe {
+            bindings::gpiod_chip_get_line_offset_from_name(
+                self.ichip.chip(),
+                name.as_ptr() as *const c_char,
+            )
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio Chip find-line",
+                IoError::last(),
+            ))
+        } else {
+            Ok(ret as u32)
+        }
+    }
+
+    /// Request a set of lines for exclusive usage.
+    pub fn request_lines(
+        &self,
+        rconfig: &RequestConfig,
+        lconfig: &LineConfig,
+    ) -> Result<LineRequest> {
+        LineRequest::new(&self.ichip, rconfig, lconfig)
+    }
+}
diff --git a/bindings/rust/src/chip_info.rs b/bindings/rust/src/chip_info.rs
new file mode 100644
index 000000000000..950368b54c6f
--- /dev/null
+++ b/bindings/rust/src/chip_info.rs
@@ -0,0 +1,69 @@ 
+// 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::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, ChipInternal, Error, Result};
+
+/// GPIO chip Information
+pub struct ChipInfo {
+    info: *mut bindings::gpiod_chip_info,
+}
+
+impl ChipInfo {
+    /// Find a GPIO chip by path.
+    pub(crate) fn new(chip: Arc<ChipInternal>) -> Result<Self> {
+        let info = unsafe { bindings::gpiod_chip_get_info(chip.chip()) };
+        if info.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio Chip get info",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { info })
+    }
+
+    /// Get the GPIO chip name as represented in the kernel.
+    pub(crate) fn name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct Chip`.
+        let name = unsafe { bindings::gpiod_chip_info_get_name(self.info) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Get the GPIO chip label as represented in the kernel.
+    pub(crate) fn label(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct Chip`.
+        let label = unsafe { bindings::gpiod_chip_info_get_label(self.info) };
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(label as *const u8, bindings::strlen(label) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Get the number of GPIO lines exposed by the chip.
+    pub(crate) fn num_lines(&self) -> u32 {
+        unsafe { bindings::gpiod_chip_info_get_num_lines(self.info) as u32 }
+    }
+}
+
+impl Drop for ChipInfo {
+    /// Close the GPIO chip info and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_chip_info_free(self.info) }
+    }
+}
diff --git a/bindings/rust/src/edge_event.rs b/bindings/rust/src/edge_event.rs
new file mode 100644
index 000000000000..89bda58709d2
--- /dev/null
+++ b/bindings/rust/src/edge_event.rs
@@ -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>
+
+use std::sync::Arc;
+use std::time::Duration;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, EdgeEventBufferInternal, Error, LineEdgeEvent, Result};
+
+/// Line edge events handling
+///
+/// An edge event object contains information about a single line edge event.
+/// It contains the event type, timestamp and the offset of the line on which
+/// the event occurred as well as two sequence numbers (global for all lines
+/// in the associated request and local for this line only).
+///
+/// Edge events are stored into an edge-event buffer object to improve
+/// performance and to limit the number of memory allocations when a large
+/// number of events are being read.
+
+pub struct EdgeEvent {
+    ibuffer: Option<Arc<EdgeEventBufferInternal>>,
+    event: *mut bindings::gpiod_edge_event,
+}
+
+impl EdgeEvent {
+    /// Get an event stored in the buffer.
+    pub(crate) fn new(
+        ibuffer: &Arc<EdgeEventBufferInternal>,
+        index: u64,
+        copy: bool,
+    ) -> Result<Self> {
+        let event = unsafe { bindings::gpiod_edge_event_buffer_get_event(ibuffer.buffer(), index) };
+        if event.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio EdgeEvent buffer-get-event",
+                IoError::last(),
+            ));
+        }
+
+        if copy {
+            let event = unsafe { bindings::gpiod_edge_event_copy(event) };
+            if event.is_null() {
+                return Err(Error::OperationFailed(
+                    "Gpio EdgeEvent copy",
+                    IoError::last(),
+                ));
+            }
+
+            Ok(Self {
+                ibuffer: None,
+                event,
+            })
+        } else {
+            Ok(Self {
+                ibuffer: Some(ibuffer.clone()),
+                event,
+            })
+        }
+    }
+
+    /// Get the event type.
+    pub fn get_event_type(&self) -> Result<LineEdgeEvent> {
+        LineEdgeEvent::new(unsafe { bindings::gpiod_edge_event_get_event_type(self.event) } as u32)
+    }
+
+    /// Get the timestamp of the event.
+    pub fn get_timestamp(&self) -> Duration {
+        Duration::from_nanos(unsafe { bindings::gpiod_edge_event_get_timestamp_ns(self.event) })
+    }
+
+    /// Get the offset of the line on which the event was triggered.
+    pub fn get_line_offset(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_get_line_offset(self.event) }
+    }
+
+    /// Get the global sequence number of the event.
+    ///
+    /// Returns sequence number of the event relative to all lines in the
+    /// associated line request.
+    pub fn get_global_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_global_seqno(self.event) }
+    }
+
+    /// Get the event sequence number specific to concerned line.
+    ///
+    /// Returns sequence number of the event relative to the line within the
+    /// lifetime of the associated line request.
+    pub fn get_line_seqno(&self) -> u64 {
+        unsafe { bindings::gpiod_edge_event_get_line_seqno(self.event) }
+    }
+}
+
+impl Drop for EdgeEvent {
+    /// Free the edge event.
+    fn drop(&mut self) {
+        // Free the event only if a copy is made
+        if self.ibuffer.is_none() {
+            unsafe { bindings::gpiod_edge_event_free(self.event) };
+        }
+    }
+}
diff --git a/bindings/rust/src/event_buffer.rs b/bindings/rust/src/event_buffer.rs
new file mode 100644
index 000000000000..bb568a80e09c
--- /dev/null
+++ b/bindings/rust/src/event_buffer.rs
@@ -0,0 +1,88 @@ 
+// 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::os::raw::c_ulong;
+use std::sync::Arc;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, EdgeEvent, Error, Result};
+
+/// Line edge events buffer
+pub(crate) struct EdgeEventBufferInternal {
+    buffer: *mut bindings::gpiod_edge_event_buffer,
+}
+
+impl EdgeEventBufferInternal {
+    /// Create a new edge event buffer.
+    ///
+    /// If capacity equals 0, it will be set to a default value of 64. If
+    /// capacity is larger than 1024, it will be limited to 1024.
+    pub fn new(capacity: u32) -> Result<Self> {
+        let buffer = unsafe { bindings::gpiod_edge_event_buffer_new(capacity as c_ulong) };
+        if buffer.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio EdgeEventBuffer new",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { buffer })
+    }
+
+    /// Private helper, Returns gpiod_edge_event_buffer
+    pub(crate) fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer {
+        self.buffer
+    }
+}
+
+impl Drop for EdgeEventBufferInternal {
+    /// Free the edge event buffer and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_edge_event_buffer_free(self.buffer) };
+    }
+}
+
+/// Line edge events buffer
+pub struct EdgeEventBuffer {
+    ibuffer: Arc<EdgeEventBufferInternal>,
+}
+
+impl EdgeEventBuffer {
+    /// Create a new edge event buffer.
+    ///
+    /// If capacity equals 0, it will be set to a default value of 64. If
+    /// capacity is larger than 1024, it will be limited to 1024.
+    pub fn new(capacity: u32) -> Result<Self> {
+        Ok(Self {
+            ibuffer: Arc::new(EdgeEventBufferInternal::new(capacity)?),
+        })
+    }
+
+    /// Private helper, Returns gpiod_edge_event_buffer
+    pub(crate) fn buffer(&self) -> *mut bindings::gpiod_edge_event_buffer {
+        self.ibuffer.buffer()
+    }
+
+    /// Get the capacity of the event buffer.
+    pub fn get_capacity(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_buffer_get_capacity(self.buffer()) as u32 }
+    }
+
+    /// Read an event stored in the buffer.
+    pub fn get_event(&self, index: u64) -> Result<EdgeEvent> {
+        EdgeEvent::new(&self.ibuffer, index, false)
+    }
+
+    /// Make copy of an edge event stored in the buffer.
+    pub fn get_event_copy(&self, index: u64) -> Result<EdgeEvent> {
+        EdgeEvent::new(&self.ibuffer, index, true)
+    }
+
+    /// Get the number of events the buffers stores.
+    pub fn get_num_events(&self) -> u32 {
+        unsafe { bindings::gpiod_edge_event_buffer_get_num_events(self.buffer()) as u32 }
+    }
+}
diff --git a/bindings/rust/src/info_event.rs b/bindings/rust/src/info_event.rs
new file mode 100644
index 000000000000..ffba23f350be
--- /dev/null
+++ b/bindings/rust/src/info_event.rs
@@ -0,0 +1,68 @@ 
+// 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::convert::TryFrom;
+use std::sync::Arc;
+use std::time::Duration;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, ChipInternal, Error, Event, LineInfo, Result};
+
+/// Line status watch events
+///
+/// Accessors for the info event objects allowing to monitor changes in GPIO
+/// line state.
+///
+/// Callers can be notified about changes in line's state using the interfaces
+/// exposed by GPIO chips. Each info event contains information about the event
+/// itself (timestamp, type) as well as a snapshot of line's state in the form
+/// of a line-info object.
+
+pub struct InfoEvent {
+    event: *mut bindings::gpiod_info_event,
+}
+
+impl InfoEvent {
+    /// Get a single chip's line's status change event.
+    pub(crate) fn new(ichip: &Arc<ChipInternal>) -> Result<Self> {
+        let event = unsafe { bindings::gpiod_chip_read_info_event(ichip.chip()) };
+        if event.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio InfoEvent event-read",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { event })
+    }
+
+    /// Private helper, Returns gpiod_info_event
+    pub(crate) fn event(&self) -> *mut bindings::gpiod_info_event {
+        self.event
+    }
+
+    /// Get the event type of the status change event.
+    pub fn get_event_type(&self) -> Result<Event> {
+        Event::new(unsafe { bindings::gpiod_info_event_get_event_type(self.event) } as u32)
+    }
+
+    /// Get the timestamp of the event, read from the monotonic clock.
+    pub fn get_timestamp(&self) -> Duration {
+        Duration::from_nanos(unsafe { bindings::gpiod_info_event_get_timestamp_ns(self.event) })
+    }
+
+    /// Get the line-info object associated with the event.
+    pub fn line_info(&self) -> Result<LineInfo> {
+        LineInfo::try_from(self)
+    }
+}
+
+impl Drop for InfoEvent {
+    /// Free the info event object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_info_event_free(self.event) }
+    }
+}
diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs
new file mode 100644
index 000000000000..2f2ac515d353
--- /dev/null
+++ b/bindings/rust/src/lib.rs
@@ -0,0 +1,317 @@ 
+// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+//
+// Rust wrappers for GPIOD APIs
+//
+// Copyright 2022 Linaro Ltd. All Rights Reserved.
+//     Viresh Kumar <viresh.kumar@linaro.org>
+
+//! libgpiod public API
+//!
+//! This is the complete documentation of the public Rust API made available to
+//! users of libgpiod.
+//!
+//! The API is logically split into several parts such as: GPIO chip & line
+//! operators, GPIO events handling etc.
+
+mod chip;
+mod chip_info;
+mod edge_event;
+mod event_buffer;
+mod info_event;
+mod line_config;
+mod line_info;
+mod line_request;
+mod request_config;
+
+use libgpiod_sys as bindings;
+
+pub use crate::chip::*;
+pub use crate::edge_event::*;
+pub use crate::event_buffer::*;
+pub use crate::info_event::*;
+pub use crate::line_config::*;
+pub use crate::line_info::*;
+pub use crate::line_request::*;
+pub use crate::request_config::*;
+
+use std::os::raw::c_char;
+use std::{slice, str};
+
+use thiserror::Error as ThisError;
+use vmm_sys_util::errno::Error as IoError;
+
+/// Result of libgpiod operations
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Error codes for libgpiod operations
+#[derive(Copy, Clone, Debug, PartialEq, ThisError)]
+pub enum Error {
+    #[error("Failed to find {0}")]
+    NameNotFound(&'static str),
+    #[error("Invalid String: {0:?}")]
+    InvalidString(str::Utf8Error),
+    #[error("Invalid {0} value: {1}")]
+    InvalidValue(&'static str, u32),
+    #[error("Operation {0} Failed: {1}")]
+    OperationFailed(&'static str, IoError),
+    #[error("Operation Timed-out")]
+    OperationTimedOut,
+}
+
+/// Direction settings.
+pub enum Direction {
+    /// Request the line(s), but don't change direction.
+    AsIs,
+    /// Direction is input - for reading the value of an externally driven GPIO line.
+    Input,
+    /// Direction is output - for driving the GPIO line.
+    Output,
+}
+
+impl Direction {
+    fn new(dir: u32) -> Result<Self> {
+        match dir {
+            bindings::GPIOD_LINE_DIRECTION_AS_IS => Ok(Direction::AsIs),
+            bindings::GPIOD_LINE_DIRECTION_INPUT => Ok(Direction::Input),
+            bindings::GPIOD_LINE_DIRECTION_OUTPUT => Ok(Direction::Output),
+            _ => Err(Error::InvalidValue("direction", dir)),
+        }
+    }
+
+    fn gpiod_direction(&self) -> u32 {
+        match self {
+            Direction::AsIs => bindings::GPIOD_LINE_DIRECTION_AS_IS,
+            Direction::Input => bindings::GPIOD_LINE_DIRECTION_INPUT,
+            Direction::Output => bindings::GPIOD_LINE_DIRECTION_OUTPUT,
+        }
+    }
+}
+
+/// Internal bias settings.
+pub enum Bias {
+    /// Don't change the bias setting when applying line config.
+    AsIs,
+    /// The internal bias state is unknown.
+    Unknown,
+    /// The internal bias is disabled.
+    Disabled,
+    /// The internal pull-up bias is enabled.
+    PullUp,
+    /// The internal pull-down bias is enabled.
+    PullDown,
+}
+
+impl Bias {
+    fn new(bias: u32) -> Result<Self> {
+        match bias {
+            bindings::GPIOD_LINE_BIAS_AS_IS => Ok(Bias::AsIs),
+            bindings::GPIOD_LINE_BIAS_UNKNOWN => Ok(Bias::Unknown),
+            bindings::GPIOD_LINE_BIAS_DISABLED => Ok(Bias::Disabled),
+            bindings::GPIOD_LINE_BIAS_PULL_UP => Ok(Bias::PullUp),
+            bindings::GPIOD_LINE_BIAS_PULL_DOWN => Ok(Bias::PullDown),
+            _ => Err(Error::InvalidValue("bias", bias)),
+        }
+    }
+
+    fn gpiod_bias(&self) -> u32 {
+        match self {
+            Bias::AsIs => bindings::GPIOD_LINE_BIAS_AS_IS,
+            Bias::Unknown => bindings::GPIOD_LINE_BIAS_UNKNOWN,
+            Bias::Disabled => bindings::GPIOD_LINE_BIAS_DISABLED,
+            Bias::PullUp => bindings::GPIOD_LINE_BIAS_PULL_UP,
+            Bias::PullDown => bindings::GPIOD_LINE_BIAS_PULL_DOWN,
+        }
+    }
+}
+
+/// Drive settings.
+pub enum Drive {
+    /// Drive setting is push-pull.
+    PushPull,
+    /// Line output is open-drain.
+    OpenDrain,
+    /// Line output is open-source.
+    OpenSource,
+}
+
+impl Drive {
+    fn new(drive: u32) -> Result<Self> {
+        match drive {
+            bindings::GPIOD_LINE_DRIVE_PUSH_PULL => Ok(Drive::PushPull),
+            bindings::GPIOD_LINE_DRIVE_OPEN_DRAIN => Ok(Drive::OpenDrain),
+            bindings::GPIOD_LINE_DRIVE_OPEN_SOURCE => Ok(Drive::OpenSource),
+            _ => Err(Error::InvalidValue("drive", drive)),
+        }
+    }
+
+    fn gpiod_drive(&self) -> u32 {
+        match self {
+            Drive::PushPull => bindings::GPIOD_LINE_DRIVE_PUSH_PULL,
+            Drive::OpenDrain => bindings::GPIOD_LINE_DRIVE_OPEN_DRAIN,
+            Drive::OpenSource => bindings::GPIOD_LINE_DRIVE_OPEN_SOURCE,
+        }
+    }
+}
+
+/// Edge detection settings.
+pub enum Edge {
+    /// Line edge detection is disabled.
+    None,
+    /// Line detects rising edge events.
+    Rising,
+    /// Line detects falling edge events.
+    Falling,
+    /// Line detects both rising and falling edge events.
+    Both,
+}
+
+impl Edge {
+    fn new(edge: u32) -> Result<Self> {
+        match edge {
+            bindings::GPIOD_LINE_EDGE_NONE => Ok(Edge::None),
+            bindings::GPIOD_LINE_EDGE_RISING => Ok(Edge::Rising),
+            bindings::GPIOD_LINE_EDGE_FALLING => Ok(Edge::Falling),
+            bindings::GPIOD_LINE_EDGE_BOTH => Ok(Edge::Both),
+            _ => Err(Error::InvalidValue("edge", edge)),
+        }
+    }
+
+    fn gpiod_edge(&self) -> u32 {
+        match self {
+            Edge::None => bindings::GPIOD_LINE_EDGE_NONE,
+            Edge::Rising => bindings::GPIOD_LINE_EDGE_RISING,
+            Edge::Falling => bindings::GPIOD_LINE_EDGE_FALLING,
+            Edge::Both => bindings::GPIOD_LINE_EDGE_BOTH,
+        }
+    }
+}
+
+/// Line config settings.
+pub enum Config {
+    /// Line direction.
+    Direction,
+    /// Edge detection.
+    EdgeDetection,
+    /// Bias.
+    Bias,
+    /// Drive.
+    Drive,
+    /// Active-low setting.
+    ActiveLow,
+    /// Debounce period.
+    DebouncePeriodUs,
+    /// Event clock type.
+    EventClock,
+    /// Output value.
+    OutputValue,
+}
+
+impl Config {
+    fn new(config: u32) -> Result<Self> {
+        match config {
+            bindings::GPIOD_LINE_CONFIG_PROP_DIRECTION => Ok(Config::Direction),
+            bindings::GPIOD_LINE_CONFIG_PROP_EDGE_DETECTION => Ok(Config::EdgeDetection),
+            bindings::GPIOD_LINE_CONFIG_PROP_BIAS => Ok(Config::Bias),
+            bindings::GPIOD_LINE_CONFIG_PROP_DRIVE => Ok(Config::Drive),
+            bindings::GPIOD_LINE_CONFIG_PROP_ACTIVE_LOW => Ok(Config::ActiveLow),
+            bindings::GPIOD_LINE_CONFIG_PROP_DEBOUNCE_PERIOD_US => Ok(Config::DebouncePeriodUs),
+            bindings::GPIOD_LINE_CONFIG_PROP_EVENT_CLOCK => Ok(Config::EventClock),
+            bindings::GPIOD_LINE_CONFIG_PROP_OUTPUT_VALUE => Ok(Config::OutputValue),
+            _ => Err(Error::InvalidValue("config", config)),
+        }
+    }
+}
+
+/// Event clock settings.
+pub enum EventClock {
+    /// Line uses the monotonic clock for edge event timestamps.
+    Monotonic,
+    /// Line uses the realtime clock for edge event timestamps.
+    Realtime,
+}
+
+impl EventClock {
+    fn new(clock: u32) -> Result<Self> {
+        match clock {
+            bindings::GPIOD_LINE_EVENT_CLOCK_MONOTONIC => Ok(EventClock::Monotonic),
+            bindings::GPIOD_LINE_EVENT_CLOCK_REALTIME => Ok(EventClock::Realtime),
+            _ => Err(Error::InvalidValue("event clock", clock)),
+        }
+    }
+
+    fn gpiod_clock(&self) -> u32 {
+        match self {
+            EventClock::Monotonic => bindings::GPIOD_LINE_EVENT_CLOCK_MONOTONIC,
+            EventClock::Realtime => bindings::GPIOD_LINE_EVENT_CLOCK_REALTIME,
+        }
+    }
+}
+
+/// Line status change event types.
+pub enum Event {
+    /// Line has been requested.
+    LineRequested,
+    /// Previously requested line has been released.
+    LineReleased,
+    /// Line configuration has changed.
+    LineConfigChanged,
+}
+
+impl Event {
+    fn new(event: u32) -> Result<Self> {
+        match event {
+            bindings::GPIOD_INFO_EVENT_LINE_REQUESTED => Ok(Event::LineRequested),
+            bindings::GPIOD_INFO_EVENT_LINE_RELEASED => Ok(Event::LineReleased),
+            bindings::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED => Ok(Event::LineConfigChanged),
+            _ => Err(Error::InvalidValue("event", event)),
+        }
+    }
+}
+
+#[derive(Copy, Clone)]
+/// Edge event types.
+pub enum LineEdgeEvent {
+    /// Rising edge event.
+    Rising,
+    /// Falling edge event.
+    Falling,
+}
+
+impl LineEdgeEvent {
+    fn new(event: u32) -> Result<Self> {
+        match event {
+            bindings::GPIOD_EDGE_EVENT_RISING_EDGE => Ok(LineEdgeEvent::Rising),
+            bindings::GPIOD_EDGE_EVENT_FALLING_EDGE => Ok(LineEdgeEvent::Falling),
+            _ => Err(Error::InvalidValue("edge event", event)),
+        }
+    }
+}
+
+/// Various libgpiod-related functions.
+
+/// Check if the file pointed to by path is a GPIO chip character device.
+///
+/// Returns true if the file exists and is a GPIO chip character device or a
+/// symbolic link to it.
+pub fn gpiod_is_gpiochip_device(path: &str) -> bool {
+    // Null-terminate the string
+    let path = path.to_owned() + "\0";
+
+    unsafe { bindings::gpiod_is_gpiochip_device(path.as_ptr() as *const c_char) }
+}
+
+/// Get the API version of the library as a human-readable string.
+pub fn gpiod_version_string() -> Result<&'static str> {
+    // SAFETY: The string returned by libgpiod is guaranteed to live forever.
+    let version = unsafe { bindings::gpiod_version_string() };
+
+    if version.is_null() {
+        return Err(Error::NameNotFound("GPIO library version"));
+    }
+
+    // SAFETY: The string is guaranteed to be valid here.
+    str::from_utf8(unsafe {
+        slice::from_raw_parts(version as *const u8, bindings::strlen(version) as usize)
+    })
+    .map_err(Error::InvalidString)
+}
diff --git a/bindings/rust/src/line_config.rs b/bindings/rust/src/line_config.rs
new file mode 100644
index 000000000000..0fc8d0b736a3
--- /dev/null
+++ b/bindings/rust/src/line_config.rs
@@ -0,0 +1,493 @@ 
+// 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::EINVAL;
+use std::os::raw::c_ulong;
+use std::time::Duration;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, Bias, Config, Direction, Drive, Edge, Error, EventClock, Result};
+
+/// Line configuration objects.
+///
+/// The line-config object contains the configuration for lines that can be
+/// used in two cases:
+///  - when making a line request
+///  - when reconfiguring a set of already requested lines.
+///
+/// A new line-config object is instantiated with a set of sane defaults
+/// for all supported configuration settings. Those defaults can be modified by
+/// the caller. Default values can be overridden by applying different values
+/// for specific lines. When making a request or reconfiguring an existing one,
+/// the overridden settings for specific lines take precedence. For lines
+/// without an override the requested default settings are used.
+///
+/// For every setting there are two mutators (one setting the default and one
+/// for the per-line override), two getters (one for reading the global
+/// default and one for retrieving the effective value for the line),
+/// a function for testing if a setting is overridden for the line
+/// and finally a function for clearing the overrides (per line).
+///
+/// The mutators don't return errors. If the set of options is too complex to
+/// be translated into kernel uAPI structures then an error will be returned at
+/// the time of the request or reconfiguration. If an invalid value was passed
+/// to any of the mutators then the default value will be silently used instead.
+///
+/// Operating on lines in struct LineConfig has no immediate effect on real
+/// GPIOs, it only manipulates the config object in memory.  Those changes are
+/// only applied to the hardware at the time of the request or reconfiguration.
+///
+/// Overrides for lines that don't end up being requested are silently ignored
+/// both in LineRequest::new() as well as in LineRequest::reconfigure_lines().
+///
+/// In cases where all requested lines are using the one configuration, the
+/// line overrides can be entirely ignored when preparing the configuration.
+
+pub struct LineConfig {
+    config: *mut bindings::gpiod_line_config,
+}
+
+impl LineConfig {
+    /// Create a new line config object.
+    pub fn new() -> Result<Self> {
+        let config = unsafe { bindings::gpiod_line_config_new() };
+
+        if config.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineConfig new",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { config })
+    }
+
+    /// Private helper, Returns gpiod_line_config
+    pub(crate) fn config(&self) -> *mut bindings::gpiod_line_config {
+        self.config
+    }
+
+    /// Resets the entire configuration stored in the object. This is useful if
+    /// the user wants to reuse the object without reallocating it.
+    pub fn reset(&mut self) {
+        unsafe { bindings::gpiod_line_config_reset(self.config) }
+    }
+
+    /// Set the default line direction.
+    pub fn set_direction_default(&mut self, direction: Direction) {
+        unsafe {
+            bindings::gpiod_line_config_set_direction_default(
+                self.config,
+                direction.gpiod_direction() as i32,
+            )
+        }
+    }
+
+    /// Set the direction for a line.
+    pub fn set_direction_override(&mut self, direction: Direction, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_direction_override(
+                self.config,
+                direction.gpiod_direction() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the direction for a line.
+    pub fn clear_direction_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_direction_override(self.config, offset) }
+    }
+
+    /// Check if the direction is overridden for a line.
+    pub fn direction_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_direction_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default direction setting.
+    pub fn get_direction_default(&self) -> Result<Direction> {
+        Direction::new(
+            unsafe { bindings::gpiod_line_config_get_direction_default(self.config) } as u32,
+        )
+    }
+
+    /// Get the direction of a given line.
+    ///
+    /// Direction setting for the line if the config object were used in a request.
+    pub fn get_direction_offset(&self, offset: u32) -> Result<Direction> {
+        Direction::new(unsafe {
+            bindings::gpiod_line_config_get_direction_offset(self.config, offset)
+        } as u32)
+    }
+
+    /// Set the default edge event detection setting.
+    pub fn set_edge_detection_default(&mut self, edge: Edge) {
+        unsafe {
+            bindings::gpiod_line_config_set_edge_detection_default(
+                self.config,
+                edge.gpiod_edge() as i32,
+            )
+        }
+    }
+
+    /// Set the edge event detection for a single line.
+    pub fn set_edge_detection_override(&mut self, edge: Edge, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_edge_detection_override(
+                self.config,
+                edge.gpiod_edge() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the edge event detection for a single line.
+    pub fn clear_edge_detection_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_edge_detection_override(self.config, offset) }
+    }
+
+    /// Check if the edge event detection is overridden for a line.
+    pub fn edge_detection_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_edge_detection_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default edge event detection setting.
+    pub fn get_edge_detection_default(&self) -> Result<Edge> {
+        Edge::new(
+            unsafe { bindings::gpiod_line_config_get_edge_detection_default(self.config) } as u32,
+        )
+    }
+
+    /// Get the edge event detection setting for a given line.
+    ///
+    /// Edge event detection setting for the line if the config object were used in a request.
+    pub fn get_edge_detection_offset(&self, offset: u32) -> Result<Edge> {
+        Edge::new(unsafe {
+            bindings::gpiod_line_config_get_edge_detection_offset(self.config, offset)
+        } as u32)
+    }
+
+    /// Set the default bias setting.
+    pub fn set_bias_default(&mut self, bias: Bias) {
+        unsafe {
+            bindings::gpiod_line_config_set_bias_default(self.config, bias.gpiod_bias() as i32)
+        }
+    }
+
+    /// Set the bias for a single line.
+    pub fn set_bias_override(&mut self, bias: Bias, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_bias_override(
+                self.config,
+                bias.gpiod_bias() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the bias for a single line.
+    pub fn clear_bias_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_bias_override(self.config, offset) }
+    }
+
+    /// Check if the bias is overridden for a line.
+    pub fn bias_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_bias_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default bias setting.
+    pub fn get_bias_default(&self) -> Result<Bias> {
+        Bias::new(unsafe { bindings::gpiod_line_config_get_bias_default(self.config) } as u32)
+    }
+
+    /// Get the bias setting for a given line.
+    ///
+    /// Bias setting used for the line if the config object were used in a request.
+    pub fn get_bias_offset(&self, offset: u32) -> Result<Bias> {
+        Bias::new(
+            unsafe { bindings::gpiod_line_config_get_bias_offset(self.config, offset) } as u32,
+        )
+    }
+
+    /// Set the default drive setting.
+    pub fn set_drive_default(&mut self, drive: Drive) {
+        unsafe {
+            bindings::gpiod_line_config_set_drive_default(self.config, drive.gpiod_drive() as i32)
+        }
+    }
+
+    /// Set the drive for a single line.
+    pub fn set_drive_override(&mut self, drive: Drive, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_drive_override(
+                self.config,
+                drive.gpiod_drive() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// clear the drive for a single line.
+    pub fn clear_drive_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_drive_override(self.config, offset) }
+    }
+
+    /// Check if the drive is overridden for a line.
+    pub fn drive_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_drive_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default drive setting.
+    pub fn get_drive_default(&self) -> Result<Drive> {
+        Drive::new(unsafe { bindings::gpiod_line_config_get_drive_default(self.config) } as u32)
+    }
+
+    /// Get the drive setting for a given line.
+    ///
+    /// The offset of the line for which to read the drive setting. Drive setting for the line if
+    /// the config object were used in a request.
+    pub fn get_drive_offset(&self, offset: u32) -> Result<Drive> {
+        Drive::new(
+            unsafe { bindings::gpiod_line_config_get_drive_offset(self.config, offset) } as u32,
+        )
+    }
+
+    /// Set default active-low setting.
+    pub fn set_active_low_default(&mut self, active_low: bool) {
+        unsafe { bindings::gpiod_line_config_set_active_low_default(self.config, active_low) }
+    }
+
+    /// Set active-low setting for a single line.
+    pub fn set_active_low_override(&mut self, active_low: bool, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_active_low_override(self.config, active_low, offset)
+        }
+    }
+
+    /// Clear a single line's active-low setting.
+    pub fn clear_active_low_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_active_low_override(self.config, offset) }
+    }
+
+    /// Check if the active-low is overridden for a line.
+    pub fn active_low_is_overridden(&mut self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_active_low_is_overridden(self.config, offset) }
+    }
+
+    /// Check the default active-low setting.
+    pub fn get_active_low_default(&self) -> bool {
+        unsafe { bindings::gpiod_line_config_get_active_low_default(self.config) }
+    }
+
+    /// Check the active-low setting of a line.
+    ///
+    /// Active-low setting for the line if the config object were used in a request.
+    pub fn get_active_low_offset(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_get_active_low_offset(self.config, offset) }
+    }
+
+    /// Set the deafult debounce period setting.
+    pub fn set_debounce_period_default(&mut self, period: Duration) {
+        unsafe {
+            bindings::gpiod_line_config_set_debounce_period_us_default(
+                self.config,
+                period.as_micros() as u64,
+            )
+        }
+    }
+
+    /// Set the debounce period for a single line.
+    pub fn set_debounce_period_override(&mut self, period: Duration, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_debounce_period_us_override(
+                self.config,
+                period.as_micros() as u64,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the debounce period for a single line.
+    pub fn clear_debounce_period_override(&mut self, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_clear_debounce_period_us_override(self.config, offset)
+        }
+    }
+
+    /// Check if the debounce period setting is overridden.
+    pub fn debounce_period_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_debounce_period_us_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default debounce period.
+    pub fn get_debounce_period_default(&self) -> Result<Duration> {
+        Ok(Duration::from_micros(unsafe {
+            bindings::gpiod_line_config_get_debounce_period_us_default(self.config)
+        }))
+    }
+
+    /// Get the debounce period for a given line.
+    ///
+    /// Debounce period for the line if the config object were used in a request, 0 if debouncing
+    /// is disabled.
+    pub fn get_debounce_period_offset(&self, offset: u32) -> Result<Duration> {
+        Ok(Duration::from_micros(unsafe {
+            bindings::gpiod_line_config_get_debounce_period_us_offset(self.config, offset)
+        }))
+    }
+
+    /// Set the default event clock setting.
+    pub fn set_event_clock_default(&mut self, clock: EventClock) {
+        unsafe {
+            bindings::gpiod_line_config_set_event_clock_default(
+                self.config,
+                clock.gpiod_clock() as i32,
+            )
+        }
+    }
+
+    /// Set the event clock for a single line.
+    pub fn set_event_clock_override(&mut self, clock: EventClock, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_event_clock_override(
+                self.config,
+                clock.gpiod_clock() as i32,
+                offset,
+            )
+        }
+    }
+
+    /// Clear the event clock for a single line.
+    pub fn clear_event_clock_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_event_clock_override(self.config, offset) }
+    }
+
+    /// Check if the event clock is overridden for a line.
+    pub fn event_clock_is_overridden(&mut self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_event_clock_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default event clock setting.
+    pub fn get_event_clock_default(&self) -> Result<EventClock> {
+        EventClock::new(
+            unsafe { bindings::gpiod_line_config_get_event_clock_default(self.config) } as u32,
+        )
+    }
+
+    /// Get the event clock setting for a given line.
+    ///
+    /// Event clock setting for the line if the config object were used in a request.
+    pub fn get_event_clock_offset(&self, offset: u32) -> Result<EventClock> {
+        EventClock::new(unsafe {
+            bindings::gpiod_line_config_get_event_clock_offset(self.config, offset)
+        } as u32)
+    }
+
+    /// Set the default output value setting.
+    pub fn set_output_value_default(&mut self, value: u32) {
+        unsafe { bindings::gpiod_line_config_set_output_value_default(self.config, value as i32) }
+    }
+
+    /// Set the output value for a line.
+    pub fn set_output_value_override(&mut self, value: u32, offset: u32) {
+        unsafe {
+            bindings::gpiod_line_config_set_output_value_override(self.config, value as i32, offset)
+        }
+    }
+
+    /// Set the output values for a set of lines.
+    pub fn set_output_values(&mut self, offsets: &[u32], values: &[i32]) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineConfig array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        unsafe {
+            bindings::gpiod_line_config_set_output_values(
+                self.config,
+                values.len() as c_ulong,
+                offsets.as_ptr(),
+                values.as_ptr(),
+            );
+        }
+
+        Ok(())
+    }
+
+    /// Clear the output value for a line.
+    pub fn clear_output_value_override(&mut self, offset: u32) {
+        unsafe { bindings::gpiod_line_config_clear_output_value_override(self.config, offset) }
+    }
+
+    /// Check if the output value is overridden for a line.
+    pub fn output_value_is_overridden(&self, offset: u32) -> bool {
+        unsafe { bindings::gpiod_line_config_output_value_is_overridden(self.config, offset) }
+    }
+
+    /// Get the default output value, 0 or 1.
+    pub fn get_output_value_default(&self) -> Result<u32> {
+        let value = unsafe { bindings::gpiod_line_config_get_output_value_default(self.config) };
+
+        if value != 0 && value != 1 {
+            Err(Error::OperationFailed(
+                "Gpio LineConfig get-output-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(value as u32)
+        }
+    }
+
+    /// Get the output value configured for a given line, 0 or 1.
+    pub fn get_output_value_offset(&self, offset: u32) -> Result<u32> {
+        let value =
+            unsafe { bindings::gpiod_line_config_get_output_value_offset(self.config, offset) };
+
+        if value != 0 && value != 1 {
+            Err(Error::OperationFailed(
+                "Gpio LineConfig get-output-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(value as u32)
+        }
+    }
+
+    /// Get the list of overridden offsets and the corresponding types of overridden settings.
+    pub fn get_overrides(&self) -> Result<Vec<(u32, Config)>> {
+        let num = unsafe { bindings::gpiod_line_config_get_num_overrides(self.config) } as usize;
+        if num == 0 {
+            return Ok(Vec::new());
+        }
+
+        let mut overrides = Vec::new();
+        let mut offset = vec![0_u32; num];
+        let mut props = vec![0_i32; num];
+
+        unsafe {
+            bindings::gpiod_line_config_get_overrides(
+                self.config,
+                offset.as_mut_ptr(),
+                props.as_mut_ptr(),
+            )
+        };
+
+        for i in 0..num {
+            overrides.push((offset[i], Config::new(props[i] as u32)?));
+        }
+
+        Ok(overrides)
+    }
+}
+
+impl Drop for LineConfig {
+    /// Free the line config object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_line_config_free(self.config) }
+    }
+}
diff --git a/bindings/rust/src/line_info.rs b/bindings/rust/src/line_info.rs
new file mode 100644
index 000000000000..70b6bd6a84bb
--- /dev/null
+++ b/bindings/rust/src/line_info.rs
@@ -0,0 +1,189 @@ 
+// 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::convert::TryFrom;
+use std::sync::Arc;
+use std::time::Duration;
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{
+    bindings, Bias, ChipInternal, Direction, Drive, Edge, Error, EventClock, InfoEvent, Result,
+};
+
+/// Line info
+///
+/// Exposes functions for retrieving kernel information about both requested and
+/// free lines.  Line info object contains an immutable snapshot of a line's status.
+///
+/// The line info contains all the publicly available information about a
+/// line, which does not include the line value.  The line must be requested
+/// to access the line value.
+
+pub struct LineInfo {
+    info: *mut bindings::gpiod_line_info,
+    ichip: Option<Arc<ChipInternal>>,
+    free: bool,
+}
+
+impl LineInfo {
+    /// Get a snapshot of information about the line and optionally start watching it for changes.
+    pub(crate) fn new(ichip: Arc<ChipInternal>, offset: u32, watch: bool) -> Result<Self> {
+        let info = if watch {
+            unsafe { bindings::gpiod_chip_watch_line_info(ichip.chip(), offset) }
+        } else {
+            unsafe { bindings::gpiod_chip_get_line_info(ichip.chip(), offset) }
+        };
+
+        if info.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineInfo line-info",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self {
+            info,
+            ichip: if watch { Some(ichip) } else { None },
+            free: watch,
+        })
+    }
+
+    /// Stop watching the line
+    pub fn unwatch(&mut self) {
+        if let Some(ichip) = &self.ichip {
+            unsafe {
+                bindings::gpiod_chip_unwatch_line_info(ichip.chip(), self.get_offset());
+            }
+            self.ichip = None;
+        }
+    }
+
+    /// Get the offset of the line within the GPIO chip.
+    ///
+    /// The offset uniquely identifies the line on the chip. The combination of the chip and offset
+    /// uniquely identifies the line within the system.
+
+    pub fn get_offset(&self) -> u32 {
+        unsafe { bindings::gpiod_line_info_get_offset(self.info) }
+    }
+
+    /// Get GPIO line's name.
+    pub fn get_name(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct LineInfo`.
+        let name = unsafe { bindings::gpiod_line_info_get_name(self.info) };
+        if name.is_null() {
+            return Err(Error::NameNotFound("GPIO line's name"));
+        }
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Returns True if the line is in use, false otherwise.
+    ///
+    /// The user space can't know exactly why a line is busy. It may have been
+    /// requested by another process or hogged by the kernel. It only matters that
+    /// the line is used and we can't request it.
+    pub fn is_used(&self) -> bool {
+        unsafe { bindings::gpiod_line_info_is_used(self.info) }
+    }
+
+    /// Get the GPIO line's consumer name.
+    pub fn get_consumer(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct LineInfo`.
+        let name = unsafe { bindings::gpiod_line_info_get_consumer(self.info) };
+        if name.is_null() {
+            return Err(Error::NameNotFound("GPIO line's consumer name"));
+        }
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(name as *const u8, bindings::strlen(name) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Get the GPIO line's direction.
+    pub fn get_direction(&self) -> Result<Direction> {
+        Direction::new(unsafe { bindings::gpiod_line_info_get_direction(self.info) } as u32)
+    }
+
+    /// Returns true if the line is "active-low", false otherwise.
+    pub fn is_active_low(&self) -> bool {
+        unsafe { bindings::gpiod_line_info_is_active_low(self.info) }
+    }
+
+    /// Get the GPIO line's bias setting.
+    pub fn get_bias(&self) -> Result<Bias> {
+        Bias::new(unsafe { bindings::gpiod_line_info_get_bias(self.info) } as u32)
+    }
+
+    /// Get the GPIO line's drive setting.
+    pub fn get_drive(&self) -> Result<Drive> {
+        Drive::new(unsafe { bindings::gpiod_line_info_get_drive(self.info) } as u32)
+    }
+
+    /// Get the current edge detection setting of the line.
+    pub fn get_edge_detection(&self) -> Result<Edge> {
+        Edge::new(unsafe { bindings::gpiod_line_info_get_edge_detection(self.info) } as u32)
+    }
+
+    /// Get the current event clock setting used for edge event timestamps.
+    pub fn get_event_clock(&self) -> Result<EventClock> {
+        EventClock::new(unsafe { bindings::gpiod_line_info_get_event_clock(self.info) } as u32)
+    }
+
+    /// Returns true if the line is debounced (either by hardware or by the
+    /// kernel software debouncer), false otherwise.
+    pub fn is_debounced(&self) -> bool {
+        unsafe { bindings::gpiod_line_info_is_debounced(self.info) }
+    }
+
+    /// Get the debounce period of the line.
+    pub fn get_debounce_period(&self) -> Duration {
+        Duration::from_micros(unsafe {
+            bindings::gpiod_line_info_get_debounce_period_us(self.info)
+        })
+    }
+}
+
+impl TryFrom<&InfoEvent> for LineInfo {
+    type Error = Error;
+
+    /// Get the Line info object associated with a event.
+    fn try_from(event: &InfoEvent) -> Result<Self> {
+        let info = unsafe { bindings::gpiod_info_event_get_line_info(event.event()) };
+        if info.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineInfo try-from",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self {
+            info,
+            ichip: None,
+            free: false,
+        })
+    }
+}
+
+impl Drop for LineInfo {
+    fn drop(&mut self) {
+        // We must not free the Line info object created from `struct InfoEvent` by calling
+        // libgpiod API.
+        if self.free {
+            self.unwatch();
+            unsafe { bindings::gpiod_line_info_free(self.info) }
+        }
+    }
+}
diff --git a/bindings/rust/src/line_request.rs b/bindings/rust/src/line_request.rs
new file mode 100644
index 000000000000..bb338e72671d
--- /dev/null
+++ b/bindings/rust/src/line_request.rs
@@ -0,0 +1,248 @@ 
+// 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::EINVAL;
+use std::os::raw::c_ulong;
+use std::sync::Arc;
+use std::time::Duration;
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, ChipInternal, EdgeEventBuffer, Error, LineConfig, RequestConfig, Result};
+
+/// Line request operations
+///
+/// Allows interaction with a set of requested lines.
+pub struct LineRequest {
+    request: *mut bindings::gpiod_line_request,
+}
+
+impl LineRequest {
+    /// Request a set of lines for exclusive usage.
+    pub(crate) fn new(
+        ichip: &Arc<ChipInternal>,
+        rconfig: &RequestConfig,
+        lconfig: &LineConfig,
+    ) -> Result<Self> {
+        let request = unsafe {
+            bindings::gpiod_chip_request_lines(ichip.chip(), rconfig.config(), lconfig.config())
+        };
+
+        if request.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest request-lines",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { request })
+    }
+
+    /// Get the number of lines in the request.
+    pub fn get_num_lines(&self) -> u32 {
+        unsafe { bindings::gpiod_line_request_get_num_lines(self.request) as u32 }
+    }
+
+    /// Get the offsets of lines in the request.
+    pub fn get_offsets(&self) -> Vec<u32> {
+        let mut offsets = vec![0; self.get_num_lines() as usize];
+
+        unsafe { bindings::gpiod_line_request_get_offsets(self.request, offsets.as_mut_ptr()) };
+        offsets
+    }
+
+    /// Get the value (0 or 1) of a single line associated with the request.
+    pub fn get_value(&self, offset: u32) -> Result<u32> {
+        let value = unsafe { bindings::gpiod_line_request_get_value(self.request, offset) };
+
+        if value != 0 && value != 1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest get-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(value as u32)
+        }
+    }
+
+    /// Get values of a subset of lines associated with the request.
+    pub fn get_values_subset(&self, offsets: &[u32], values: &mut Vec<i32>) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        let ret = unsafe {
+            bindings::gpiod_line_request_get_values_subset(
+                self.request,
+                offsets.len() as c_ulong,
+                offsets.as_ptr(),
+                values.as_mut_ptr(),
+            )
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest get-values-subset",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get values of all lines associated with the request.
+    pub fn get_values(&self, values: &mut Vec<i32>) -> Result<()> {
+        if values.len() != self.get_num_lines() as usize {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        let ret =
+            unsafe { bindings::gpiod_line_request_get_values(self.request, values.as_mut_ptr()) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest get-values",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Set the value of a single line associated with the request.
+    pub fn set_value(&self, offset: u32, value: i32) -> Result<()> {
+        let ret = unsafe { bindings::gpiod_line_request_set_value(self.request, offset, !!value) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest set-value",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get values of a subset of lines associated with the request.
+    pub fn set_values_subset(&self, offsets: &[u32], values: &[i32]) -> Result<()> {
+        if offsets.len() != values.len() {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        let ret = unsafe {
+            bindings::gpiod_line_request_set_values_subset(
+                self.request,
+                offsets.len() as c_ulong,
+                offsets.as_ptr(),
+                values.as_ptr(),
+            )
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest set-values-subset",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get values of all lines associated with the request.
+    pub fn set_values(&self, values: &[i32]) -> Result<()> {
+        if values.len() != self.get_num_lines() as usize {
+            return Err(Error::OperationFailed(
+                "Gpio LineRequest array size mismatch",
+                IoError::new(EINVAL),
+            ));
+        }
+
+        let ret = unsafe { bindings::gpiod_line_request_set_values(self.request, values.as_ptr()) };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest set-values",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Update the configuration of lines associated with the line request.
+    pub fn reconfigure_lines(&self, lconfig: &LineConfig) -> Result<()> {
+        let ret = unsafe {
+            bindings::gpiod_line_request_reconfigure_lines(self.request, lconfig.config())
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest reconfigure-lines",
+                IoError::last(),
+            ))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Get the file descriptor associated with the line request.
+    pub fn get_fd(&self) -> u32 {
+        unsafe { bindings::gpiod_line_request_get_fd(self.request) as u32 }
+    }
+
+    /// Wait for edge events on any of the lines associated with the request.
+    pub fn wait_edge_event(&self, timeout: Duration) -> Result<()> {
+        let ret = unsafe {
+            bindings::gpiod_line_request_wait_edge_event(self.request, timeout.as_nanos() as i64)
+        };
+
+        match ret {
+            -1 => Err(Error::OperationFailed(
+                "Gpio LineRequest edge-event-wait",
+                IoError::last(),
+            )),
+            0 => Err(Error::OperationTimedOut),
+            _ => Ok(()),
+        }
+    }
+
+    /// Get a number of edge events from a line request.
+    ///
+    /// This function will block if no event was queued for the line.
+    pub fn read_edge_event(&self, buffer: &EdgeEventBuffer, max_events: u32) -> Result<u32> {
+        let ret = unsafe {
+            bindings::gpiod_line_request_read_edge_event(
+                self.request,
+                buffer.buffer(),
+                max_events as c_ulong,
+            )
+        };
+
+        if ret == -1 {
+            Err(Error::OperationFailed(
+                "Gpio LineRequest edge-event-read",
+                IoError::last(),
+            ))
+        } else {
+            Ok(ret as u32)
+        }
+    }
+}
+
+impl Drop for LineRequest {
+    /// Release the requested lines and free all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_line_request_release(self.request) }
+    }
+}
diff --git a/bindings/rust/src/request_config.rs b/bindings/rust/src/request_config.rs
new file mode 100644
index 000000000000..3e667d28cc1f
--- /dev/null
+++ b/bindings/rust/src/request_config.rs
@@ -0,0 +1,122 @@ 
+// 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::os::raw::{c_char, c_ulong};
+use std::{slice, str};
+
+use vmm_sys_util::errno::Error as IoError;
+
+use super::{bindings, Error, Result};
+
+/// Request configuration objects
+///
+/// Request config objects are used to pass a set of options to the kernel at
+/// the time of the line request. Similarly to the line-config - the mutators
+/// don't return error values. If the values are invalid, in general they are
+/// silently adjusted to acceptable ranges.
+
+pub struct RequestConfig {
+    config: *mut bindings::gpiod_request_config,
+}
+
+impl RequestConfig {
+    /// Create a new request config object.
+    pub fn new() -> Result<Self> {
+        let config = unsafe { bindings::gpiod_request_config_new() };
+        if config.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio RequestConfig new",
+                IoError::last(),
+            ));
+        }
+
+        Ok(Self { config })
+    }
+
+    /// Private helper, Returns gpiod_request_config
+    pub(crate) fn config(&self) -> *mut bindings::gpiod_request_config {
+        self.config
+    }
+
+    /// Set the consumer name for the request.
+    ///
+    /// If the consumer string is too long, it will be truncated to the max
+    /// accepted length.
+    pub fn set_consumer(&self, consumer: &str) {
+        // Null-terminate the string
+        let consumer = consumer.to_owned() + "\0";
+
+        unsafe {
+            bindings::gpiod_request_config_set_consumer(
+                self.config,
+                consumer.as_ptr() as *const c_char,
+            )
+        }
+    }
+
+    /// Get the consumer name configured in the request config.
+    pub fn get_consumer(&self) -> Result<&str> {
+        // SAFETY: The string returned by libgpiod is guaranteed to live as long
+        // as the `struct RequestConfig`.
+        let consumer = unsafe { bindings::gpiod_request_config_get_consumer(self.config) };
+        if consumer.is_null() {
+            return Err(Error::OperationFailed(
+                "Gpio RequestConfig get-consumer",
+                IoError::last(),
+            ));
+        }
+
+        // SAFETY: The string is guaranteed to be valid here.
+        str::from_utf8(unsafe {
+            slice::from_raw_parts(consumer as *const u8, bindings::strlen(consumer) as usize)
+        })
+        .map_err(Error::InvalidString)
+    }
+
+    /// Set the offsets of the lines to be requested.
+    ///
+    /// If too many offsets were specified, the offsets above the limit accepted
+    /// by the kernel (64 lines) are silently dropped.
+    pub fn set_offsets(&self, offsets: &[u32]) {
+        unsafe {
+            bindings::gpiod_request_config_set_offsets(
+                self.config,
+                offsets.len() as c_ulong,
+                offsets.as_ptr(),
+            )
+        }
+    }
+
+    /// Get the offsets of lines in the request config.
+    pub fn get_offsets(&self) -> Vec<u32> {
+        let num = unsafe { bindings::gpiod_request_config_get_num_offsets(self.config) };
+        let mut offsets = vec![0; num as usize];
+
+        unsafe { bindings::gpiod_request_config_get_offsets(self.config, offsets.as_mut_ptr()) };
+        offsets
+    }
+
+    /// Set the size of the kernel event buffer for the request.
+    ///
+    /// The kernel may adjust the value if it's too high. If set to 0, the
+    /// default value will be used.
+    pub fn set_event_buffer_size(&self, size: u32) {
+        unsafe {
+            bindings::gpiod_request_config_set_event_buffer_size(self.config, size as c_ulong)
+        }
+    }
+
+    /// Get the edge event buffer size for the request config.
+    pub fn get_event_buffer_size(&self) -> u32 {
+        unsafe { bindings::gpiod_request_config_get_event_buffer_size(self.config) as u32 }
+    }
+}
+
+impl Drop for RequestConfig {
+    /// Free the request config object and release all associated resources.
+    fn drop(&mut self) {
+        unsafe { bindings::gpiod_request_config_free(self.config) }
+    }
+}