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@X@X@X@X", "@X@Q@R{1211:H#Mos3}@r@X@X@X@X@T@R{1211:H#Mos3}@r", ""}, + {"{a{B@XBCC:H#h4zx}x{{BBCC:H#h4zx}", + "@X@H@Q@R{12BB:H#EmCT}@r@G@D@T@R{12BB:H#EmCT}@r", ""}, + {"{123456789", "@N@Q@R{1211:H#Mos3}@r@G@G@G@G", ""}, + {"{1234:HX", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:H#\xe2\x82\xac", "@N@Q@R{1211:H#Mos3}@r@G@G", ""}, + {"{1234:H#|", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:H#abcd|", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{12345", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{|{\xe2\x82\xac", "@N@Q@R{1211:H#Mos3}@r@N@T@R{1211:H#Mos3}@r@G@G", ""}, + {"{1234:X:#gOys}{1312:X:#N4Ge}", + "@E@R{1211:R:12#TQxe}@r@E@R{1311:R:13#PksK}@r", ""}, + {"@X{1211:Y#cYlF}{1312:Y#qyFL}", + "@X@Q@R{1211:H#Mos3}@r@E@T@R{1211:H#Mos3}@r@E@o@R{1311:R:13#PksK}@r", ""}, + {"{1234:JX", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:J:\xe2\x82\xac", "@N@Q@R{1211:H#Mos3}@r@G@G", ""}, + {"{1234:J:|", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:J:123456789a_", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:J::", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:J:0#BzfY|", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:J:0#BzfY+", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:J:0#abcd}", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:J:AAAAAAAAAAAAAAAAAAAAADerb7v#hu_C}", "@J@Q@R{1212:H#Md21}@r", ""}, + {"{1234:J:Derb7v#r2GB}", "@J@Q@R{1212:H#Md21}@r", ""}, + {"{1234:L:0:0:", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:L:0#AZrc+", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:L:0#uykR+", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"@X{1212:L:A:A#EnK5+#AAAA}", + "@X@Q@R{1211:H#Mos3}@r@L@o@R{1311:R:12#uAfx}@r@G@G@G@G@G@G", ""}, + {"{@L+SGVsbG8sIHdvcmxkI#", "@L@N@Q@R{1211:H#Mos3}@r", "Hello, world "}, + {"{@L+SGVsbG8sIHdvcmxkI|", "@L@N@Q@R{1211:H#Mos3}@r", "Hello, world "}, + {"{@L+SGVsbG8sIHdvcmxkIQ==#b1BU}", "@L@l@Q@R{1212:H#Md21}@r", + "Hello, world!"}, + {"{1234:S:BI0VniHZUMh:A#8aeE}", "@S@Q@R{1212:H#Md21}@r", ""}, + {"{1234:S:BI0VniHZUMh:A#8aeE+", "@N@Q@R{1211:H#Mos3}@r", ""}, + {"{1234:S:BI0VniHZUMh:AKvN76vN76vN#c-zW}", "@S@R{1211:R:12#TQxe}@r", ""}, + {"{1234:C:BI0VniHZUMh:A#gl_H+", "@C@R{1211:R:12#TQxe}@r", ""}, + {"{1234:C:BI0VniHZUMh:N#vKHp+SGVsbG8sIHdvcmxkIQ==#b1BU}", + "@C@c@Q@R{1212:H#Md21}@r", "Hello, world!"}, + {"@X{121@S@X2:A#FhB3}@X@X@X@X@X{1313:A#TOcs}@X{1413:A#y8uF}", + "@X@Q@R{1211:H#Mos3}@r@X@A@o@Q@R{1312:S:A:Ej#5Yfj}@r@X@X@X@X@T@R{1312:S:A:" + "Ej#5Yfj}@r@X@A@o@s@X@A", + ""}, + {"@X{1212:A@C#FhB3}@X", + "@X@Q@R{1211:H#Mos3}@r@A@o@Q@R{1312:C:RW:G#sdj6+BlubBlub#1V-z}@r@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@Q@R{1211:H#Mos3}@r@A@o@Q@R{1312:S:D:E#AzkU}@r@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@Q@R{1413:S:F:G#So3p}@r@X@X@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@Q@R{1211:H#Mos3}@r@A@o@Q@R{1312:C:C:G#J4sp+BlubBlub#1V-z}@r@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@Q@R{1413:C:D:G#4RFf+BlubBlub#1V-z}@r@X@X@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@Q@R{1211:H#Mos3}@r@A@o@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@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 _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel