[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