From b83a3af0188e7dc5b4604528df54c362559850da Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Fri, 9 Oct 2026 18:25:55 -0700 Subject: [PATCH 1/7] hostmot2: bound absolute encoder formats and preserve allocations --- src/hal/drivers/mesa-hostmot2/abs_encoder.c | 41 ++++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/abs_encoder.c b/src/hal/drivers/mesa-hostmot2/abs_encoder.c index 4ee291240b1..7a8e2cfe079 100644 --- a/src/hal/drivers/mesa-hostmot2/abs_encoder.c +++ b/src/hal/drivers/mesa-hostmot2/abs_encoder.c @@ -274,13 +274,20 @@ int hm2_absenc_parse_format(hm2_sserial_remote_t *chan, hm2_absenc_format_t *de "not get the pins you expected\n"); } else if (strchr("bBuUsSeEfFpPgGhHlLmM", *format)){ + hm2_sserial_data_t *confs; hm2_sserial_data_t *conf; - chan->num_confs++; - chan->confs = (hm2_sserial_data_t *)rtapi_krealloc(chan->confs, - chan->num_confs * sizeof(hm2_sserial_data_t), + + if (q > RTAPI_UINT8_MAX) { + HM2_ERR_NO_LL("Field length %d exceeds the supported maximum\n", q); + return -EINVAL; + } + confs = rtapi_krealloc(chan->confs, + (chan->num_confs + 1) * sizeof(*confs), RTAPI_GFP_KERNEL); + if (confs == NULL) return -ENOMEM; + chan->confs = confs; - conf = &(chan->confs[chan->num_confs - 1]); + conf = &(chan->confs[chan->num_confs++]); conf->DataDir = LBP_IN; conf->DataLength = q; rtapi_strxcpy(conf->NameString, name); @@ -369,6 +376,10 @@ int hm2_absenc_parse_format(hm2_sserial_remote_t *chan, hm2_absenc_format_t *de else { // Not a % format, append name + if (nameptr == &name[sizeof(name) - 1]) { + HM2_ERR_NO_LL("Absolute encoder field name is too long\n"); + return -EINVAL; + } *nameptr++ = *format++; *nameptr = 0; } @@ -412,12 +423,6 @@ int hm2_absenc_parse_md(hostmot2_t *hm2, int md_index) { if (hm2->absenc.num_chans == 0) { // first time though hm2->absenc.clock_frequency = md->clock_freq; - hm2->absenc.ssi_busy_flags = rtapi_kmalloc(sizeof(rtapi_u32), RTAPI_GFP_KERNEL); - *hm2->absenc.ssi_busy_flags = 0; - hm2->absenc.biss_busy_flags = rtapi_kmalloc(sizeof(rtapi_u32), RTAPI_GFP_KERNEL); - *hm2->absenc.biss_busy_flags = 0; - hm2->absenc.fabs_busy_flags = rtapi_kmalloc(sizeof(rtapi_u32), RTAPI_GFP_KERNEL); - *hm2->absenc.fabs_busy_flags = 0; } for (index = 0 ; index < md->instances ; index ++){ @@ -434,12 +439,18 @@ int hm2_absenc_parse_md(hostmot2_t *hm2, int md_index) { goto fail1; } if (index == def->index && md->gtag == def->gtag){ + hm2_sserial_remote_t *chans; + has_format = true; - hm2->absenc.num_chans += 1; - hm2->absenc.chans = rtapi_krealloc(hm2->absenc.chans, - hm2->absenc.num_chans * sizeof(hm2_sserial_remote_t), + chans = rtapi_krealloc(hm2->absenc.chans, + (hm2->absenc.num_chans + 1) * sizeof(*chans), RTAPI_GFP_KERNEL); - chan = &hm2->absenc.chans[hm2->absenc.num_chans - 1]; + if (chans == NULL) { + HM2_ERR("out of memory allocating absolute encoder channel\n"); + goto fail1; + } + hm2->absenc.chans = chans; + chan = &hm2->absenc.chans[hm2->absenc.num_chans++]; memset(chan, 0, sizeof(hm2_sserial_remote_t)); chan->index = index; chan->myinst = md->gtag; @@ -529,6 +540,8 @@ void hm2_absenc_process_tram_read(hostmot2_t *hm2, long period) { err_tag[i] = 1; } } + // These pointers are supplied by hm2_absenc_register_tram() whenever + // the corresponding global-start address enables this dereference. if (((chan->myinst == HM2_GTAG_SSI) && (*hm2->absenc.ssi_busy_flags & (1 << chan->index))) || ((chan->myinst == HM2_GTAG_BISS) From c04d3c26758b8560b923471522c695cdf0da9ea5 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Fri, 9 Oct 2026 18:25:55 -0700 Subject: [PATCH 2/7] hostmot2: validate Smart Serial flash geometry --- src/hal/drivers/mesa-hostmot2/setsserial.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/setsserial.c b/src/hal/drivers/mesa-hostmot2/setsserial.c index c7ee787775c..fe18b86a6d6 100644 --- a/src/hal/drivers/mesa-hostmot2/setsserial.c +++ b/src/hal/drivers/mesa-hostmot2/setsserial.c @@ -176,7 +176,7 @@ int sslbp_read_cookie(void){ return res; } -rtapi_u8 sslbp_read_byte(rtapi_u32 addr){ +int sslbp_read_byte(rtapi_u32 addr){ rtapi_u32 buff = READ_REM_BYTE_CMD + addr; rtapi_u32 res; HM2WRITE(remote->reg_cs_addr, buff); @@ -303,6 +303,7 @@ int sslbp_flash(char *fname){ struct rtapi_device dev; int r; unsigned write_sz, erase_sz; + int write_exp, erase_exp; if (strstr("8i20", remote->name)){ if (hm2->sserial.version < 37){ @@ -348,8 +349,17 @@ int sslbp_flash(char *fname){ if (setup_start() < 0) goto fail0; flash_start(); - write_sz = 1u << sslbp_read_byte(LBPFLASHWRITESIZELOC); - erase_sz = 1u << sslbp_read_byte(LBPFLASHERASESIZELOC); + write_exp = sslbp_read_byte(LBPFLASHWRITESIZELOC); + erase_exp = sslbp_read_byte(LBPFLASHERASESIZELOC); + if (write_exp < 3 || write_exp >= (int)(sizeof(write_sz) * 8) + || erase_exp < 0 || erase_exp < write_exp + || erase_exp >= (int)(sizeof(erase_sz) * 8)) { + HM2_ERR("Invalid flash geometry exponents write=%d erase=%d\n", + write_exp, erase_exp); + goto fail0; + } + write_sz = 1u << write_exp; + erase_sz = 1u << erase_exp; HM2_PRINT("Write Size = %x, Erase Size = %x\n", write_sz, erase_sz); flash_stop(); @@ -412,6 +422,7 @@ int sslbp_flash(char *fname){ fail0: flash_stop(); + rtapi_release_firmware(fw); return -1; } @@ -496,5 +507,3 @@ void rtapi_app_exit(void) } - - From f90b600c40ed4f68feaeea2122baf294e6dbbbd8 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Fri, 9 Oct 2026 18:26:07 -0700 Subject: [PATCH 3/7] hostmot2: harden Smart Serial parsing and cleanup --- src/hal/drivers/mesa-hostmot2/sserial.c | 102 +++++++++++++++--------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/sserial.c b/src/hal/drivers/mesa-hostmot2/sserial.c index bf70a0cc307..5a5431cf387 100644 --- a/src/hal/drivers/mesa-hostmot2/sserial.c +++ b/src/hal/drivers/mesa-hostmot2/sserial.c @@ -308,6 +308,9 @@ int hm2_sserial_get_bytes(hostmot2_t *hm2, *(ptr++) = (unsigned char)data; } + if (string < 0) { + *ptr = '\0'; + } return addr; } @@ -431,8 +434,6 @@ int hm2_sserial_create_params(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ for (i = 0 ; i < chan->num_globals ; i++){ global = chan->globals[i]; - r = 0; - hal_dir = (global.DataDir == LBP_IN) ? HAL_RO : HAL_RW; chan->params[i].type = global.DataType; @@ -525,21 +526,23 @@ int hm2_sserial_get_globals_list(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ } // process is a subset of global. The only way to tell is to compare for (i = 0; i < chan->num_confs ; i ++) { - if (chan->confs[i].ParmAddr == data.ParmAddr){i = 1000;} + if (chan->confs[i].ParmAddr == data.ParmAddr) break; } - if (data.RecordType == LBP_DATA && i < 1000) { + if (data.RecordType == LBP_DATA && i == chan->num_confs) { + hm2_sserial_data_t *globals; + addr = hm2_sserial_get_bytes(hm2, chan, &(data.UnitString), addr, -1); if (addr < 0){ return -EINVAL;} addr = hm2_sserial_get_bytes(hm2, chan, &(data.NameString), addr, -1); if (addr < 0){ return -EINVAL;} HM2_DBG("Global: %s RecordType: %02X Datatype: %02X Dir: %02X Addr: %04X Length: %i\n", data.NameString, data.RecordType, data.DataType, data.DataDir, data.ParmAddr, data.DataLength); - chan->num_globals++; - chan->globals = (hm2_sserial_data_t *) - rtapi_krealloc(chan->globals, - chan->num_globals * sizeof(hm2_sserial_data_t), + globals = rtapi_krealloc(chan->globals, + (chan->num_globals + 1) * sizeof(*globals), RTAPI_GFP_KERNEL); - chan->globals[chan->num_globals - 1] = data; + if (globals == NULL) return -ENOMEM; + chan->globals = globals; + chan->globals[chan->num_globals++] = data; } else if (data.RecordType== LBP_MODE){ char * type; @@ -642,6 +645,8 @@ int hm2_sserial_parse_md(hostmot2_t *hm2, int md_index){ r = -ENOMEM; goto fail0; } + memset(hm2->sserial.instance, 0, + hm2->sserial.num_instances * sizeof(*hm2->sserial.instance)); // We can't create the pins until we know what is on each channel, and // can't communicate until the pin directions are set up. @@ -678,6 +683,10 @@ int hm2_sserial_parse_md(hostmot2_t *hm2, int md_index){ // Now iterate through the sserial instances, seeing what is on the enabled pins. for (i = 0 ; i < md->instances ; i++) { + // Keep probing physical ports until the configured number of populated + // ports has been found. Empty lower-numbered ports do not consume an + // entry in the compact instance array. + if (count >= hm2->sserial.num_instances) break; hm2_sserial_instance_t *inst = &hm2->sserial.instance[count]; inst->index = i; inst->num_channels = chan_counts[i]; @@ -815,9 +824,8 @@ int hm2_sserial_parse_md(hostmot2_t *hm2, int md_index){ HM2_ERR("Failed to restart device %i on instance\n", inst->device_id); goto fail0;} - if ((r = hm2_sserial_check_local_errors(hm2, inst)) < 0) { - //goto fail0; // Ignore it for the moment. - } + // Ignore startup errors here; the realtime path tracks them. + (void)hm2_sserial_check_local_errors(hm2, inst); //only increment the instance index if this one is populated //otherwise the "slot" is re-used to keep active ports //contiguous in the array @@ -1043,7 +1051,6 @@ int hm2_sserial_read_configs(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ ptoc=(buff & 0xffff); if (ptoc == 0) {return chan->num_confs;} // Old 8i20 or 7i64 - c = m = 0; chan->num_confs = 0; do { addr = 0; @@ -1054,11 +1061,12 @@ int hm2_sserial_read_configs(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ } if (rectype == LBP_DATA) { + hm2_sserial_data_t *confs = rtapi_krealloc(chan->confs, + (chan->num_confs + 1) * sizeof(*confs), + RTAPI_GFP_KERNEL); + if (confs == NULL) return -ENOMEM; + chan->confs = confs; c = chan->num_confs++; - chan->confs = (hm2_sserial_data_t *) - rtapi_krealloc(chan->confs, - chan->num_confs * sizeof(hm2_sserial_data_t), - RTAPI_GFP_KERNEL); addr = hm2_sserial_get_bytes(hm2, chan, &chan->confs[c], addr, 14); if (addr < 0){ return -EINVAL;} addr = hm2_sserial_get_bytes(hm2, chan, @@ -1081,12 +1089,12 @@ int hm2_sserial_read_configs(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ HM2_DBG("Process: %s RecordType: %02X Datatype: %02X Dir: %02X Addr: %04X Length: %i\n", chan->confs[c].NameString, chan->confs[c].RecordType,chan->confs[c].DataType, chan->confs[c].DataDir, chan->confs[c].ParmAddr, chan->confs[c].DataLength); } else if (rectype == LBP_MODE ) { - chan->num_modes++; - m = chan->num_modes - 1; - chan->modes = (hm2_sserial_mode_t *) - rtapi_krealloc(chan->modes, - chan->num_modes * sizeof(hm2_sserial_mode_t), - RTAPI_GFP_KERNEL); + hm2_sserial_mode_t *modes = rtapi_krealloc(chan->modes, + (chan->num_modes + 1) * sizeof(*modes), + RTAPI_GFP_KERNEL); + if (modes == NULL) return -ENOMEM; + chan->modes = modes; + m = chan->num_modes++; addr = hm2_sserial_get_bytes(hm2, chan, &chan->modes[m], addr, 4); if (addr < 0){ return -EINVAL;} addr = hm2_sserial_get_bytes(hm2, chan, @@ -1457,7 +1465,7 @@ int hm2_sserial_register_tram(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ inst->timer = 20000000; *inst->command_reg_write = 0x800; // stop all break; - case 1: + case 1: ret = hm2_sserial_wait(hm2, inst, period); if (ret > 0) break; // FIXME: Assigns 100 to state3 and then unconditionally assigns 2 @@ -1665,7 +1673,8 @@ void hm2_sserial_write_pins(hostmot2_t *hm2, hm2_sserial_instance_t *inst){ "if this is happening frequently.\n", inst->index, hm2->llio->name, inst->index); } - fault_count = hal_set_ui32(inst->fault_count, fault_count + hal_get_ui32(inst->fault_inc)); + hal_set_ui32(inst->fault_count, + fault_count + hal_get_ui32(inst->fault_inc)); *inst->command_reg_write = 0x80000000; // set bit31 for ignored cmd return; // give the register chance to clear } @@ -1674,11 +1683,12 @@ void hm2_sserial_write_pins(hostmot2_t *hm2, hm2_sserial_instance_t *inst){ } if (fault_count > hal_get_ui32(inst->fault_dec)) { - fault_count = hal_set_ui32(inst->fault_count, fault_count - hal_get_ui32(inst->fault_dec)); + hal_set_ui32(inst->fault_count, + fault_count - hal_get_ui32(inst->fault_dec)); } else { - fault_count = hal_set_ui32(inst->fault_count, 0); + hal_set_ui32(inst->fault_count, 0); } // All seems well, handle the pins. @@ -1696,6 +1706,7 @@ void hm2_sserial_write_pins(hostmot2_t *hm2, hm2_sserial_instance_t *inst){ hm2_sserial_data_t *conf = &chan->confs[p]; hm2_sserial_pins_t *pin = &chan->pins[p]; if (conf->DataDir & 0xC0){ + buff = 0; switch (conf->DataType){ case LBP_PAD: // do nothing @@ -2185,24 +2196,37 @@ void hm2_sserial_force_write(hostmot2_t *hm2){ void hm2_sserial_cleanup(hostmot2_t *hm2){ int i,r; rtapi_u32 buff; - for (i = 1 ; i < hm2->sserial.num_instances; i++){ + if (hm2->sserial.instance == NULL) return; + + for (i = 0 ; i < hm2->sserial.num_instances; i++){ //Shut down the sserial devices rather than leave that to the watchdog. - buff = 0x800; - hm2->llio->write(hm2->llio, - hm2->sserial.instance[i].command_reg_addr, - &buff, - sizeof(rtapi_u32)); + if (hm2->sserial.instance[i].command_reg_addr != 0) { + buff = 0x800; + hm2->llio->write(hm2->llio, + hm2->sserial.instance[i].command_reg_addr, + &buff, + sizeof(rtapi_u32)); + } if (hm2->sserial.instance[i].remotes != NULL){ for (r = 0 ; r < hm2->sserial.instance[i].num_remotes; r++){ - if (hm2->sserial.instance[i].remotes[r].num_confs > 0){ - rtapi_kfree(hm2->sserial.instance[i].remotes[r].confs); - }; - if (hm2->sserial.instance[i].remotes[r].num_modes > 0){ - rtapi_kfree(hm2->sserial.instance[i].remotes[r].modes); - } + hm2_sserial_remote_t *remote = + &hm2->sserial.instance[i].remotes[r]; + + rtapi_kfree(remote->confs); + remote->confs = NULL; + remote->num_confs = 0; + rtapi_kfree(remote->modes); + remote->modes = NULL; + remote->num_modes = 0; + rtapi_kfree(remote->globals); + remote->globals = NULL; + remote->num_globals = 0; } rtapi_kfree(hm2->sserial.instance[i].remotes); + hm2->sserial.instance[i].remotes = NULL; } + hm2->sserial.instance[i].num_remotes = 0; } + hm2->sserial.num_instances = 0; } From 33ac6f67607c429968af5b71b9b675db993e3287 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Fri, 9 Oct 2026 18:26:07 -0700 Subject: [PATCH 4/7] hostmot2: compare Smart Serial parameters as floating-point values --- src/hal/drivers/mesa-hostmot2/sserial.c | 43 ++++++++++--------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/sserial.c b/src/hal/drivers/mesa-hostmot2/sserial.c index 5a5431cf387..aab4b15c1df 100644 --- a/src/hal/drivers/mesa-hostmot2/sserial.c +++ b/src/hal/drivers/mesa-hostmot2/sserial.c @@ -28,11 +28,6 @@ #include "hostmot2.h" -// Local definition of labs() because RTAI compile barfs on using labs(). It -// could be optimized using compiler built-ins, but the single use in here is -// not critical. -static inline rtapi_s64 xlabs(rtapi_s64 x) { return x < 0 ? -x : x; } - int getbits(hm2_sserial_remote_t *chan, rtapi_u64 *val, int start, int len){ //load the bits from the registers in to bit 0+ of *val int i; @@ -1395,12 +1390,9 @@ int hm2_sserial_register_tram(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ } int hm2_sserial_update_params(hostmot2_t *hm2, hm2_sserial_instance_t *inst, long period){ - // init this here to silence a compiler warning - hm2_sserial_remote_t *r = &(inst->remotes[0]); + hm2_sserial_remote_t *r; hm2_sserial_params_t *p; hm2_sserial_data_t *g; - int shift; // used for floating point comparisons - switch (hal_get_ui32(inst->state2)){ case 0: // init loop counters inst->r_index = 0; @@ -1437,26 +1429,25 @@ int hm2_sserial_register_tram(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ return hal_set_ui32(inst->state2, 2); // increment indices case LBP_FLOAT: case LBP_NONVOL_FLOAT: - // comparing floats that might have different sizes is not trivial - // this does a bitwise comparison of as many mantissa bits as might - // be expected to have been sent by the sserial remote + // Parameter read/write supports IEEE binary32 and + // binary64 only; 8/16-bit float formats are not + // decoded by hm2_sserial_get_param_value(). switch (g->DataLength){ - // ( double significand - variable type significand) - default: - HM2_ERR("Non IEEE float type parameter of length %i\n", g->DataLength); - /* Fallthrough */ - case 8: - shift = (52 - 4); break; // 1.3.4 minifloat, if we ever add them - case 16: - shift = (52 - 10); break; case 32: - shift = (52 - 23); break; + if ((float)hal_get_real(p->param.r) + != (float)p->float_written) break; + return hal_set_ui32(inst->state2, 2); case 64: - shift = 0; - } - // FIXME: This overlayed s64 read is very wrong! - if (xlabs((hal_get_sint(p->param.s) - p->s64_written) >> shift) > 2) break; - return hal_set_ui32(inst->state2, 2); // increment indices + if (hal_get_real(p->param.r) + != p->float_written) break; + return hal_set_ui32(inst->state2, 2); + default: + HM2_ERR("Non IEEE float type parameter of length %i\n", + g->DataLength); + p->type = LBP_PAD; // warn once, then ignore + return hal_set_ui32(inst->state2, 2); + } + break; default: return hal_set_ui32(inst->state2, 2); // increment indices } From 2409e6a791157a3d4888708556d1162aef2ec0a2 Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Fri, 9 Oct 2026 18:40:32 -0700 Subject: [PATCH 5/7] hostmot2: avoid repeated writes of unchanged NaN parameters --- src/hal/drivers/mesa-hostmot2/sserial.c | 17 ++- tests/hostmot2/sserial/README | 22 +++ tests/hostmot2/sserial/byte-test.c | 39 +++++ tests/hostmot2/sserial/expected | 2 + tests/hostmot2/sserial/test.c | 180 ++++++++++++++++++++++++ tests/hostmot2/sserial/test.sh | 20 +++ 6 files changed, 274 insertions(+), 6 deletions(-) create mode 100644 tests/hostmot2/sserial/README create mode 100644 tests/hostmot2/sserial/byte-test.c create mode 100644 tests/hostmot2/sserial/expected create mode 100644 tests/hostmot2/sserial/test.c create mode 100644 tests/hostmot2/sserial/test.sh diff --git a/src/hal/drivers/mesa-hostmot2/sserial.c b/src/hal/drivers/mesa-hostmot2/sserial.c index aab4b15c1df..58cf54743b0 100644 --- a/src/hal/drivers/mesa-hostmot2/sserial.c +++ b/src/hal/drivers/mesa-hostmot2/sserial.c @@ -1428,18 +1428,22 @@ int hm2_sserial_register_tram(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ if (hal_get_ui32(p->param.u) != p->u32_written) break; return hal_set_ui32(inst->state2, 2); // increment indices case LBP_FLOAT: - case LBP_NONVOL_FLOAT: + case LBP_NONVOL_FLOAT: { + double value = hal_get_real(p->param.r); + double written = p->float_written; // Parameter read/write supports IEEE binary32 and // binary64 only; 8/16-bit float formats are not // decoded by hm2_sserial_get_param_value(). switch (g->DataLength){ case 32: - if ((float)hal_get_real(p->param.r) - != (float)p->float_written) break; - return hal_set_ui32(inst->state2, 2); + value = (float)value; + written = (float)written; + /* Fallthrough */ case 64: - if (hal_get_real(p->param.r) - != p->float_written) break; + // An unchanged NaN must not repeatedly stop + // the port and rewrite the parameter. + if (value != written + && !(isnan(value) && isnan(written))) break; return hal_set_ui32(inst->state2, 2); default: HM2_ERR("Non IEEE float type parameter of length %i\n", @@ -1448,6 +1452,7 @@ int hm2_sserial_register_tram(hostmot2_t *hm2, hm2_sserial_remote_t *chan){ return hal_set_ui32(inst->state2, 2); } break; + } default: return hal_set_ui32(inst->state2, 2); // increment indices } diff --git a/tests/hostmot2/sserial/README b/tests/hostmot2/sserial/README new file mode 100644 index 00000000000..6e9907387c3 --- /dev/null +++ b/tests/hostmot2/sserial/README @@ -0,0 +1,22 @@ +HostMot2 Smart Serial software regression tests +============================================= + +These compile the production drivers with mock LLIO and HAL storage. No +hardware is accessed. Run after configuring/building a run-in-place tree: + + scripts/rip-environment runtests tests/hostmot2/sserial + SANITIZERS=address,undefined bash tests/hostmot2/sserial/test.sh + +CC can select another C compiler. Sanitizers are optional. + +Coverage: format/name bounds, failed realloc preserving existing data, +encoder H format, string termination/case conversion, repeated cleanup, +parameter comparison at 32/64-bit precision (including NaN, infinity and +signed zero), unsupported widths, and byte-reader success/error values. + +These are not hardware validation. Before approval, record the tested commit, +board and remote model, firmware, HAL configuration, and observed results for: +discovery with an empty first port; normal process I/O; volatile/nonvolatile +parameter writes; reconnect/error recovery; repeated load/unload; and the +applicable SSI/BiSS/Fanuc encoder paths. Firmware flashing remains untested +on hardware. The setup-mode failure-state changes from #4144 are excluded. diff --git a/tests/hostmot2/sserial/byte-test.c b/tests/hostmot2/sserial/byte-test.c new file mode 100644 index 00000000000..87eb643cbec --- /dev/null +++ b/tests/hostmot2/sserial/byte-test.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include "hostmot2.h" + +extern hostmot2_t *hm2; +extern hm2_sserial_remote_t *remote; +int sslbp_read_byte(rtapi_u32 addr); +static rtapi_u32 returned_word; +static int fail_doit, timeout; +void rtapi_print_msg(msg_level_t level, const char *fmt, ...) { (void)level; (void)fmt; } +void rtapi_print(const char *fmt, ...) { (void)fmt; } +long long rtapi_get_time(void) { static long long t; return t += 10000000; } +void rtapi_delay(long ns) { (void)ns; } +static int mock_read(hm2_lowlevel_io_t *io, rtapi_u32 address, void *data, int size) { + (void)io; assert(size == 4); + rtapi_u32 word = address == remote->rw_addr[0] ? returned_word : + address == remote->data_reg_addr ? fail_doit : timeout; + memcpy(data, &word, sizeof(word)); + return 1; +} +static int mock_write(hm2_lowlevel_io_t *io, rtapi_u32 addr, const void *data, int size) { + (void)io; (void)addr; (void)data; assert(size == 4); return 1; +} +int main(void) { + hm2_lowlevel_io_t llio = { .read = mock_read, .write = mock_write }; + hostmot2_t board = { .llio = &llio }; + hm2_sserial_remote_t chan = { .command_reg_addr = 0x100, .data_reg_addr = 0x200, + .reg_cs_addr = 0x300, .rw_addr = {0x400} }; + hm2 = &board; remote = &chan; + for (unsigned byte = 0; byte < 256; byte++) { + returned_word = 0xabcdef00u | byte; + assert(sslbp_read_byte(0) == (int)byte); + } + fail_doit = 1; assert(sslbp_read_byte(0) == -1); + fail_doit = 0; timeout = 1; assert(sslbp_read_byte(0) == -1); + puts("PASS: all 256 byte results (upper bits discarded), doit error and timeout preserved as -1"); + return 0; +} diff --git a/tests/hostmot2/sserial/expected b/tests/hostmot2/sserial/expected new file mode 100644 index 00000000000..e71f56ba536 --- /dev/null +++ b/tests/hostmot2/sserial/expected @@ -0,0 +1,2 @@ +PASS: format/name bounds, allocation failure, H encoding, 130 string cases, repeat cleanup, 12 float cases +PASS: all 256 byte results (upper bits discarded), doit error and timeout preserved as -1 diff --git a/tests/hostmot2/sserial/test.c b/tests/hostmot2/sserial/test.c new file mode 100644 index 00000000000..d2fec275a5b --- /dev/null +++ b/tests/hostmot2/sserial/test.c @@ -0,0 +1,180 @@ +#undef realloc +#include +#include +#include +#include +#include +#include +#include "hostmot2.h" + +int hm2_absenc_parse_format(hm2_sserial_remote_t *, hm2_absenc_format_t *); +int hm2_sserial_get_bytes(hostmot2_t *, hm2_sserial_remote_t *, void *, int, int); +int hm2_sserial_update_params(hostmot2_t *, hm2_sserial_instance_t *, long); + +static int fail_alloc; +static unsigned stop_writes; +static unsigned rom_index; +static unsigned char rom[128]; +void *hm2_test_realloc(void *p, size_t size) { + return fail_alloc ? NULL : realloc(p, 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_snprintf(char *buffer, unsigned long size, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int result = vsnprintf(buffer, size, fmt, ap); + va_end(ap); + return result; +} +long simple_strtol(const char *s, char **end, int base) { return strtol(s, end, base); } +long long rtapi_get_time(void) { return 0; } +void rtapi_delay(long ns) { (void)ns; } +static int read_word(hm2_lowlevel_io_t *io, rtapi_u32 addr, void *p, int size) { + (void)io; + assert(size == 4); + rtapi_u32 value = addr == 0x300 ? rom[rom_index++] : 0; + memcpy(p, &value, sizeof(value)); + return 1; +} +static int write_word(hm2_lowlevel_io_t *io, rtapi_u32 addr, const void *p, int size) { + (void)io; + assert(size == 4 && addr != 0); + rtapi_u32 value; + memcpy(&value, p, sizeof(value)); + if (value == 0x800) stop_writes++; + return 1; +} +static void test_formats(void) { + hm2_sserial_remote_t chan = {0}; + hm2_absenc_format_t def = {0}; + char *format = def.string; + for (unsigned length = 1; length <= 48; length++) { + memset(format, 'a', length); + strcpy(format + length, "%32u"); + assert(hm2_absenc_parse_format(&chan, &def) == 0); + assert(chan.num_confs == 1 && chan.confs[0].DataLength == 32); + assert(strlen(chan.confs[0].NameString) == length); + free(chan.confs); chan.confs = NULL; chan.num_confs = 0; + } + memset(format, 'a', 49); strcpy(format + 49, "%32u"); + assert(hm2_absenc_parse_format(&chan, &def) == -EINVAL); + assert(chan.confs == NULL && chan.num_confs == 0); + strcpy(format, "value%256u"); + assert(hm2_absenc_parse_format(&chan, &def) == -EINVAL); + strcpy(format, "low%16lhigh%16H"); + assert(hm2_absenc_parse_format(&chan, &def) == 0); + assert(chan.num_confs == 2 && chan.confs[1].DataType == LBP_ENCODER_H); + void *old = chan.confs; + fail_alloc = 1; + strcpy(format, "next%32u"); + assert(hm2_absenc_parse_format(&chan, &def) == -ENOMEM); + assert(chan.num_confs == 2 && chan.confs == old); + fail_alloc = 0; + free(chan.confs); +} +static void test_strings(hostmot2_t *hm2) { + hm2_sserial_remote_t chan = {0}; + chan.reg_cs_addr = 0x100; chan.command_reg_addr = 0x200; chan.rw_addr[0] = 0x300; + char buffer[HM2_SSERIAL_MAX_STRING_LENGTH + 1]; + for (unsigned length = 0; length <= 64; length++) { + for (int mode = -2; mode <= -1; mode++) { + memset(rom, 'A', sizeof(rom)); rom[length] = 0; + memset(buffer, 0x7f, sizeof(buffer)); rom_index = 0; + int next = hm2_sserial_get_bytes(hm2, &chan, buffer, 0, mode); + assert(next > 0 && next <= HM2_SSERIAL_MAX_STRING_LENGTH); + assert(memchr(buffer, 0, sizeof(buffer)) != NULL); + if (length) assert(buffer[0] == (mode == -1 ? 'a' : 'A')); + } + } +} +static void test_cleanup(hostmot2_t *hm2) { + hm2_sserial_cleanup(hm2); + hm2_sserial_instance_t inst[3] = {0}; + hm2->sserial.instance = inst; hm2->sserial.num_instances = 3; + for (unsigned i = 0; i < 2; i++) { + inst[i].command_reg_addr = 0x100 + i * 4; + inst[i].num_remotes = 2; + inst[i].remotes = calloc(2, sizeof(*inst[i].remotes)); + assert(inst[i].remotes); + for (unsigned r = 0; r < 2; r++) { + hm2_sserial_remote_t *remote = &inst[i].remotes[r]; + remote->confs = calloc(1, sizeof(*remote->confs)); + remote->modes = calloc(1, sizeof(*remote->modes)); + remote->globals = calloc(1, sizeof(*remote->globals)); + /* Also cover allocated pointers whose counts are still zero. */ + remote->num_confs = remote->num_modes = remote->num_globals = r; + } + } + stop_writes = 0; + hm2_sserial_cleanup(hm2); + assert(stop_writes == 2 && hm2->sserial.num_instances == 0); + for (unsigned i = 0; i < 3; i++) assert(!inst[i].remotes && !inst[i].num_remotes); + hm2_sserial_cleanup(hm2); + assert(stop_writes == 2); + hm2->sserial.instance = NULL; +} +static void compare_param(hostmot2_t *hm2, unsigned bits, double current, double written, int changed) { + rtapi_uint state2 = 1, state3 = 0; + rtapi_real value = current; + rtapi_u32 command = 0; + hm2_sserial_data_t global = { .DataLength = bits }; + hm2_sserial_params_t param = { .type = LBP_FLOAT, .float_written = written }; + param.param.r = (hal_real_t)&value; + hm2_sserial_remote_t remote = { .num_globals = 1, .globals = &global, .params = ¶m }; + hm2_sserial_instance_t inst = { .num_remotes = 1, .remotes = &remote, + .state2 = (hal_uint_t)&state2, .state3 = (hal_uint_t)&state3, .command_reg_write = &command }; + hm2_sserial_update_params(hm2, &inst, 1000000); + assert(state2 == (changed ? 1 : 2)); + assert(state3 == (changed ? 1 : 0)); + assert(command == (changed ? 0x800 : 0)); + if (bits != 32 && bits != 64) assert(param.type == LBP_PAD); +} +static void check_nan_cycles(hostmot2_t *hm2, unsigned bits, double written, unsigned expected_writes) { + rtapi_uint state2 = 0, state3 = 0; + rtapi_sint debug = 0; + rtapi_real value = NAN; + rtapi_u32 command = 0, command_read = 0, data_read = 0, cs = 0, payload[2] = {0}; + hm2_sserial_data_t global = { .DataLength = bits }; + hm2_sserial_params_t param = { .type = LBP_FLOAT, .float_written = written }; + param.param.r = (hal_real_t)&value; + hm2_sserial_remote_t remote = { .num_globals = 1, .globals = &global, + .params = ¶m, .reg_cs_write = &cs, .write = {&payload[0], &payload[1]} }; + hm2_sserial_instance_t inst = { .num_remotes = 1, .remotes = &remote, + .state2 = (hal_uint_t)&state2, .state3 = (hal_uint_t)&state3, + .debug = (hal_sint_t)&debug, .command_reg_write = &command, + .command_reg_read = &command_read, .data_reg_read = &data_read }; + unsigned writes = 0, stops = 0; + for (unsigned n = 0; n < 100; n++) { + command = 0; + hm2_sserial_update_params(hm2, &inst, 1000000); + if (command == 0x1001) writes++; + if (command == 0x800) stops++; + } + assert(writes == expected_writes && stops == 2 * expected_writes); +} +int main(void) { + hm2_lowlevel_io_t llio = { .read = read_word, .write = write_word }; + hostmot2_t hm2 = { .llio = &llio }; + test_formats(); test_strings(&hm2); test_cleanup(&hm2); + compare_param(&hm2, 32, 0.1, (double)(float)0.1, 0); + compare_param(&hm2, 32, 1.25, 1.0, 1); + compare_param(&hm2, 64, 0.1, (double)(float)0.1, 1); + compare_param(&hm2, 64, -1.25, -1.25, 0); + compare_param(&hm2, 64, 0.0, -0.0, 0); + compare_param(&hm2, 32, INFINITY, INFINITY, 0); + compare_param(&hm2, 32, NAN, NAN, 0); + compare_param(&hm2, 64, NAN, NAN, 0); + compare_param(&hm2, 32, NAN, 1.0, 1); + compare_param(&hm2, 64, 1.0, NAN, 1); + compare_param(&hm2, 8, 1.0, 2.0, 0); + compare_param(&hm2, 16, 1.0, 2.0, 0); + check_nan_cycles(&hm2, 32, NAN, 0); + check_nan_cycles(&hm2, 64, NAN, 0); + check_nan_cycles(&hm2, 32, 1.0, 1); + check_nan_cycles(&hm2, 64, 1.0, 1); + puts("PASS: format/name bounds, allocation failure, H encoding, 130 string cases, repeat cleanup, 12 float cases"); + return 0; +} diff --git a/tests/hostmot2/sserial/test.sh b/tests/hostmot2/sserial/test.sh new file mode 100644 index 00000000000..405fe8e2f43 --- /dev/null +++ b/tests/hostmot2/sserial/test.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eu +repo_root=$(cd "$(dirname "$0")/../../.." && pwd) +test_dir="$repo_root/tests/hostmot2/sserial" +task_tmp=$(mktemp -d) +trap 'rm -f "$task_tmp/check" "$task_tmp/byte"; 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 +driver="$repo_root/src/hal/drivers/mesa-hostmot2" +"$cc" "${flags[@]}" -Drealloc=hm2_test_realloc "$driver/sserial.c" "$driver/abs_encoder.c" \ + "$test_dir/test.c" -Wl,--gc-sections -lm -o "$task_tmp/check" +"$task_tmp/check" +"$cc" "${flags[@]}" "$driver/setsserial.c" "$test_dir/byte-test.c" \ + -Wl,--gc-sections -o "$task_tmp/byte" +"$task_tmp/byte" From 18c4f1a2ab05075f94b4dc87618a877fdfee95fa Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Sat, 10 Oct 2026 09:16:23 -0700 Subject: [PATCH 6/7] hostmot2: release unprobed Smart Serial pins --- src/hal/drivers/mesa-hostmot2/sserial.c | 10 +++ tests/hostmot2/sserial/README | 4 +- tests/hostmot2/sserial/expected | 1 + tests/hostmot2/sserial/test.c | 108 ++++++++++++++++++++++++ tests/hostmot2/sserial/test.sh | 2 +- 5 files changed, 123 insertions(+), 2 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/sserial.c b/src/hal/drivers/mesa-hostmot2/sserial.c index 58cf54743b0..2bd50dfcb06 100644 --- a/src/hal/drivers/mesa-hostmot2/sserial.c +++ b/src/hal/drivers/mesa-hostmot2/sserial.c @@ -828,6 +828,16 @@ int hm2_sserial_parse_md(hostmot2_t *hm2, int md_index){ } } + // Unprobed physical ports must not be claimed by hm2_configure_pins(). + for (pin = 0; pin < hm2->num_pins; pin++) { + if ((hm2->pin[pin].sec_tag == HM2_GTAG_SMARTSERIAL + || hm2->pin[pin].sec_tag == HM2_GTAG_SMARTSERIALB) + && hm2->pin[pin].sec_unit >= i + && hm2->pin[pin].sec_unit < md->instances) { + hm2->pin[pin].sec_tag = 0; + } + } + hm2->sserial.num_instances = count; // because of the extra increment // Stop the sserial ports. diff --git a/tests/hostmot2/sserial/README b/tests/hostmot2/sserial/README index 6e9907387c3..dc6773439f5 100644 --- a/tests/hostmot2/sserial/README +++ b/tests/hostmot2/sserial/README @@ -12,7 +12,9 @@ CC can select another C compiler. Sanitizers are optional. Coverage: format/name bounds, failed realloc preserving existing data, encoder H format, string termination/case conversion, repeated cleanup, parameter comparison at 32/64-bit precision (including NaN, infinity and -signed zero), unsupported widths, and byte-reader success/error values. +signed zero), unsupported widths, byte-reader success/error values, and +Smart Serial/Smart Serial B discovery with empty leading ports and disabled +trailing ports (including GPIO pin allocation). These are not hardware validation. Before approval, record the tested commit, board and remote model, firmware, HAL configuration, and observed results for: diff --git a/tests/hostmot2/sserial/expected b/tests/hostmot2/sserial/expected index e71f56ba536..6307ca6e517 100644 --- a/tests/hostmot2/sserial/expected +++ b/tests/hostmot2/sserial/expected @@ -1,2 +1,3 @@ PASS: format/name bounds, allocation failure, H encoding, 130 string cases, repeat cleanup, 12 float cases +PASS: Smart Serial and Smart Serial B discovery preserves active pins and releases unused ports to GPIO PASS: all 256 byte results (upper bits discarded), doit error and timeout preserved as -1 diff --git a/tests/hostmot2/sserial/test.c b/tests/hostmot2/sserial/test.c index d2fec275a5b..b165424fe8b 100644 --- a/tests/hostmot2/sserial/test.c +++ b/tests/hostmot2/sserial/test.c @@ -15,6 +15,53 @@ static int fail_alloc; static unsigned stop_writes; static unsigned rom_index; static unsigned char rom[128]; +static rtapi_u64 hal_storage[4096]; +static size_t hal_used; +static int discovery_port = -1; +static unsigned last_command[3], command_reads[3]; + +void *hal_malloc(long size) { + if (size <= 0) return NULL; + size_t words = (size + sizeof(*hal_storage) - 1) / sizeof(*hal_storage); + assert(hal_used + words <= sizeof(hal_storage) / sizeof(*hal_storage)); + void *p = &hal_storage[hal_used]; + hal_used += words; + return p; +} +#define MOCK_HAL_NEW(name, ref_type, value_type) \ +int name(int compid, hal_pdir_t dir, ref_type *ref, value_type def, const char *fmt, ...) { \ + (void)compid; (void)dir; (void)fmt; \ + *ref = (ref_type)hal_malloc(sizeof(rtapi_u64)); \ + *(value_type *)*ref = def; \ + return 0; \ +} +MOCK_HAL_NEW(hal_pin_new_bool, hal_bool_t, rtapi_bool) +MOCK_HAL_NEW(hal_pin_new_si32, hal_sint_t, rtapi_s32) +MOCK_HAL_NEW(hal_pin_new_ui32, hal_uint_t, rtapi_u32) +MOCK_HAL_NEW(hal_pin_new_real, hal_real_t, rtapi_real) +MOCK_HAL_NEW(hal_param_new_bool, hal_bool_t, rtapi_bool) +MOCK_HAL_NEW(hal_param_new_si32, hal_sint_t, rtapi_s32) +MOCK_HAL_NEW(hal_param_new_ui32, hal_uint_t, rtapi_u32) +MOCK_HAL_NEW(hal_param_new_real, hal_real_t, rtapi_real) +int hal_param_new_fake(int compid, hal_refs_u *refs) { + (void)compid; memset(refs, 0, sizeof(*refs)); return 0; +} +int hm2_md_is_consistent(hostmot2_t *hm2, int i, rtapi_u8 v, rtapi_u8 n, rtapi_u32 s, rtapi_u32 m) { + (void)v; (void)s; (void)m; + return n == (hm2->md[i].gtag == HM2_GTAG_SMARTSERIALB ? 10 : 6); +} +int hm2_md_is_consistent_or_complain(hostmot2_t *hm2, int i, rtapi_u8 v, rtapi_u8 n, rtapi_u32 s, rtapi_u32 m) { + return hm2_md_is_consistent(hm2, i, v, n, s, m); +} +const char *hm2_get_general_function_name(int tag) { (void)tag; return "SmartSerial"; } +int hm2_register_tram_read_region(hostmot2_t *hm2, rtapi_u16 addr, rtapi_u16 size, rtapi_u32 **p) { + (void)hm2; (void)addr; (void)size; *p = hal_malloc(sizeof(**p)); return 0; +} +int hm2_register_tram_write_region(hostmot2_t *hm2, rtapi_u16 addr, rtapi_u16 size, rtapi_u32 **p) { + return hm2_register_tram_read_region(hm2, addr, size, p); +} +void hm2_stepgen_allocate_pins(hostmot2_t *hm2) { (void)hm2; } + void *hm2_test_realloc(void *p, size_t size) { return fail_alloc ? NULL : realloc(p, size); } @@ -36,6 +83,16 @@ static int read_word(hm2_lowlevel_io_t *io, rtapi_u32 addr, void *p, int size) { (void)io; assert(size == 4); rtapi_u32 value = addr == 0x300 ? rom[rom_index++] : 0; + if (discovery_port >= 0) { + unsigned port = (addr & 0xff) / 0x40; + assert(port < 3); + if ((addr & 0xff00) == 0x1000) command_reads[port]++; + if ((addr & 0xff00) == 0x1100 + && (last_command[port] & 0xff00) == (READ_LOCAL_CMD & 0xff00)) value = 43; + if (addr == 0x1400u + 0x40 * discovery_port) value = 0x41416937; + // A discovered remote with empty process/global tables. + if (addr == 0x1500u + 0x40 * discovery_port) value = 0x00020001; + } memcpy(p, &value, sizeof(value)); return 1; } @@ -45,8 +102,57 @@ static int write_word(hm2_lowlevel_io_t *io, rtapi_u32 addr, const void *p, int rtapi_u32 value; memcpy(&value, p, sizeof(value)); if (value == 0x800) stop_writes++; + if (discovery_port >= 0 && (addr & 0xff00) == 0x1000) { + unsigned port = (addr & 0xff) / 0x40; + assert(port < 3); + last_command[port] = value; + } return 1; } +static void test_discovery(void) { + const int tags[] = {HM2_GTAG_SMARTSERIAL, HM2_GTAG_SMARTSERIALB}; + for (unsigned t = 0; t < sizeof(tags) / sizeof(*tags); t++) { + for (discovery_port = 0; discovery_port < 3; discovery_port++) { + hm2_lowlevel_io_t llio = { .read = read_word, .write = write_word }; + hostmot2_t board = { .llio = &llio }; + hm2_pin_t pins[24] = {0}; + rtapi_u32 source = 0; + hal_used = 0; + memset(hal_storage, 0, sizeof(hal_storage)); + memset(last_command, 0, sizeof(last_command)); + memset(command_reads, 0, sizeof(command_reads)); + for (unsigned p = 0; p < 24; p++) { + pins[p].primary_tag = HM2_GTAG_IOPORT; + pins[p].bit_num = p; + if (p < 3) { + pins[p].sec_tag = tags[t]; + pins[p].sec_unit = p; + pins[p].sec_pin = 0x81; + } + } + board.pin = pins; board.num_pins = 24; + board.ioport.num_instances = 1; board.idrom.port_width = 24; + board.ioport.alt_source_reg = &source; + board.ioport.ddr_addr = 0x2000; board.ioport.alt_source_addr = 0x2100; + board.config.num_sserials = 1; board.sserial.baudrate = -1; + memset(board.config.sserial_modes, 'x', sizeof(board.config.sserial_modes)); + for (int p = 0; p <= discovery_port; p++) board.config.sserial_modes[p][0] = '0'; + board.md[0] = (hm2_module_descriptor_t){ .gtag = tags[t], .instances = 3, + .base_address = 0x1000, .instance_stride = 0x40, .register_stride = 0x100 }; + assert(hm2_sserial_parse_md(&board, 0) == 1); + assert(board.sserial.instance[0].index == discovery_port); + hm2_configure_pins(&board); + assert(source == (1u << discovery_port)); + for (int p = 0; p < 3; p++) { + assert((command_reads[p] != 0) == (p <= discovery_port)); + assert(pins[p].gtag == (p == discovery_port ? tags[t] : HM2_GTAG_IOPORT)); + assert(pins[p].direction == (p == discovery_port ? HM2_PIN_DIR_IS_OUTPUT : HM2_PIN_DIR_IS_INPUT)); + } + hm2_sserial_cleanup(&board); + } + } + discovery_port = -1; +} static void test_formats(void) { hm2_sserial_remote_t chan = {0}; hm2_absenc_format_t def = {0}; @@ -176,5 +282,7 @@ int main(void) { check_nan_cycles(&hm2, 32, 1.0, 1); check_nan_cycles(&hm2, 64, 1.0, 1); puts("PASS: format/name bounds, allocation failure, H encoding, 130 string cases, repeat cleanup, 12 float cases"); + test_discovery(); + puts("PASS: Smart Serial and Smart Serial B discovery preserves active pins and releases unused ports to GPIO"); return 0; } diff --git a/tests/hostmot2/sserial/test.sh b/tests/hostmot2/sserial/test.sh index 405fe8e2f43..32dbfcb6035 100644 --- a/tests/hostmot2/sserial/test.sh +++ b/tests/hostmot2/sserial/test.sh @@ -13,7 +13,7 @@ if [ -n "${SANITIZERS:-}" ]; then fi driver="$repo_root/src/hal/drivers/mesa-hostmot2" "$cc" "${flags[@]}" -Drealloc=hm2_test_realloc "$driver/sserial.c" "$driver/abs_encoder.c" \ - "$test_dir/test.c" -Wl,--gc-sections -lm -o "$task_tmp/check" + "$driver/pins.c" "$test_dir/test.c" -Wl,--gc-sections -lm -o "$task_tmp/check" "$task_tmp/check" "$cc" "${flags[@]}" "$driver/setsserial.c" "$test_dir/byte-test.c" \ -Wl,--gc-sections -o "$task_tmp/byte" From cb2b0b4506250f3cdac8d6e6f8dc67bc19006beb Mon Sep 17 00:00:00 2001 From: joseph calderon Date: Mon, 12 Oct 2026 10:33:14 -0700 Subject: [PATCH 7/7] hostmot2: use configured flags in mock tests --- tests/hostmot2/harness.mk | 16 ++++++++++++++++ tests/hostmot2/sserial/README | 1 + tests/hostmot2/sserial/byte-test.c | 2 +- tests/hostmot2/sserial/test.sh | 24 +++++++++++------------- 4 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 tests/hostmot2/harness.mk 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)" diff --git a/tests/hostmot2/sserial/README b/tests/hostmot2/sserial/README index dc6773439f5..89d9a599a9b 100644 --- a/tests/hostmot2/sserial/README +++ b/tests/hostmot2/sserial/README @@ -7,6 +7,7 @@ hardware is accessed. Run after configuring/building a run-in-place tree: scripts/rip-environment runtests tests/hostmot2/sserial SANITIZERS=address,undefined bash tests/hostmot2/sserial/test.sh +Uses the configured realtime compiler and flags from src/Makefile. CC can select another C compiler. Sanitizers are optional. Coverage: format/name bounds, failed realloc preserving existing data, diff --git a/tests/hostmot2/sserial/byte-test.c b/tests/hostmot2/sserial/byte-test.c index 87eb643cbec..87df1aaee92 100644 --- a/tests/hostmot2/sserial/byte-test.c +++ b/tests/hostmot2/sserial/byte-test.c @@ -7,7 +7,7 @@ extern hostmot2_t *hm2; extern hm2_sserial_remote_t *remote; int sslbp_read_byte(rtapi_u32 addr); static rtapi_u32 returned_word; -static int fail_doit, timeout; +static rtapi_u32 fail_doit, timeout; void rtapi_print_msg(msg_level_t level, const char *fmt, ...) { (void)level; (void)fmt; } void rtapi_print(const char *fmt, ...) { (void)fmt; } long long rtapi_get_time(void) { static long long t; return t += 10000000; } diff --git a/tests/hostmot2/sserial/test.sh b/tests/hostmot2/sserial/test.sh index 32dbfcb6035..143c32fab67 100644 --- a/tests/hostmot2/sserial/test.sh +++ b/tests/hostmot2/sserial/test.sh @@ -1,20 +1,18 @@ #!/bin/bash set -eu repo_root=$(cd "$(dirname "$0")/../../.." && pwd) -test_dir="$repo_root/tests/hostmot2/sserial" task_tmp=$(mktemp -d) trap 'rm -f "$task_tmp/check" "$task_tmp/byte"; 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 -driver="$repo_root/src/hal/drivers/mesa-hostmot2" -"$cc" "${flags[@]}" -Drealloc=hm2_test_realloc "$driver/sserial.c" "$driver/abs_encoder.c" \ - "$driver/pins.c" "$test_dir/test.c" -Wl,--gc-sections -lm -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=hal/drivers/mesa-hostmot2/sserial.c hal/drivers/mesa-hostmot2/abs_encoder.c hal/drivers/mesa-hostmot2/pins.c ../tests/hostmot2/sserial/test.c" \ + TEST_CFLAGS=-Drealloc=hm2_test_realloc TEST_LIBS=-lm \ + "TEST_OUTPUT=$task_tmp/check" >&2 "$task_tmp/check" -"$cc" "${flags[@]}" "$driver/setsserial.c" "$test_dir/byte-test.c" \ - -Wl,--gc-sections -o "$task_tmp/byte" +make "${make_args[@]}" hm2-test \ + "TEST_SOURCES=hal/drivers/mesa-hostmot2/setsserial.c ../tests/hostmot2/sserial/byte-test.c" \ + "TEST_OUTPUT=$task_tmp/byte" >&2 "$task_tmp/byte"