[PATCH 4/4] dev/io: Add packet processor

Sebastian Huber sebastian.huber at embedded-brains.de
Mon Jan 15 09:46:02 UTC 2024


The I/O packet processor provides a simple mechanism to exchange
reliable and in-order data through transmitting and receiving one
character at a time.

The I/O packet processor does not buffer data.  The processor uses a
stop-and-wait automatic repeat request method. There is at most one packet
in transmission.  The data transfer is done using a single character input
and output method.  The protocol uses 12-bit sequence numbers, so a host
could use a sliding window method to increase throughput.  All integers and
data are base64url encoded.  A 24-bit CRC is used to ensure the data
integrity.  The '{' character starts a packet.  The '}' character terminates
a packet.  The '#' character prefixes a 24-bit CRC value.  The ':' character
separates fields.  The '+' character prefixes data fields.  The following
packets are defined:

* hello: {<12-bit seq><12-bit ack>:H#<24-bit CRC>}

* acknowledge: {<12-bit seq><12-bit ack>:A#<24-bit CRC>}

* reject: {<12-bit seq><12-bit ack>
  :R:<12-bit seq of rejected packet>#<24-bit CRC>}

* jump: {<12-bit seq><12-bit ack>:J:<64-bit address>#<24-bit CRC>}

* load: {<12-bit seq><12-bit ack>:L:<64-bit load address>
  <64-bit load size in bytes>#<24-bit CRC>+<data to load>#<24-bit CRC>}

* signal: {<12-bit seq><12-bit ack>:S:<64-bit signal number>:
  <64-bit signal value>#<24-bit CRC>}

* channel: {<12-bit seq><12-bit ack>:C:<64-bit channel number>
  <64-bit data size in bytes>#<24-bit CRC>+<channel data>#<24-bit CRC>}

The intended use case are boot loaders and test runners.  For example, test
runners may interface with an external test server performing equipment
handling on request using the I/O packet processor.

Use _IO_Packet_initialize() to initialize the I/O packet processor.  Use
_IO_Packet_process() to drive the packet processing.  You can enqueue
packets for transmission with _IO_Packet_enqueue().  You can reliably send
signals with _IO_Packet_send().  You can reliably transmit and receive
channel data with _IO_Packet_channel_push() and
_IO_Packet_channel_exchange().

A simple boot loader could be implemented like this:

  #include <bsp.h>
  #include <rtems/bspIo.h>
  #include <rtems/counter.h>
  #include <rtems/dev/io.h>

  static void output_char( IO_Packet_control *self, uint8_t ch )
  {
    (void) self;
    rtems_putc( ch );
  }

  static IO_Packet_status event_handler(
    IO_Packet_control       *self,
    IO_Packet_event_control *ctrl,
    IO_Packet_event          evt
  )
  {
    (void) ctrl;

    switch ( evt ) {
      case IO_PACKET_EVENT_JUMP:
        _IO_Packet_send_acknowledge( self );
        bsp_restart( _IO_Packet_get_jump_address( self ) );
        break;
      case IO_PACKET_EVENT_LOAD_END:
        _IO_Packet_send_acknowledge( self );
        break;
      case IO_PACKET_EVENT_OUTPUT_END:
        rtems_putc( '\n' );
        break;
      default:
        break;
    }

    return IO_PACKET_SUCCESSFUL;
  }

  static uint32_t clock_monotonic( IO_Packet_control *self )
  {
    (void) self;
    return rtems_counter_read();
  }

  static void Init( rtems_task_argument arg )
  {
    IO_Packet_control self;
    IO_Packet_event_control event;

    (void) arg;
    _IO_Packet_initialize( &self, 0, NULL, output_char, clock_monotonic );
    _IO_Packet_prepend_event_handler( &self, &event, event_handler );

    while ( true ) {
      _IO_Packet_process( &self, getchark() );
    }
  }
---
 cpukit/dev/iopacket.c             | 837 ++++++++++++++++++++++++++++++
 cpukit/include/rtems/dev/io.h     | 827 +++++++++++++++++++++++++++++
 spec/build/cpukit/librtemscpu.yml |   1 +
 testsuites/unit/tc-io-packet.c    | 575 ++++++++++++++++++++
 4 files changed, 2240 insertions(+)
 create mode 100644 cpukit/dev/iopacket.c

diff --git a/cpukit/dev/iopacket.c b/cpukit/dev/iopacket.c
new file mode 100644
index 0000000000..eeca3a955e
--- /dev/null
+++ b/cpukit/dev/iopacket.c
@@ -0,0 +1,837 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2023 embedded brains GmbH & Co. KG
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rtems/dev/io.h>
+
+#include <limits.h>
+#include <string.h>
+
+#define SEQ_SHIFT 12
+#define SEQ_MASK UINT32_C(0xfff)
+#define ACK_VALID UINT32_C(0x1000000)
+#define SEQ_VALID UINT32_C(0x2000000)
+
+void _IO_Packet_append_event_handler(IO_Packet_control* self,
+                                     IO_Packet_event_control* ctrl,
+                                     IO_Packet_event_handler handler) {
+  ctrl->handler = handler;
+  ctrl->next = NULL;
+  IO_Packet_event_control* tail = self->event_head;
+  IO_Packet_event_control** next = &self->event_head;
+
+  while (true) {
+    if (tail == NULL) {
+      *next = ctrl;
+      return;
+    }
+
+    next = &tail->next;
+    tail = tail->next;
+  }
+}
+
+void _IO_Packet_prepend_event_handler(IO_Packet_control* self,
+                                      IO_Packet_event_control* ctrl,
+                                      IO_Packet_event_handler handler) {
+  ctrl->handler = handler;
+  ctrl->next = self->event_head;
+  self->event_head = ctrl;
+}
+
+void _IO_Packet_remove_event_handler(IO_Packet_control* self,
+                                     IO_Packet_event_control* pkt) {
+  IO_Packet_event_control* other = self->event_head;
+  IO_Packet_event_control** prev = &self->event_head;
+
+  while (other != NULL) {
+    if (pkt == other) {
+      *prev = pkt->next;
+      return;
+    }
+
+    prev = &other->next;
+    other = other->next;
+  }
+}
+
+static void event(IO_Packet_control* self, IO_Packet_event event) {
+  IO_Packet_event_control* ctrl = self->event_head;
+
+  while (ctrl != NULL) {
+    IO_Packet_event_control* next = ctrl->next;
+    IO_Packet_status status = (*ctrl->handler)(self, ctrl, event);
+
+    if (status != IO_PACKET_CONTINUE) {
+      return;
+    }
+
+    ctrl = next;
+  }
+}
+
+static void output_char(IO_Packet_control* self, uint8_t ch) {
+  (*self->output_char)(self, ch);
+}
+
+static uint32_t output_start(IO_Packet_control* self, uint8_t packet_type) {
+  event(self, IO_PACKET_EVENT_OUTPUT_BEGIN);
+  output_char(self, '{');
+  uint32_t crc = IO_CRC24Q_SEED;
+  uint32_t seq_ack = (self->my_seq << SEQ_SHIFT) | self->other_seq;
+
+  for (int i = 18; i >= 0; i -= 6) {
+    uint8_t ch = _IO_Base64url_table[(seq_ack >> i) & 0x3f];
+    output_char(self, ch);
+    crc = _IO_CRC24Q_update(crc, ch);
+  }
+
+  output_char(self, ':');
+  crc = _IO_CRC24Q_update(crc, ':');
+  output_char(self, packet_type);
+  crc = _IO_CRC24Q_update(crc, packet_type);
+
+  return crc;
+}
+
+static uint32_t output_value(IO_Packet_control* self,
+                             uint32_t crc,
+                             uint64_t value) {
+  output_char(self, ':');
+  crc = _IO_CRC24Q_update(crc, ':');
+
+  int i = 60;
+
+  /* Skip leading zeros */
+  while (i >= 6) {
+    if (((value >> i) & 0x3f) != 0) {
+      break;
+    }
+
+    i -= 6;
+  }
+
+  while (i >= 0) {
+    uint8_t ch = _IO_Base64url_table[(value >> i) & 0x3f];
+    output_char(self, ch);
+    crc = _IO_CRC24Q_update(crc, ch);
+    i -= 6;
+  }
+
+  return crc;
+}
+
+static void output_crc(IO_Packet_control* self, uint32_t crc) {
+  output_char(self, '#');
+
+  for (int i = 18; i >= 0; i -= 6) {
+    uint8_t ch = _IO_Base64url_table[(crc >> i) & 0x3f];
+    output_char(self, ch);
+  }
+}
+
+static void output_end(IO_Packet_control* self) {
+  output_char(self, '}');
+  event(self, IO_PACKET_EVENT_OUTPUT_END);
+}
+
+static void inc_my_seq(IO_Packet_control* self) {
+  self->my_seq = (self->my_seq + UINT32_C(1)) & SEQ_MASK;
+}
+
+static void make_pending(IO_Packet_control* self, IO_Packet_packet* pkt) {
+  inc_my_seq(self);
+  self->snd_pending = pkt;
+  event(self, IO_PACKET_EVENT_SEND_DEQUEUE);
+}
+
+static void output_packet(IO_Packet_control* self,
+                          IO_Packet_packet* pkt,
+                          uint32_t t0) {
+  (*pkt->output)(self, pkt);
+  uint32_t t1 = (*self->clock_monotonic)(self);
+  self->snd_timeout = t0 + 4 * (t1 - t0);
+}
+
+static void output_simple_packet(IO_Packet_control* self, uint8_t packet_type) {
+  uint32_t crc = output_start(self, packet_type);
+  output_crc(self, crc);
+  output_end(self);
+}
+
+static void send_done(IO_Packet_control* self) {
+  IO_Packet_packet* pending = self->snd_pending;
+
+  if (pending != NULL) {
+    IO_Packet_packet* next = pending->next;
+    self->snd_pending = NULL;
+    self->snd_head = next;
+
+    if (next == NULL) {
+      self->snd_tail = &self->snd_head;
+    }
+
+    event(self, IO_PACKET_EVENT_SEND_DONE);
+    (*pending->done)(self, pending);
+  }
+}
+
+static void output_response(IO_Packet_control* self, uint8_t packet_type) {
+  uint32_t seq_ack = self->seq_ack;
+
+  if ((seq_ack & ACK_VALID) != 0 && (seq_ack & SEQ_MASK) == self->my_seq) {
+    send_done(self);
+  }
+
+  if ((seq_ack & SEQ_VALID) != 0) {
+    self->other_seq = (seq_ack >> SEQ_SHIFT) & SEQ_MASK;
+  }
+
+  self->state = IO_PACKET_STATE_START;
+
+  if (packet_type == 'R' && self->snd_pending == NULL) {
+    inc_my_seq(self);
+    uint32_t crc = output_start(self, packet_type);
+    crc = output_value(self, crc, (seq_ack >> SEQ_SHIFT) & SEQ_MASK);
+    output_crc(self, crc);
+    output_end(self);
+    return;
+  }
+
+  IO_Packet_packet* head = self->snd_head;
+
+  if (head == NULL) {
+    if (self->packet_type != 'A') {
+      inc_my_seq(self);
+      output_simple_packet(self, packet_type);
+    }
+
+    return;
+  }
+
+  if (self->snd_pending != NULL) {
+    event(self, IO_PACKET_EVENT_SEND_AGAIN);
+  } else {
+    make_pending(self, head);
+  }
+
+  output_packet(self, head, (*self->clock_monotonic)(self));
+}
+
+static void output_nack(IO_Packet_control* self) {
+  event(self, IO_PACKET_EVENT_NACK);
+  output_response(self, 'A');
+}
+
+void _IO_Packet_output_acknowledge(IO_Packet_control* self) {
+  output_response(self, 'A');
+}
+
+void _IO_Packet_output_reject(IO_Packet_control* self) {
+  output_response(self, 'R');
+}
+
+void _IO_Packet_initialize(
+    IO_Packet_control* self,
+    uint32_t seq,
+    int (*input_char_handler)(IO_Packet_control*),
+    void (*output_char_handler)(IO_Packet_control*, uint8_t),
+    uint32_t (*clock_monotonic_handler)(IO_Packet_control*)) {
+  self = memset(self, 0, sizeof(*self));
+  seq &= SEQ_MASK;
+  self->my_seq = seq;
+  self->other_seq = seq;
+  self->hello.output = _IO_Packet_output_hello;
+  self->hello.done = _IO_Packet_done_default;
+  self->snd_head = &self->hello;
+  self->snd_tail = &self->hello.next;
+  self->input_char = input_char_handler;
+  self->output_char = output_char_handler;
+  self->clock_monotonic = clock_monotonic_handler;
+}
+
+static void begin(IO_Packet_control* self) {
+  self->state = IO_PACKET_STATE_SEQ_ACK;
+  self->crc_calculated = IO_CRC24Q_SEED;
+  self->seq_ack_idx = 0;
+  self->seq_ack = 0;
+}
+
+static void receive_crc(IO_Packet_control* self) {
+  self->crc_received = 0;
+  self->crc_idx = 0;
+  self->state = IO_PACKET_STATE_CRC;
+}
+
+static void update_crc(IO_Packet_control* self, uint8_t ch) {
+  self->crc_calculated = _IO_CRC24Q_update(self->crc_calculated, ch);
+}
+
+static void do_seq_ack(IO_Packet_control* self, uint8_t ch) {
+  update_crc(self, ch);
+  size_t seq_ack_idx = self->seq_ack_idx;
+
+  if (seq_ack_idx < 4) {
+    if (ch < RTEMS_ARRAY_SIZE(_IO_Base64_decode_table)) {
+      uint8_t decoded_ch = _IO_Base64_decode_table[ch];
+
+      if (decoded_ch <= 63) {
+        self->seq_ack = (self->seq_ack << 6) | decoded_ch;
+        self->seq_ack_idx = seq_ack_idx + 1;
+      } else {
+        output_nack(self);
+      }
+    } else {
+      output_nack(self);
+    }
+  } else {
+    switch (ch) {
+      case ':':
+        self->state = IO_PACKET_STATE_TYPE;
+        break;
+      default:
+        output_nack(self);
+        break;
+    }
+  }
+}
+
+static void do_type(IO_Packet_control* self, uint8_t ch) {
+  update_crc(self, ch);
+  self->packet_type = ch;
+
+  switch (ch) {
+    case 'C':
+      self->packet_done_event = IO_PACKET_EVENT_CHANNEL_END;
+      self->state = IO_PACKET_STATE_COLON;
+      break;
+    case 'S':
+      self->packet_done_event = IO_PACKET_EVENT_SIGNAL;
+      self->state = IO_PACKET_STATE_COLON;
+      break;
+    case 'L':
+      self->packet_done_event = IO_PACKET_EVENT_LOAD_END;
+      self->state = IO_PACKET_STATE_COLON;
+      break;
+    case 'J':
+      self->packet_done_event = IO_PACKET_EVENT_JUMP;
+      self->state = IO_PACKET_STATE_COLON;
+      break;
+    case 'H':
+      self->packet_done_event = IO_PACKET_EVENT_HELLO;
+      self->state = IO_PACKET_STATE_HASH;
+      break;
+    case 'A':
+      self->packet_done_event = IO_PACKET_EVENT_ACKNOWLEDGE;
+      self->state = IO_PACKET_STATE_HASH;
+      break;
+    default:
+      self->packet_done_event = IO_PACKET_EVENT_REJECT;
+      self->state = IO_PACKET_STATE_REJECT;
+      break;
+  }
+}
+
+static void do_colon(IO_Packet_control* self, uint8_t ch) {
+  switch (ch) {
+    case ':':
+      update_crc(self, ch);
+      self->value_idx = 0;
+      self->values[0] = 0;
+      self->state = IO_PACKET_STATE_VALUE;
+      break;
+    default:
+      output_nack(self);
+      break;
+  }
+}
+
+static void update_value(IO_Packet_control* self, uint8_t ch) {
+  if (ch >= RTEMS_ARRAY_SIZE(_IO_Base64_decode_table)) {
+    output_nack(self);
+    return;
+  }
+
+  uint8_t decoded_ch = _IO_Base64_decode_table[ch];
+
+  if (decoded_ch > 63) {
+    output_nack(self);
+    return;
+  }
+
+  uint64_t value = self->values[self->value_idx];
+
+  if ((value & UINT64_C(0xfc00000000000000)) != 0) {
+    output_nack(self);
+    return;
+  }
+
+  self->values[self->value_idx] = (value << 6) | decoded_ch;
+  update_crc(self, ch);
+}
+
+static void do_value(IO_Packet_control* self, uint8_t ch) {
+  switch (ch) {
+    case '#':
+      ++self->value_idx;
+      receive_crc(self);
+      break;
+    case ':':
+      if ((self->packet_type == 'C' || self->packet_type == 'L' ||
+           self->packet_type == 'S') &&
+          self->value_idx == 0) {
+        update_crc(self, ch);
+        self->value_idx = 1;
+        self->values[1] = 0;
+      } else {
+        output_nack(self);
+      }
+
+      break;
+    default:
+      update_value(self, ch);
+      break;
+  }
+}
+
+static void do_hash(IO_Packet_control* self, uint8_t ch) {
+  switch (ch) {
+    case '#':
+      receive_crc(self);
+      break;
+    default:
+      output_nack(self);
+      break;
+  }
+}
+
+static void do_reject(IO_Packet_control* self, uint8_t ch) {
+  switch (ch) {
+    case '#':
+      receive_crc(self);
+      break;
+    default:
+      update_crc(self, ch);
+      break;
+  }
+}
+
+static void next_component(IO_Packet_control* self) {
+  if (self->value_idx != 2) {
+    output_nack(self);
+    return;
+  }
+
+  self->crc_calculated = IO_CRC24Q_SEED;
+
+  switch (self->packet_type) {
+    case 'C': {
+      self->b64_decode.target = NULL;
+      self->state = IO_PACKET_STATE_DECODE_DATA;
+      event(self, IO_PACKET_EVENT_CHANNEL_BEGIN);
+
+      if (self->b64_decode.target == NULL) {
+        _IO_Packet_output_reject(self);
+      }
+
+      break;
+    }
+    case 'L':
+      _IO_Base64_decode_initialize(&self->b64_decode,
+                                   (uint8_t*)(uintptr_t)self->values[0],
+                                   self->values[1]);
+      self->state = IO_PACKET_STATE_DECODE_DATA;
+      event(self, IO_PACKET_EVENT_LOAD_BEGIN);
+      break;
+    default:
+      output_nack(self);
+      break;
+  }
+}
+
+static void check_crc(IO_Packet_control* self, uint8_t ch) {
+  if ((self->crc_calculated & IO_CRC24Q_MASK) != self->crc_received) {
+    output_nack(self);
+    return;
+  }
+
+  uint32_t seq_ack = self->seq_ack | ACK_VALID;
+  uint32_t other_seq = (seq_ack >> SEQ_SHIFT) & SEQ_MASK;
+
+  if (((self->other_seq - other_seq) & SEQ_MASK) < SEQ_MASK / 2) {
+    event(self, IO_PACKET_EVENT_DUPLICATE);
+    _IO_Packet_output_acknowledge(self);
+    return;
+  }
+
+  if (self->packet_done_event == IO_PACKET_EVENT_REJECT) {
+    self->seq_ack = seq_ack;
+    event(self, IO_PACKET_EVENT_REJECT);
+    _IO_Packet_output_reject(self);
+    return;
+  }
+
+  switch (ch) {
+    case '}':
+      self->seq_ack = seq_ack | SEQ_VALID;
+      event(self, self->packet_done_event);
+
+      if (self->state != IO_PACKET_STATE_START) {
+        if (self->packet_type == 'A') {
+          output_response(self, 'A');
+        } else {
+          self->seq_ack = seq_ack;
+          _IO_Packet_output_reject(self);
+        }
+      }
+
+      break;
+    case '+':
+      self->seq_ack = seq_ack;
+      next_component(self);
+      break;
+    default:
+      output_nack(self);
+      break;
+  }
+}
+
+static void do_crc(IO_Packet_control* self, uint8_t ch) {
+  size_t crc_idx = self->crc_idx;
+
+  if (crc_idx < 4) {
+    if (ch < RTEMS_ARRAY_SIZE(_IO_Base64_decode_table)) {
+      uint8_t decoded_ch = _IO_Base64_decode_table[ch];
+
+      if (decoded_ch <= 63) {
+        self->crc_received = (self->crc_received << 6) | decoded_ch;
+        self->crc_idx = crc_idx + 1;
+      } else {
+        output_nack(self);
+      }
+    } else {
+      output_nack(self);
+    }
+  } else {
+    check_crc(self, ch);
+  }
+}
+
+static void do_decode_data(IO_Packet_control* self, uint8_t ch) {
+  switch (ch) {
+    case '#':
+      if (self->b64_decode.target == self->b64_decode.target_end) {
+        receive_crc(self);
+      } else {
+        output_nack(self);
+      }
+      break;
+    default: {
+      IO_Base64_decode_status status = _IO_Base64_decode(&self->b64_decode, ch);
+
+      if (status == IO_BASE64_DECODE_SUCCESS) {
+        update_crc(self, ch);
+      } else {
+        output_nack(self);
+      }
+
+      break;
+    }
+  }
+}
+
+static void idle_processing(IO_Packet_control* self) {
+  event(self, IO_PACKET_EVENT_NOTHING);
+
+  if (self->state != IO_PACKET_STATE_START) {
+    return;
+  }
+
+  IO_Packet_packet* head = self->snd_head;
+
+  if (head == NULL) {
+    return;
+  }
+
+  uint32_t now = (*self->clock_monotonic)(self);
+
+  if (self->snd_pending != NULL) {
+    if ((self->snd_timeout - now) <= UINT32_MAX / 2) {
+      return;
+    }
+
+    event(self, IO_PACKET_EVENT_SEND_AGAIN);
+  } else {
+    make_pending(self, head);
+  }
+
+  output_packet(self, head, now);
+}
+
+void _IO_Packet_process(IO_Packet_control* self, int ch_or_nothing) {
+  if (ch_or_nothing == -1) {
+    idle_processing(self);
+    return;
+  }
+
+  uint8_t ch = (uint8_t)ch_or_nothing;
+
+  if (ch == '{') {
+    begin(self);
+    return;
+  }
+
+  switch (self->state) {
+    case IO_PACKET_STATE_SEQ_ACK:
+      do_seq_ack(self, ch);
+      break;
+    case IO_PACKET_STATE_TYPE:
+      do_type(self, ch);
+      break;
+    case IO_PACKET_STATE_COLON:
+      do_colon(self, ch);
+      break;
+    case IO_PACKET_STATE_VALUE:
+      do_value(self, ch);
+      break;
+    case IO_PACKET_STATE_DECODE_DATA:
+      do_decode_data(self, ch);
+      break;
+    case IO_PACKET_STATE_HASH:
+      do_hash(self, ch);
+      break;
+    case IO_PACKET_STATE_CRC:
+      do_crc(self, ch);
+      break;
+    case IO_PACKET_STATE_REJECT:
+      do_reject(self, ch);
+      break;
+    default:
+      /* Wait for packet start */
+      event(self, IO_PACKET_EVENT_GARBAGE);
+      break;
+  }
+}
+
+void _IO_Packet_done_default(IO_Packet_control* self, IO_Packet_packet* pkt) {
+  (void)self;
+  (void)pkt;
+}
+
+void _IO_Packet_output_hello(IO_Packet_control* self, IO_Packet_packet* pkt) {
+  (void)pkt;
+  output_simple_packet(self, 'H');
+}
+
+void _IO_Packet_output_signal(IO_Packet_control* self, IO_Packet_packet* pkt) {
+  uint32_t crc = output_start(self, 'S');
+  IO_Packet_signal_packet* signal_pkt = (IO_Packet_signal_packet*)pkt;
+  crc = output_value(self, crc, signal_pkt->signal_number);
+  crc = output_value(self, crc, signal_pkt->signal_value);
+  output_crc(self, crc);
+  output_end(self);
+}
+
+static void b64_output_char(int c, void* arg) {
+  IO_Packet_control* self = arg;
+  output_char(self, (uint8_t)c);
+  self->crc_calculated = _IO_CRC24Q_update(self->crc_calculated, (uint8_t)c);
+}
+
+void _IO_Packet_output_channel(IO_Packet_control* self, IO_Packet_packet* pkt) {
+  uint32_t crc = output_start(self, 'C');
+  IO_Packet_channel_packet* channel_pkt = (IO_Packet_channel_packet*)pkt;
+  crc = output_value(self, crc, channel_pkt->channel_number);
+  crc = output_value(self, crc, channel_pkt->data_size);
+  output_crc(self, crc);
+  output_char(self, '+');
+  self->crc_calculated = IO_CRC24Q_SEED;
+  _IO_Base64url(b64_output_char, self, channel_pkt->data_begin,
+                channel_pkt->data_size, NULL, INT_MAX);
+  output_crc(self, self->crc_calculated);
+  output_end(self);
+}
+
+void _IO_Packet_cancel(IO_Packet_control* self, IO_Packet_packet* pkt) {
+  IO_Packet_packet* enq_pkt = self->snd_head;
+  IO_Packet_packet** prev = &self->snd_head;
+
+  if (self->snd_pending == pkt) {
+    self->snd_pending = NULL;
+  }
+
+  while (enq_pkt != NULL) {
+    if (pkt == enq_pkt) {
+      *prev = pkt->next;
+
+      if (self->snd_tail == &pkt->next) {
+        self->snd_tail = prev;
+      }
+
+      return;
+    }
+
+    prev = &enq_pkt->next;
+    enq_pkt = enq_pkt->next;
+  }
+}
+
+static void done_success(IO_Packet_control* self, IO_Packet_packet* pkt) {
+  (void)self;
+  IO_Packet_packet_transfer* transfer = (IO_Packet_packet_transfer*)pkt;
+  transfer->status = IO_PACKET_SUCCESSFUL;
+}
+
+IO_Packet_status _IO_Packet_send(IO_Packet_control* self,
+                                 IO_Packet_packet_transfer* transfer,
+                                 uint32_t timeout) {
+  if (self->input_char == NULL) {
+    _IO_Packet_remove_event_handler(self, &transfer->event);
+    return IO_PACKET_NO_INPUT_CHAR_HANDLER;
+  }
+
+  transfer->status = IO_PACKET_CONTINUE;
+  _IO_Packet_enqueue(self, &transfer->base);
+  _IO_Packet_process(self, -1);
+
+  bool check_for_timeout = (timeout != 0);
+  uint32_t t0 = 0;
+
+  if (check_for_timeout) {
+    t0 = (*self->clock_monotonic)(self);
+  }
+
+  while (true) {
+    _IO_Packet_process(self, (*self->input_char)(self));
+
+    if (transfer->status != IO_PACKET_CONTINUE) {
+      return transfer->status;
+    }
+
+    if (check_for_timeout) {
+      uint32_t t1 = (*self->clock_monotonic)(self);
+      uint32_t new_timeout = timeout - (t1 - t0);
+
+      if (new_timeout > timeout) {
+        _IO_Packet_cancel(self, &transfer->base);
+        _IO_Packet_remove_event_handler(self, &transfer->event);
+        return IO_PACKET_TIMEOUT;
+      }
+
+      timeout = new_timeout;
+      t0 = t1;
+    }
+  }
+}
+
+IO_Packet_status _IO_Packet_signal(IO_Packet_control* self,
+                                   uint64_t signal_number,
+                                   uint64_t signal_value,
+                                   uint32_t timeout) {
+  IO_Packet_packet_transfer transfer;
+  _IO_Packet_initialize_signal(&transfer.signal, signal_number, signal_value,
+                               done_success);
+  return _IO_Packet_send(self, &transfer, timeout);
+}
+
+IO_Packet_status _IO_Packet_channel_push(IO_Packet_control* self,
+                                         uint64_t channel_number,
+                                         const void* data_begin,
+                                         size_t data_size,
+                                         uint32_t timeout) {
+  IO_Packet_packet_transfer transfer;
+  _IO_Packet_initialize_channel(&transfer.channel, channel_number, data_begin,
+                                data_size, done_success);
+  return _IO_Packet_send(self, &transfer, timeout);
+}
+
+typedef struct {
+  IO_Packet_packet_transfer base;
+  void* receive_begin;
+  size_t receive_size_max;
+  size_t receive_size_return;
+} channel_transfer;
+
+static IO_Packet_status channel_exchange_event(IO_Packet_control* self,
+                                               IO_Packet_event_control* ctrl,
+                                               IO_Packet_event evt) {
+  channel_transfer* transfer =
+      RTEMS_CONTAINER_OF(ctrl, channel_transfer, base.event);
+
+  if (evt == IO_PACKET_EVENT_CHANNEL_BEGIN) {
+    if (_IO_Packet_get_channel_number(self) !=
+        transfer->base.channel.channel_number) {
+      return IO_PACKET_CONTINUE;
+    }
+
+    size_t size = _IO_Packet_get_channel_size(self);
+
+    if (size <= transfer->receive_size_max) {
+      transfer->receive_size_return = size;
+      _IO_Packet_set_channel_target(self, transfer->receive_begin);
+    } else {
+      transfer->base.status = IO_PACKET_OVERFLOW;
+      _IO_Packet_remove_event_handler(self, ctrl);
+    }
+
+    return IO_PACKET_SUCCESSFUL;
+  }
+
+  if (evt == IO_PACKET_EVENT_CHANNEL_END) {
+    if (_IO_Packet_get_channel_number(self) !=
+        transfer->base.channel.channel_number) {
+      return IO_PACKET_CONTINUE;
+    }
+
+    transfer->base.status = IO_PACKET_SUCCESSFUL;
+    _IO_Packet_remove_event_handler(self, ctrl);
+    _IO_Packet_output_acknowledge(self);
+    return IO_PACKET_SUCCESSFUL;
+  }
+
+  return IO_PACKET_CONTINUE;
+}
+
+IO_Packet_status _IO_Packet_channel_exchange(IO_Packet_control* self,
+                                             uint64_t channel_number,
+                                             const void* transmit_begin,
+                                             size_t transmit_size,
+                                             void* receive_begin,
+                                             size_t* receive_size,
+                                             uint32_t timeout) {
+  channel_transfer transfer;
+  _IO_Packet_initialize_channel(&transfer.base.channel, channel_number,
+                                transmit_begin, transmit_size,
+                                _IO_Packet_done_default);
+  transfer.receive_begin = receive_begin;
+  transfer.receive_size_max = *receive_size;
+  transfer.receive_size_return = 0;
+  _IO_Packet_prepend_event_handler(self, &transfer.base.event,
+                                   channel_exchange_event);
+  IO_Packet_status status = _IO_Packet_send(self, &transfer.base, timeout);
+  *receive_size = transfer.receive_size_return;
+  return status;
+}
diff --git a/cpukit/include/rtems/dev/io.h b/cpukit/include/rtems/dev/io.h
index 5547a0f48a..1f8492f524 100644
--- a/cpukit/include/rtems/dev/io.h
+++ b/cpukit/include/rtems/dev/io.h
@@ -292,6 +292,833 @@ void _IO_Relax( void );
 
 /** @} */
 
+/**
+ * @defgroup RTEMSDeviceIOPacket I/O Packet Processor
+ *
+ * @ingroup RTEMSDeviceIO
+ *
+ * @brief The I/O packet processor provides a simple mechanism to exchange
+ *   reliable and in-order data through transmitting and receiving one
+ *   character at a time.
+ *
+ * The I/O packet processor does not buffer data.  The processor uses a
+ * stop-and-wait automatic repeat request method. There is at most one packet
+ * in transmission.  The data transfer is done using a single character input
+ * and output method.  The protocol uses 12-bit sequence numbers, so a host
+ * could use a sliding window method to increase throughput.  All integers and
+ * data are base64url encoded.  A 24-bit CRC is used to ensure the data
+ * integrity.  The '{' character starts a packet.  The '}' character terminates
+ * a packet.  The '#' character prefixes a 24-bit CRC value.  The ':' character
+ * separates fields.  The '+' character prefixes data fields.  The following
+ * packets are defined:
+ *
+ * * hello: {<12-bit seq><12-bit ack>:H#<24-bit CRC>}
+ *
+ * * acknowledge: {<12-bit seq><12-bit ack>:A#<24-bit CRC>}
+ *
+ * * reject: {<12-bit seq><12-bit ack>
+ *   :R:<12-bit seq of rejected packet>#<24-bit CRC>}
+ *
+ * * jump: {<12-bit seq><12-bit ack>:J:<64-bit address>#<24-bit CRC>}
+ *
+ * * load: {<12-bit seq><12-bit ack>:L:<64-bit load address>
+ *   <64-bit load size in bytes>#<24-bit CRC>+<data to load>#<24-bit CRC>}
+ *
+ * * signal: {<12-bit seq><12-bit ack>:S:<64-bit signal number>:
+ *   <64-bit signal value>#<24-bit CRC>}
+ *
+ * * channel: {<12-bit seq><12-bit ack>:C:<64-bit channel number>
+ *   <64-bit data size in bytes>#<24-bit CRC>+<channel data>#<24-bit CRC>}
+ *
+ * The intended use case are boot loaders and test runners.  For example, test
+ * runners may interface with an external test server performing equipment
+ * handling on request using the I/O packet processor.
+ *
+ * Use _IO_Packet_initialize() to initialize the I/O packet processor.  Use
+ * _IO_Packet_process() to drive the packet processing.  You can enqueue
+ * packets for transmission with _IO_Packet_enqueue().  You can reliably send
+ * signals with _IO_Packet_send().  You can reliably transmit and receive
+ * channel data with _IO_Packet_channel_push() and
+ * _IO_Packet_channel_exchange().
+ *
+ * A simple boot loader could be implemented like this:
+ *
+ * @code
+ * #include <bsp.h>
+ * #include <rtems/bspIo.h>
+ * #include <rtems/counter.h>
+ * #include <rtems/dev/io.h>
+ *
+ * static void output_char( IO_Packet_control *self, uint8_t ch )
+ * {
+ *   (void) self;
+ *   rtems_putc( ch );
+ * }
+ *
+ * static IO_Packet_status event_handler(
+ *   IO_Packet_control       *self,
+ *   IO_Packet_event_control *ctrl,
+ *   IO_Packet_event          evt
+ * )
+ * {
+ *   (void) ctrl;
+ *
+ *   switch ( evt ) {
+ *     case IO_PACKET_EVENT_JUMP:
+ *       _IO_Packet_send_acknowledge( self );
+ *       bsp_restart( _IO_Packet_get_jump_address( self ) );
+ *       break;
+ *     case IO_PACKET_EVENT_LOAD_END:
+ *       _IO_Packet_send_acknowledge( self );
+ *       break;
+ *     case IO_PACKET_EVENT_OUTPUT_END:
+ *       rtems_putc( '\n' );
+ *       break;
+ *     default:
+ *       break;
+ *   }
+ *
+ *   return IO_PACKET_SUCCESSFUL;
+ * }
+ *
+ * static uint32_t clock_monotonic( IO_Packet_control *self )
+ * {
+ *   (void) self;
+ *   return rtems_counter_read();
+ * }
+ *
+ * static void Init( rtems_task_argument arg )
+ * {
+ *   IO_Packet_control self;
+ *   IO_Packet_event_control event;
+ *
+ *   (void) arg;
+ *   _IO_Packet_initialize( &self, 0, NULL, output_char, clock_monotonic );
+ *   _IO_Packet_prepend_event_handler( &self, &event, event_handler );
+ *
+ *   while ( true ) {
+ *     _IO_Packet_process( &self, getchark() );
+ *   }
+ * }
+ * @endcode
+ *
+ * @{
+ */
+
+/**
+ * @brief These enumerators represent I/O packet processing states.
+ */
+typedef enum {
+  IO_PACKET_STATE_START,
+  IO_PACKET_STATE_COLON,
+  IO_PACKET_STATE_CRC,
+  IO_PACKET_STATE_DECODE_DATA,
+  IO_PACKET_STATE_HASH,
+  IO_PACKET_STATE_REJECT,
+  IO_PACKET_STATE_SEQ_ACK,
+  IO_PACKET_STATE_TYPE,
+  IO_PACKET_STATE_VALUE
+} IO_Packet_state;
+
+/**
+ * @brief These enumerators represent I/O packet events.
+ */
+typedef enum {
+  /**
+   * @brief This event happens when an acknowledge package was received.
+   */
+  IO_PACKET_EVENT_ACKNOWLEDGE,
+
+  /**
+   * @brief This event happens when channel data may get received.
+   *
+   * Call _IO_Packet_set_channel_target() to set a target for the received
+   * channel data, otherwise the packet is rejected.
+   *
+   * @see _IO_Packet_get_channel_number() and _IO_Packet_get_channel_size().
+   */
+  IO_PACKET_EVENT_CHANNEL_BEGIN,
+
+  /**
+   * @brief This event happens when channel data was received successfully.
+   *
+   * Call _IO_Packet_output_acknowledge() to acknowledge the channel data,
+   * otherwise the packet is rejected.
+   *
+   * @see _IO_Packet_get_channel_number() and _IO_Packet_get_channel_size().
+   */
+  IO_PACKET_EVENT_CHANNEL_END,
+
+  /**
+   * @brief This event happens when an duplicate packet was received.
+   */
+  IO_PACKET_EVENT_DUPLICATE,
+
+  /**
+   * @brief This event happens when a garbage character was received.
+   */
+  IO_PACKET_EVENT_GARBAGE,
+
+  /**
+   * @brief This event happens when a hello package was received.
+   *
+   * Call _IO_Packet_output_acknowledge() to acknowledge the hello, otherwise
+   * the packet is rejected.
+   */
+  IO_PACKET_EVENT_HELLO,
+
+  /**
+   * @brief This event happens when a jump should take place.
+   *
+   * Since a jump usually does not return, you should acknowledge the packet
+   * explicitly through a call to _IO_Packet_output_acknowledge().
+   *
+   * @see _IO_Packet_get_jump_address().
+   */
+  IO_PACKET_EVENT_JUMP,
+
+  /**
+   * @brief This event happens when data load may should take place.
+   *
+   * Some actions may be performed to enable loading through updated MMU/MPU
+   * configurations.  To reject loading of data, call
+   * _IO_Packet_output_reject() for this event.
+   *
+   * @see _IO_Packet_get_load_address() and _IO_Packet_get_load_size().
+   */
+  IO_PACKET_EVENT_LOAD_BEGIN,
+
+  /**
+   * @brief This event happens when data was successfully load.
+   *
+   * Some actions may be performed to flush/invalidate caches or restore
+   * MMU/MPU settings.
+   *
+   * @see _IO_Packet_get_load_address() and _IO_Packet_get_load_size().
+   */
+  IO_PACKET_EVENT_LOAD_END,
+
+  /**
+   * @brief This event happens when a not acknowledge packet is transmitted.
+   */
+  IO_PACKET_EVENT_NACK,
+
+  /**
+   * @brief This event happens when idle processing is performed.
+   */
+  IO_PACKET_EVENT_NOTHING,
+
+  /**
+   * @brief This event happens when a packet output starts.
+   */
+  IO_PACKET_EVENT_OUTPUT_BEGIN,
+
+  /**
+   * @brief This event happens when a packet output ends.
+   */
+  IO_PACKET_EVENT_OUTPUT_END,
+
+  /**
+   * @brief This event happens when a packet is rejected.
+   */
+  IO_PACKET_EVENT_REJECT,
+
+  /**
+   * @brief This event happens when a packet is transmitted again.
+   */
+  IO_PACKET_EVENT_SEND_AGAIN,
+
+  /**
+   * @brief This event happens when a packet is dequeued for transmission.
+   */
+  IO_PACKET_EVENT_SEND_DEQUEUE,
+
+  /**
+   * @brief This event happens when a transmitted packet was acknowledged.
+   */
+  IO_PACKET_EVENT_SEND_DONE,
+
+  /**
+   * @brief This event happens when a signal was received.
+   *
+   * Call _IO_Packet_output_acknowledge() to acknowledge the signal, otherwise
+   * the packet is rejected.
+   *
+   * @see _IO_Packet_get_signal_number() and _IO_Packet_get_signal_value().
+   */
+  IO_PACKET_EVENT_SIGNAL
+} IO_Packet_event;
+
+/**
+ * @brief These enumerators represent the status of I/O packet requests.
+ */
+typedef enum {
+  /**
+   * @brief Indicates a successful operation.
+   */
+  IO_PACKET_SUCCESSFUL,
+
+  /**
+   * @brief Indicates that the event handling should continue.
+   */
+  IO_PACKET_CONTINUE,
+
+  /**
+   * @brief Indicates that the request timed out.
+   */
+  IO_PACKET_TIMEOUT,
+
+  /**
+   * @brief Indicates that a data buffer is not large enough.
+   */
+  IO_PACKET_OVERFLOW,
+
+  /**
+   * @brief Indicates that no input character handler was available.
+   */
+  IO_PACKET_NO_INPUT_CHAR_HANDLER
+} IO_Packet_status;
+
+typedef struct IO_Packet_packet IO_Packet_packet;
+
+typedef struct IO_Packet_control IO_Packet_control;
+
+typedef struct IO_Packet_event_control IO_Packet_event_control;
+
+typedef IO_Packet_status ( *IO_Packet_event_handler )(
+  IO_Packet_control *,
+  IO_Packet_event_control *,
+  IO_Packet_event
+);
+
+/**
+ * @brief This structure represents an I/O packet event handler.
+ */
+struct IO_Packet_event_control {
+  IO_Packet_event_control *next;
+  IO_Packet_event_handler handler;
+};
+
+/**
+ * @brief This structure contains a packet to send.
+ */
+struct IO_Packet_packet {
+  IO_Packet_packet *next;
+  void ( *output )( IO_Packet_control *, IO_Packet_packet * );
+  void ( *done )( IO_Packet_control *, IO_Packet_packet * );
+};
+
+/**
+ * @brief This structure contains a signal packet to send.
+ */
+typedef struct {
+  IO_Packet_packet base;
+  uint64_t signal_number;
+  uint64_t signal_value;
+} IO_Packet_signal_packet;
+
+/**
+ * @brief This structure contains a channel packet to send.
+ */
+typedef struct {
+  IO_Packet_packet base;
+  uint64_t channel_number;
+  const void *data_begin;
+  size_t data_size;
+} IO_Packet_channel_packet;
+
+/**
+ * @brief This structure represents an I/O packet transfer.
+ */
+typedef struct {
+  union {
+    IO_Packet_packet base;
+    IO_Packet_channel_packet channel;
+    IO_Packet_signal_packet signal;
+  };
+  IO_Packet_status status;
+  IO_Packet_event_control event;
+} IO_Packet_packet_transfer;
+
+/**
+ * @brief This structure represents an I/O packet processor.
+ */
+struct IO_Packet_control {
+  IO_Packet_state state;
+  IO_Packet_event_control *event_head;
+  IO_Packet_event packet_done_event;
+  uint32_t my_seq;
+  uint32_t other_seq;
+  size_t seq_ack_idx;
+  uint32_t seq_ack;
+  uint8_t packet_type;
+  uint32_t crc_calculated;
+  uint32_t crc_received;
+  size_t crc_idx;
+  size_t value_idx;
+  uint64_t values[ 2 ];
+  IO_Base64_decode_control b64_decode;
+  uint32_t snd_timeout;
+  IO_Packet_packet *snd_pending;
+  IO_Packet_packet *snd_head;
+  IO_Packet_packet **snd_tail;
+  IO_Packet_packet hello;
+  void ( *output_char )( IO_Packet_control *, uint8_t );
+  uint32_t ( *clock_monotonic )( IO_Packet_control * );
+  int ( *input_char )( IO_Packet_control * );
+};
+
+/**
+ * @brief Initializes the I/O packet processor.
+ *
+ * @param[out] self is the I/O packet processor to initialize.
+ *
+ * @param seq is the initial sequence and acknowledge number.
+ *
+ * @param input_char_handler is the handler to get characters.  This handler is
+ *   optional and may be NULL.
+ *
+ * @param output_char_handler is the handler to output characters.
+ *
+ * @param clock_monotonic_handler is the handler to get the current value of a
+ *   monotonic clock.
+ */
+void _IO_Packet_initialize(
+  IO_Packet_control *self,
+  uint32_t           seq,
+  int             ( *input_char_handler )( IO_Packet_control * ),
+  void            ( *output_char_handler )( IO_Packet_control *, uint8_t ),
+  uint32_t        ( *clock_monotonic_handler )( IO_Packet_control * )
+);
+
+/**
+ * @brief Appends the event handler to the event handler list of the I/O packet
+ *   processor.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[out] ctrl is the I/O event control for the handler.
+ *
+ * @param handler is the I/O event handler.
+ */
+void _IO_Packet_append_event_handler(
+  IO_Packet_control       *self,
+  IO_Packet_event_control *ctrl,
+  IO_Packet_event_handler  handler
+);
+
+/**
+ * @brief Prepends the event handler to the event handler list of the I/O packet
+ *   processor.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[out] ctrl is the I/O event control for the handler.
+ *
+ * @param handler is the I/O event handler.
+ */
+void _IO_Packet_prepend_event_handler(
+  IO_Packet_control       *self,
+  IO_Packet_event_control *ctrl,
+  IO_Packet_event_handler  handler
+);
+
+/**
+ * @brief Removes the event handler from the event handler list of the I/O
+ *   packet processor.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[in] ctrl is the I/O event control to remove.
+ */
+void _IO_Packet_remove_event_handler(
+  IO_Packet_control       *self,
+  IO_Packet_event_control *ctrl
+);
+
+/**
+ * @brief Processes the character or performs the idle processing.
+ *
+ * It may output at most one packet per call using the output character
+ * handler.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param ch is the character to process or -1 to perform the idle processing.
+ */
+void _IO_Packet_process( IO_Packet_control *self, int ch );
+
+/**
+ * @brief Sends the packet and waits for a non-continuation status.
+ *
+ * The status change shall be done by the provided packet send done handler or
+ * the event handler.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[in, out] transfer is the packet transfer.
+ *
+ * @param timeout is the optional request timeout.  When the value is zero, the
+ *   function waits forever for the non-continuation status.  Otherwise, the function
+ *   returns with a timeout status if the non-continuation status was not
+ *   established within the specified time frame.  The timeout value is with
+ *   respect to the used monotonic clock handler.
+ */
+IO_Packet_status _IO_Packet_send(
+  IO_Packet_control         *self,
+  IO_Packet_packet_transfer *transfer,
+  uint32_t                   timeout
+);
+
+/**
+ * @brief Sends the signal and waits for the acknowledge.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param signal_number is the signal number.
+ *
+ * @param signal_value is the signal value.
+ *
+ * @param timeout is the optional request timeout.  When the value is zero, the
+ *   function waits forever for the acknowledge.  Otherwise, the function
+ *   returns with a timeout status if no acknowledge was received within the
+ *   specified time frame.  The timeout value is with respect to the used
+ *   monotonic clock handler.
+ */
+IO_Packet_status _IO_Packet_signal(
+  IO_Packet_control *self,
+  uint64_t           signal_number,
+  uint64_t           signal_value,
+  uint32_t           timeout
+);
+
+/**
+ * @brief Pushes the data to the channel and waits for the acknowledge.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param channel_number is the channel number.
+ *
+ * @param data_begin is the begin address of the data area to push.
+ *
+ * @param data_size is the size in bytes of the data area to push.
+ *
+ * @param timeout is the optional request timeout.  When the value is zero, the
+ *   function waits forever for the acknowledge.  Otherwise, the function
+ *   returns with a timeout status if no acknowledge was received within the
+ *   specified time frame.  The timeout value is with respect to the used
+ *   monotonic clock handler.
+ */
+IO_Packet_status _IO_Packet_channel_push(
+  IO_Packet_control *self,
+  uint64_t           channel_number,
+  const void        *data_begin,
+  size_t             data_size,
+  uint32_t           timeout
+);
+
+/**
+ * @brief Pushes the data to the channel and waits for an
+ *   acknowledge with response data.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param channel_number is the channel number.
+ *
+ * @param transmit_begin is the begin address of the data area to transmit.
+ *
+ * @param transmit_size is the size in bytes of the data area to transmit.
+ *
+ * @param[out] receive_begin is the begin address of the data area to receive data.
+ *
+ * @param[in, out] receive_size is the pointer to a buffer size object.  On
+ *   entry, the value of this object is the size in bytes of the data area to
+ *   receive data.  On return, the value of this object is the size in bytes of
+ *   the data received.
+ *
+ * @param timeout is the optional request timeout.  When the value is zero, the
+ *   function waits forever for the acknowledge and received data.  Otherwise,
+ *   the function returns with a timeout status if no acknowledge or data was
+ *   received within the specified time frame.  The timeout value is with
+ *   respect to the used monotonic clock handler.
+ */
+IO_Packet_status _IO_Packet_channel_exchange(
+  IO_Packet_control *self,
+  uint64_t           channel_number,
+  const void        *transmit_begin,
+  size_t             transmit_size,
+  void              *receive_begin,
+  size_t            *receive_size,
+  uint32_t           timeout
+);
+
+/**
+ * @brief Gets the jump address.
+ *
+ * It may be used in ::IO_PACKET_EVENT_JUMP events.
+ *
+ * @return Returns the jump address.
+ */
+static inline void *_IO_Packet_get_jump_address(
+  const IO_Packet_control *self
+)
+{
+  return (void *)(uintptr_t) self->values[ 0 ];
+}
+
+/**
+ * @brief Gets the load address.
+ *
+ * It may be used in ::IO_PACKET_EVENT_LOAD_BEGIN and
+ * ::IO_PACKET_EVENT_LOAD_END events.
+ *
+ * @return Returns the load address.
+ */
+static inline void *_IO_Packet_get_load_address(
+  const IO_Packet_control *self
+)
+{
+  return (void *)(uintptr_t) self->values[ 0 ];
+}
+
+/**
+ * @brief Gets the load size.
+ *
+ * It may be used in ::IO_PACKET_EVENT_LOAD_BEGIN and
+ * ::IO_PACKET_EVENT_LOAD_END events.
+ *
+ * @return Returns the load size.
+ */
+static inline size_t _IO_Packet_get_load_size(
+  const IO_Packet_control *self
+)
+{
+  return (size_t) self->values[ 1 ];
+}
+
+/**
+ * @brief Gets the signal number.
+ *
+ * It may be used in ::IO_PACKET_EVENT_SIGNAL events.
+ *
+ * @return Returns the signal number.
+ */
+static inline uint64_t _IO_Packet_get_signal_number(
+  const IO_Packet_control *self
+)
+{
+  return self->values[ 0 ];
+}
+
+/**
+ * @brief Gets the signal value.
+ *
+ * It may be used in ::IO_PACKET_EVENT_SIGNAL events.
+ *
+ * @return Returns the signal value.
+ */
+static inline uint64_t _IO_Packet_get_signal_value(
+  const IO_Packet_control *self
+)
+{
+  return self->values[ 1 ];
+}
+
+/**
+ * @brief Gets the channel number.
+ *
+ * It may be used in ::IO_PACKET_EVENT_CHANNEL_BEGIN and
+ * ::IO_PACKET_EVENT_CHANNEL_END events.
+ *
+ * @return Returns the channel number.
+ */
+static inline uint64_t _IO_Packet_get_channel_number(
+  const IO_Packet_control *self
+)
+{
+  return self->values[ 0 ];
+}
+
+/**
+ * @brief Gets the channel data size in bytes.
+ *
+ * It may be used in ::IO_PACKET_EVENT_CHANNEL_BEGIN and
+ * ::IO_PACKET_EVENT_CHANNEL_END events.
+ *
+ * @return Returns the channel data size in bytes.
+ */
+static inline size_t _IO_Packet_get_channel_size(
+  const IO_Packet_control *self
+)
+{
+  return (size_t) self->values[ 1 ];
+}
+
+/**
+ * @brief Sets the channel data target address.
+ *
+ * It may be used in ::IO_PACKET_EVENT_CHANNEL_BEGIN events.
+ *
+ * @param[out] target is the channel data target address.  The target area
+ *   shall be large enough to receive _IO_Packet_get_channel_size() bytes.
+ */
+static inline void _IO_Packet_set_channel_target(
+  IO_Packet_control *self,
+  void              *target
+)
+{
+  _IO_Base64_decode_initialize(
+    &self->b64_decode,
+    target,
+    _IO_Packet_get_channel_size( self )
+  );
+}
+
+/**
+ * @brief Outputs an acknowledge packet.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ */
+void _IO_Packet_output_acknowledge( IO_Packet_control *self );
+
+/**
+ * @brief Outputs a reject packet.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ */
+void _IO_Packet_output_reject( IO_Packet_control *self );
+
+/**
+ * @brief Does nothing.
+ *
+ * @param[in] self is the I/O packet processor.
+ *
+ * @param[in] pkt is the successfully transmitted packet.
+ */
+void _IO_Packet_done_default(
+  IO_Packet_control *self,
+  IO_Packet_packet *pkt
+);
+
+/**
+ * @brief Outputs a hello packet.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[in, out] pkt is the packet to output.
+ */
+void _IO_Packet_output_hello(
+  IO_Packet_control *self,
+  IO_Packet_packet *pkt
+);
+
+/**
+ * @brief Outputs a signal packet.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[in, out] pkt is the packet to output.
+ */
+void _IO_Packet_output_signal(
+  IO_Packet_control *self,
+  IO_Packet_packet *pkt
+);
+
+/**
+ * @brief Outputs a channel packet.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[in, out] pkt is the packet to output.
+ */
+void _IO_Packet_output_channel(
+  IO_Packet_control *self,
+  IO_Packet_packet *pkt
+);
+
+/**
+ * @brief Enqueues an initialized packet for transmission.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[in, out] pkt is the packet to enqueue.
+ */
+static inline void _IO_Packet_enqueue(
+  IO_Packet_control *self,
+  IO_Packet_packet  *pkt
+)
+{
+  pkt->next = NULL;
+  *self->snd_tail = pkt;
+  self->snd_tail = &pkt->next;
+}
+
+/**
+ * @brief Removes the packet from the send queue.
+ *
+ * @param[in, out] self is the I/O packet processor.
+ *
+ * @param[in, out] pkt is the packet to cancel.
+ */
+void _IO_Packet_cancel(
+  IO_Packet_control *self,
+  IO_Packet_packet  *pkt
+);
+
+/**
+ * @brief Initializes the signal packet.
+ *
+ * @param[out] pkt is the packet to enqueue.
+ *
+ * @param signal_number is the signal number.
+ *
+ * @param signal_value is the signal value.
+ *
+ * @param done is the transfer done handler.
+ */
+static inline void _IO_Packet_initialize_signal(
+  IO_Packet_signal_packet *pkt,
+  uint64_t                 signal_number,
+  uint64_t                 signal_value,
+  void                  ( *done )( IO_Packet_control *, IO_Packet_packet * )
+)
+{
+  pkt->base.output = _IO_Packet_output_signal;
+  pkt->base.done = done;
+  pkt->signal_number = signal_number;
+  pkt->signal_value = signal_value;
+}
+
+/**
+ * @brief Initializes the channel packet.
+ *
+ * @param[out] pkt is the packet to enqueue.
+ *
+ * @param channel_number is the channel number.
+ *
+ * @param data_begin is the begin address of the data area.
+ *
+ * @param data_size is the size in bytes of the data area.
+ *
+ * @param done is the transfer done handler.
+ */
+static inline void _IO_Packet_initialize_channel(
+  IO_Packet_channel_packet *pkt,
+  uint64_t                  channel_number,
+  const void               *data_begin,
+  size_t                    data_size,
+  void                   ( *done )( IO_Packet_control *, IO_Packet_packet * )
+)
+{
+  pkt->base.output = _IO_Packet_output_channel;
+  pkt->base.done = done;
+  pkt->channel_number = channel_number;
+  pkt->data_begin = data_begin;
+  pkt->data_size = data_size;
+}
+
+/** @} */
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/spec/build/cpukit/librtemscpu.yml b/spec/build/cpukit/librtemscpu.yml
index 7392eb9643..af5baa97f9 100644
--- a/spec/build/cpukit/librtemscpu.yml
+++ b/spec/build/cpukit/librtemscpu.yml
@@ -542,6 +542,7 @@ source:
 - cpukit/dev/iobase64.c
 - cpukit/dev/iobase64decode.c
 - cpukit/dev/iocrc24q.c
+- cpukit/dev/iopacket.c
 - cpukit/dev/ioprintf.c
 - cpukit/dev/iorelax.c
 - cpukit/dev/iovprintf.c
diff --git a/testsuites/unit/tc-io-packet.c b/testsuites/unit/tc-io-packet.c
index 2ed95ae460..82c55285ad 100644
--- a/testsuites/unit/tc-io-packet.c
+++ b/testsuites/unit/tc-io-packet.c
@@ -27,8 +27,583 @@
 
 #include <rtems/dev/io.h>
 
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/endian.h>
+
 #include <rtems/test.h>
 
+typedef struct {
+  IO_Packet_control base;
+  IO_Packet_event_control event;
+  IO_Packet_event_control event_success;
+  IO_Packet_event_control event_continue;
+  size_t response_idx;
+  char response_buf[256];
+  char load_buf[32];
+  uint32_t counter;
+  IO_Packet_signal_packet signal_pkt;
+  IO_Packet_channel_packet channel_pkt;
+  const char* input;
+  uint32_t crc;
+} test_control;
+
+typedef struct {
+  const char* input;
+  const char* response;
+  const char* load;
+} test_case;
+
+static const test_case test_cases[] = {
+    {"@X at X@X at X@X", "@X at Q@R{1211:H#Mos3}@r at X@X at X@X at T@R{1211:H#Mos3}@r", ""},
+    {"{a{B at XBCC:H#h4zx}x{{BBCC:H#h4zx}",
+     "@X at H@Q at R{12BB:H#EmCT}@r at G@D at T@R{12BB:H#EmCT}@r", ""},
+    {"{123456789", "@N at Q@R{1211:H#Mos3}@r at G@G at G@G", ""},
+    {"{1234:HX", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:H#\xe2\x82\xac", "@N at Q@R{1211:H#Mos3}@r at G@G", ""},
+    {"{1234:H#|", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:H#abcd|", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{12345", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{|{\xe2\x82\xac", "@N at Q@R{1211:H#Mos3}@r at N@T at R{1211:H#Mos3}@r at G@G", ""},
+    {"{1234:X:#gOys}{1312:X:#N4Ge}",
+     "@E at R{1211:R:12#TQxe}@r at E@R{1311:R:13#PksK}@r", ""},
+    {"@X{1211:Y#cYlF}{1312:Y#qyFL}",
+     "@X at Q@R{1211:H#Mos3}@r at E@T at R{1211:H#Mos3}@r at E@o at R{1311:R:13#PksK}@r", ""},
+    {"{1234:JX", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:J:\xe2\x82\xac", "@N at Q@R{1211:H#Mos3}@r at G@G", ""},
+    {"{1234:J:|", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:J:123456789a_", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:J::", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:J:0#BzfY|", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:J:0#BzfY+", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:J:0#abcd}", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:J:AAAAAAAAAAAAAAAAAAAAADerb7v#hu_C}", "@J at Q@R{1212:H#Md21}@r", ""},
+    {"{1234:J:Derb7v#r2GB}", "@J at Q@R{1212:H#Md21}@r", ""},
+    {"{1234:L:0:0:", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:L:0#AZrc+", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:L:0#uykR+", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"@X{1212:L:A:A#EnK5+#AAAA}",
+     "@X at Q@R{1211:H#Mos3}@r at L@o at R{1311:R:12#uAfx}@r at G@G at G@G at G@G", ""},
+    {"{@L+SGVsbG8sIHdvcmxkI#", "@L at N@Q at R{1211:H#Mos3}@r", "Hello, world "},
+    {"{@L+SGVsbG8sIHdvcmxkI|", "@L at N@Q at R{1211:H#Mos3}@r", "Hello, world "},
+    {"{@L+SGVsbG8sIHdvcmxkIQ==#b1BU}", "@L at l@Q at R{1212:H#Md21}@r",
+     "Hello, world!"},
+    {"{1234:S:BI0VniHZUMh:A#8aeE}", "@S at Q@R{1212:H#Md21}@r", ""},
+    {"{1234:S:BI0VniHZUMh:A#8aeE+", "@N at Q@R{1211:H#Mos3}@r", ""},
+    {"{1234:S:BI0VniHZUMh:AKvN76vN76vN#c-zW}", "@S at R{1211:R:12#TQxe}@r", ""},
+    {"{1234:C:BI0VniHZUMh:A#gl_H+", "@C at R{1211:R:12#TQxe}@r", ""},
+    {"{1234:C:BI0VniHZUMh:N#vKHp+SGVsbG8sIHdvcmxkIQ==#b1BU}",
+     "@C at c@Q at R{1212:H#Md21}@r", "Hello, world!"},
+    {"@X{121 at S@X2:A#FhB3}@X at X@X at X@X{1313:A#TOcs}@X{1413:A#y8uF}",
+     "@X at Q@R{1211:H#Mos3}@r at X@A at o@Q at R{1312:S:A:Ej#5Yfj}@r at X@X at X@X at T@R{1312:S:A:"
+     "Ej#5Yfj}@r at X@A at o@s at X@A",
+     ""},
+    {"@X{1212:A at C#FhB3}@X",
+     "@X at Q@R{1211:H#Mos3}@r at A@o at Q@R{1312:C:RW:G#sdj6+BlubBlub#1V-z}@r at X", ""}};
+
+static void process(test_control* self, const char* input) {
+  uint32_t crc = self->crc;
+
+  while (*input != '\0') {
+    crc = _IO_CRC24Q_update(crc, (uint8_t)*input);
+    _IO_Packet_process(&self->base, (uint8_t)*input);
+    ++input;
+  }
+
+  self->crc = crc;
+}
+
+static void process_char(int c, void* arg) {
+  test_control* self = (test_control*)arg;
+  self->crc = _IO_CRC24Q_update(self->crc, (uint8_t)c);
+  _IO_Packet_process(&self->base, (uint8_t)c);
+}
+
+static void output_char(IO_Packet_control* base, uint8_t ch) {
+  test_control* self = (test_control*)base;
+  size_t idx = self->response_idx;
+  self->response_idx = idx + 1;
+
+  if (idx < sizeof(self->response_buf) - 1) {
+    self->response_buf[idx] = (char)ch;
+  }
+}
+
+static int input_char(IO_Packet_control* base) {
+  test_control* self = (test_control*)base;
+  const char* input = self->input;
+  uint8_t ch = (uint8_t)*input;
+
+  if (ch == '\0') {
+    return -1;
+  }
+
+  self->input = input + 1;
+  return ch;
+}
+
+static IO_Packet_status event(IO_Packet_control* base,
+                              IO_Packet_event_control* ctrl,
+                              IO_Packet_event evt) {
+  test_control* self = (test_control*)base;
+  T_eq_ptr(ctrl, &self->event);
+  output_char(base, '@');
+
+  switch (evt) {
+    case IO_PACKET_EVENT_ACKNOWLEDGE:
+      output_char(base, 'A');
+      break;
+    case IO_PACKET_EVENT_CHANNEL_BEGIN: {
+      output_char(base, 'C');
+      T_eq_u64(_IO_Packet_get_channel_number(base),
+               UINT64_C(0x1234567887654321));
+      size_t size = _IO_Packet_get_channel_size(base);
+
+      if (size != 0) {
+        _IO_Packet_set_channel_target(base, &self->load_buf[0]);
+        T_eq_sz(size, 0xd);
+      }
+
+      break;
+    }
+    case IO_PACKET_EVENT_CHANNEL_END:
+      output_char(base, 'c');
+      _IO_Packet_output_acknowledge(base);
+      break;
+    case IO_PACKET_EVENT_DUPLICATE:
+      output_char(base, 'D');
+      break;
+    case IO_PACKET_EVENT_GARBAGE:
+      output_char(base, 'G');
+      break;
+    case IO_PACKET_EVENT_HELLO:
+      output_char(base, 'H');
+      _IO_Packet_output_acknowledge(base);
+      break;
+    case IO_PACKET_EVENT_JUMP:
+      output_char(base, 'J');
+      _IO_Packet_output_acknowledge(base);
+      T_eq_uptr((uintptr_t)_IO_Packet_get_jump_address(base),
+                (uintptr_t)0xdeadbeef);
+      break;
+    case IO_PACKET_EVENT_NOTHING:
+      output_char(base, 'X');
+      break;
+    case IO_PACKET_EVENT_LOAD_BEGIN:
+      output_char(base, 'L');
+
+      if (_IO_Packet_get_load_address(base) == NULL) {
+        _IO_Packet_output_reject(base);
+      } else {
+        T_eq_ptr(_IO_Packet_get_load_address(base), &self->load_buf[0]);
+        T_eq_sz(_IO_Packet_get_load_size(base), 13);
+      }
+
+      break;
+    case IO_PACKET_EVENT_LOAD_END:
+      output_char(base, 'l');
+      _IO_Packet_output_acknowledge(base);
+      T_eq_ptr(_IO_Packet_get_load_address(base), &self->load_buf[0]);
+      T_eq_sz(_IO_Packet_get_load_size(base), 13);
+      break;
+    case IO_PACKET_EVENT_OUTPUT_BEGIN:
+      output_char(base, 'R');
+      break;
+    case IO_PACKET_EVENT_OUTPUT_END:
+      output_char(base, 'r');
+      break;
+    case IO_PACKET_EVENT_SEND_DONE:
+      output_char(base, 'o');
+      break;
+    case IO_PACKET_EVENT_REJECT:
+      output_char(base, 'E');
+      break;
+    case IO_PACKET_EVENT_NACK:
+      output_char(base, 'N');
+      break;
+    case IO_PACKET_EVENT_SEND_DEQUEUE:
+      output_char(base, 'Q');
+      break;
+    case IO_PACKET_EVENT_SEND_AGAIN:
+      output_char(base, 'T');
+      break;
+    case IO_PACKET_EVENT_SIGNAL: {
+      output_char(base, 'S');
+      T_eq_u64(_IO_Packet_get_signal_number(base),
+               UINT64_C(0x1234567887654321));
+      uint64_t value = _IO_Packet_get_signal_value(base);
+
+      if (value == 0) {
+        _IO_Packet_output_acknowledge(base);
+      } else {
+        T_eq_u64(value, UINT64_C(0xabcdefabcdefabcd));
+      }
+
+      break;
+    }
+    default:
+      output_char(base, 'U');
+      break;
+  }
+
+  return IO_PACKET_SUCCESSFUL;
+}
+
+static IO_Packet_status event_success(IO_Packet_control* base,
+                                      IO_Packet_event_control* ctrl,
+                                      IO_Packet_event evt) {
+  (void)evt;
+  test_control* self = (test_control*)base;
+  T_eq_ptr(ctrl, &self->event_success);
+  output_char(base, '@');
+  output_char(base, 'Z');
+  return IO_PACKET_SUCCESSFUL;
+}
+
+static IO_Packet_status event_continue(IO_Packet_control* base,
+                                       IO_Packet_event_control* ctrl,
+                                       IO_Packet_event evt) {
+  (void)evt;
+  test_control* self = (test_control*)base;
+  T_eq_ptr(ctrl, &self->event_continue);
+  output_char(base, '@');
+  output_char(base, 'Y');
+  return IO_PACKET_CONTINUE;
+}
+
+static IO_Packet_status event_channel_load(IO_Packet_control* base,
+                                           IO_Packet_event_control* ctrl,
+                                           IO_Packet_event evt) {
+  test_control* self = (test_control*)base;
+  T_eq_ptr(ctrl, &self->event);
+
+  if (evt == IO_PACKET_EVENT_CHANNEL_BEGIN) {
+    if (_IO_Packet_get_channel_number(base) != 3) {
+      return IO_PACKET_CONTINUE;
+    }
+
+    T_lt_sz(_IO_Packet_get_channel_size(base), sizeof(self->load_buf));
+    _IO_Packet_set_channel_target(base, &self->load_buf[0]);
+    return IO_PACKET_SUCCESSFUL;
+  }
+
+  if (evt == IO_PACKET_EVENT_CHANNEL_END) {
+    if (_IO_Packet_get_channel_number(base) != 3) {
+      return IO_PACKET_CONTINUE;
+    }
+
+    _IO_Packet_output_acknowledge(base);
+    return IO_PACKET_SUCCESSFUL;
+  }
+
+  return IO_PACKET_CONTINUE;
+}
+
+static void output_load(test_control* self) {
+  _IO_Packet_process(&self->base, '{');
+  self->crc = IO_CRC24Q_SEED;
+  process(self, "1234:L:");
+
+  uint8_t addr[9];
+  addr[0] = 0;
+  be64enc(&addr[1], (uint64_t)(uintptr_t)&self->load_buf[0]);
+  _IO_Base64url(process_char, self, &addr[0], sizeof(addr), NULL, INT_MAX);
+
+  /* The 'N' is the length of "Hello, world!" */
+  process(self, ":N");
+
+  _IO_Packet_process(&self->base, '#');
+
+  for (int i = 18; i >= 0; i -= 6) {
+    uint8_t ch = _IO_Base64url_table[(self->crc >> i) & 0x3f];
+    _IO_Packet_process(&self->base, ch);
+  }
+}
+
+static void signal_done(IO_Packet_control* base, IO_Packet_packet* pkt) {
+  test_control* self = (test_control*)base;
+  output_char(base, '@');
+  output_char(base, 's');
+  T_eq_ptr(pkt, &self->signal_pkt);
+}
+
+static void channel_done(IO_Packet_control* base, IO_Packet_packet* pkt) {
+  test_control* self = (test_control*)base;
+  output_char(base, '@');
+  output_char(base, 'e');
+  T_eq_ptr(pkt, &self->channel_pkt);
+}
+
+static uint32_t now(IO_Packet_control* base) {
+  test_control* self = (test_control*)base;
+  uint32_t counter = self->counter;
+  self->counter = counter + 1;
+  return self->counter;
+}
+
+static const char channel_data[] = {0x06, 0x5b, 0x9b, 0x06, 0x5b, 0x9b};
+
+static void clear_response(test_control* self) {
+  self->response_idx = 0;
+  memset(&self->response_buf[0], 0, sizeof(self->response_buf));
+}
+
+static void initialize_test_control(test_control* self) {
+  self->input = "";
+  memset(&self->base, 0xff, sizeof(self->base));
+  memset(&self->load_buf[0], 0, sizeof(self->load_buf));
+  clear_response(self);
+  _IO_Packet_initialize(&self->base, 3445, NULL, output_char, now);
+  _IO_Packet_prepend_event_handler(&self->base, &self->event, event);
+}
+
+T_TEST_CASE(IOPacket) {
+  test_control self;
+
+  for (size_t i = 0; i < RTEMS_ARRAY_SIZE(test_cases); ++i) {
+    initialize_test_control(&self);
+    const test_case* tc = &test_cases[i];
+    const char* ch = tc->input;
+
+    while (*ch != '\0') {
+      if (*ch == '@') {
+        ++ch;
+        switch (*ch) {
+          case 'X':
+            _IO_Packet_process(&self.base, -1);
+            break;
+          case 'L':
+            output_load(&self);
+            break;
+          case 'S':
+            _IO_Packet_initialize_signal(&self.signal_pkt, 0, 0x123,
+                                         signal_done);
+            _IO_Packet_enqueue(&self.base, &self.signal_pkt.base);
+            break;
+          case 'C':
+            _IO_Packet_initialize_channel(&self.channel_pkt, 0x456,
+                                          &channel_data[0],
+                                          sizeof(channel_data), channel_done);
+            _IO_Packet_enqueue(&self.base, &self.channel_pkt.base);
+            break;
+          default:
+            T_unreachable();
+            break;
+        }
+      } else {
+        _IO_Packet_process(&self.base, (uint8_t)*ch);
+      }
+
+      ++ch;
+    }
+
+    T_eq_str(&self.response_buf[0], tc->response);
+    T_eq_str(&self.load_buf[0], tc->load);
+  }
+}
+
+T_TEST_CASE(IOPacketCancel) {
+  test_control self;
+  initialize_test_control(&self);
+  T_null(self.base.snd_pending);
+  T_eq_ptr(self.base.snd_head, &self.base.hello);
+  T_eq_ptr(self.base.snd_tail, &self.base.hello.next);
+
+  _IO_Packet_process(&self.base, -1);
+  T_eq_ptr(self.base.snd_pending, &self.base.hello);
+  T_eq_ptr(self.base.snd_head, &self.base.hello);
+  T_eq_ptr(self.base.snd_tail, &self.base.hello.next);
+
+  IO_Packet_packet pkt;
+  memset(&pkt, 0xff, sizeof(pkt));
+  _IO_Packet_enqueue(&self.base, &pkt);
+  T_eq_ptr(self.base.snd_head, &self.base.hello);
+  T_eq_ptr(self.base.snd_tail, &pkt.next);
+
+  IO_Packet_packet pkt_2;
+  memset(&pkt_2, 0xff, sizeof(pkt_2));
+  _IO_Packet_enqueue(&self.base, &pkt_2);
+  T_eq_ptr(self.base.snd_head, &self.base.hello);
+  T_eq_ptr(self.base.snd_tail, &pkt_2.next);
+
+  _IO_Packet_cancel(&self.base, &self.base.hello);
+  T_null(self.base.snd_pending);
+  T_eq_ptr(self.base.snd_head, &pkt);
+  T_eq_ptr(self.base.snd_tail, &pkt_2.next);
+
+  _IO_Packet_cancel(&self.base, &pkt_2);
+  T_null(self.base.snd_pending);
+  T_eq_ptr(self.base.snd_head, &pkt);
+  T_eq_ptr(self.base.snd_tail, &pkt.next);
+
+  /* Double cancel has no effects */
+  _IO_Packet_cancel(&self.base, &pkt_2);
+  T_null(self.base.snd_pending);
+  T_eq_ptr(self.base.snd_head, &pkt);
+  T_eq_ptr(self.base.snd_tail, &pkt.next);
+
+  _IO_Packet_cancel(&self.base, &pkt);
+  T_null(self.base.snd_pending);
+  T_null(self.base.snd_head);
+  T_eq_ptr(self.base.snd_tail, &self.base.snd_head);
+}
+
+T_TEST_CASE(IOPacketSignal) {
+  test_control self;
+  initialize_test_control(&self);
+  IO_Packet_status status = _IO_Packet_signal(&self.base, 1, 2, 0);
+  T_eq_int(status, IO_PACKET_NO_INPUT_CHAR_HANDLER);
+  T_eq_str(&self.response_buf[0], "");
+
+  self.base.input_char = input_char;
+  self.input = "{1212:A#FhB3}{1313:A#TOcs}";
+  status = _IO_Packet_signal(&self.base, 3, 4, 0);
+  T_eq_int(status, IO_PACKET_SUCCESSFUL);
+  T_null(self.base.snd_pending);
+  T_null(self.base.snd_head);
+  T_eq_str(&self.response_buf[0],
+           "@X at Q@R{1211:H#Mos3}@r at A@o at Q@R{1312:S:D:E#AzkU}@r at A@o");
+
+  clear_response(&self);
+  status = _IO_Packet_signal(&self.base, 5, 6, 2);
+  T_eq_int(status, IO_PACKET_TIMEOUT);
+  T_null(self.base.snd_pending);
+  T_null(self.base.snd_head);
+  T_eq_str(&self.response_buf[0],
+           "@X at Q@R{1413:S:F:G#So3p}@r at X@X at T@R{1413:S:F:G#So3p}@r");
+}
+
+T_TEST_CASE(IOPacketChannelPush) {
+  test_control self;
+  initialize_test_control(&self);
+  IO_Packet_status status = _IO_Packet_channel_push(
+      &self.base, 1, &channel_data[0], sizeof(channel_data), 0);
+  T_eq_int(status, IO_PACKET_NO_INPUT_CHAR_HANDLER);
+  T_eq_str(&self.response_buf[0], "");
+
+  self.base.input_char = input_char;
+  self.input = "{1212:A#FhB3}{1313:A#TOcs}";
+  status = _IO_Packet_channel_push(&self.base, 2, &channel_data[0],
+                                   sizeof(channel_data), 0);
+  T_eq_int(status, IO_PACKET_SUCCESSFUL);
+  T_null(self.base.snd_pending);
+  T_null(self.base.snd_head);
+  T_eq_str(
+      &self.response_buf[0],
+      "@X at Q@R{1211:H#Mos3}@r at A@o at Q@R{1312:C:C:G#J4sp+BlubBlub#1V-z}@r at A@o");
+
+  clear_response(&self);
+  status = _IO_Packet_channel_push(&self.base, 3, &channel_data[0],
+                                   sizeof(channel_data), 2);
+  T_eq_int(status, IO_PACKET_TIMEOUT);
+  T_null(self.base.snd_pending);
+  T_null(self.base.snd_head);
+  T_eq_str(&self.response_buf[0],
+           "@X at Q@R{1413:C:D:G#4RFf+BlubBlub#1V-z}@r at X@X at T@R{1413:C:D:G#4RFf+"
+           "BlubBlub#1V-z}@r");
+}
+
+T_TEST_CASE(IOPacketChannelExchange) {
+  test_control self;
+  initialize_test_control(&self);
+  _IO_Packet_remove_event_handler(&self.base, &self.event);
+
+  char receive_buf[32];
+  size_t receive_size = sizeof(receive_buf);
+  memset(&receive_buf[0], 0, sizeof(receive_buf));
+  IO_Packet_status status = _IO_Packet_channel_exchange(
+      &self.base, 1, &channel_data[0], sizeof(channel_data), &receive_buf[0],
+      &receive_size, 0);
+  T_eq_int(status, IO_PACKET_NO_INPUT_CHAR_HANDLER);
+  T_eq_str(&self.response_buf[0], "");
+  T_eq_sz(receive_size, 0);
+
+  self.base.input_char = input_char;
+  self.input = "{1212:A#FhB3}{1313:C:B:I#J6Gn+Zm9vIGJhcgA=#oHjZ}";
+  receive_size = sizeof(receive_buf);
+  memset(&receive_buf[0], 0, sizeof(receive_buf));
+  status = _IO_Packet_channel_exchange(&self.base, 1, &channel_data[0],
+                                       sizeof(channel_data), &receive_buf[0],
+                                       &receive_size, 0);
+  T_eq_int(status, IO_PACKET_SUCCESSFUL);
+  T_null(self.base.snd_pending);
+  T_null(self.base.snd_head);
+  T_eq_str(&self.response_buf[0],
+           "{1211:H#Mos3}{1312:C:B:G#pIL-+BlubBlub#1V-z}{1413:A#y8uF}");
+  T_eq_sz(receive_size, 8);
+  T_eq_str(&receive_buf[0], "foo bar");
+
+  clear_response(&self);
+  _IO_Packet_append_event_handler(&self.base, &self.event, event_channel_load);
+  self.input =
+      "{1415:C:D:F#_jiM+b29wcwA=#xfkE}{1515:C:C:I#uBx9+Zm9vIGJhcgA=#oHjZ}";
+  receive_size = 7;
+  memset(&receive_buf[0], 0, sizeof(receive_buf));
+  status = _IO_Packet_channel_exchange(&self.base, 2, &channel_data[0],
+                                       sizeof(channel_data), &receive_buf[0],
+                                       &receive_size, 0);
+  T_eq_int(status, IO_PACKET_OVERFLOW);
+  T_null(self.base.snd_pending);
+  T_null(self.base.snd_head);
+  T_eq_str(&self.response_buf[0],
+           "{1513:C:C:G#mcuA+BlubBlub#1V-z}{1614:A#e961}{1714:R:15#Eohl}");
+  T_eq_sz(receive_size, 0);
+  T_eq_str(&receive_buf[0], "");
+  T_eq_str(&self.load_buf[0], "oops");
+  _IO_Packet_remove_event_handler(&self.base, &self.event);
+}
+
+T_TEST_CASE(IOPacketEventHandler) {
+  test_control self;
+  initialize_test_control(&self);
+  T_eq_ptr(self.base.event_head, &self.event);
+
+  _IO_Packet_process(&self.base, -1);
+  process(&self, "{1212:A#FhB3}{1313:A#TOcs}");
+  T_eq_str(&self.response_buf[0], "@X at Q@R{1211:H#Mos3}@r at A@o at A");
+
+  clear_response(&self);
+  _IO_Packet_remove_event_handler(&self.base, &self.event);
+  T_null(self.base.event_head);
+  _IO_Packet_process(&self.base, -1);
+  T_eq_str(&self.response_buf[0], "");
+
+  clear_response(&self);
+  _IO_Packet_append_event_handler(&self.base, &self.event_success,
+                                  event_success);
+  _IO_Packet_append_event_handler(&self.base, &self.event_continue,
+                                  event_continue);
+  T_eq_ptr(self.base.event_head, &self.event_success);
+  _IO_Packet_process(&self.base, -1);
+  T_eq_str(&self.response_buf[0], "@Z");
+
+  _IO_Packet_remove_event_handler(&self.base, &self.event_success);
+  T_eq_ptr(self.base.event_head, &self.event_continue);
+
+  _IO_Packet_remove_event_handler(&self.base, &self.event_continue);
+  T_null(self.base.event_head);
+
+  clear_response(&self);
+  _IO_Packet_prepend_event_handler(&self.base, &self.event_success,
+                                   event_success);
+  _IO_Packet_prepend_event_handler(&self.base, &self.event_continue,
+                                   event_continue);
+  T_eq_ptr(self.base.event_head, &self.event_continue);
+  _IO_Packet_process(&self.base, -1);
+  T_eq_str(&self.response_buf[0], "@Y at Z");
+
+  _IO_Packet_remove_event_handler(&self.base, &self.event_success);
+  T_eq_ptr(self.base.event_head, &self.event_continue);
+
+  /* Double remove has no effects */
+  _IO_Packet_remove_event_handler(&self.base, &self.event_success);
+  T_eq_ptr(self.base.event_head, &self.event_continue);
+
+  _IO_Packet_remove_event_handler(&self.base, &self.event_continue);
+  T_null(self.base.event_head);
+}
+
 T_TEST_CASE(IOCRC24Q) {
   uint32_t state = _IO_CRC24Q_update(IO_CRC24Q_SEED, 0);
   T_eq_u32(state, 0);
-- 
2.35.3



More information about the devel mailing list