shared-mem-queue 0.4.0

Single-writer single-reader queues which can be used for inter-processor-communication in a shared memory region
Documentation
// Copyright Open Logistics Foundation
//
// Licensed under the Open Logistics Foundation License 1.3.
// For details on the licensing terms, see the LICENSE file.
// SPDX-License-Identifier: OLFL-1.3

#![cfg_attr(not(test), no_std)]

//! This `no_std` library implements simple lock-free single-writer single-reader queues which can
//! be used to communicate efficiently over shared memory. They can especially be used for
//! inter-process-communication or, on systems with multiple processors accessing the same memory,
//! even for inter-processor-communication. They can also be used as single-thread FIFO queues or to
//! communicate between threads or tasks in `no_std` environments where the `std::sync::mpsc`
//! types, which are safer alternatives for these use cases, are not available.
//!
//! Currently, this library implements two FIFO queue types:
//! 1. [`ByteQueue`](byte_queue::ByteQueue): This queue implements a byte-oriented interface. For
//!    example, it may be used when transmission boundaries can be ignored, e.g. for text-based
//!    communication like logging. Or this queue type may be used whenever it is trivial to restore
//!    packet boundaries, e.g. for transmitting streams of fixed-size data like streams of the same
//!    value type. For implementation details, see the [`byte_queue`] module.
//! 2. [`MsgQueue`](msg_queue::MsgQueue): This queue implements a message-/packet-/datagram-based
//!    interface, i.e. it will preserve packet boundaries. On the reader-side, it will only deliver
//!    complete and CRC-checked packets. For example, this queue can be used whenever packet
//!    boundaries should be preserved, e.g. for variable-sized serialized data. This queue is
//!    implemented on top of the `ByteQueue`. It uses the `ByteQueue` to transfer data following a
//!    custom protocol format. For further implementation details, see the [`msg_queue`] module.
//!
//! Initially, this library has been developed as a simple efficient solution to communicate
//! between the Cortex-M4 and the Cortex-A7 on STM32MP1 microprocessors. It assumes that `u32` can
//! be written and read atomically. This requirement holds true for many modern architectures, it
//! is fulfilled by the x86 and ARM architectures, both 32 bit and 64 bit.
//!
//! # Usage Examples
//!
//! ## Single-Processor Single-Thread Byte Queue
//! ```
//! # use shared_mem_queue::byte_queue::ByteQueue;
//! type AlignedType = u32; // `u32` for proper alignment of the buffer regardless of its size
//! const LEN_SCALER: usize = core::mem::size_of::<AlignedType>();
//! let mut buffer = [0 as AlignedType; 128];
//! let mut queue = unsafe {
//!     ByteQueue::create(buffer.as_mut_ptr() as *mut u8, buffer.len() * LEN_SCALER)
//! };
//! let tx = [1, 2, 3, 4];
//! queue.write_blocking(&tx);
//! let mut rx = [0u8; 4];
//! queue.consume_blocking(&mut rx);
//! assert_eq!(&tx, &rx);
//! ```
//!
//! ## Single-Processor Multi-Thread Byte Queue
//!
//! A more realistic example involves creating a reader and a writer separately; although not shown
//! here, they may be moved to a different thread. Since the `ByteQueue` is instantiated with a raw
//! pointer instead of a reference, the borrow checker does not track the lifetime of the
//! underlying buffer.
//! ```
//! # use shared_mem_queue::byte_queue::ByteQueue;
//! const LEN_U32_TO_U8_SCALER: usize = core::mem::size_of::<u32>();
//! let mut buffer = [123 as u32; 17];
//! let mut writer = unsafe {
//!     ByteQueue::create(
//!         buffer.as_mut_ptr() as *mut u8,
//!         buffer.len() * LEN_U32_TO_U8_SCALER,
//!     )
//! };
//! let mut reader = unsafe {
//!     ByteQueue::attach(
//!         buffer.as_mut_ptr() as *mut u8,
//!         buffer.len() * LEN_U32_TO_U8_SCALER,
//!     )
//! };
//! let tx = [1, 2, 3, 4];
//! writer.write_blocking(&tx);
//! let mut rx = [0u8; 4];
//! reader.consume_blocking(&mut rx);
//! assert_eq!(&tx, &rx);
//! ```
//!
//! ## Single-Procesor Message Queue
//! ```
//! # use shared_mem_queue::byte_queue::ByteQueue;
//! # use shared_mem_queue::msg_queue::MsgQueue;
//! const DEFAULT_PREFIX: &'static [u8] = b"DEFAULT_PREFIX: "; // 16 byte long
//! let mut bq_buf = [0u32; 64];
//! let mut msg_queue = unsafe {
//!     MsgQueue::new(
//!         ByteQueue::create(bq_buf.as_mut_ptr() as *mut u8, bq_buf.len() * 4),
//!         DEFAULT_PREFIX,
//!         [0u8; 64 * 4]
//!     )
//! };
//!
//! let msg = b"Hello, World!";
//! let result = msg_queue.write_or_fail(msg);
//! assert!(result.is_ok());
//! let read_msg = msg_queue.read_or_fail().unwrap();
//! assert_eq!(read_msg, msg);
//! ```
//!
//! ## Shared-Memory Byte Queue
//!
//! In general, an `mmap` call is required to access the queue from a Linux system. This can be
//! done with the [`memmap` crate](https://crates.io/crates/memmap). The following example probably
//! panics when executed naively because access to `/dev/mem` requires root
//! privileges. Additionally, the example memory region in use is probably not viable for this
//! queue on most systems. In the following example, `ByteQueue::attach` is used, i.e. it is
//! assumed that another processor or process has already initialized a `ByteQueue` in the
//! same memory region.
//! ```no_run
//! # use shared_mem_queue::byte_queue::ByteQueue;
//! # use std::convert::TryInto;
//! let shared_mem_start = 0x10048000; // example
//! let shared_mem_len = 0x00008000;   // region
//! let dev_mem = std::fs::OpenOptions::new()
//!     .read(true)
//!     .write(true)
//!     .open("/dev/mem")
//!     .expect("Could not open /dev/mem, do you have root privileges?");
//! let mut mmap = unsafe {
//!     memmap::MmapOptions::new()
//!         .len(shared_mem_len)
//!         .offset(shared_mem_start.try_into().unwrap())
//!         .map_mut(&dev_mem)
//!         .unwrap()
//! };
//! let mut channel = unsafe {
//!     ByteQueue::attach(mmap.as_mut_ptr(), shared_mem_len)
//! };
//! ```
//!
//! ## Bi-Directional Shared-Memory Communication
//!
//! In most inter-processor-communication scenarios, two queues will be required for bi-directional
//! communication. A single `mmap` call is sufficient, the memory region can be split manually
//! afterwards:
//! ```no_run
//! # use shared_mem_queue::byte_queue::ByteQueue;
//! # use std::convert::TryInto;
//! let shared_mem_start = 0x10048000; // example
//! let shared_mem_len = 0x00008000;   // region
//! let dev_mem = std::fs::OpenOptions::new()
//!     .read(true)
//!     .write(true)
//!     .open("/dev/mem")
//!     .expect("Could not open /dev/mem, do you have root privileges?");
//! let mut mmap = unsafe {
//!     memmap::MmapOptions::new()
//!         .len(shared_mem_len)
//!         .offset(shared_mem_start.try_into().unwrap())
//!         .map_mut(&dev_mem)
//!         .unwrap()
//! };
//! let mut channel_write = unsafe {
//!     ByteQueue::attach(mmap.as_mut_ptr(), shared_mem_len / 2)
//! };
//! let mut channel_read = unsafe {
//!     ByteQueue::attach(mmap.as_mut_ptr().add(shared_mem_len / 2), shared_mem_len / 2)
//! };
//! ```
//!
//! # License
//!
//! Open Logistics Foundation License\
//! Version 1.3, January 2023
//!
//! See the LICENSE file in the top-level directory.
//!
//! # Contact
//!
//! Fraunhofer IML Embedded Rust Group - <embedded-rust@iml.fraunhofer.de>

pub mod byte_queue;
pub mod msg_queue;