esb_ng/payload.rs
1use crate::Error;
2use core::ops::{Deref, DerefMut};
3use bbq2::{
4 queue::BBQueue,
5 traits::{coordination::cas::AtomicCoord, notifier::maitake::MaiNotSpsc, storage::Inline},
6};
7
8// | SW USE | ACTUAL DMA PART |
9// | rssi - 1 byte | pipe - 1 byte | length - 1 byte | pid_no_ack - 1 byte | payload - 1 to 252 bytes |
10
11type FramedGrantR<const N: usize> = bbq2::prod_cons::framed::FramedGrantR<
12 &'static BBQueue<Inline<N>, AtomicCoord, MaiNotSpsc>,
13 Inline<N>,
14 AtomicCoord,
15 MaiNotSpsc,
16 u16,
17>;
18type FramedGrantW<const N: usize> = bbq2::prod_cons::framed::FramedGrantW<
19 &'static BBQueue<Inline<N>, AtomicCoord, MaiNotSpsc>,
20 Inline<N>,
21 AtomicCoord,
22 MaiNotSpsc,
23 u16,
24>;
25
26/// A builder for an `EsbHeader` structure
27///
28/// The builder is converted into an `EsbHeader` by calling the
29/// `check()` method.
30///
31/// ## Example
32///
33/// ```rust
34/// use esb::EsbHeaderBuilder;
35///
36/// let header_result = EsbHeaderBuilder::default()
37/// .max_payload(252)
38/// .pid(0)
39/// .pipe(0)
40/// .no_ack(true)
41/// .check();
42///
43/// assert!(header_result.is_ok());
44/// ```
45///
46/// ## Default Header Contents
47///
48/// By default, the following settings will be used:
49///
50/// | Field | Default Value |
51/// | :--- | :--- |
52/// | pid | 0 |
53/// | no_ack | true |
54/// | length | 0 |
55/// | pipe | 0 |
56///
57#[derive(Debug, Clone, Eq, PartialEq)]
58pub struct EsbHeaderBuilder(EsbHeader);
59
60impl Default for EsbHeaderBuilder {
61 fn default() -> Self {
62 EsbHeaderBuilder(EsbHeader {
63 rssi: 0,
64 pid_no_ack: 0,
65 length: 0,
66 pipe: 0,
67 })
68 }
69}
70
71impl EsbHeaderBuilder {
72 /// Set the pipe. Must be in the range 0..=7.
73 pub fn pipe(mut self, pipe: u8) -> Self {
74 self.0.pipe = pipe;
75 self
76 }
77
78 /// Set the max payload. Must be in the range 0..=252.
79 pub fn max_payload(mut self, max_payload: u8) -> Self {
80 self.0.length = max_payload;
81 self
82 }
83
84 /// Enable/disable acknowledgment
85 pub fn no_ack(mut self, no_ack: bool) -> Self {
86 // TODO(AJM): We should probably just call this
87 // method "ack", or "enable_ack", because "no_ack"
88 // is really confusing.
89 if no_ack {
90 self.0.pid_no_ack &= 0b1111_1110;
91 } else {
92 self.0.pid_no_ack |= 0b0000_0001;
93 }
94 self
95 }
96
97 /// Set the pid. Must be in the range 0..=3.
98 pub fn pid(mut self, pid: u8) -> Self {
99 // TODO(AJM): Do we want the user to set the pid? isn't this an
100 // internal "retry" counter?
101 self.0.pid_no_ack &= 0b0000_0001;
102 self.0.pid_no_ack |= pid << 1;
103 self
104 }
105
106 /// Finalize the header.
107 ///
108 /// If the set parameters are out of range, an error will be returned.
109 pub fn check(self) -> Result<EsbHeader, Error> {
110 let bad_length = self.0.length > 252;
111 let bad_pipe = self.0.pipe > 7;
112
113 // This checks if "pid" > 3, where pid_no_ack is pid << 1.
114 let bad_pid = self.0.pid_no_ack > 0b0000_0111;
115
116 if bad_length || bad_pid || bad_pipe {
117 return Err(Error::InvalidParameters);
118 }
119
120 Ok(self.0)
121 }
122}
123
124/// The non-payload portion of an ESB packet
125///
126/// This is typically used to create a Packet Grant using methods
127/// from the [`EsbApp`](struct.EsbApp.html) or [`EsbIrq`](struct.EsbIrq.html)
128/// interfaces.
129///
130/// ## Example
131///
132/// ```rust
133/// use esb::EsbHeader;
134///
135/// let builder_result = EsbHeader::build()
136/// .max_payload(252)
137/// .pid(0)
138/// .pipe(1)
139/// .no_ack(true)
140/// .check();
141///
142/// let new_result = EsbHeader::new(
143/// 252,
144/// 0,
145/// 1,
146/// true,
147/// );
148///
149/// assert_eq!(builder_result, new_result);
150/// ```
151///
152#[derive(Debug, Clone, Copy, Eq, PartialEq)]
153pub struct EsbHeader {
154 rssi: u8,
155 // TODO(AJM): We can probably combine the 3 bits of pipe
156 // into the pid_no_ack field to save another byte of space.
157 // We just need to mask it out in EsbIrq before handing it
158 // to the radio to process.
159 pipe: u8,
160 pub(crate) length: u8,
161 pid_no_ack: u8,
162}
163
164/// The "packed" representation of an [`EsbHeader`]
165#[repr(transparent)]
166pub(crate) struct HeaderBytes(pub(crate) [u8; 4]);
167
168impl EsbHeader {
169 /// Create a new packet header using a builder pattern
170 ///
171 /// See the docs for [`EsbBuilder`](struct.EsbHeaderBuilder.html) for more
172 /// information.
173 pub fn build() -> EsbHeaderBuilder {
174 EsbHeaderBuilder::default()
175 }
176
177 /// Create a new packet header
178 ///
179 /// Notes on valid values:
180 ///
181 /// * `max_payload_length` must be between 0 and 252 bytes, inclusive.
182 /// * `pid` must be between 0 and 3, inclusive.
183 /// * `pipe` must be between 0 and 7, inclusive.
184 pub fn new(max_payload_length: u8, pid: u8, pipe: u8, no_ack: bool) -> Result<Self, Error> {
185 EsbHeaderBuilder::default()
186 .max_payload(max_payload_length)
187 .pid(pid)
188 .pipe(pipe)
189 .no_ack(no_ack)
190 .check()
191 }
192
193 /// convert into a packed representation meant for internal
194 /// data queuing purposes
195 fn into_bytes(self) -> HeaderBytes {
196 HeaderBytes([
197 self.rssi,
198 self.pipe,
199 // DO NOT REORDER!
200 self.length,
201 self.pid_no_ack,
202 ])
203 }
204
205 /// convert from a packed representation
206 pub(crate) fn from_bytes(bytes: HeaderBytes) -> Self {
207 Self {
208 rssi: bytes.0[Self::rssi_idx()],
209 pipe: bytes.0[Self::pipe_idx()],
210 length: bytes.0[Self::length_idx()],
211 pid_no_ack: bytes.0[Self::pid_no_ack_idx()],
212 }
213 }
214
215 /// Accessor for the Pipe ID of the packet
216 pub fn pid(self) -> u8 {
217 self.pid_no_ack >> 1
218 }
219
220 /// Accessor for the no-ack field of the packet
221 pub fn no_ack(self) -> bool {
222 self.pid_no_ack & 1 != 1
223 }
224
225 /// Accessor for the length (in bytes) of the payload
226 pub fn payload_len(self) -> u16 {
227 self.length.into()
228 }
229
230 /// Accessor for the rssi of the payload
231 pub fn rssi(self) -> u8 {
232 self.rssi
233 }
234
235 /// Byte index of the RSSI field
236 const fn rssi_idx() -> usize {
237 0
238 }
239
240 /// Byte index of the pipe field
241 const fn pipe_idx() -> usize {
242 1
243 }
244
245 /// Byte index of the payload length field
246 const fn length_idx() -> usize {
247 // DO NOT CHANGE! HW DEPENDANT
248 2
249 }
250
251 /// Byte index of the pid_no_ack field
252 const fn pid_no_ack_idx() -> usize {
253 // DO NOT CHANGE! HW DEPENDANT
254 3
255 }
256
257 /// Size of the header (packed) in bytes
258 pub(crate) const fn header_size() -> u16 {
259 core::mem::size_of::<HeaderBytes>() as u16
260 }
261
262 /// Offset of the bytes needed for DMA processing
263 const fn dma_payload_offset() -> usize {
264 2
265 }
266}
267
268/// A handle representing a grant of a readable packet
269///
270/// This exposes the bytes of a payload that have either
271/// been sent FROM the app, and are being read by the RADIO,
272/// or a payload that has send FROM the Radio, and is being
273/// read by the app
274pub struct PayloadR<const N: usize> {
275 grant: FramedGrantR<N>,
276}
277
278impl<const N: usize> PayloadR<N>
279{
280 /// Create a wrapped Payload Grant from a raw BBQueue Framed Grant
281 pub(crate) fn new(raw_grant: FramedGrantR<N>) -> Self {
282 Self { grant: raw_grant }
283 }
284
285 /// Obtain a copy of the header encoded in the current grant
286 pub fn get_header(&self) -> EsbHeader {
287 const LEN: usize = EsbHeader::header_size() as usize;
288 let mut bytes = [0u8; LEN];
289 bytes.copy_from_slice(&self.grant[..LEN]);
290 EsbHeader::from_bytes(HeaderBytes(bytes))
291 }
292
293 /// Obtain a pointer to the data to provide to the RADIO DMA.
294 ///
295 /// This includes part of the header, as well as the full payload
296 pub(crate) fn dma_pointer(&self) -> *const u8 {
297 self.grant[EsbHeader::dma_payload_offset()..].as_ptr()
298 }
299
300 /// Utility method to use with the CCM peripheral present in Nordic's devices. This gives a
301 /// slice starting from the pipe field of the header.
302 pub fn ccm_slice(&self) -> &[u8] {
303 &self.grant[EsbHeader::pipe_idx()..]
304 }
305
306 /// An accessor function for the pipe of the current grant
307 pub fn pipe(&self) -> u8 {
308 self.grant[EsbHeader::pipe_idx()]
309 }
310
311 /// An accessor function to get the pipe id of the current grant
312 pub fn pid(&self) -> u8 {
313 self.grant[EsbHeader::pid_no_ack_idx()] >> 1
314 }
315
316 /// An accessor function for the no-ack field of the current grant
317 pub fn no_ack(&self) -> bool {
318 self.grant[EsbHeader::pid_no_ack_idx()] & 1 != 1
319 }
320
321 /// An accessor function to get the size of the payload of the current grant
322 pub fn payload_len(&self) -> usize {
323 self.grant[EsbHeader::length_idx()] as usize
324 }
325
326 /// This function marks the packet as read, and restores the space
327 /// in the buffer for re-use.
328 ///
329 /// If this function is NOT explicitly called (e.g. the grant is just)
330 /// implicitly dropped. The packet will not be released, and the next
331 /// PayloadR grant will contain the *same* packet.
332 pub fn release(self) {
333 self.grant.release()
334 }
335
336 // /// Set whether the payload should automatically release on drop
337 // #[inline(always)]
338 // pub fn auto_release(&mut self, is_auto: bool) {
339 // self.grant.auto_release(is_auto);
340 // }
341}
342
343impl<const N: usize> Deref for PayloadR<N>
344{
345 type Target = [u8];
346
347 /// Provide read only access to the payload of a grant
348 fn deref(&self) -> &Self::Target {
349 &self.grant[EsbHeader::header_size().into()..]
350 }
351}
352
353impl<const N: usize> DerefMut for PayloadR<N>
354{
355 /// provide read/write access to the payload portion of the grant
356 fn deref_mut(&mut self) -> &mut [u8] {
357 &mut self.grant[EsbHeader::header_size().into()..]
358 }
359}
360
361pub struct PayloadW<const N: usize> {
362 grant: FramedGrantW<N>,
363}
364
365impl<const N: usize> PayloadW<N>
366{
367 /// Update the header contained within this grant.
368 ///
369 /// This can be used to modify the pipe, length, etc. of the
370 /// packet.
371 ///
372 /// ## NOTE:
373 ///
374 /// The `length` of the packet can not be increased, only shrunk. If a larger
375 /// payload is needed, you must drop the current payload grant, and obtain a new
376 /// one. If the new header has a larger `length` than the current `length`, then
377 /// it will be truncated.
378 pub fn update_header(&mut self, mut header: EsbHeader) {
379 // TODO(AJM): Technically, we could drop the current grant, and request a larger one
380 // here, and it would totally work. However for now, let's just truncate, because growing
381 // the buffer would first have to be implemented in BBQueue.
382
383 // `length` must always be 0..=252 (checked by constructor), so `u8` cast is
384 // appropriate here
385 let payload_max = self.grant.len().saturating_sub(EsbHeader::header_size().into());
386 header.length = header.length.min(payload_max as u8);
387 self.grant[..EsbHeader::header_size().into()].copy_from_slice(&header.into_bytes().0);
388 }
389
390 /// Utility method to use with the CCM peripheral present in Nordic's devices. This gives a
391 /// slice starting from the pipe field of the header.
392 ///
393 /// # Safety
394 ///
395 /// This gives raw mutable access to the header part of the packet, modification on these parts
396 /// are not checked, the user must ensure that:
397 ///
398 /// * The pipe field remains in a valid range.
399 /// * The pid_no_ack remains valid and accepted by the esb protocol.
400 /// * The length field remains smaller or equal than the length used when requesting this
401 /// particular PayloadW.
402 ///
403 /// This method is not a recommended way to update the header, it is here to provide a way to
404 /// use this abstraction with the CCM hardware peripheral.
405 /// The length field present in this slice contains the payload length requested during the
406 /// creation of this type, that is, the maximum payload size that this particular grant can
407 /// contain. When using this slice to store the output of the CCM operation, the CCM peripheral
408 /// will modify this field, the user must ensure that this field remains in a valid range.
409 pub unsafe fn ccm_slice(&mut self) -> &mut [u8] {
410 &mut self.grant[EsbHeader::pipe_idx()..]
411 }
412
413 /// Obtain a writable grant from the application side.
414 ///
415 /// This method should only be used from within `EsbApp`.
416 pub(crate) fn new_from_app(mut raw_grant: FramedGrantW<N>, header: EsbHeader) -> Self {
417 raw_grant[..EsbHeader::header_size().into()].copy_from_slice(&header.into_bytes().0);
418 Self { grant: raw_grant }
419 }
420
421 /// Obtain a writable grant from the RADIO/interrupt side.
422 ///
423 /// This method should only be used from within `EsbIrq`.
424 pub(crate) fn new_from_radio(raw_grant: FramedGrantW<N>) -> Self {
425 Self { grant: raw_grant }
426 }
427
428 pub(crate) fn dma_pointer(&mut self) -> *mut u8 {
429 self.grant[EsbHeader::dma_payload_offset()..].as_mut_ptr()
430 }
431
432 /// Update the pipe field.
433 ///
434 /// Pipe must be between 0 and 7, inclusive.
435 #[inline]
436 pub(crate) fn set_pipe(&mut self, pipe: u8) {
437 self.grant[EsbHeader::pipe_idx()] = pipe;
438 }
439
440 /// Update the rssi field.
441 #[inline]
442 pub(crate) fn set_rssi(&mut self, rssi: u8) {
443 self.grant[EsbHeader::rssi_idx()] = rssi;
444 }
445
446 /// An accessor function to get the pipe id of the current grant
447 pub fn pipe(&self) -> u8 {
448 self.grant[EsbHeader::pipe_idx()]
449 }
450
451 /// An accessor function to get the pipe id of the current grant
452 pub fn pid(&self) -> u8 {
453 self.grant[EsbHeader::pid_no_ack_idx()] >> 1
454 }
455
456 /// An accessor function for the no-ack field of the current grant
457 pub fn no_ack(&self) -> bool {
458 self.grant[EsbHeader::pid_no_ack_idx()] & 1 != 1
459 }
460
461 /// An accessor function to get the maximum size of the payload of the current grant
462 pub fn payload_len(&self) -> u16 {
463 self.grant[EsbHeader::length_idx()] as u16
464 }
465
466 /// Commit the entire granted packet and payload
467 ///
468 /// If this function or `commit` are not explicitly called, e.g.
469 /// the `PayloadW` is implicitly dropped, the packet will not be
470 /// sent.
471 pub fn commit_all(self) {
472 let payload_len = self.payload_len();
473 self.grant.commit(payload_len + EsbHeader::header_size())
474 }
475
476 // /// Set the amount to automatically commit on drop
477 // ///
478 // /// If `None` is given, then the packet will not be commited. If `Some(0)`
479 // /// is given, then an empty packet will be committed automatically
480 // pub fn to_commit(&mut self, amt: Option<usize>) {
481 // if let Some(amt) = amt {
482 // let payload_max = self.grant.len().saturating_sub(EsbHeader::header_size().into());
483 // let payload_len = payload_max.min(amt);
484 // self.grant[EsbHeader::length_idx()] = payload_len as u8;
485 // self.grant.to_commit(payload_len + EsbHeader::header_size());
486 // } else {
487 // self.grant.to_commit(0);
488 // }
489 // }
490
491 /// Commit the packed, including the first `used` bytes of the payload
492 ///
493 /// If this function or `commit_all` are not explicitly called, e.g.
494 /// the `PayloadW` is implicitly dropped, the packet will not be
495 /// sent.
496 ///
497 /// If `used` is larger than the maximum size of the grant (or of the
498 /// ESB protocol), the packet will be truncated.
499 pub fn commit(mut self, used: usize) {
500 let payload_len: u16 = self.payload_len().min(used as u16);
501 self.grant[EsbHeader::length_idx()] = payload_len as u8;
502
503 self.grant.commit(payload_len + EsbHeader::header_size())
504 }
505}
506
507impl<const N: usize> Deref for PayloadW<N>
508{
509 type Target = [u8];
510
511 /// provide read only access to the payload portion of the grant
512 fn deref(&self) -> &Self::Target {
513 &self.grant[EsbHeader::header_size().into()..]
514 }
515}
516
517impl<const N: usize> DerefMut for PayloadW<N>
518{
519 /// provide read/write access to the payload portion of the grant
520 fn deref_mut(&mut self) -> &mut [u8] {
521 &mut self.grant[EsbHeader::header_size().into()..]
522 }
523}