shared_mem_queue/lib.rs
1// Copyright Open Logistics Foundation
2//
3// Licensed under the Open Logistics Foundation License 1.3.
4// For details on the licensing terms, see the LICENSE file.
5// SPDX-License-Identifier: OLFL-1.3
6
7#![cfg_attr(not(test), no_std)]
8
9//! This `no_std` library implements simple lock-free single-writer single-reader queues which can
10//! be used to communicate efficiently over shared memory. They can especially be used for
11//! inter-process-communication or, on systems with multiple processors accessing the same memory,
12//! even for inter-processor-communication. They can also be used as single-thread FIFO queues or to
13//! communicate between threads or tasks in `no_std` environments where the `std::sync::mpsc`
14//! types, which are safer alternatives for these use cases, are not available.
15//!
16//! Currently, this library implements two FIFO queue types:
17//! 1. [`ByteQueue`](byte_queue::ByteQueue): This queue implements a byte-oriented interface. For
18//! example, it may be used when transmission boundaries can be ignored, e.g. for text-based
19//! communication like logging. Or this queue type may be used whenever it is trivial to restore
20//! packet boundaries, e.g. for transmitting streams of fixed-size data like streams of the same
21//! value type. For implementation details, see the [`byte_queue`] module.
22//! 2. [`MsgQueue`](msg_queue::MsgQueue): This queue implements a message-/packet-/datagram-based
23//! interface, i.e. it will preserve packet boundaries. On the reader-side, it will only deliver
24//! complete and CRC-checked packets. For example, this queue can be used whenever packet
25//! boundaries should be preserved, e.g. for variable-sized serialized data. This queue is
26//! implemented on top of the `ByteQueue`. It uses the `ByteQueue` to transfer data following a
27//! custom protocol format. For further implementation details, see the [`msg_queue`] module.
28//!
29//! Initially, this library has been developed as a simple efficient solution to communicate
30//! between the Cortex-M4 and the Cortex-A7 on STM32MP1 microprocessors. It assumes that `u32` can
31//! be written and read atomically. This requirement holds true for many modern architectures, it
32//! is fulfilled by the x86 and ARM architectures, both 32 bit and 64 bit.
33//!
34//! # Usage Examples
35//!
36//! ## Single-Processor Single-Thread Byte Queue
37//! ```
38//! # use shared_mem_queue::byte_queue::ByteQueue;
39//! type AlignedType = u32; // `u32` for proper alignment of the buffer regardless of its size
40//! const LEN_SCALER: usize = core::mem::size_of::<AlignedType>();
41//! let mut buffer = [0 as AlignedType; 128];
42//! let mut queue = unsafe {
43//! ByteQueue::create(buffer.as_mut_ptr() as *mut u8, buffer.len() * LEN_SCALER)
44//! };
45//! let tx = [1, 2, 3, 4];
46//! queue.write_blocking(&tx);
47//! let mut rx = [0u8; 4];
48//! queue.consume_blocking(&mut rx);
49//! assert_eq!(&tx, &rx);
50//! ```
51//!
52//! ## Single-Processor Multi-Thread Byte Queue
53//!
54//! A more realistic example involves creating a reader and a writer separately; although not shown
55//! here, they may be moved to a different thread. Since the `ByteQueue` is instantiated with a raw
56//! pointer instead of a reference, the borrow checker does not track the lifetime of the
57//! underlying buffer.
58//! ```
59//! # use shared_mem_queue::byte_queue::ByteQueue;
60//! const LEN_U32_TO_U8_SCALER: usize = core::mem::size_of::<u32>();
61//! let mut buffer = [123 as u32; 17];
62//! let mut writer = unsafe {
63//! ByteQueue::create(
64//! buffer.as_mut_ptr() as *mut u8,
65//! buffer.len() * LEN_U32_TO_U8_SCALER,
66//! )
67//! };
68//! let mut reader = unsafe {
69//! ByteQueue::attach(
70//! buffer.as_mut_ptr() as *mut u8,
71//! buffer.len() * LEN_U32_TO_U8_SCALER,
72//! )
73//! };
74//! let tx = [1, 2, 3, 4];
75//! writer.write_blocking(&tx);
76//! let mut rx = [0u8; 4];
77//! reader.consume_blocking(&mut rx);
78//! assert_eq!(&tx, &rx);
79//! ```
80//!
81//! ## Single-Procesor Message Queue
82//! ```
83//! # use shared_mem_queue::byte_queue::ByteQueue;
84//! # use shared_mem_queue::msg_queue::MsgQueue;
85//! const DEFAULT_PREFIX: &'static [u8] = b"DEFAULT_PREFIX: "; // 16 byte long
86//! let mut bq_buf = [0u32; 64];
87//! let mut msg_queue = unsafe {
88//! MsgQueue::new(
89//! ByteQueue::create(bq_buf.as_mut_ptr() as *mut u8, bq_buf.len() * 4),
90//! DEFAULT_PREFIX,
91//! [0u8; 64 * 4]
92//! )
93//! };
94//!
95//! let msg = b"Hello, World!";
96//! let result = msg_queue.write_or_fail(msg);
97//! assert!(result.is_ok());
98//! let read_msg = msg_queue.read_or_fail().unwrap();
99//! assert_eq!(read_msg, msg);
100//! ```
101//!
102//! ## Shared-Memory Byte Queue
103//!
104//! In general, an `mmap` call is required to access the queue from a Linux system. This can be
105//! done with the [`memmap` crate](https://crates.io/crates/memmap). The following example probably
106//! panics when executed naively because access to `/dev/mem` requires root
107//! privileges. Additionally, the example memory region in use is probably not viable for this
108//! queue on most systems. In the following example, `ByteQueue::attach` is used, i.e. it is
109//! assumed that another processor or process has already initialized a `ByteQueue` in the
110//! same memory region.
111//! ```no_run
112//! # use shared_mem_queue::byte_queue::ByteQueue;
113//! # use std::convert::TryInto;
114//! let shared_mem_start = 0x10048000; // example
115//! let shared_mem_len = 0x00008000; // region
116//! let dev_mem = std::fs::OpenOptions::new()
117//! .read(true)
118//! .write(true)
119//! .open("/dev/mem")
120//! .expect("Could not open /dev/mem, do you have root privileges?");
121//! let mut mmap = unsafe {
122//! memmap::MmapOptions::new()
123//! .len(shared_mem_len)
124//! .offset(shared_mem_start.try_into().unwrap())
125//! .map_mut(&dev_mem)
126//! .unwrap()
127//! };
128//! let mut channel = unsafe {
129//! ByteQueue::attach(mmap.as_mut_ptr(), shared_mem_len)
130//! };
131//! ```
132//!
133//! ## Bi-Directional Shared-Memory Communication
134//!
135//! In most inter-processor-communication scenarios, two queues will be required for bi-directional
136//! communication. A single `mmap` call is sufficient, the memory region can be split manually
137//! afterwards:
138//! ```no_run
139//! # use shared_mem_queue::byte_queue::ByteQueue;
140//! # use std::convert::TryInto;
141//! let shared_mem_start = 0x10048000; // example
142//! let shared_mem_len = 0x00008000; // region
143//! let dev_mem = std::fs::OpenOptions::new()
144//! .read(true)
145//! .write(true)
146//! .open("/dev/mem")
147//! .expect("Could not open /dev/mem, do you have root privileges?");
148//! let mut mmap = unsafe {
149//! memmap::MmapOptions::new()
150//! .len(shared_mem_len)
151//! .offset(shared_mem_start.try_into().unwrap())
152//! .map_mut(&dev_mem)
153//! .unwrap()
154//! };
155//! let mut channel_write = unsafe {
156//! ByteQueue::attach(mmap.as_mut_ptr(), shared_mem_len / 2)
157//! };
158//! let mut channel_read = unsafe {
159//! ByteQueue::attach(mmap.as_mut_ptr().add(shared_mem_len / 2), shared_mem_len / 2)
160//! };
161//! ```
162//!
163//! # License
164//!
165//! Open Logistics Foundation License\
166//! Version 1.3, January 2023
167//!
168//! See the LICENSE file in the top-level directory.
169//!
170//! # Contact
171//!
172//! Fraunhofer IML Embedded Rust Group - <embedded-rust@iml.fraunhofer.de>
173
174pub mod byte_queue;
175pub mod msg_queue;