From a1aa5d3952c29188d7dba36f37cc4fdacd478f39 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Sun, 30 Aug 2026 10:27:41 -0700 Subject: [PATCH 1/6] hostmot2: harden Ethernet packet handling --- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 134 ++++++++++++++++-------- src/hal/drivers/mesa-hostmot2/hm2_eth.h | 5 +- 2 files changed, 96 insertions(+), 43 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 76766382041..b0843b3c78a 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -71,7 +71,7 @@ static int *kvlist_lookup(struct rtapi_list_head *head, const char *name) { struct kvlist *ent = rtapi_list_entry(ptr, struct kvlist, list); if(strncmp(name, ent->key, sizeof(ent->key)) == 0) return &ent->value; } - struct kvlist *ent = rtapi_kzalloc(sizeof(struct kvlist), RTAPI_GPF_KERNEL); + struct kvlist *ent = rtapi_kzalloc(sizeof(struct kvlist), RTAPI_GFP_KERNEL); if(!ent) return NULL; strncpy(ent->key, name, sizeof(ent->key)-1); @@ -680,7 +680,7 @@ static char* inet_ntoa_buf(struct in_addr in, char *buf, size_t n) { } char* fetch_ifname(int sockfd, char *buf, size_t n) { - struct sockaddr_in srcaddr; + struct sockaddr_in srcaddr = {0}; struct ifaddrs *ifa, *it; socklen_t addrlen = sizeof(srcaddr); @@ -707,7 +707,7 @@ char* fetch_ifname(int sockfd, char *buf, size_t n) { } int install_firewall_board(int sockfd) { - struct sockaddr_in srcaddr, dstaddr; + struct sockaddr_in srcaddr = {0}, dstaddr = {0}; char srchost[16], dsthost[16]; // enough for 255.255.255.255\0 char dport_s[8], sport_s[8]; @@ -800,11 +800,11 @@ int fetch_hwaddr(hm2_eth_t *board, unsigned char buf[6]) { unsigned char response[6]; LBP16_INIT_PACKET4(packet, 0x4983, 0x0002); int res = eth_socket_send(board, &packet, sizeof(packet)); - if(res < 0) return -errno; + if(res != (int)sizeof(packet)) return res < 0 ? -errno : -EIO; int i=0; res = eth_socket_recv(board, &response, sizeof(response), RECV_TIMEOUT_NON_RT_NS); - if(res < 0) return -errno; + if(res != (int)sizeof(response)) return res < 0 ? -errno : -EIO; // eeprom order is backwards from arp AF_LOCAL order for(i=0; i<6; i++) buf[i] = response[5-i]; @@ -866,14 +866,24 @@ static inline int eth_socket_recv(hm2_eth_t *board, void *buffer, int len, int r /// hm2_eth io functions +static bool hm2_eth_valid_transfer_size(int size) { + return size > 0 + && (size % sizeof(rtapi_u32)) == 0 + && size / (int)sizeof(rtapi_u32) <= LBP16_MAX_PACKET_DATA_SIZE; +} + static int hm2_eth_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *buffer, int size) { hm2_eth_t *board = this->private; int send, recv; - rtapi_u8 tmp_buffer[size + 4]; + rtapi_u8 tmp_buffer[LBP16_MAX_PACKET_DATA_SIZE * sizeof(rtapi_u32)]; long long t1, t2; if (comm_active == 0) return 1; if (size == 0) return 1; + if (!hm2_eth_valid_transfer_size(size)) { + THIS_ERR("hm2_eth_read: invalid transfer size %d\n", size); + return 0; + } board->read_cnt++; if(rtapi_task_self() >= 0) { @@ -890,8 +900,12 @@ static int hm2_eth_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *buffer, i LBP16_INIT_PACKET4(read_packet, CMD_READ_HOSTMOT2_ADDR32_INCR(size/4), addr & 0xFFFF); send = eth_socket_send(board, (void*) &read_packet, sizeof(read_packet)); - if(send < 0) - LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); + if(send != (int)sizeof(read_packet)) { + LL_PRINT("ERROR: sending read packet%s%s\n", + send < 0 ? ": " : "", + send < 0 ? strerror(errno) : ""); + return 0; + } LL_PRINT_IF(debug, "read(%d) : PACKET SENT [CMD:%02X%02X | ADDR: %02X%02X | SIZE: %d]\n", board->read_cnt, read_packet.cmd_hi, read_packet.cmd_lo, read_packet.addr_lo, read_packet.addr_hi, size); @@ -904,7 +918,7 @@ static int hm2_eth_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *buffer, i } else { LL_PRINT_IF(debug, "read(%d) : PACKET RECV [SIZE: %d | TIME: %llu]\n", board->read_cnt, recv, t2 - t1); } - if (recv < 0) + if (recv != size) return 0; memcpy(buffer, tmp_buffer, size); return 1; // success @@ -962,9 +976,13 @@ static int hm2_eth_send_queued_reads(hm2_lowlevel_io_t *this) { board->queue_reads_count++; board->queue_buff_size += sizeof(board->confirm_rw_cnt); - send = eth_socket_send(board, (void*) &board->read_packet, board->read_packet_ptr - board->read_packet); - if(send < 0) { - LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); + size_t packet_size = board->read_packet_ptr - board->read_packet; + send = eth_socket_send(board, (void*) &board->read_packet, packet_size); + if(send != (int)packet_size) { + LL_PRINT("ERROR: sending queued-read packet%s%s\n", + send < 0 ? ": " : "", + send < 0 ? strerror(errno) : ""); + hm2_eth_reset_queued_reads(board); return 0; } return 1; @@ -1067,26 +1085,41 @@ static int hm2_eth_reset(hm2_lowlevel_io_t *this) { lbp16_cmd_addr_data32 bite_packet; LBP16_INIT_PACKET8(bite_packet, CMD_WRITE_HOSTMOT2_ADDR32_INCR(1), HM2_ADDR_WATCHDOG, 0x0001); int ret = eth_socket_send(board, (void*) &bite_packet, sizeof(bite_packet)); - if(ret < 0) perror("eth_socket_send(bite_packet)"); - return ret < 0 ? -errno : 0; + if(ret != (int)sizeof(bite_packet)) { + if (ret < 0) perror("eth_socket_send(bite_packet)"); + return ret < 0 ? -errno : -EIO; + } + return 0; } static int hm2_eth_enqueue_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *buffer, int size) { hm2_eth_t *board = this->private; if (comm_active == 0) return 1; if (size == 0) return 1; - - //Check size we are going to write + if (!hm2_eth_valid_transfer_size(size)) { + THIS_ERR("hm2_eth_enqueue_read: invalid transfer size %d\n", size); + return 0; + } + // hm2_eth_send_queued_reads() appends two internal confirmation reads. + if (board->queue_reads_count >= MAX_ETH_READS - 2) { + THIS_ERR("hm2_eth_enqueue_read: read queue full (%d)\n", board->queue_reads_count); + return 0; + } size_t read_packet_size = board->read_packet_ptr - board->read_packet; - if (read_packet_size + sizeof(lbp16_cmd_addr) > sizeof(board->read_packet)) { - LL_PRINT("ERROR: enqueue_read: buffer full\n"); + size_t read_packet_trailer_size = + 3 * sizeof(lbp16_cmd_addr) + sizeof(board->read_cnt); + if (read_packet_size + sizeof(lbp16_cmd_addr) + + read_packet_trailer_size > sizeof(board->read_packet)) { + THIS_ERR("hm2_eth_enqueue_read: read packet buffer size exceeded\n"); return 0; } - if (board->queue_reads_count + 1 > MAX_ETH_READS) { - LL_PRINT("ERROR: enqueue_read: queue_reads full\n"); + size_t response_trailer_size = + sizeof(board->rxudpcount) + sizeof(board->confirm_rw_cnt); + if ((size_t)board->queue_buff_size + size + response_trailer_size + > HM2_ETH_PACKET_SIZE) { + THIS_ERR("hm2_eth_enqueue_read: response packet size exceeded\n"); return 0; } - LBP16_INIT_PACKET4(*(lbp16_cmd_addr*)board->read_packet_ptr, CMD_READ_HOSTMOT2_ADDR32_INCR(size/4), addr); board->read_packet_ptr += sizeof(lbp16_cmd_addr); board->queue_reads[board->queue_reads_count].buffer = buffer; @@ -1111,6 +1144,10 @@ static int hm2_eth_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const void *bu if (comm_active == 0) return 1; if (size == 0) return 1; + if (!hm2_eth_valid_transfer_size(size)) { + THIS_ERR("hm2_eth_write: invalid transfer size %d\n", size); + return 0; + } hm2_eth_t *board = this->private; board->write_cnt++; @@ -1118,8 +1155,12 @@ static int hm2_eth_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const void *bu LBP16_INIT_PACKET4(packet.wr_packet, CMD_WRITE_HOSTMOT2_ADDR32_INCR(size/4), addr & 0xFFFF); send = eth_socket_send(board, (void*) &packet, sizeof(lbp16_cmd_addr) + size); - if(send < 0) - LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); + if(send != (int)(sizeof(lbp16_cmd_addr) + size)) { + LL_PRINT("ERROR: sending write packet%s%s\n", + send < 0 ? ": " : "", + send < 0 ? strerror(errno) : ""); + return 0; + } LL_PRINT_IF(debug, "write(%d): PACKET SENT [CMD:%02X%02X | ADDR: %02X%02X | SIZE: %d]\n", board->write_cnt, packet.wr_packet.cmd_hi, packet.wr_packet.cmd_lo, packet.wr_packet.addr_lo, packet.wr_packet.addr_hi, size); @@ -1149,28 +1190,36 @@ static int hm2_eth_send_queued_writes(hm2_lowlevel_io_t *this) { board->has_written_cnt = 1; t0 = rtapi_get_time(); - send = eth_socket_send(board, (void*) &board->write_packet, board->write_packet_ptr - board->write_packet); - if(send < 0) { - LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); + size_t packet_size = board->write_packet_ptr - board->write_packet; + send = eth_socket_send(board, (void*) &board->write_packet, packet_size); + if(send != (int)packet_size) { + LL_PRINT("ERROR: sending queued-write packet%s%s\n", + send < 0 ? ": " : "", + send < 0 ? strerror(errno) : ""); } t1 = rtapi_get_time(); LL_PRINT_IF(debug, "enqueue_write(%d) : PACKET SEND [SIZE: %d | TIME: %llu]\n", board->write_cnt, send, t1 - t0); board->write_packet_ptr = board->write_packet; - return send < 0 ? 0 : 1; + return send == (int)packet_size; } static int hm2_eth_enqueue_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const void *buffer, int size) { hm2_eth_t *board = this->private; if (comm_active == 0) return 1; if (size == 0) return 1; - lbp16_cmd_addr *packet = (lbp16_cmd_addr *) board->write_packet_ptr; - - //Check size we are going to write + if (!hm2_eth_valid_transfer_size(size)) { + THIS_ERR("hm2_eth_enqueue_write: invalid transfer size %d\n", size); + return 0; + } size_t write_packet_size = board->write_packet_ptr - board->write_packet; - if (write_packet_size + sizeof(*packet) + size > sizeof(board->write_packet)) { - LL_PRINT("ERROR: enqueue_write: buffer full\n"); + size_t write_packet_trailer_size = + sizeof(lbp16_cmd_addr) + sizeof(board->write_cnt); + if (write_packet_size + sizeof(lbp16_cmd_addr) + size + + write_packet_trailer_size > sizeof(board->write_packet)) { + THIS_ERR("hm2_eth_enqueue_write: write packet buffer size exceeded\n"); return 0; } + lbp16_cmd_addr *packet = (lbp16_cmd_addr *) board->write_packet_ptr; LBP16_INIT_PACKET4_PTR(packet, CMD_WRITE_HOSTMOT2_ADDR32_INCR(size/4), addr); board->write_packet_ptr += sizeof(*packet); @@ -1200,19 +1249,19 @@ static int hm2_eth_probe(hm2_eth_t *board) { lbp16_cmd_addr read_packet; int ret, send, recv; - char board_name[16] = {}; - char llio_name[16] = {}; + char board_name[16] = {0}; + char llio_name[16] = {0}; LBP16_INIT_PACKET4(read_packet, CMD_READ_BOARD_INFO_ADDR16_INCR(16/2), 0); send = eth_socket_send(board, (void*) &read_packet, sizeof(read_packet)); - if(send < 0) { + if(send != (int)sizeof(read_packet)) { LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); - return -errno; + return send < 0 ? -errno : -EIO; } recv = eth_socket_recv(board, (void*) &board_name, 16, RECV_TIMEOUT_NON_RT_NS); - if(recv < 0) { + if(recv != (int)sizeof(board_name)) { LL_PRINT("ERROR: receiving packet: %s\n", strerror(errno)); - return -errno; + return recv < 0 ? -errno : -EIO; } board = &boards[boards_count]; @@ -1561,12 +1610,15 @@ static int hm2_eth_probe(hm2_eth_t *board) { // (such as 0 or -1) could be passed here and the layer which can // legitimately read idroms would read the values and store them, but // that wasn't trivial to do. - rtapi_u32 read_data; - hm2_eth_read(&board->llio, HM2_ADDR_IDROM_OFFSET, &read_data, 4); + rtapi_u32 read_data = 0; + if (!hm2_eth_read(&board->llio, HM2_ADDR_IDROM_OFFSET, + &read_data, sizeof(read_data))) + return -EIO; unsigned int idrom_address = read_data & 0xffff; hm2_idrom_t idrom; memset(&idrom, 0, sizeof(idrom)); - hm2_eth_read(&board->llio, idrom_address, &idrom, sizeof(idrom)); + if (!hm2_eth_read(&board->llio, idrom_address, &idrom, sizeof(idrom))) + return -EIO; board->llio.num_ioport_connectors = idrom.io_ports; board->llio.pins_per_connector = idrom.port_width; diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.h b/src/hal/drivers/mesa-hostmot2/hm2_eth.h index 5eae27bc85a..068319fbff1 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.h +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.h @@ -28,6 +28,7 @@ #define HM2_LLIO_NAME "hm2_eth" #define MAX_ETH_READS 64 +#define HM2_ETH_PACKET_SIZE 1400 typedef struct { void *buffer; @@ -57,13 +58,13 @@ struct hm2_eth_t { //Only for evl implementation bool is_evl_oob_active; - rtapi_u8 read_packet[1400]; + rtapi_u8 read_packet[HM2_ETH_PACKET_SIZE]; rtapi_u8 *read_packet_ptr; hm2_read_queue_entry_t queue_reads[MAX_ETH_READS]; int queue_reads_count; int queue_buff_size; - rtapi_u8 write_packet[1400]; + rtapi_u8 write_packet[HM2_ETH_PACKET_SIZE]; rtapi_u8 *write_packet_ptr; uint32_t read_cnt, write_cnt; struct { From 36503d81abd48189c34e87d3f1737637500ca0f0 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Sun, 30 Aug 2026 10:27:50 -0700 Subject: [PATCH 2/6] hostmot2: remove stale 7i95 connector assignments --- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 34 ++----------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index b0843b3c78a..d27de407ce4 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -1418,23 +1418,8 @@ static int hm2_eth_probe(hm2_eth_t *board) { // DB25, 17 pins used, IO 34 to IO 50 board->llio.ioport_connector_name[0] = "P1"; - // terminal block, 10 pins used, enc 0-2 + // terminal block board->llio.ioport_connector_name[1] = "TB1"; - - // terminal block, 10 pins used, enc 3-5 - board->llio.ioport_connector_name[2] = "TB2"; - - // terminal block, 8 pins used, Step & Dir 0-3 - board->llio.ioport_connector_name[3] = "TB3"; - - // terminal block, 10 pins used, Step & Dir 4,5, serial Rx/Tx/Txen 0,1 - board->llio.ioport_connector_name[2] = "TB4"; - - // terminal block, 8 inputs, 6 SSR outputs - board->llio.ioport_connector_name[3] = "TB5"; - - // terminal block, 16 inputs - board->llio.ioport_connector_name[2] = "TB6"; board->llio.fpga_part_number = "6slx9tqg144"; board->llio.num_leds = 4; @@ -1448,23 +1433,8 @@ static int hm2_eth_probe(hm2_eth_t *board) { // DB25, 17 pins used, IO 34 to IO 50 board->llio.ioport_connector_name[0] = "P1"; - // terminal block, 10 pins used, enc 0-2 + // terminal block board->llio.ioport_connector_name[1] = "TB1"; - - // terminal block, 10 pins used, enc 3-5 - board->llio.ioport_connector_name[2] = "TB2"; - - // terminal block, 8 pins used, Step & Dir 0-3 - board->llio.ioport_connector_name[3] = "TB3"; - - // terminal block, 10 pins used, Step & Dir 4,5, serial Rx/Tx/Txen 0,1 - board->llio.ioport_connector_name[2] = "TB4"; - - // terminal block, 8 inputs, 6 SSR outputs - board->llio.ioport_connector_name[3] = "TB5"; - - // terminal block, 16 inputs - board->llio.ioport_connector_name[2] = "TB6"; board->llio.fpga_part_number = "T20F256"; board->llio.num_leds = 4; From 9cc505c3c03ae0645a8ec1612f1643e2a1d24c93 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Fri, 9 Oct 2026 18:47:59 -0700 Subject: [PATCH 3/6] tests: cover HostMot2 Ethernet packet bounds and failures --- tests/hostmot2/eth/README | 27 +++++++++ tests/hostmot2/eth/expected | 1 + tests/hostmot2/eth/test.c | 118 ++++++++++++++++++++++++++++++++++++ tests/hostmot2/eth/test.sh | 15 +++++ 4 files changed, 161 insertions(+) create mode 100644 tests/hostmot2/eth/README create mode 100644 tests/hostmot2/eth/expected create mode 100644 tests/hostmot2/eth/test.c create mode 100644 tests/hostmot2/eth/test.sh diff --git a/tests/hostmot2/eth/README b/tests/hostmot2/eth/README new file mode 100644 index 00000000000..6d3744d36ce --- /dev/null +++ b/tests/hostmot2/eth/README @@ -0,0 +1,27 @@ +HostMot2 Ethernet software regression tests +========================================== + +Includes the production hm2_eth.c with mock socket send/receive callbacks. +No hardware, network traffic, firewall changes or realtime privileges are used. +Run after configuring/building a run-in-place tree: + + scripts/rip-environment runtests tests/hostmot2/eth + SANITIZERS=address,undefined bash tests/hostmot2/eth/test.sh + +CC can select another C compiler. Sanitizers are optional. +Checks cover transfer sizes -8..520, short/failed reads and writes, unchanged +destinations after failed reads, queue limits, response-buffer bounds, +trailer reservation, and queue cleanup after failed sends. +They do not simulate FPGA timing, queued receive/recovery, or board discovery. + +Hardware validation remains pending. With outputs safely disabled, record +the exact commit, board/firmware, HAL configuration, servo period and results: + +- Normal cyclic reads/writes on an Ethernet Mesa board (e.g. 7i96S), including + a configuration with large TRAM traffic. +- Controlled network interruption, error reporting, recovery and restart. +- 7i95 connector labels (P1/TB1) against the board and firmware. +- PktUART/hm2_modbus traffic if that hardware is available. + +These mocks establish host-side bounds and failure behavior, not hardware +compatibility or realtime performance. diff --git a/tests/hostmot2/eth/expected b/tests/hostmot2/eth/expected new file mode 100644 index 00000000000..77daa4ad9f4 --- /dev/null +++ b/tests/hostmot2/eth/expected @@ -0,0 +1 @@ +PASS: transfer sizes -8..520, short/failed I/O, queue capacity, response bounds and trailer reservation diff --git a/tests/hostmot2/eth/test.c b/tests/hostmot2/eth/test.c new file mode 100644 index 00000000000..9672d5d5c61 --- /dev/null +++ b/tests/hostmot2/eth/test.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include "hm2_eth.c" + +static int send_result = -2, recv_result = -2; +static int send_calls, recv_calls, last_send_size; + +void rtapi_print_msg(msg_level_t level, const char *fmt, ...) { + (void)level; + (void)fmt; +} +void rtapi_print(const char *fmt, ...) { (void)fmt; } +int rtapi_task_self(void) { return -1; } +long long rtapi_get_time(void) { return 0; } + +static int mock_send(hm2_eth_t *board, const void *buffer, int len) { + (void)board; + (void)buffer; + assert(len > 0 && len <= HM2_ETH_PACKET_SIZE); + last_send_size = len; + send_calls++; + return send_result == -2 ? len : send_result; +} +static int mock_recv(hm2_eth_t *board, void *buffer, int len, int timeout) { + (void)board; + (void)timeout; + recv_calls++; + int count = recv_result == -2 ? len : recv_result; + if (count > 0) memset(buffer, 0x42, count < len ? count : len); + return count; +} +static void reset(hm2_eth_t *board) { + memset(board, 0, sizeof(*board)); + board->llio.private = board; + board->read_packet_ptr = board->read_packet; + board->write_packet_ptr = board->write_packet; + board->eth_socket_send = mock_send; + board->eth_socket_recv = mock_recv; + send_result = recv_result = -2; + send_calls = recv_calls = 0; + comm_active = 1; +} + +int main(void) { + hm2_eth_t board; + unsigned char data[1400] = {0}; + for (int size = -8; size <= 520; size++) { + reset(&board); + int valid = size == 0 || (size > 0 && size <= 508 && size % 4 == 0); + assert(hm2_eth_read(&board.llio, 0x1000, data, size) == valid); + assert(hm2_eth_write(&board.llio, 0x1000, data, size) == valid); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, size) == valid); + assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, size) == valid); + if (!valid || size == 0) assert(send_calls == 0 && recv_calls == 0); + } + for (int count = -1; count < 8; count++) { + reset(&board); + memset(data, 0x99, sizeof(data)); + recv_result = count; + assert(hm2_eth_read(&board.llio, 0x1000, data, 8) == 0); + for (unsigned i = 0; i < sizeof(data); i++) assert(data[i] == 0x99); + } + for (int count = -1; count < 4; count++) { + reset(&board); + send_result = count; + assert(hm2_eth_read(&board.llio, 0x1000, data, 8) == 0); + assert(recv_calls == 0); + } + for (int count = -1; count < 12; count++) { + reset(&board); + send_result = count; + assert(hm2_eth_write(&board.llio, 0x1000, data, 8) == 0); + } + reset(&board); + for (unsigned i = 0; i < 62; i++) + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 1); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 0); + assert(board.queue_reads_count == 62); + assert(hm2_eth_send_queued_reads(&board.llio) == 1); + assert(board.queue_reads_count == 64); + + reset(&board); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 508) == 1); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 508) == 1); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 372) == 1); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 0); + assert(board.queue_buff_size == 1388); + assert(hm2_eth_send_queued_reads(&board.llio) == 1); + assert(board.queue_buff_size == 1398); + + reset(&board); + for (unsigned i = 0; i < 174; i++) + assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, 4) == 1); + assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, 4) == 0); + assert(board.write_packet_ptr - board.write_packet == 1392); + assert(hm2_eth_send_queued_writes(&board.llio) == 1); + assert(last_send_size == 1400); + assert(board.write_packet_ptr == board.write_packet); + + for (int count = -1; count < 20; count++) { + reset(&board); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 1); + send_result = count; + assert(hm2_eth_send_queued_reads(&board.llio) == 0); + assert(board.queue_buff_size == 0 && board.queue_reads_count == 0); + assert(board.read_packet_ptr == board.read_packet); + } + for (int count = -1; count < 16; count++) { + reset(&board); + send_result = count; + assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, 4) == 1); + assert(hm2_eth_send_queued_writes(&board.llio) == 0); + assert(board.write_packet_ptr == board.write_packet); + } + puts("PASS: transfer sizes -8..520, short/failed I/O, queue capacity, response bounds and trailer reservation"); + return 0; +} diff --git a/tests/hostmot2/eth/test.sh b/tests/hostmot2/eth/test.sh new file mode 100644 index 00000000000..c1035e51e5e --- /dev/null +++ b/tests/hostmot2/eth/test.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -eu +repo_root=$(cd "$(dirname "$0")/../../.." && pwd) +task_tmp=$(mktemp -d) +trap 'rm -f "$task_tmp/check"; rmdir "$task_tmp"' EXIT +cc=${CC:-cc} +flags=(-O1 -g -fno-strict-aliasing -fwrapv -DRTAPI -DUSPACE -D_GNU_SOURCE + -I"$repo_root/include" -I"$repo_root/src" -I"$repo_root/src/hal/drivers/mesa-hostmot2" + -ffunction-sections -fdata-sections) +if [ -n "${SANITIZERS:-}" ]; then + flags+=("-fsanitize=$SANITIZERS" -fno-sanitize-recover=all) +fi +"$cc" "${flags[@]}" "$repo_root/tests/hostmot2/eth/test.c" \ + -Wl,--gc-sections -o "$task_tmp/check" +"$task_tmp/check" From 6c7badf17517a9ed427060d791202f34f443dc7c Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Fri, 9 Oct 2026 19:06:52 -0700 Subject: [PATCH 4/6] hostmot2: reject receive after failed Ethernet queued sends Route an empty receive queue through the existing soft-error policy before allocating the receive buffer or touching the socket. A failed queued send clears this queue even though HostMot2 still calls receive. Cover failed and short sends, unchanged destinations, error-limit escalation, user reset, and a successful queued retry with mock I/O. --- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 8 +++- tests/hostmot2/eth/README | 5 +- tests/hostmot2/eth/expected | 1 + tests/hostmot2/eth/test.c | 63 ++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index d27de407ce4..2d76fe879f3 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -1019,7 +1019,6 @@ static void decrement_soft_error(hm2_eth_t *board) { static int hm2_eth_receive_queued_reads(hm2_lowlevel_io_t *this) { hm2_eth_t *board = this->private; int recv, i = 0; - rtapi_u8 tmp_buffer[board->queue_buff_size]; unsigned long long t1, t2; t1 = rtapi_get_time(); @@ -1031,6 +1030,13 @@ static int hm2_eth_receive_queued_reads(hm2_lowlevel_io_t *this) { board->comm_error_counter = 0; } + // A failed send clears the queue, but HostMot2 still calls receive. + if(board->queue_buff_size == 0) { + if(!record_soft_error(board)) return 0; + return -EAGAIN; + } + rtapi_u8 tmp_buffer[board->queue_buff_size]; + long read_timeout = board->hal ? hal_get_si32(board->hal->read_timeout) : 1600000; if(read_timeout <= 0)//less than or equal to 0, use 80% of the thread period. read_timeout = 80; diff --git a/tests/hostmot2/eth/README b/tests/hostmot2/eth/README index 6d3744d36ce..f1afbcb5d6f 100644 --- a/tests/hostmot2/eth/README +++ b/tests/hostmot2/eth/README @@ -11,8 +11,9 @@ Run after configuring/building a run-in-place tree: CC can select another C compiler. Sanitizers are optional. Checks cover transfer sizes -8..520, short/failed reads and writes, unchanged destinations after failed reads, queue limits, response-buffer bounds, -trailer reservation, and queue cleanup after failed sends. -They do not simulate FPGA timing, queued receive/recovery, or board discovery. +trailer reservation, queue cleanup and receive rejection after failed sends, +soft-error limits, user error reset, and a successful queued retry. +They do not simulate FPGA timing, hardware recovery, or board discovery. Hardware validation remains pending. With outputs safely disabled, record the exact commit, board/firmware, HAL configuration, servo period and results: diff --git a/tests/hostmot2/eth/expected b/tests/hostmot2/eth/expected index 77daa4ad9f4..4129a7252c9 100644 --- a/tests/hostmot2/eth/expected +++ b/tests/hostmot2/eth/expected @@ -1 +1,2 @@ PASS: transfer sizes -8..520, short/failed I/O, queue capacity, response bounds and trailer reservation +PASS: failed-send receive rejection, soft-error limit, user reset and successful queued retry diff --git a/tests/hostmot2/eth/test.c b/tests/hostmot2/eth/test.c index 9672d5d5c61..88d52eadf86 100644 --- a/tests/hostmot2/eth/test.c +++ b/tests/hostmot2/eth/test.c @@ -5,6 +5,7 @@ static int send_result = -2, recv_result = -2; static int send_calls, recv_calls, last_send_size; +static int queued_reply; void rtapi_print_msg(msg_level_t level, const char *fmt, ...) { (void)level; @@ -23,11 +24,15 @@ static int mock_send(hm2_eth_t *board, const void *buffer, int len) { return send_result == -2 ? len : send_result; } static int mock_recv(hm2_eth_t *board, void *buffer, int len, int timeout) { - (void)board; (void)timeout; recv_calls++; int count = recv_result == -2 ? len : recv_result; if (count > 0) memset(buffer, 0x42, count < len ? count : len); + if (queued_reply && count == len) { + assert(len >= 10); + memcpy((char *)buffer + len - 8, &board->read_cnt, 4); + memcpy((char *)buffer + len - 4, &board->write_cnt, 4); + } return count; } static void reset(hm2_eth_t *board) { @@ -39,9 +44,58 @@ static void reset(hm2_eth_t *board) { board->eth_socket_recv = mock_recv; send_result = recv_result = -2; send_calls = recv_calls = 0; + queued_reply = 0; comm_active = 1; } +static void test_failed_send_recovery(void) { + hm2_eth_t board; + reset(&board); + typeof(*board.hal) hal = {0}; + rtapi_sint timeout = 100000, limit = 2, increment = 1, decrement = 1, level = 0; + rtapi_uint error = 0, total = 0, exceeded = 0, io_error = 0; + hal_bool_t io_error_pin = (hal_bool_t)&io_error; + hal.read_timeout = (hal_sint_t)&timeout; + hal.packet_error_limit = (hal_sint_t)&limit; + hal.packet_error_increment = (hal_sint_t)&increment; + hal.packet_error_decrement = (hal_sint_t)&decrement; + hal.packet_error_level = (hal_sint_t)&level; + hal.packet_error = (hal_bool_t)&error; + hal.packet_error_total = (hal_uint_t)&total; + hal.packet_error_exceeded = (hal_bool_t)&exceeded; + board.hal = &hal; + board.llio.io_error = &io_error_pin; + rtapi_u32 data = 0x99999999; + + for (unsigned attempt = 0; attempt < 3; attempt++) { + if (attempt == 2) hal_set_bool(io_error_pin, 0); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, &data, 4) == 1); + send_result = -1; + assert(hm2_eth_send_queued_reads(&board.llio) == 0); + assert(hm2_eth_receive_queued_reads(&board.llio) == (attempt == 1 ? 0 : -EAGAIN)); + assert(data == 0x99999999 && recv_calls == 0); + assert(board.llio.needs_soft_reset); + assert(hal_get_bool(hal.packet_error)); + assert(hal_get_ui32(hal.packet_error_total) == attempt + 1); + assert(hal_get_si32(hal.packet_error_level) == (attempt == 1 ? 2 : 1)); + assert(hal_get_bool(io_error_pin) == (attempt == 1)); + assert(hal_get_bool(hal.packet_error_exceeded) == (attempt == 1)); + } + + send_result = -2; + queued_reply = 1; + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, &data, 4) == 1); + assert(hm2_eth_send_queued_reads(&board.llio) == 1); + assert(hm2_eth_receive_queued_reads(&board.llio) == 1); + assert(data == 0x42424242 && recv_calls == 1); + assert(board.queue_buff_size == 0 && board.queue_reads_count == 0); + assert(!hal_get_bool(hal.packet_error) && !hal_get_bool(hal.packet_error_exceeded)); + assert(hal_get_si32(hal.packet_error_level) == 0); + assert(hal_get_ui32(hal.packet_error_total) == 3); + assert(!hal_get_bool(io_error_pin)); + puts("PASS: failed-send receive rejection, soft-error limit, user reset and successful queued retry"); +} + int main(void) { hm2_eth_t board; unsigned char data[1400] = {0}; @@ -105,6 +159,12 @@ int main(void) { assert(hm2_eth_send_queued_reads(&board.llio) == 0); assert(board.queue_buff_size == 0 && board.queue_reads_count == 0); assert(board.read_packet_ptr == board.read_packet); + for (recv_result = -1; recv_result <= 0; recv_result++) { + memset(data, 0x99, sizeof(data)); + assert(hm2_eth_receive_queued_reads(&board.llio) == -EAGAIN); + assert(recv_calls == 0); + for (unsigned i = 0; i < sizeof(data); i++) assert(data[i] == 0x99); + } } for (int count = -1; count < 16; count++) { reset(&board); @@ -114,5 +174,6 @@ int main(void) { assert(board.write_packet_ptr == board.write_packet); } puts("PASS: transfer sizes -8..520, short/failed I/O, queue capacity, response bounds and trailer reservation"); + test_failed_send_recovery(); return 0; } From bdf0346286156d89d5e7f20cb8fc36d186fd47f5 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Sat, 10 Oct 2026 09:16:21 -0700 Subject: [PATCH 5/6] hostmot2: reject incomplete Ethernet batches --- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 39 +++++++--- src/hal/drivers/mesa-hostmot2/hm2_eth.h | 3 + tests/hostmot2/eth/README | 3 +- tests/hostmot2/eth/expected | 2 +- tests/hostmot2/eth/test.c | 98 +++++++++++++++++-------- 5 files changed, 102 insertions(+), 43 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 2d76fe879f3..69a7fc4d0a7 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -928,12 +928,18 @@ static void hm2_eth_reset_queued_reads(hm2_eth_t *board){ board->read_packet_ptr = board->read_packet; board->queue_reads_count = 0; board->queue_buff_size = 0; + board->read_queue_error = false; } static int hm2_eth_send_queued_reads(hm2_lowlevel_io_t *this) { hm2_eth_t *board = this->private; int send; + if (board->read_queue_error) { + hm2_eth_reset_queued_reads(board); + return 0; + } + //Check size we are going to write size_t read_packet_size = board->read_packet_ptr - board->read_packet; if (read_packet_size + 3*sizeof(lbp16_cmd_addr) + sizeof(board->read_cnt) > sizeof(board->read_packet)) { @@ -1018,6 +1024,8 @@ static void decrement_soft_error(hm2_eth_t *board) { static int hm2_eth_receive_queued_reads(hm2_lowlevel_io_t *this) { hm2_eth_t *board = this->private; + bool write_batch_dropped = board->write_batch_dropped; + board->write_batch_dropped = false; // This receive accounts for the failed cycle. int recv, i = 0; unsigned long long t1, t2; t1 = rtapi_get_time(); @@ -1074,7 +1082,8 @@ static int hm2_eth_receive_queued_reads(hm2_lowlevel_io_t *this) { // The has_written_cnt is necessary because the thread cycle uses first // read, then write. If we didn't check, then we'd be using uninitialized // data read back from the board. - if(board->has_written_cnt && board->write_cnt != board->confirm_rw_cnt.write_cnt) { + if(write_batch_dropped || (board->has_written_cnt && board->write_cnt != board->confirm_rw_cnt.write_cnt)) { + if(write_batch_dropped && !board->hal) return 0; result = record_soft_error(board); } else { decrement_soft_error(board); @@ -1102,14 +1111,15 @@ static int hm2_eth_enqueue_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *b hm2_eth_t *board = this->private; if (comm_active == 0) return 1; if (size == 0) return 1; + if (board->read_queue_error) return 0; if (!hm2_eth_valid_transfer_size(size)) { THIS_ERR("hm2_eth_enqueue_read: invalid transfer size %d\n", size); - return 0; + goto fail; } // hm2_eth_send_queued_reads() appends two internal confirmation reads. if (board->queue_reads_count >= MAX_ETH_READS - 2) { THIS_ERR("hm2_eth_enqueue_read: read queue full (%d)\n", board->queue_reads_count); - return 0; + goto fail; } size_t read_packet_size = board->read_packet_ptr - board->read_packet; size_t read_packet_trailer_size = @@ -1117,14 +1127,14 @@ static int hm2_eth_enqueue_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *b if (read_packet_size + sizeof(lbp16_cmd_addr) + read_packet_trailer_size > sizeof(board->read_packet)) { THIS_ERR("hm2_eth_enqueue_read: read packet buffer size exceeded\n"); - return 0; + goto fail; } size_t response_trailer_size = sizeof(board->rxudpcount) + sizeof(board->confirm_rw_cnt); if ((size_t)board->queue_buff_size + size + response_trailer_size > HM2_ETH_PACKET_SIZE) { THIS_ERR("hm2_eth_enqueue_read: response packet size exceeded\n"); - return 0; + goto fail; } LBP16_INIT_PACKET4(*(lbp16_cmd_addr*)board->read_packet_ptr, CMD_READ_HOSTMOT2_ADDR32_INCR(size/4), addr); board->read_packet_ptr += sizeof(lbp16_cmd_addr); @@ -1134,6 +1144,10 @@ static int hm2_eth_enqueue_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *b board->queue_reads_count++; board->queue_buff_size += size; return 1; + +fail: + board->read_queue_error = true; + return 0; } static int hm2_eth_enqueue_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const void *buffer, int size); @@ -1180,10 +1194,12 @@ static int hm2_eth_send_queued_writes(hm2_lowlevel_io_t *this) { //Check size we are going to write size_t write_packet_size = board->write_packet_ptr - board->write_packet; - if (write_packet_size + sizeof(lbp16_cmd_addr) + sizeof(board->write_cnt) > sizeof(board->write_packet)) { - LL_PRINT("ERROR: send_queued_writes: buffer full, dropping all data\n"); + if (board->write_queue_error || write_packet_size + sizeof(lbp16_cmd_addr) + sizeof(board->write_cnt) > sizeof(board->write_packet)) { + LL_PRINT("ERROR: send_queued_writes: invalid batch, dropping all data\n"); //We need to drop the data to recover board->write_packet_ptr = board->write_packet; + board->write_queue_error = false; + board->write_batch_dropped = true; return 0; } @@ -1213,9 +1229,10 @@ static int hm2_eth_enqueue_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const hm2_eth_t *board = this->private; if (comm_active == 0) return 1; if (size == 0) return 1; + if (board->write_queue_error) return 0; if (!hm2_eth_valid_transfer_size(size)) { THIS_ERR("hm2_eth_enqueue_write: invalid transfer size %d\n", size); - return 0; + goto fail; } size_t write_packet_size = board->write_packet_ptr - board->write_packet; size_t write_packet_trailer_size = @@ -1223,7 +1240,7 @@ static int hm2_eth_enqueue_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const if (write_packet_size + sizeof(lbp16_cmd_addr) + size + write_packet_trailer_size > sizeof(board->write_packet)) { THIS_ERR("hm2_eth_enqueue_write: write packet buffer size exceeded\n"); - return 0; + goto fail; } lbp16_cmd_addr *packet = (lbp16_cmd_addr *) board->write_packet_ptr; @@ -1232,6 +1249,10 @@ static int hm2_eth_enqueue_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const memcpy(board->write_packet_ptr, buffer, size); board->write_packet_ptr += size; return 1; + +fail: + board->write_queue_error = true; + return 0; } static int hm2_eth_set_force_enqueue(hm2_lowlevel_io_t *this, int do_enqueue) { diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.h b/src/hal/drivers/mesa-hostmot2/hm2_eth.h index 068319fbff1..b46d2f90288 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.h +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.h @@ -63,9 +63,12 @@ struct hm2_eth_t { hm2_read_queue_entry_t queue_reads[MAX_ETH_READS]; int queue_reads_count; int queue_buff_size; + bool read_queue_error; rtapi_u8 write_packet[HM2_ETH_PACKET_SIZE]; rtapi_u8 *write_packet_ptr; + bool write_queue_error; + bool write_batch_dropped; // Retained across later flushes until receive reports it. uint32_t read_cnt, write_cnt; struct { // These two fields must be kept together. They are read by a single diff --git a/tests/hostmot2/eth/README b/tests/hostmot2/eth/README index f1afbcb5d6f..d3c2324c141 100644 --- a/tests/hostmot2/eth/README +++ b/tests/hostmot2/eth/README @@ -12,7 +12,8 @@ CC can select another C compiler. Sanitizers are optional. Checks cover transfer sizes -8..520, short/failed reads and writes, unchanged destinations after failed reads, queue limits, response-buffer bounds, trailer reservation, queue cleanup and receive rejection after failed sends, -soft-error limits, user error reset, and a successful queued retry. +whole-batch rejection, soft-error limits, user error reset, and a successful +queued retry. A later successful write flush must not hide a rejected batch. They do not simulate FPGA timing, hardware recovery, or board discovery. Hardware validation remains pending. With outputs safely disabled, record diff --git a/tests/hostmot2/eth/expected b/tests/hostmot2/eth/expected index 4129a7252c9..fb541038c1e 100644 --- a/tests/hostmot2/eth/expected +++ b/tests/hostmot2/eth/expected @@ -1,2 +1,2 @@ PASS: transfer sizes -8..520, short/failed I/O, queue capacity, response bounds and trailer reservation -PASS: failed-send receive rejection, soft-error limit, user reset and successful queued retry +PASS: failed/rejected batches, soft-error limit, user reset and successful queued retry diff --git a/tests/hostmot2/eth/test.c b/tests/hostmot2/eth/test.c index 88d52eadf86..0169c23d688 100644 --- a/tests/hostmot2/eth/test.c +++ b/tests/hostmot2/eth/test.c @@ -48,7 +48,10 @@ static void reset(hm2_eth_t *board) { comm_active = 1; } -static void test_failed_send_recovery(void) { +/* 0: failed read send, 1: rejected read, 2: rejected write, + * 3: rejected forced write followed by a successful flush, + * 4/5: rejected write plus a failed read receive/send. */ +static void test_failed_batch_recovery(unsigned mode) { hm2_eth_t board; reset(&board); typeof(*board.hal) hal = {0}; @@ -66,14 +69,36 @@ static void test_failed_send_recovery(void) { board.hal = &hal; board.llio.io_error = &io_error_pin; rtapi_u32 data = 0x99999999; + int read_sent = mode >= 2 && mode != 5; + int read_received = read_sent && mode != 4; for (unsigned attempt = 0; attempt < 3; attempt++) { if (attempt == 2) hal_set_bool(io_error_pin, 0); + if (mode >= 2) { + int sends = send_calls; + assert(hm2_eth_set_force_enqueue(&board.llio, 1) == 1); + assert(hm2_eth_write(&board.llio, 0x1000, &data, 4) == 1); + assert(hm2_eth_write(&board.llio, 0x1000, &data, 3) == 0); + assert(hm2_eth_write(&board.llio, 0x1000, &data, 4) == 0); + assert(hm2_eth_set_force_enqueue(&board.llio, 0) == 0); + assert(send_calls == sends); + if (mode == 3) { + assert(hm2_eth_enqueue_write(&board.llio, 0x1000, &data, 4) == 1); + assert(hm2_eth_send_queued_writes(&board.llio) == 1); + } + } assert(hm2_eth_enqueue_read(&board.llio, 0x1000, &data, 4) == 1); - send_result = -1; - assert(hm2_eth_send_queued_reads(&board.llio) == 0); - assert(hm2_eth_receive_queued_reads(&board.llio) == (attempt == 1 ? 0 : -EAGAIN)); - assert(data == 0x99999999 && recv_calls == 0); + if (mode == 0 || mode == 5) send_result = -1; + if (mode == 4) recv_result = -1; + if (mode == 1) { + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, &data, 3) == 0); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, &data, 4) == 0); + } + queued_reply = 1; + assert(hm2_eth_send_queued_reads(&board.llio) == read_sent); + assert(hm2_eth_receive_queued_reads(&board.llio) == (attempt == 1 ? 0 : read_received ? 1 : -EAGAIN)); + assert(data == (read_received ? 0x42424242 : 0x99999999)); + assert(recv_calls == (read_sent ? (int)attempt + 1 : 0)); assert(board.llio.needs_soft_reset); assert(hal_get_bool(hal.packet_error)); assert(hal_get_ui32(hal.packet_error_total) == attempt + 1); @@ -82,18 +107,17 @@ static void test_failed_send_recovery(void) { assert(hal_get_bool(hal.packet_error_exceeded) == (attempt == 1)); } - send_result = -2; + send_result = recv_result = -2; queued_reply = 1; assert(hm2_eth_enqueue_read(&board.llio, 0x1000, &data, 4) == 1); assert(hm2_eth_send_queued_reads(&board.llio) == 1); assert(hm2_eth_receive_queued_reads(&board.llio) == 1); - assert(data == 0x42424242 && recv_calls == 1); + assert(data == 0x42424242 && recv_calls == (read_sent ? 4 : 1)); assert(board.queue_buff_size == 0 && board.queue_reads_count == 0); assert(!hal_get_bool(hal.packet_error) && !hal_get_bool(hal.packet_error_exceeded)); assert(hal_get_si32(hal.packet_error_level) == 0); assert(hal_get_ui32(hal.packet_error_total) == 3); assert(!hal_get_bool(io_error_pin)); - puts("PASS: failed-send receive rejection, soft-error limit, user reset and successful queued retry"); } int main(void) { @@ -126,31 +150,40 @@ int main(void) { send_result = count; assert(hm2_eth_write(&board.llio, 0x1000, data, 8) == 0); } - reset(&board); - for (unsigned i = 0; i < 62; i++) - assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 1); - assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 0); - assert(board.queue_reads_count == 62); - assert(hm2_eth_send_queued_reads(&board.llio) == 1); - assert(board.queue_reads_count == 64); + for (int overflow = 0; overflow <= 1; overflow++) { + reset(&board); + for (unsigned i = 0; i < 62; i++) + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 1); + if (overflow) assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 0); + assert(hm2_eth_send_queued_reads(&board.llio) == !overflow); + assert(board.queue_reads_count == (overflow ? 0 : 64)); + assert(send_calls == !overflow); - reset(&board); - assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 508) == 1); - assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 508) == 1); - assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 372) == 1); - assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 0); - assert(board.queue_buff_size == 1388); - assert(hm2_eth_send_queued_reads(&board.llio) == 1); - assert(board.queue_buff_size == 1398); + reset(&board); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 508) == 1); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 508) == 1); + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 372) == 1); + if (overflow) assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 0); + assert(hm2_eth_send_queued_reads(&board.llio) == !overflow); + assert(board.queue_buff_size == (overflow ? 0 : 1398)); + assert(send_calls == !overflow); - reset(&board); - for (unsigned i = 0; i < 174; i++) - assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, 4) == 1); - assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, 4) == 0); - assert(board.write_packet_ptr - board.write_packet == 1392); - assert(hm2_eth_send_queued_writes(&board.llio) == 1); - assert(last_send_size == 1400); - assert(board.write_packet_ptr == board.write_packet); + reset(&board); + for (unsigned i = 0; i < 174; i++) + assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, 4) == 1); + if (overflow) assert(hm2_eth_enqueue_write(&board.llio, 0x1000, data, 4) == 0); + assert(hm2_eth_send_queued_writes(&board.llio) == !overflow); + assert(send_calls == !overflow); + if (!overflow) assert(last_send_size == 1400); + assert(board.write_packet_ptr == board.write_packet); + /* Initialization has no HAL error pins: report the dropped write once. */ + queued_reply = 1; + for (unsigned retry = 0; retry < 2; retry++) { + assert(hm2_eth_enqueue_read(&board.llio, 0x1000, data, 4) == 1); + assert(hm2_eth_send_queued_reads(&board.llio) == 1); + assert(hm2_eth_receive_queued_reads(&board.llio) == (!overflow || retry)); + } + } for (int count = -1; count < 20; count++) { reset(&board); @@ -174,6 +207,7 @@ int main(void) { assert(board.write_packet_ptr == board.write_packet); } puts("PASS: transfer sizes -8..520, short/failed I/O, queue capacity, response bounds and trailer reservation"); - test_failed_send_recovery(); + for (unsigned mode = 0; mode < 6; mode++) test_failed_batch_recovery(mode); + puts("PASS: failed/rejected batches, soft-error limit, user reset and successful queued retry"); return 0; } From cd4306e409d8b6c80ce4f08ccf917d0c9ac84702 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Mon, 12 Oct 2026 10:33:14 -0700 Subject: [PATCH 6/6] hostmot2: use configured flags in mock tests --- tests/hostmot2/eth/README | 1 + tests/hostmot2/eth/test.sh | 16 +++++++--------- tests/hostmot2/harness.mk | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 tests/hostmot2/harness.mk diff --git a/tests/hostmot2/eth/README b/tests/hostmot2/eth/README index d3c2324c141..ec35610514e 100644 --- a/tests/hostmot2/eth/README +++ b/tests/hostmot2/eth/README @@ -8,6 +8,7 @@ Run after configuring/building a run-in-place tree: scripts/rip-environment runtests tests/hostmot2/eth SANITIZERS=address,undefined bash tests/hostmot2/eth/test.sh +Uses the configured realtime compiler and flags from src/Makefile. CC can select another C compiler. Sanitizers are optional. Checks cover transfer sizes -8..520, short/failed reads and writes, unchanged destinations after failed reads, queue limits, response-buffer bounds, diff --git a/tests/hostmot2/eth/test.sh b/tests/hostmot2/eth/test.sh index c1035e51e5e..7269de2233f 100644 --- a/tests/hostmot2/eth/test.sh +++ b/tests/hostmot2/eth/test.sh @@ -3,13 +3,11 @@ set -eu repo_root=$(cd "$(dirname "$0")/../../.." && pwd) task_tmp=$(mktemp -d) trap 'rm -f "$task_tmp/check"; rmdir "$task_tmp"' EXIT -cc=${CC:-cc} -flags=(-O1 -g -fno-strict-aliasing -fwrapv -DRTAPI -DUSPACE -D_GNU_SOURCE - -I"$repo_root/include" -I"$repo_root/src" -I"$repo_root/src/hal/drivers/mesa-hostmot2" - -ffunction-sections -fdata-sections) -if [ -n "${SANITIZERS:-}" ]; then - flags+=("-fsanitize=$SANITIZERS" -fno-sanitize-recover=all) -fi -"$cc" "${flags[@]}" "$repo_root/tests/hostmot2/eth/test.c" \ - -Wl,--gc-sections -o "$task_tmp/check" +make_args=(-s --no-print-directory -C "$repo_root/src" -f Makefile + -f "$repo_root/tests/hostmot2/harness.mk" -o Makefile -o Makefile.inc + TRIVIAL_BUILD=yes) +if [ -n "${CC:-}" ]; then make_args+=("CC=$CC"); fi +make "${make_args[@]}" hm2-test \ + "TEST_SOURCES=../tests/hostmot2/eth/test.c" \ + "TEST_OUTPUT=$task_tmp/check" >&2 "$task_tmp/check" diff --git a/tests/hostmot2/harness.mk b/tests/hostmot2/harness.mk new file mode 100644 index 00000000000..d9c8a7233a1 --- /dev/null +++ b/tests/hostmot2/harness.mk @@ -0,0 +1,16 @@ +# Used with src/Makefile so mocks inherit the configured realtime build flags. +ifneq ($(BUILD_SYS),uspace) +$(error HostMot2 mock tests require a configured uspace build) +endif + +ifneq ($(SANITIZERS),) +HM2_TEST_SANITIZERS = -fsanitize=$(SANITIZERS) -fno-sanitize-recover=all +endif + +.PHONY: hm2-test +hm2-test: + $(CC) $(OPT) $(DEBUG) $(EXTRA_DEBUG) -DRTAPI \ + $(EXTRA_CFLAGS) $(XENOMAI_EVL_CFLAGS) $(HM2_TEST_SANITIZERS) \ + -I$(BASEPWD)/hal/drivers/mesa-hostmot2 -ffunction-sections -fdata-sections \ + $(TEST_CFLAGS) $(TEST_SOURCES) -Wl,--gc-sections $(TEST_LIBS) \ + $(XENOMAI_EVL_LDFLAGS) $(LDFLAGS) -o "$(TEST_OUTPUT)"