1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// 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
//! 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>