diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 76766382041..69a7fc4d0a7 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 @@ -914,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)) { @@ -962,9 +982,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; @@ -1000,8 +1024,9 @@ 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; - rtapi_u8 tmp_buffer[board->queue_buff_size]; unsigned long long t1, t2; t1 = rtapi_get_time(); @@ -1013,6 +1038,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; @@ -1050,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); @@ -1067,26 +1100,42 @@ 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 (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); + 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); + goto fail; + } 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"); - return 0; + 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"); + goto fail; } - if (board->queue_reads_count + 1 > MAX_ETH_READS) { - LL_PRINT("ERROR: enqueue_read: queue_reads full\n"); - return 0; + 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"); + 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); board->queue_reads[board->queue_reads_count].buffer = buffer; @@ -1095,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); @@ -1111,6 +1164,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 +1175,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); @@ -1133,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; } @@ -1149,34 +1212,47 @@ 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 (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); + goto fail; + } 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"); - return 0; + 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"); + goto fail; } + 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); 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) { @@ -1200,19 +1276,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]; @@ -1369,23 +1445,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; @@ -1399,23 +1460,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; @@ -1561,12 +1607,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..b46d2f90288 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,14 +58,17 @@ 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; + bool read_queue_error; - rtapi_u8 write_packet[1400]; + 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 new file mode 100644 index 00000000000..ec35610514e --- /dev/null +++ b/tests/hostmot2/eth/README @@ -0,0 +1,30 @@ +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 + +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, +trailer reservation, queue cleanup and receive rejection after failed sends, +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 +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..fb541038c1e --- /dev/null +++ b/tests/hostmot2/eth/expected @@ -0,0 +1,2 @@ +PASS: transfer sizes -8..520, short/failed I/O, queue capacity, response bounds and trailer reservation +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 new file mode 100644 index 00000000000..0169c23d688 --- /dev/null +++ b/tests/hostmot2/eth/test.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include "hm2_eth.c" + +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; + (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)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) { + 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; + queued_reply = 0; + comm_active = 1; +} + +/* 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}; + 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; + 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); + 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); + 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 = 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 == (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)); +} + +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); + } + 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); + 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); + 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); + 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 (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); + 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"); + 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; +} diff --git a/tests/hostmot2/eth/test.sh b/tests/hostmot2/eth/test.sh new file mode 100644 index 00000000000..7269de2233f --- /dev/null +++ b/tests/hostmot2/eth/test.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eu +repo_root=$(cd "$(dirname "$0")/../../.." && pwd) +task_tmp=$(mktemp -d) +trap 'rm -f "$task_tmp/check"; rmdir "$task_tmp"' EXIT +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)"