From e95f1d11f2c855f60cb52bcbd7e3a0fbb3d83fe7 Mon Sep 17 00:00:00 2001 From: Aaron Cuevas Lopez <1830303+aacuevas@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:11:56 +0100 Subject: [PATCH 1/2] Modify register interface and ft600 driver for oniv2 --- api/liboni/drivers/ft600/circbuffer.c | 8 +- api/liboni/drivers/ft600/onidriver_ft600.c | 217 +++++++----- api/liboni/oni.c | 377 ++++++++++++++------- api/liboni/oni.h | 55 +++ api/liboni/onidefs.h | 25 +- api/liboni/onidriver.h | 62 ++++ api/liboni/onidriverloader.c | 3 + api/liboni/onidriverloader.h | 7 + 8 files changed, 530 insertions(+), 224 deletions(-) diff --git a/api/liboni/drivers/ft600/circbuffer.c b/api/liboni/drivers/ft600/circbuffer.c index 20607aa..4338be3 100644 --- a/api/liboni/drivers/ft600/circbuffer.c +++ b/api/liboni/drivers/ft600/circbuffer.c @@ -10,9 +10,13 @@ int circBufferInit(circ_buffer_t* buffer) return 1; } -void circBufferRelease(circ_buffer_t* buffer) +void circBufferRelease(circ_buffer_t *buffer) { - if (buffer->buffer != NULL) free(buffer->buffer); + if (buffer->buffer != NULL) + { + free(buffer->buffer); + buffer->buffer = NULL; + } } int circBufferCanWrite(circ_buffer_t* buffer, size_t size) { diff --git a/api/liboni/drivers/ft600/onidriver_ft600.c b/api/liboni/drivers/ft600/onidriver_ft600.c index 07030d0..9a30ac9 100644 --- a/api/liboni/drivers/ft600/onidriver_ft600.c +++ b/api/liboni/drivers/ft600/onidriver_ft600.c @@ -78,6 +78,10 @@ const char usbdesc[] = "Open Ephys FT600 USB board"; #define CHECK_FTERR(exp) {if(exp!=FT_OK){oni_ft600_free_ctx(ctx);return ONI_EINIT;}} #define CHECK_NULL(exp) {if(exp==NULL){oni_ft600_free_ctx(ctx);return ONI_EBADALLOC;}} #define CHECK_ERR(exp) {if(exp==0){oni_ft600_free_ctx(ctx);return ONI_EINIT;}} +#define FREE_AND_RELEASE(exp) {if (exp != NULL) { free(exp); exp = NULL;} } + +// Bytes that a register operation compound comand takes +#define MAX_REG_OP_SIZE 45 typedef enum { @@ -96,6 +100,13 @@ typedef enum const oni_driver_info_t driverInfo = {.name = "ft600", .major = 1, .minor = 0, .patch = 5, .pre_release = NULL}; +typedef struct { + oni_dev_idx_t dev_idx; + oni_reg_addr_t dev_addr; + oni_reg_val_t value; + oni_reg_val_t rw; +} oni_ft600_reg_operation_t; + struct oni_ft600_ctx_impl { oni_size_t inBlockSize; oni_size_t outBlockSize; @@ -125,12 +136,10 @@ struct oni_ft600_ctx_impl { OVERLAPPED sigOverlapped; OVERLAPPED outOverlapped; #endif - struct { - oni_dev_idx_t dev_idx; - oni_reg_addr_t dev_addr; - oni_reg_val_t value; - oni_reg_val_t rw; - } regOperation; + oni_ft600_reg_operation_t* regOperation; + size_t regOperationIndex; + size_t maxRegOperation; + uint8_t* regOperationBuffer; }; typedef struct oni_ft600_ctx_impl* oni_ft600_ctx; @@ -288,8 +297,6 @@ void oni_ft600_update_control(oni_ft600_ctx ctx) } #endif -static inline oni_conf_off_t _oni_register_offset(oni_config_t reg); - inline void oni_ft600_restart_acq(oni_ft600_ctx ctx) { ctx->nextReadIndex = 0; @@ -371,13 +378,18 @@ oni_driver_ctx oni_driver_create_ctx(void) ctx->regBuffer.size = DEFAULT_REGSIZE; ctx->signalBuffer.size = DEFAULT_SIGNALSIZE; ctx->numInOverlapped = DEFAULT_OVERLAPPED; + ctx->regOperation = NULL; + ctx->regOperationBuffer = NULL; + ctx->regOperationIndex = 0; return ctx; } void oni_ft600_free_ctx(oni_ft600_ctx ctx) { - if (ctx->inBuffer != NULL) free(ctx->inBuffer); - if (ctx->auxBuffer != NULL) free(ctx->auxBuffer); + FREE_AND_RELEASE(ctx->regOperation); + FREE_AND_RELEASE(ctx->inBuffer); + FREE_AND_RELEASE(ctx->auxBuffer); + FREE_AND_RELEASE(ctx->regOperationBuffer); circBufferRelease(&ctx->signalBuffer); circBufferRelease(&ctx->regBuffer); if (ctx->ftHandle != NULL) @@ -398,8 +410,9 @@ void oni_ft600_free_ctx(oni_ft600_ctx ctx) for (unsigned int i = 0; i < ctx->numInOverlapped; i++) FT_ReleaseOverlapped(ctx->ftHandle, &ctx->inOverlapped[i]); free(ctx->inOverlapped); + ctx->inOverlapped = NULL; } - if (ctx->inTransferred != NULL) free(ctx->inTransferred); + FREE_AND_RELEASE(ctx->inTransferred); #ifdef POLL_CONTROL mutex_destroy(&ctx->controlMutex); #endif @@ -563,6 +576,19 @@ int oni_driver_init(oni_driver_ctx driver_ctx, int host_idx) oni_ft600_free_ctx(ctx); return res; } + // get maxQ and allocate register buffer + oni_reg_val_t val; + res = oni_driver_read_config(ctx, ONI_ATTR_MAX_REGISTER_Q_SIZE, &val); + if (res != ONI_ESUCCESS) { + oni_ft600_free_ctx(ctx); + return res; + } + ctx->maxRegOperation = val; + ctx->regOperation = malloc(val * sizeof(oni_ft600_reg_operation_t)); + CHECK_NULL(ctx->regOperation); + ctx->regOperationBuffer = malloc(val * MAX_REG_OP_SIZE); + CHECK_NULL(ctx->regOperationBuffer); + ctx->state = STATE_INIT; return ONI_ESUCCESS; } @@ -724,73 +750,127 @@ int oni_driver_write_stream(oni_driver_ctx driver_ctx, return size; } +int oni_driver_prepare_register_operation(oni_driver_ctx driver_ctx, size_t num) +{ + CTX_CAST; + if (num > ctx->maxRegOperation) + return ONI_EINVALARG; + ctx->regOperationIndex = 0; + return ONI_ESUCCESS; +} + +int oni_driver_commit_register_operation(oni_driver_ctx driver_ctx) +{ + CTX_CAST; + + if (ctx->regOperationIndex == 0) + return ONI_EINVALSTATE; + + size_t i = 0; + for (size_t op = 0; op < ctx->regOperationIndex; op++) + { + ctx->regOperationBuffer[(9 * i)] = CMD_WRITEREG; + *(uint32_t *)(ctx->regOperationBuffer + 1 + (9 * i)) + = (uint32_t)ONI_OP_RI_DEV_ADDR; + *(uint32_t *)(ctx->regOperationBuffer + 5 + (9 * i)) + = ctx->regOperation[op].dev_idx; + i++; + ctx->regOperationBuffer[(9 * i)] = CMD_WRITEREG; + *(uint32_t *)(ctx->regOperationBuffer + 1 + (9 * i)) + = (uint32_t)ONI_OP_RI_REG_ADDR; + *(uint32_t *)(ctx->regOperationBuffer + 5 + (9 * i)) + = ctx->regOperation[op].dev_addr; + i++; + ctx->regOperationBuffer[(9 * i)] = CMD_WRITEREG; + *(uint32_t *)(ctx->regOperationBuffer + 1 + (9 * i)) + = (uint32_t)ONI_OP_RI_RW; + *(uint32_t *)(ctx->regOperationBuffer + 5 + (9 * i)) + = ctx->regOperation[op].rw; + i++; + if (ctx->regOperation[op].rw) { + ctx->regOperationBuffer[(9 * i)] = CMD_WRITEREG; + *(uint32_t *)(ctx->regOperationBuffer + 1 + (9 * i)) + = (uint32_t)ONI_OP_RI_REG_VAL; + *(uint32_t *)(ctx->regOperationBuffer + 5 + (9 * i)) + = ctx->regOperation[op].value; + i++; + } + ctx->regOperationBuffer[(9 * i)] = CMD_WRITEREG; + *(uint32_t *)(ctx->regOperationBuffer + 1 + (9 * i)) + = (uint32_t)ONI_OP_RI_TRIGGER; + *(uint32_t *)(ctx->regOperationBuffer + 5 + (9 * i)) = 1; + i++; + } + size_t size = i * 9; + ctx->regOperationIndex = 0; + int res = oni_ft600_sendcmd(ctx, ctx->regOperationBuffer, size); + if (res != ONI_ESUCCESS) + return res; + return ONI_ESUCCESS; + +} + +void oni_driver_cancel_register_operation(oni_driver_ctx driver_ctx) +{ + CTX_CAST; + ctx->regOperationIndex = 0; +} + int oni_driver_write_config(oni_driver_ctx driver_ctx, oni_config_t reg, oni_reg_val_t value) { CTX_CAST; - uint8_t buffer[45]; - size_t size; - //to avoid cluttering the USB interface will all the operations required for a device - //register access, we latch them together and send them in a single USB operation - if (reg == ONI_CONFIG_DEV_IDX) + uint8_t buffer[9]; + size_t size = 9; + + if (reg == ONI_OP_RI_DEV_ADDR) { - ctx->regOperation.dev_idx = value; + if (ctx->regOperationIndex >= ctx->maxRegOperation) + return ONI_EBUFFERSIZE; + ctx->regOperation[ctx->regOperationIndex].dev_idx = value; return ONI_ESUCCESS; } - if (reg == ONI_CONFIG_REG_ADDR) + if (reg == ONI_OP_RI_REG_ADDR) { - ctx->regOperation.dev_addr = value; + if (ctx->regOperationIndex >= ctx->maxRegOperation) + return ONI_EBUFFERSIZE; + ctx->regOperation[ctx->regOperationIndex].dev_addr = value; return ONI_ESUCCESS; } - if (reg == ONI_CONFIG_REG_VALUE) + if (reg == ONI_OP_RI_REG_VAL) { - ctx->regOperation.value = value; + if (ctx->regOperationIndex >= ctx->maxRegOperation) + return ONI_EBUFFERSIZE; + ctx->regOperation[ctx->regOperationIndex].value = value; return ONI_ESUCCESS; } - if (reg == ONI_CONFIG_RW) + if (reg == ONI_OP_RI_RW) { - ctx->regOperation.rw = value; + if (ctx->regOperationIndex >= ctx->maxRegOperation) + return ONI_EBUFFERSIZE; + ctx->regOperation[ctx->regOperationIndex].rw = value; return ONI_ESUCCESS; } - if (reg == ONI_CONFIG_TRIG) + if (reg == ONI_OP_RI_TRIGGER) { - size_t i = 0; - buffer[(9*i)] = CMD_WRITEREG; - *(uint32_t*)(buffer + 1 + (9 * i)) = _oni_register_offset(ONI_CONFIG_DEV_IDX); - *(uint32_t*)(buffer + 5 + (9 * i)) = ctx->regOperation.dev_idx; - i++; - buffer[(9 * i)] = CMD_WRITEREG; - *(uint32_t*)(buffer + 1 + (9 * i)) = _oni_register_offset(ONI_CONFIG_REG_ADDR); - *(uint32_t*)(buffer + 5 + (9 * i)) = ctx->regOperation.dev_addr; - i++; - buffer[(9 * i)] = CMD_WRITEREG; - *(uint32_t*)(buffer + 1 + (9 * i)) = _oni_register_offset(ONI_CONFIG_RW); - *(uint32_t*)(buffer + 5 + (9 * i)) = ctx->regOperation.rw; - i++; - if (ctx->regOperation.rw) - { - buffer[(9 * i)] = CMD_WRITEREG; - *(uint32_t*)(buffer + 1 + (9 * i)) = _oni_register_offset(ONI_CONFIG_REG_VALUE); - *(uint32_t*)(buffer + 5 + (9 * i)) = ctx->regOperation.value; - i++; - } - buffer[(9 * i)] = CMD_WRITEREG; - *(uint32_t*)(buffer + 1 + (9 * i)) = _oni_register_offset(ONI_CONFIG_TRIG); - *(uint32_t*)(buffer + 5 + (9 * i)) = 1; - i++; - size = i * 9; + if (ctx->regOperationIndex >= ctx->maxRegOperation) + return ONI_EBUFFERSIZE; + ctx->regOperationIndex++; + return ONI_ESUCCESS; } else { buffer[0] = CMD_WRITEREG; - *(uint32_t*)(buffer + 1) = _oni_register_offset(reg); + *(uint32_t*)(buffer + 1) = (uint32_t)reg; *(uint32_t*)(buffer + 5) = value; - size = 9; } - if (reg == ONI_CONFIG_RESET && value != 0) + if (reg == ONI_OP_SOFT_RESET && value != 0) { + // NB: this cannot be on the callback because in this case + // we want to stop acquisition on the driver level before + // issuing the soft reset oni_ft600_stop_acq(ctx); } int res = oni_ft600_sendcmd(ctx, buffer, size); @@ -803,7 +883,7 @@ int oni_driver_read_config(oni_driver_ctx driver_ctx, oni_config_t reg, oni_reg_ CTX_CAST; uint8_t buffer[5]; buffer[0] = CMD_READREG; - *(uint32_t*)(buffer + 1) = _oni_register_offset(reg); + *(uint32_t *)(buffer + 1) = (uint32_t)reg; int res = oni_ft600_sendcmd(ctx, buffer, 5); if (res != ONI_ESUCCESS) return res; @@ -935,32 +1015,5 @@ const oni_driver_info_t* oni_driver_info(void) return &driverInfo; } -static inline oni_conf_off_t _oni_register_offset(oni_config_t reg) -{ - switch (reg) { - case ONI_CONFIG_DEV_IDX: - return CONFDEVIDXOFFSET; - case ONI_CONFIG_REG_ADDR: - return CONFADDROFFSET; - case ONI_CONFIG_REG_VALUE: - return CONFVALUEOFFSET; - case ONI_CONFIG_RW: - return CONFRWOFFSET; - case ONI_CONFIG_TRIG: - return CONFTRIGOFFSET; - case ONI_CONFIG_RUNNING: - return CONFRUNNINGOFFSET; - case ONI_CONFIG_RESET: - return CONFRESETOFFSET; - case ONI_CONFIG_SYSCLKHZ: - return CONFSYSCLKHZOFFSET; - case ONI_CONFIG_ACQCLKHZ: - return CONFACQCLKHZOFFSET; - case ONI_CONFIG_RESETACQCOUNTER: - return CONFRESETACQCOUNTER; - case ONI_CONFIG_HWADDRESS: - return CONFHWADDRESS; - default: - return 0; - } -} + + diff --git a/api/liboni/oni.c b/api/liboni/oni.c index 219245a..d88e03d 100644 --- a/api/liboni/oni.c +++ b/api/liboni/oni.c @@ -87,6 +87,9 @@ struct oni_ctx_impl { IDLE, RUNNING } run_state; + + // controller capabilities + oni_controller_caps_t controller_caps; }; // Signal flags @@ -153,11 +156,36 @@ int oni_init_ctx(oni_ctx ctx, int host_idx) int rc = ctx->driver.init(ctx->driver.ctx, host_idx); if (rc) return rc; + // NB: Populate the capabilities structure + + oni_reg_val_t val; + + // TODO: Check that the spec version matches the expected by the library + rc = _oni_read_config(ctx, ONI_SPEC_VER, &val); + if (rc) return rc; + ctx->controller_caps.spec_ver.major = (val >> 24)&0xFF; + ctx->controller_caps.spec_ver.minor = (val >> 16) & 0xFF; + ctx->controller_caps.spec_ver.patch = (val >> 8) & 0xFF; + ctx->controller_caps.spec_ver.reserved = (val >> 0) & 0xFF; + + rc = _oni_read_config(ctx, ONI_ATTR_READ_STR_ALIGN, &ctx->controller_caps.read_str_align); + if (rc) return rc; + + rc = _oni_read_config(ctx, ONI_ATTR_WRITE_STR_ALIGN, &ctx->controller_caps.write_str_align); + if (rc) return rc; + + rc = _oni_read_config(ctx, ONI_ATTR_MAX_REGISTER_Q_SIZE, &ctx->controller_caps.max_register_q_size); + if (rc) return rc; + + rc = _oni_read_config(ctx, ONI_ATTR_NUM_SYNC_DEVS, &ctx->controller_caps.num_sync_devs); + if (rc) return rc; + + // NB: Trigger reset routine (populates device table and key acquisition // parameters) Success will set ctx->run_state to IDLE // Set the reset register - rc = _oni_write_config(ctx, ONI_CONFIG_RESET, 1); + rc = _oni_write_config(ctx, ONI_OP_SOFT_RESET, 1); if (rc) return rc; // Get device table etc @@ -227,7 +255,7 @@ int oni_get_opt(const oni_ctx ctx, int ctx_opt, void *value, size_t *option_len) if (*option_len < ONI_REGSZ) return ONI_EBUFFERSIZE; - int rc = _oni_read_config(ctx, ONI_CONFIG_RUNNING, value); + int rc = _oni_read_config(ctx, ONI_OP_ACQ_RUNNING, value); if (rc) return rc; *option_len = ONI_REGSZ; @@ -242,7 +270,7 @@ int oni_get_opt(const oni_ctx ctx, int ctx_opt, void *value, size_t *option_len) if (*option_len < ONI_REGSZ) return ONI_EBUFFERSIZE; - int rc = _oni_read_config(ctx, ONI_CONFIG_SYSCLKHZ, value); + int rc = _oni_read_config(ctx, ONI_OP_SYS_CLK_HZ, value); if (rc) return rc; *option_len = ONI_REGSZ; @@ -257,7 +285,7 @@ int oni_get_opt(const oni_ctx ctx, int ctx_opt, void *value, size_t *option_len) if (*option_len < ONI_REGSZ) return ONI_EBUFFERSIZE; - int rc = _oni_read_config(ctx, ONI_CONFIG_ACQCLKHZ, value); + int rc = _oni_read_config(ctx, ONI_OP_ACQ_CLK_HZ, value); if (rc) return rc; *option_len = ONI_REGSZ; @@ -272,7 +300,7 @@ int oni_get_opt(const oni_ctx ctx, int ctx_opt, void *value, size_t *option_len) if (*option_len < ONI_REGSZ) return ONI_EBUFFERSIZE; - int rc = _oni_read_config(ctx, ONI_CONFIG_HWADDRESS, value); + int rc = _oni_read_config(ctx, ONI_OP_SYNC_HW_ADDR, value); if (rc) return rc; *option_len = ONI_REGSZ; @@ -337,31 +365,8 @@ int oni_get_opt(const oni_ctx ctx, int ctx_opt, void *value, size_t *option_len) case ONI_OPT_RESET: case ONI_OPT_RESETACQCOUNTER: return ONI_EWRITEONLY; - default: { - - // Attempt to read to custom (outside ONI spec) configuration - // option - assert(ctx_opt >= ONI_OPT_CUSTOMBEGIN && "Invalid custom configuration register."); - if (ctx_opt < ONI_OPT_CUSTOMBEGIN) - return ONI_EPROTCONFIG; - - assert(ctx->run_state > UNINITIALIZED && "Context state must be IDLE or RUNNING."); - if (ctx->run_state < IDLE) - return ONI_EINVALSTATE; - - if (*option_len < ONI_REGSZ) - return ONI_EBUFFERSIZE; - - int rc = _oni_read_config(ctx, - ONI_CONFIG_CUSTOMBEGIN - + (ctx_opt - ONI_OPT_CUSTOMBEGIN), - value); - if (rc) return rc; - - *option_len = ONI_REGSZ; - - break; - } + default: + return ONI_EINVALOPT; } return ONI_ESUCCESS; } @@ -380,7 +385,7 @@ int oni_set_opt(oni_ctx ctx, int ctx_opt, const void *value, size_t option_len) return ONI_EBUFFERSIZE; int rc = _oni_write_config( - ctx, ONI_CONFIG_RUNNING, *(oni_reg_val_t*)value); + ctx, ONI_OP_ACQ_RUNNING, *(oni_reg_val_t*)value); if (rc) return rc; // Dump buffers @@ -407,7 +412,7 @@ int oni_set_opt(oni_ctx ctx, int ctx_opt, const void *value, size_t option_len) if (*(oni_reg_val_t *)value != 0) { int rc = _oni_write_config( - ctx, ONI_CONFIG_RESET, *(oni_reg_val_t*)value); + ctx, ONI_OP_SOFT_RESET, *(oni_reg_val_t*)value); if (rc) return rc; // Get device table etc @@ -427,7 +432,7 @@ int oni_set_opt(oni_ctx ctx, int ctx_opt, const void *value, size_t option_len) if (*(oni_reg_val_t *)value != 0) { int rc = _oni_write_config( - ctx, ONI_CONFIG_RESETACQCOUNTER, *(oni_reg_val_t *)value); + ctx, ONI_OP_ACQ_CNT_RESET, *(oni_reg_val_t *)value); if (rc) return rc; } @@ -442,7 +447,7 @@ int oni_set_opt(oni_ctx ctx, int ctx_opt, const void *value, size_t option_len) return ONI_EBUFFERSIZE; int rc = _oni_write_config( - ctx, ONI_CONFIG_HWADDRESS, *(oni_reg_val_t *)value); + ctx, ONI_OP_SYNC_HW_ADDR, *(oni_reg_val_t *)value); if (rc) return rc; break; @@ -507,28 +512,8 @@ int oni_set_opt(oni_ctx ctx, int ctx_opt, const void *value, size_t option_len) case ONI_OPT_MAXREADFRAMESIZE: case ONI_OPT_MAXWRITEFRAMESIZE: return ONI_EREADONLY; - default: { - - // Attempt to write to custom (outside ONI spec) configuration - // option - assert(ctx_opt >= ONI_OPT_CUSTOMBEGIN && "Invalid custom configuration register."); - if (ctx_opt < ONI_OPT_CUSTOMBEGIN) - return ONI_EPROTCONFIG; - - assert(ctx->run_state > UNINITIALIZED && "Context state must be IDLE or RUNNING."); - if (ctx->run_state < IDLE) - return ONI_EINVALSTATE; - - if (option_len != ONI_REGSZ) - return ONI_EBUFFERSIZE; - - int rc = _oni_write_config(ctx, - ONI_CONFIG_CUSTOMBEGIN + (ctx_opt - ONI_OPT_CUSTOMBEGIN), - *(oni_reg_val_t *)value); - if (rc) return rc; - - break; - } + default: + return ONI_EINVALOPT; } return ctx->driver.set_opt_callback(ctx->driver.ctx, ctx_opt, value, option_len); @@ -544,87 +529,168 @@ int oni_set_driver_opt(oni_ctx ctx, int drv_opt, const void* value, size_t optio return ctx->driver.set_opt(ctx->driver.ctx, drv_opt, value, option_len); } -int oni_write_reg(const oni_ctx ctx, - oni_dev_idx_t dev_idx, - oni_reg_addr_t addr, - oni_reg_val_t value) +#define REG_ERROR(x) \ + do { \ + ctx->driver.cancel_register_operation(ctx->driver.ctx); \ + return x; \ + } while (0); +int oni_reg_access(const oni_ctx ctx, oni_reg_operation_t *operations, int num) { assert(ctx != NULL && "Context is NULL"); assert(ctx->run_state > UNINITIALIZED && "Context must be INITIALIZED."); + assert(operations != NULL && "Operation array is NULL"); + assert(num > 0 && "Number of registers must be > 0"); // Make sure we are not already in config triggered state oni_reg_val_t trig = 0; - int rc = _oni_read_config(ctx, ONI_CONFIG_TRIG, &trig); - if (rc) return rc; - - if (trig != 0) return ONI_ERETRIG; - - // Set config registers and trigger a write - rc = _oni_write_config(ctx, ONI_CONFIG_DEV_IDX, dev_idx); - if (rc) return rc; - rc = _oni_write_config(ctx, ONI_CONFIG_REG_ADDR, addr); - if (rc) return rc; - rc = _oni_write_config(ctx, ONI_CONFIG_REG_VALUE, value); - if (rc) return rc; + int rc = _oni_read_config(ctx, ONI_OP_RI_TRIGGER, &trig); + if (rc) + return rc; + + if (trig != 0) + return ONI_ERETRIG; + + size_t done = 0; + int res = ONI_ESUCCESS; + while (done < num) + { + size_t batch = min((num - done), ctx->controller_caps.max_register_q_size); + rc = ctx->driver.prepare_register_operation(ctx->driver.ctx, batch); + if (rc) + REG_ERROR(rc); + for (size_t i = 0; i < batch; i++) + { + size_t idx = done + i; + oni_reg_optype_t op = operations[idx].optype; + + rc = _oni_write_config(ctx, ONI_OP_RI_DEV_ADDR, operations[idx].dev_idx); + if (rc) + REG_ERROR(rc); + ; + rc = _oni_write_config(ctx, ONI_OP_RI_REG_ADDR, operations[idx].addr); + if (rc) + REG_ERROR(rc); + if (op == ONI_REG_OP_WRITE) { + rc = _oni_write_config(ctx, ONI_OP_RI_REG_VAL, operations[idx].value); + if (rc) + REG_ERROR(rc); + } - oni_reg_val_t rw = 1; - rc = _oni_write_config(ctx, ONI_CONFIG_RW, rw); - if (rc) return rc; + oni_reg_val_t rw = (oni_reg_val_t)op; + rc = _oni_write_config(ctx, ONI_OP_RI_RW, rw); + if (rc) + REG_ERROR(rc); - trig = 1; - rc = _oni_write_config(ctx, ONI_CONFIG_TRIG, trig); - if (rc) return rc; + trig = 1; + rc = _oni_write_config(ctx, ONI_OP_RI_TRIGGER, trig); + if (rc) + REG_ERROR(rc); + } + rc = ctx->driver.commit_register_operation(ctx->driver.ctx); + if (rc) + REG_ERROR(rc); + for (size_t i = 0; i < batch; i++) + { + size_t idx = done + i; + char response[20]; + oni_signal_t sig_type = NULLSIG; + oni_signal_t filter + = CONFIGRACK | CONFIGRNACK | CONFIGWACK | CONFIGWNACK; + + _oni_pump_signal_data(ctx, filter, &sig_type, response, 20); + if (operations[idx].optype == ONI_REG_OP_READ) + { + if (sig_type == CONFIGRACK) + { + operations[idx].timestamp = *(oni_fifo_time_t *)(response + 0); + operations[idx].hub_timestamp = *(oni_fifo_time_t *)(response + 8); + operations[idx].value = *(oni_reg_val_t *)(response + 16); + operations[idx].result = ONI_ESUCCESS; + } + else if (sig_type == CONFIGRNACK) + { + operations[idx].result = ONI_EDREGREADFAILURE; + res = ONI_EBATCHREG; + } + else + { + // NB: if an invalid type is here, something has gone very wrong + return ONI_EOUTOFSEQUENCE; + } + } + else + { + if (sig_type == CONFIGWACK) { + operations[idx].timestamp = *(oni_fifo_time_t *)(response + 0); + operations[idx].hub_timestamp = *(oni_fifo_time_t *)(response + 8); + operations[idx].result = ONI_ESUCCESS; + } + else if (sig_type == CONFIGWNACK) + { + operations[idx].result = ONI_EDREGWRITEFAILURE; + res = ONI_EBATCHREG; + } + else + { + // NB: if an invalid type is here, something has gone very wrong + return ONI_EOUTOFSEQUENCE; + } + } + + } - // Wait for response from hardware - oni_signal_t type; - rc = _oni_pump_signal_type(ctx, CONFIGWACK | CONFIGWNACK, &type); - if (rc) return rc; + done += batch; + } + return res; - if (type == CONFIGWNACK) return ONI_EWRITEFAILURE; +} +#undef REG_ERROR - return ONI_ESUCCESS; +int oni_write_reg(const oni_ctx ctx, + oni_dev_idx_t dev_idx, + oni_reg_addr_t addr, + oni_reg_val_t value) +{ + oni_reg_operation_t op = {.dev_idx = dev_idx, + .addr = addr, + .value = value, + .optype = ONI_REG_OP_WRITE}; + int rc = oni_reg_access(ctx, &op, 1); + + int res; + if (rc == ONI_ESUCCESS || rc == ONI_EBATCHREG) + { + res = op.result; + } + else + { + res = rc; + } + res = rc; + + return res; } int oni_read_reg(const oni_ctx ctx, - oni_dev_idx_t dev_idx, - oni_reg_addr_t addr, - oni_reg_val_t *value) + oni_dev_idx_t dev_idx, + oni_reg_addr_t addr, + oni_reg_val_t* value) { - assert(ctx != NULL && "Context is NULL"); - assert(ctx->run_state > UNINITIALIZED && "Context must be INITIALIZED."); - - // Make sure we are not already in config triggered state - oni_reg_val_t trig = 0; - int rc = _oni_read_config(ctx, ONI_CONFIG_TRIG, &trig); - if (rc) return rc; - - if (trig != 0) return ONI_ERETRIG; - - // Set configuration registers and trigger a write - rc = _oni_write_config(ctx, ONI_CONFIG_DEV_IDX, dev_idx); - if (rc) return rc; - rc = _oni_write_config(ctx, ONI_CONFIG_REG_ADDR, addr); - if (rc) return rc; - - oni_reg_val_t rw = 0; - rc = _oni_write_config(ctx, ONI_CONFIG_RW, rw); - if (rc) return rc; - - trig = 1; - rc = _oni_write_config(ctx, ONI_CONFIG_TRIG, trig); - if (rc) return rc; - - // Wait for response from hardware - oni_signal_t type; - rc = _oni_pump_signal_type(ctx, CONFIGRACK | CONFIGRNACK, &type); - if (rc) return rc; - - if (type == CONFIGRNACK) return ONI_EREADFAILURE; - - rc = _oni_read_config(ctx, ONI_CONFIG_REG_VALUE, value); - if (rc) return rc; + oni_reg_operation_t op = {.dev_idx = dev_idx, + .addr = addr, + .optype = ONI_REG_OP_READ}; + int rc = oni_reg_access(ctx, &op, 1); + + int res; + if (rc == ONI_ESUCCESS || rc == ONI_EBATCHREG) { + res = op.result; + *value = op.value; + } else { + res = rc; + } + res = rc; - return ONI_ESUCCESS; + return res; } // NB: Although it seems that with fixed sized reads, we should be able to just @@ -907,11 +973,80 @@ const char *oni_error_str(int err) { return "ONI Controller is not compatible with driver translator"; } + case ONI_EDREGREADFAILURE: { + return "Failure to read from a device register"; + } + case ONI_EDREGWRITEFAILURE: { + return "Failure to write to a device register"; + } + case ONI_EBATCHREG: { + return "Batch operation completed but not all register accesses were succesful"; + } + case ONI_EOUTOFSEQUENCE: + { + return "Received response is out of sequence"; + } default: return "Unknown error"; } } +void oni_create_reg_read_continuous(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t start, + size_t num) +{ + for (size_t i = 0; i < num; i++) + { + ops[i].dev_idx = dev_idx; + ops[i].addr = start + i; + ops[i].optype = ONI_REG_OP_READ; + } +} + +void oni_create_reg_read_sparse(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t *addresses, + size_t num) +{ + for (size_t i = 0; i < num; i++) + { + ops[i].dev_idx = dev_idx; + ops[i].addr = addresses[i]; + ops[i].optype = ONI_REG_OP_READ; + } +} + +void oni_create_reg_write_continuous(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t start, + oni_reg_val_t *values, + size_t num) +{ + for (size_t i = 0; i < num; i++) + { + ops[i].dev_idx = dev_idx; + ops[i].addr = start + i; + ops[i].value = values[i]; + ops[i].optype = ONI_REG_OP_WRITE; + } +} + +void oni_create_reg_write_sparse(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t *addresses, + oni_reg_val_t *values, + size_t num) +{ + for (size_t i = 0; i < num; i++) + { + ops[i].dev_idx = dev_idx; + ops[i].addr = addresses[i]; + ops[i].value = values[i]; + ops[i].optype = ONI_REG_OP_WRITE; + } +} + static inline oni_dev_idx_t _oni_hash32(oni_dev_idx_t x) { x = ((x >> 16) ^ x) * 0x45d9f3b; diff --git a/api/liboni/oni.h b/api/liboni/oni.h index 09704d8..15d7220 100644 --- a/api/liboni/oni.h +++ b/api/liboni/oni.h @@ -55,6 +55,38 @@ typedef struct { } oni_frame_t; +// Human-readable specification version +typedef struct { + uint8_t major; + uint8_t minor; + uint8_t patch; + uint8_t reserved; +} oni_version_t; + +// ONI spec 2.0 controller capabilities +typedef struct { + oni_version_t spec_ver; + oni_reg_val_t read_str_align; + oni_reg_val_t write_str_align; + oni_reg_val_t max_register_q_size; + oni_reg_val_t num_sync_devs; +} oni_controller_caps_t; + +typedef enum { + ONI_REG_OP_READ = 0, + ONI_REG_OP_WRITE = 1 +} oni_reg_optype_t; + +typedef struct { + oni_dev_idx_t dev_idx; + oni_reg_addr_t addr; + oni_reg_optype_t optype; + oni_reg_val_t value; + oni_fifo_time_t timestamp; + oni_fifo_time_t hub_timestamp; + int result; +} oni_reg_operation_t; + // Context management ONI_EXPORT oni_ctx oni_create_ctx(const char *drv_name); ONI_EXPORT int oni_init_ctx(oni_ctx ctx, int host_idx); @@ -71,6 +103,8 @@ ONI_EXPORT int oni_set_driver_opt(oni_ctx ctx, int drv_opt, const void *value, s // Hardware inspection, manipulation, and IO ONI_EXPORT int oni_read_reg(const oni_ctx ctx, oni_dev_idx_t dev_idx, oni_reg_addr_t addr, oni_reg_val_t *value); ONI_EXPORT int oni_write_reg(const oni_ctx ctx, oni_dev_idx_t dev_idx, oni_reg_addr_t addr, oni_reg_val_t value); +ONI_EXPORT int oni_reg_access(const oni_ctx ctx, oni_reg_operation_t* operations, int num); + ONI_EXPORT int oni_read_frame(const oni_ctx ctx, oni_frame_t **frame); ONI_EXPORT int oni_create_frame(const oni_ctx ctx, oni_frame_t **frame, oni_dev_idx_t dev_idx, void *data, size_t data_sz); ONI_EXPORT int oni_write_frame(const oni_ctx ctx, const oni_frame_t *frame); @@ -80,6 +114,27 @@ ONI_EXPORT void oni_destroy_frame(oni_frame_t *frame); ONI_EXPORT void oni_version(int *major, int *minor, int *patch); ONI_EXPORT const oni_driver_info_t* oni_get_driver_info(const oni_ctx ctx); ONI_EXPORT const char *oni_error_str(int err); +ONI_EXPORT void oni_create_reg_read_continuous(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t start, + size_t num); + +ONI_EXPORT void oni_create_reg_read_sparse(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t* addresses, + size_t num); + +ONI_EXPORT void oni_create_reg_write_continuous(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t start, + oni_reg_val_t* values, + size_t num); + +ONI_EXPORT void oni_create_reg_write_sparse(oni_reg_operation_t *ops, + oni_dev_idx_t dev_idx, + oni_reg_addr_t *addresses, + oni_reg_val_t *values, + size_t num); #ifdef __cplusplus } diff --git a/api/liboni/onidefs.h b/api/liboni/onidefs.h index 89e4aa5..c805aa7 100644 --- a/api/liboni/onidefs.h +++ b/api/liboni/onidefs.h @@ -54,28 +54,15 @@ enum { ONI_EDEVIDXREPEAT = -26, // Device table contains repeated device indices ONI_EPROTCONFIG = -27, // Attempted to directly read or write a protected configuration option ONI_EBADFRAME = -28, // Received malformed frame - ONI_EBADCONTROLLER = -29, // ONI Controller is not compatible - + ONI_EBADCONTROLLER = -29, // ONI Controller is not compatible with driver translator + ONI_EDREGREADFAILURE = -30, // Failure to read from a evice register + ONI_EDREGWRITEFAILURE = -31, // Failure to write to a device register + ONI_EBATCHREG = - 32, // Batch operation completed but not all register accesses were succesful + ONI_EOUTOFSEQUENCE = -33, // Received response is out of sequence // NB: Always at bottom - ONI_MINERRORNUM = -30 + ONI_MINERRORNUM = -34 }; -// Registers available in the specification -typedef enum { - ONI_CONFIG_DEV_IDX = 0, - ONI_CONFIG_REG_ADDR, - ONI_CONFIG_REG_VALUE, - ONI_CONFIG_RW, - ONI_CONFIG_TRIG, - ONI_CONFIG_RUNNING, - ONI_CONFIG_RESET, - ONI_CONFIG_SYSCLKHZ, - ONI_CONFIG_ACQCLKHZ, - ONI_CONFIG_RESETACQCOUNTER, - ONI_CONFIG_HWADDRESS, - ONI_CONFIG_CUSTOMBEGIN, -} oni_config_t; - // Fixed width device types // TODO: I feel like oni.h and onidefs.h should only deal with standard signed // or unsigned integer types and it should be the drivers' job to translate diff --git a/api/liboni/onidriver.h b/api/liboni/onidriver.h index a73f335..54967bb 100644 --- a/api/liboni/onidriver.h +++ b/api/liboni/onidriver.h @@ -20,6 +20,30 @@ typedef enum { // Generic pointer for driver-specific options typedef void *oni_driver_ctx; +// Registers available in the specification +typedef enum { + // operation registers (0x0000-0x3FFF) + ONI_OP_SOFT_RESET = 0x0000, + ONI_OP_ACQ_RUNNING = 0x0001, + ONI_OP_SYS_CLK_HZ = 0x0002, + ONI_OP_ACQ_CLK_HZ = 0x0003, + ONI_OP_ACQ_CNT_RESET = 0x0004, + ONI_OP_SYNC_HW_ADDR = 0x0005, + + ONI_OP_RI_DEV_ADDR = 0x0006, + ONI_OP_RI_REG_ADDR = 0x0007, + ONI_OP_RI_REG_VAL = 0x0008, + ONI_OP_RI_RW = 0x0009, + ONI_OP_RI_TRIGGER = 0x000A, + + //Specification parameters (0x4000-0x7FFF) + ONI_SPEC_VER = 0x4000, + ONI_ATTR_READ_STR_ALIGN = 0x4001, + ONI_ATTR_WRITE_STR_ALIGN = 0x4002, + ONI_ATTR_MAX_REGISTER_Q_SIZE = 0x4003, + ONI_ATTR_NUM_SYNC_DEVS = 0x4004 +} oni_config_t; + // Prototype functions for drivers. Every driver has to implement these #ifndef ONI_DRIVER_IGNORE_FUNCTION_PROTOTYPES // For use only for including in the main library driver loader #ifdef _WIN32 @@ -28,16 +52,53 @@ typedef void *oni_driver_ctx; #define ONI_DRIVER_EXPORT #endif +/* Driver lifetime management */ +/****************************************************************************/ + +// Creates a driver translator instance ONI_DRIVER_EXPORT oni_driver_ctx oni_driver_create_ctx(void); +// Destroys the friver translatos instance ONI_DRIVER_EXPORT int oni_driver_destroy_ctx(oni_driver_ctx); // Initialize driver. Argument is the host device index ONI_DRIVER_EXPORT int oni_driver_init(oni_driver_ctx driver_ctx, int host_idx); + +/* Stream channel operations */ +/****************************************************************************/ + +// Reads from a stream channel ONI_DRIVER_EXPORT int oni_driver_read_stream(oni_driver_ctx driver_ctx, oni_read_stream_t stream, void *data, size_t size); +// Writes to a stream channel ONI_DRIVER_EXPORT int oni_driver_write_stream(oni_driver_ctx driver_ctx, oni_write_stream_t stream, const char *data, size_t size); + +/* Configuration channel operations */ +/***************************************************************************/ + +// Reads from a configuration address ONI_DRIVER_EXPORT int oni_driver_read_config(oni_driver_ctx driver_ctx, oni_config_t config, oni_reg_val_t *value); +// Writes to a configuration address ONI_DRIVER_EXPORT int oni_driver_write_config(oni_driver_ctx driver_ctx, oni_config_t config, oni_reg_val_t value); +/* Device register interface */ +/****************************************************************************/ +/* These must be called before andafter a device register access sequence */ +/* if multiple register are queued, prepare must be called before the first */ +/* operation and commit after the last */ +/* Driver translators might have empty implementations of these if not */ +/* required by its operation */ +/****************************************************************************/ + +// Inform the driver translator that a sequence of num device register operations is +// going to be performed next +ONI_DRIVER_EXPORT int oni_driver_prepare_register_operation(oni_driver_ctx driver_ctx, size_t num); + +// Commits a register sequence of device register operations +// previously started by oni_driver_prepare_register_operation +ONI_DRIVER_EXPORT int oni_driver_commit_register_operation(oni_driver_ctx driver_ctx); + +// Cancels a pending register operation if there was some error +ONI_DRIVER_EXPORT void oni_driver_cancel_register_operation(oni_driver_ctx driver_ctx); + // This gets called when oni_set_opt is called. This method does not need to // perform any configuration but it is provided for the driver to do some // internal adjustments if required @@ -45,6 +106,7 @@ ONI_DRIVER_EXPORT int oni_driver_set_opt_callback(oni_driver_ctx driver_ctx, int // Functions to get and set set driver-specific options. This kind of optiosn // must be avoided when necessary to allow for a general interface +// Internally, thse functions can call the hardware-specific registers (0x8000-0xBFFF) if needed ONI_DRIVER_EXPORT int oni_driver_set_opt(oni_driver_ctx driver_ctx, int driver_option, const void *value, size_t option_len); ONI_DRIVER_EXPORT int oni_driver_get_opt(oni_driver_ctx driver_ctx, int driver_option, void *value, size_t* option_len); diff --git a/api/liboni/onidriverloader.c b/api/liboni/onidriverloader.c index d0239ef..ac21835 100644 --- a/api/liboni/onidriverloader.c +++ b/api/liboni/onidriverloader.c @@ -88,6 +88,9 @@ int oni_create_driver(const char* lib_name, oni_driver_t* driver) LOAD_FUNCTION(write_stream); LOAD_FUNCTION(read_config); LOAD_FUNCTION(write_config); + LOAD_FUNCTION(prepare_register_operation); + LOAD_FUNCTION(commit_register_operation); + LOAD_FUNCTION(cancel_register_operation); LOAD_FUNCTION(set_opt_callback); LOAD_FUNCTION(set_opt); LOAD_FUNCTION(get_opt); diff --git a/api/liboni/onidriverloader.h b/api/liboni/onidriverloader.h index 72fe3a0..36eed68 100644 --- a/api/liboni/onidriverloader.h +++ b/api/liboni/onidriverloader.h @@ -25,6 +25,10 @@ typedef int(*oni_driver_write_stream_f)(oni_driver_ctx, oni_write_stream_t, cons typedef int(*oni_driver_read_config_f)(oni_driver_ctx, oni_config_t, oni_reg_val_t *); typedef int(*oni_driver_write_config_f)(oni_driver_ctx, oni_config_t, oni_reg_val_t); +typedef int(*oni_driver_prepare_register_operation_f)(oni_driver_ctx, size_t); +typedef int(*oni_driver_commit_register_operation_f)(oni_driver_ctx); +typedef void(*oni_driver_cancel_register_operation_f)(oni_driver_ctx); + typedef int(*oni_driver_set_opt_f)(oni_driver_ctx, int, const void *, size_t); typedef int(*oni_driver_get_opt_f)(oni_driver_ctx, int, void *, size_t *); typedef int(*oni_driver_set_opt_callback_f)(oni_driver_ctx, int, const void *, size_t); @@ -42,6 +46,9 @@ typedef struct oni_driver { oni_driver_write_stream_f write_stream; oni_driver_read_config_f read_config; oni_driver_write_config_f write_config; + oni_driver_prepare_register_operation_f prepare_register_operation; + oni_driver_commit_register_operation_f commit_register_operation; + oni_driver_cancel_register_operation_f cancel_register_operation; oni_driver_set_opt_callback_f set_opt_callback; oni_driver_set_opt_f set_opt; oni_driver_get_opt_f get_opt; From 6bfd5ce74fc75e6f424bd5ace8915aa392914bc0 Mon Sep 17 00:00:00 2001 From: Aaron Cuevas Lopez <1830303+aacuevas@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:44:14 +0100 Subject: [PATCH 2/2] Remove debug lines and add clarifying comment to single-reg access --- api/liboni/oni.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/liboni/oni.c b/api/liboni/oni.c index d88e03d..c1a9085 100644 --- a/api/liboni/oni.c +++ b/api/liboni/oni.c @@ -658,6 +658,10 @@ int oni_write_reg(const oni_ctx ctx, int rc = oni_reg_access(ctx, &op, 1); int res; + + // NB: for single accesses such as this, we just return + // the particular ESUCCESS or EREG(READ/WRITE)FAILURE + // message stored on the result field if (rc == ONI_ESUCCESS || rc == ONI_EBATCHREG) { res = op.result; @@ -666,7 +670,6 @@ int oni_write_reg(const oni_ctx ctx, { res = rc; } - res = rc; return res; } @@ -688,7 +691,6 @@ int oni_read_reg(const oni_ctx ctx, } else { res = rc; } - res = rc; return res; }