From 7d61bc50bb9f7f5276a9cf10b8d81575ae2780d5 Mon Sep 17 00:00:00 2001 From: Lindolfo Meira Date: Tue, 11 Aug 2026 13:18:35 -0300 Subject: [PATCH 1/3] sipeed-slogic-combo8: Initial driver skeleton Add the basic structure of the SiPEED SLogic Combo8 logic analyzer driver: USB device scanning, device open/close and the driver registration with the supported scan and device options. Signed-off-by: Lindolfo Meira --- Makefile.am | 6 + configure.ac | 1 + src/hardware/sipeed-slogic-combo8/api.c | 154 +++++++++++++++++++ src/hardware/sipeed-slogic-combo8/protocol.c | 43 ++++++ src/hardware/sipeed-slogic-combo8/protocol.h | 35 +++++ 5 files changed, 239 insertions(+) create mode 100644 src/hardware/sipeed-slogic-combo8/api.c create mode 100644 src/hardware/sipeed-slogic-combo8/protocol.c create mode 100644 src/hardware/sipeed-slogic-combo8/protocol.h diff --git a/Makefile.am b/Makefile.am index 3b68142f2..899a7e569 100644 --- a/Makefile.am +++ b/Makefile.am @@ -701,6 +701,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/siglent-sds/protocol.c \ src/hardware/siglent-sds/api.c endif +if HW_SIPEED_SLOGIC_COMBO8 +src_libdrivers_la_SOURCES += \ + src/hardware/sipeed-slogic-combo8/protocol.h \ + src/hardware/sipeed-slogic-combo8/protocol.c \ + src/hardware/sipeed-slogic-combo8/api.c +endif if HW_SYSCLK_LWLA src_libdrivers_la_SOURCES += \ src/hardware/sysclk-lwla/lwla.h \ diff --git a/configure.ac b/configure.ac index 024dd4da2..4e1aeb7ad 100644 --- a/configure.ac +++ b/configure.ac @@ -385,6 +385,7 @@ SR_DRIVER([serial DMM], [serial-dmm], [serial_comm]) SR_DRIVER([serial LCR], [serial-lcr], [serial_comm]) SR_DRIVER([SIGLENT SDL10x0], [siglent-sdl10x0]) SR_DRIVER([Siglent SDS], [siglent-sds]) +SR_DRIVER([SiPEED Slogic Combo8], [sipeed-slogic-combo8]) SR_DRIVER([Sysclk LWLA], [sysclk-lwla], [libusb]) SR_DRIVER([Sysclk SLA5032], [sysclk-sla5032], [libusb]) SR_DRIVER([Teleinfo], [teleinfo], [serial_comm]) diff --git a/src/hardware/sipeed-slogic-combo8/api.c b/src/hardware/sipeed-slogic-combo8/api.c new file mode 100644 index 000000000..8dff303b9 --- /dev/null +++ b/src/hardware/sipeed-slogic-combo8/api.c @@ -0,0 +1,154 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2026 Lindolfo Meira + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "protocol.h" + +static struct sr_dev_driver sipeed_slogic_combo8_driver_info; + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + struct drv_context *drvc; + GSList *devices; + + (void)options; + + devices = NULL; + drvc = di->context; + drvc->instances = NULL; + + /* TODO: scan for devices, either based on a SR_CONF_CONN option + * or on a USB scan. */ + + return devices; +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + (void)sdi; + + /* TODO: get handle from sdi->conn and open it. */ + + return SR_OK; +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + (void)sdi; + + /* TODO: get handle from sdi->conn and close it. */ + + return SR_OK; +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + int ret; + + (void)sdi; + (void)data; + (void)cg; + + ret = SR_OK; + switch (key) { + /* TODO */ + default: + return SR_ERR_NA; + } + + return ret; +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + int ret; + + (void)sdi; + (void)data; + (void)cg; + + ret = SR_OK; + switch (key) { + /* TODO */ + default: + ret = SR_ERR_NA; + } + + return ret; +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + int ret; + + (void)sdi; + (void)data; + (void)cg; + + ret = SR_OK; + switch (key) { + /* TODO */ + default: + return SR_ERR_NA; + } + + return ret; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) +{ + /* TODO: configure hardware, reset acquisition state, set up + * callbacks and send header packet. */ + + (void)sdi; + + return SR_OK; +} + +static int dev_acquisition_stop(struct sr_dev_inst *sdi) +{ + /* TODO: stop acquisition. */ + + (void)sdi; + + return SR_OK; +} + +static struct sr_dev_driver sipeed_slogic_combo8_driver_info = { + .name = "sipeed-slogic-combo8", + .longname = "SiPEED Slogic Combo8", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = std_dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = dev_open, + .dev_close = dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(sipeed_slogic_combo8_driver_info); diff --git a/src/hardware/sipeed-slogic-combo8/protocol.c b/src/hardware/sipeed-slogic-combo8/protocol.c new file mode 100644 index 000000000..2a4e8bd21 --- /dev/null +++ b/src/hardware/sipeed-slogic-combo8/protocol.c @@ -0,0 +1,43 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2026 Lindolfo Meira + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "protocol.h" + +SR_PRIV int sipeed_slogic_combo8_receive_data(int fd, int revents, void *cb_data) +{ + const struct sr_dev_inst *sdi; + struct dev_context *devc; + + (void)fd; + + sdi = cb_data; + if (!sdi) + return TRUE; + + devc = sdi->priv; + if (!devc) + return TRUE; + + if (revents == G_IO_IN) { + /* TODO */ + } + + return TRUE; +} diff --git a/src/hardware/sipeed-slogic-combo8/protocol.h b/src/hardware/sipeed-slogic-combo8/protocol.h new file mode 100644 index 000000000..6c14b0729 --- /dev/null +++ b/src/hardware/sipeed-slogic-combo8/protocol.h @@ -0,0 +1,35 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2026 Lindolfo Meira + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_SIPEED_SLOGIC_COMBO8_PROTOCOL_H +#define LIBSIGROK_HARDWARE_SIPEED_SLOGIC_COMBO8_PROTOCOL_H + +#include +#include +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "sipeed-slogic-combo8" + +struct dev_context { +}; + +SR_PRIV int sipeed_slogic_combo8_receive_data(int fd, int revents, void *cb_data); + +#endif From 38d8b848f286f4adaa52f0156bfd005bf2864e3c Mon Sep 17 00:00:00 2001 From: Lindolfo Meira Date: Tue, 11 Aug 2026 14:28:02 -0300 Subject: [PATCH 2/3] sipeed-slogic-combo8: Actual driver implementation Implement the acquisition engine: the vendor USB protocol to start and stop streaming, the bulk-transfer handling with the fixed-size transfer buffer, and the bit expansion for the packed 2ch/4ch capture modes. Signed-off-by: Lindolfo Meira --- README.devices | 1 + configure.ac | 2 +- src/hardware/sipeed-slogic-combo8/api.c | 295 ++++++++--- src/hardware/sipeed-slogic-combo8/protocol.c | 526 ++++++++++++++++++- src/hardware/sipeed-slogic-combo8/protocol.h | 211 +++++++- 5 files changed, 966 insertions(+), 69 deletions(-) diff --git a/README.devices b/README.devices index 17f59f629..75bb70498 100644 --- a/README.devices +++ b/README.devices @@ -159,6 +159,7 @@ The following drivers/devices do not need any firmware upload: - serial-dmm (including all subdrivers) - serial-lcr (including all subdrivers) - siglent-sds + - sipeed-slogic-combo8 - teleinfo - testo - tondaj-sl-814 diff --git a/configure.ac b/configure.ac index 4e1aeb7ad..42cbe4ca6 100644 --- a/configure.ac +++ b/configure.ac @@ -385,7 +385,7 @@ SR_DRIVER([serial DMM], [serial-dmm], [serial_comm]) SR_DRIVER([serial LCR], [serial-lcr], [serial_comm]) SR_DRIVER([SIGLENT SDL10x0], [siglent-sdl10x0]) SR_DRIVER([Siglent SDS], [siglent-sds]) -SR_DRIVER([SiPEED Slogic Combo8], [sipeed-slogic-combo8]) +SR_DRIVER([SiPEED Slogic Combo8], [sipeed-slogic-combo8], [libusb]) SR_DRIVER([Sysclk LWLA], [sysclk-lwla], [libusb]) SR_DRIVER([Sysclk SLA5032], [sysclk-sla5032], [libusb]) SR_DRIVER([Teleinfo], [teleinfo], [serial_comm]) diff --git a/src/hardware/sipeed-slogic-combo8/api.c b/src/hardware/sipeed-slogic-combo8/api.c index 8dff303b9..31e76ba9e 100644 --- a/src/hardware/sipeed-slogic-combo8/api.c +++ b/src/hardware/sipeed-slogic-combo8/api.c @@ -1,6 +1,7 @@ /* * This file is part of the libsigrok project. * + * Copyright (C) 2023 taorye * Copyright (C) 2026 Lindolfo Meira * * This program is free software: you can redistribute it and/or modify @@ -20,116 +21,292 @@ #include #include "protocol.h" -static struct sr_dev_driver sipeed_slogic_combo8_driver_info; +static const uint32_t scanopts[] = { + SR_CONF_CONN, +}; + +static const uint32_t drvopts[] = { + SR_CONF_LOGIC_ANALYZER, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; static GSList *scan(struct sr_dev_driver *di, GSList *options) { + int ret; + int i; + struct sr_dev_inst *sdi; + struct sr_usb_dev_inst *usb; struct drv_context *drvc; + struct dev_context *devc; + struct sr_config *option; + struct libusb_device_descriptor des; GSList *devices; + GSList *l, *conn_devices; + const char *conn; + char cbuf[128]; + char *iManufacturer, *iProduct, *iSerialNumber, *iPortPath; - (void)options; - + conn = NULL; devices = NULL; drvc = di->context; - drvc->instances = NULL; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ + for (l = options; l; l = l->next) { + option = l->data; + switch (option->key) { + case SR_CONF_CONN: + conn = g_variant_get_string(option->data, NULL); + sr_info("Use conn: %s", conn); + break; + default: + sr_warn("Unhandled option key: %u", option->key); + } + } + + if (!conn) + conn = USB_CONN; + + conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn); + for (l = conn_devices; l; l = l->next) { + usb = l->data; + ret = sr_usb_open(drvc->sr_ctx->libusb_ctx, usb); + if (ret != SR_OK) { + sr_usb_dev_inst_free(usb); + continue; + } + + libusb_get_device_descriptor(libusb_get_device(usb->devhdl), + &des); + + cbuf[0] = '\0'; + libusb_get_string_descriptor_ascii(usb->devhdl, + des.iManufacturer, + (unsigned char *)cbuf, + sizeof(cbuf)); + iManufacturer = g_strdup(cbuf[0] ? cbuf : "Sipeed"); + + cbuf[0] = '\0'; + libusb_get_string_descriptor_ascii(usb->devhdl, des.iProduct, + (unsigned char *)cbuf, + sizeof(cbuf)); + iProduct = g_strdup(cbuf[0] ? cbuf : "SLogic Analyzer"); + + cbuf[0] = '\0'; + libusb_get_string_descriptor_ascii(usb->devhdl, + des.iSerialNumber, + (unsigned char *)cbuf, + sizeof(cbuf)); + iSerialNumber = g_strdup(cbuf); + + cbuf[0] = '\0'; + usb_get_port_path(libusb_get_device(usb->devhdl), cbuf, + sizeof(cbuf)); + iPortPath = g_strdup(cbuf); + + sr_usb_close(usb); + + sdi = sr_dev_inst_user_new(iManufacturer, iProduct, NULL); + g_free(iManufacturer); + g_free(iProduct); + + if (!sdi) { + g_free(iSerialNumber); + g_free(iPortPath); + sr_usb_dev_inst_free(usb); + continue; + } + + sdi->driver = di; + sdi->serial_num = iSerialNumber; + sdi->connection_id = iPortPath; + sdi->status = SR_ST_INACTIVE; + sdi->conn = usb; + sdi->inst_type = SR_INST_USB; + + for (i = 0; i < 8; i++) { + sr_snprintf_ascii(cbuf, sizeof(cbuf), "D%d", i); + sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, cbuf); + } + + devc = g_malloc0(sizeof(struct dev_context)); + devc_set_samplerate(devc, samplerates[7]); + sdi->priv = devc; + + devices = g_slist_append(devices, sdi); + } + + g_slist_free(conn_devices); - return devices; + return std_scan_complete(di, devices); } static int dev_open(struct sr_dev_inst *sdi) { - (void)sdi; + int ret; + struct sr_usb_dev_inst *usb; + struct dev_context *devc; + struct sr_dev_driver *di; + struct drv_context *drvc; - /* TODO: get handle from sdi->conn and open it. */ + if (!sdi) + return SR_ERR_DEV_CLOSED; - return SR_OK; + usb = sdi->conn; + devc = sdi->priv; + di = sdi->driver; + drvc = di->context; + + ret = sr_usb_open(drvc->sr_ctx->libusb_ctx, usb); + if (ret != SR_OK) + return ret; + + ret = libusb_claim_interface(usb->devhdl, 0); + if (ret != LIBUSB_SUCCESS) { + switch (ret) { + case LIBUSB_ERROR_BUSY: + sr_err("Unable to claim USB interface. Another " + "program or driver has already claimed it."); + break; + case LIBUSB_ERROR_NO_DEVICE: + sr_err("Device has been disconnected."); + break; + default: + sr_err("Unable to claim interface: %s.", + libusb_error_name(ret)); + break; + } + sr_usb_close(usb); + return SR_ERR; + } + + if (devc->cur_samplerate == 0) + devc_set_samplerate(devc, samplerates[7]); + + return std_dummy_dev_open(sdi); } static int dev_close(struct sr_dev_inst *sdi) { - (void)sdi; - - /* TODO: get handle from sdi->conn and close it. */ + int ret; + struct sr_usb_dev_inst *usb; + + if (!sdi) + return SR_ERR_ARG; + + usb = sdi->conn; + + if (usb && usb->devhdl) { + /* + * Note: do not gate this on sdi->status. sr_dev_close() sets + * the status to SR_ST_INACTIVE before invoking this callback, + * so gating on SR_ST_ACTIVE would leak the libusb handle and + * keep interface 0 claimed, making a subsequent open of the + * same device fail with BUSY. + */ + ret = libusb_release_interface(usb->devhdl, 0); + if (ret != LIBUSB_SUCCESS && ret != LIBUSB_ERROR_NO_DEVICE) + sr_err("Unable to release Interface: %s.", + libusb_error_name(ret)); + sr_usb_close(usb); + } - return SR_OK; + return std_dummy_dev_close(sdi); } static int config_get(uint32_t key, GVariant **data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; - (void)sdi; - (void)data; (void)cg; - ret = SR_OK; + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + switch (key) { - /* TODO */ + case SR_CONF_SAMPLERATE: + *data = g_variant_new_uint64(devc->cur_samplerate); + break; + case SR_CONF_LIMIT_SAMPLES: + *data = g_variant_new_uint64(devc->limit_samples); + break; default: return SR_ERR_NA; } - return ret; + return SR_OK; } static int config_set(uint32_t key, GVariant *data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; + struct sr_channel *ch; + GSList *l; + size_t idx; - (void)sdi; - (void)data; (void)cg; - ret = SR_OK; + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + switch (key) { - /* TODO */ + case SR_CONF_SAMPLERATE: + if (std_u64_idx(data, ARRAY_AND_SIZE(samplerates)) < 0) { + return SR_ERR_ARG; + } else { + devc_set_samplerate(devc, g_variant_get_uint64(data)); + idx = 0; + for (l = sdi->channels; l; l = l->next, idx++) { + ch = l->data; + if (ch->type == SR_CHANNEL_LOGIC) { + sr_dev_channel_enable( + ch, (idx < + devc->cur_samplechannel) ? + TRUE : + FALSE); + } else { + return SR_ERR_BUG; + } + } + } + break; + case SR_CONF_LIMIT_SAMPLES: + devc->limit_samples = g_variant_get_uint64(data); + break; default: - ret = SR_ERR_NA; + return SR_ERR_NA; } - return ret; + return SR_OK; } static int config_list(uint32_t key, GVariant **data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { - int ret; - - (void)sdi; - (void)data; - (void)cg; - - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, + devopts); + case SR_CONF_SAMPLERATE: + *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates)); + break; default: return SR_ERR_NA; } - return ret; -} - -static int dev_acquisition_start(const struct sr_dev_inst *sdi) -{ - /* TODO: configure hardware, reset acquisition state, set up - * callbacks and send header packet. */ - - (void)sdi; - - return SR_OK; -} - -static int dev_acquisition_stop(struct sr_dev_inst *sdi) -{ - /* TODO: stop acquisition. */ - - (void)sdi; - return SR_OK; } @@ -147,8 +324,8 @@ static struct sr_dev_driver sipeed_slogic_combo8_driver_info = { .config_list = config_list, .dev_open = dev_open, .dev_close = dev_close, - .dev_acquisition_start = dev_acquisition_start, - .dev_acquisition_stop = dev_acquisition_stop, + .dev_acquisition_start = sipeed_slogic_combo8_acquisition_start, + .dev_acquisition_stop = sipeed_slogic_combo8_acquisition_stop, .context = NULL, }; SR_REGISTER_DEV_DRIVER(sipeed_slogic_combo8_driver_info); diff --git a/src/hardware/sipeed-slogic-combo8/protocol.c b/src/hardware/sipeed-slogic-combo8/protocol.c index 2a4e8bd21..8a9ce0ce9 100644 --- a/src/hardware/sipeed-slogic-combo8/protocol.c +++ b/src/hardware/sipeed-slogic-combo8/protocol.c @@ -1,6 +1,7 @@ /* * This file is part of the libsigrok project. * + * Copyright (C) 2023 taorye * Copyright (C) 2026 Lindolfo Meira * * This program is free software: you can redistribute it and/or modify @@ -20,24 +21,533 @@ #include #include "protocol.h" -SR_PRIV int sipeed_slogic_combo8_receive_data(int fd, int revents, void *cb_data) +static int handle_events(int fd, int revents, void *cb_data); + +/* Forward declaration so finish_acquisition() can be called from + * receive_transfer(). */ +static void finish_acquisition(struct sr_dev_inst *sdi); + +static void submit_data(void *data, size_t len, struct sr_dev_inst *sdi) +{ + struct sr_datafeed_logic logic = { + .length = len, + .unitsize = 1, + .data = data, + }; + + struct sr_datafeed_packet packet = { .type = SR_DF_LOGIC, + .payload = &logic }; + + sr_session_send(sdi, &packet); +} + +static void free_transfer(struct libusb_transfer *transfer) { - const struct sr_dev_inst *sdi; + struct sr_dev_inst *sdi; struct dev_context *devc; + size_t i; + + if (!transfer) + return; + + sdi = transfer->user_data; + devc = sdi->priv; + + for (i = 0; i < NUM_CONCURRENT_TRANSFERS; i++) { + if (devc->transfers[i] == transfer) { + devc->transfers[i] = NULL; + break; + } + } + + g_free(transfer->buffer); + transfer->buffer = NULL; + libusb_free_transfer(transfer); + + if (devc->transfers_used > 0) + devc->transfers_used--; +} + +/* + * Tear down a completed or aborted acquisition: + * - send SR_DF_END to the session + * - remove the USB event source from the main loop + * - free the bit-expansion buffer + */ +static void finish_acquisition(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_dev_driver *di; + struct drv_context *drvc; + + devc = sdi->priv; + di = sdi->driver; + drvc = di->context; + + sr_dbg("finish_acquisition: sending DF_END and removing USB source"); + + usb_source_remove(sdi->session, drvc->sr_ctx); + std_session_send_df_end(sdi); + + if (devc->expand_buf) { + g_free(devc->expand_buf); + devc->expand_buf = NULL; + devc->expand_buf_size = 0; + } +} + +static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) +{ + int ret; + struct sr_dev_inst *sdi; + struct dev_context *devc; + uint8_t *ptr; + uint8_t mask; + size_t len; + size_t step; + size_t expanded_len; + size_t bytes_to_transfer; + size_t i, j; + + sdi = transfer->user_data; + devc = sdi->priv; + + sr_dbg("USB status: %d, actual_length: %d", transfer->status, + transfer->actual_length); + + if (devc->acq_aborted) { + free_transfer(transfer); + if (devc->transfers_used == 0) + finish_acquisition(sdi); + return; + } + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + case LIBUSB_TRANSFER_TIMED_OUT: + /* + * A completed or timed-out transfer that carried no data is + * evidence the device may have stalled. Track consecutive + * empty results and abort once the threshold is reached so we + * don't loop forever; any data-carrying result means the + * stream is healthy and resets. + */ + if (transfer->actual_length == 0) { + devc->empty_transfer_count++; + sr_dbg("Empty transfer (%d/%d)", + devc->empty_transfer_count, MAX_EMPTY_TRANSFERS); + if (devc->empty_transfer_count >= MAX_EMPTY_TRANSFERS) { + sr_warn("Too many consecutive empty transfers, " + "aborting acquisition"); + free_transfer(transfer); + sipeed_slogic_combo8_acquisition_stop(sdi); + if (devc->transfers_used == 0) + finish_acquisition(sdi); + return; + } + } else { + devc->empty_transfer_count = 0; + } + break; + + case LIBUSB_TRANSFER_CANCELLED: + case LIBUSB_TRANSFER_NO_DEVICE: + default: + free_transfer(transfer); + if (devc->transfers_used == 0) + finish_acquisition(sdi); + return; + } + + sr_dbg("Transferring: %zu, transferred: %zu, sum: %zu/%zu", + devc->bytes_transferring, devc->bytes_transferred, + (devc->bytes_transferred + devc->bytes_transferring), + devc->bytes_need_transfer); + + devc->bytes_transferred += transfer->actual_length; + if (devc->bytes_transferring >= (size_t)transfer->length) + devc->bytes_transferring -= transfer->length; + else + devc->bytes_transferring = 0; + + if (transfer->actual_length > 0) { + ptr = transfer->buffer; + len = transfer->actual_length; + + if (devc->cur_samplechannel != 8 && + devc->cur_samplechannel > 0) { + /* + * Packed mode: the firmware packs multiple samples per + * byte. Expand into the pre-allocated buffer (one + * sample per byte). + */ + step = 8 / devc->cur_samplechannel; + mask = 0xff >> (8 - devc->cur_samplechannel); + expanded_len = len * step; + + if (devc->expand_buf && + expanded_len <= devc->expand_buf_size) { + ptr = devc->expand_buf; + } else { + /* + * Fallback: expand_buf missing or too small + * (shouldn't happen with correct sizing in + * acquisition_start). Allocate on the spot. + */ + sr_warn("expand_buf too small or NULL, " + "falling back to g_malloc"); + ptr = g_malloc(expanded_len); + if (!ptr) { + sr_err("Failed to allocate expansion " + "buffer"); + free_transfer(transfer); + sipeed_slogic_combo8_acquisition_stop( + sdi); + return; + } + } + + for (i = 0; i < (size_t)transfer->actual_length; i++) { + for (j = 0; j < step; j++) { + ptr[i * step + j] = + mask & + (transfer->buffer[i] >> + (j * devc->cur_samplechannel)); + } + } + len = expanded_len; + } + + submit_data(ptr, len, sdi); + + /* Free fallback allocation if we couldn't use expand_buf. */ + if (devc->cur_samplechannel != 8 && + devc->cur_samplechannel > 0 && ptr != devc->expand_buf) + g_free(ptr); + } + + /* Determine how many bytes to request in the next submission. */ + bytes_to_transfer = 0; + if (devc->bytes_need_transfer > 0) { + if (devc->bytes_transferred + devc->bytes_transferring < + devc->bytes_need_transfer) { + bytes_to_transfer = devc->bytes_need_transfer - + (devc->bytes_transferred + + devc->bytes_transferring); + } + } else { + /* Continuous mode: keep the pipeline full. */ + bytes_to_transfer = devc->transfers_buffer_size; + } + + if (bytes_to_transfer > devc->transfers_buffer_size) + bytes_to_transfer = devc->transfers_buffer_size; + + if (bytes_to_transfer > 0 && !devc->acq_aborted) { + transfer->length = bytes_to_transfer; + transfer->actual_length = 0; + /* + * Use a stable per-transfer timeout, not one that shrinks as + * transfers complete. + */ + transfer->timeout = devc->timeout; + ret = libusb_submit_transfer(transfer); + if (ret != LIBUSB_SUCCESS) { + sr_warn("Failed to resubmit transfer: %s", + libusb_error_name(ret)); + free_transfer(transfer); + } else { + devc->bytes_transferring += bytes_to_transfer; + } + } else { + free_transfer(transfer); + } + + if (devc->transfers_used == 0) { + if (devc->acq_aborted) { + finish_acquisition(sdi); + } else { + sr_dbg("All transfers completed normally, stopping " + "acquisition"); + sipeed_slogic_combo8_acquisition_stop(sdi); + /* + * sipeed_slogic_combo8_acquisition_stop() sets + * acq_aborted and cancels any remaining transfers. If + * there really are none left, finish now; otherwise the + * cancellation callbacks will call + * finish_acquisition(). + */ + if (devc->transfers_used == 0) + finish_acquisition(sdi); + } + } +} + +static int handle_events(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct drv_context *drvc; + struct timeval tv; + size_t i; (void)fd; + (void)revents; sdi = cb_data; + devc = sdi->priv; + drvc = ((struct sr_dev_driver *)sdi->driver)->context; + + if (devc->acq_aborted) { + /* Cancel any transfers that are still outstanding. */ + for (i = 0; i < NUM_CONCURRENT_TRANSFERS; ++i) { + if (devc->transfers[i]) + libusb_cancel_transfer(devc->transfers[i]); + } + /* + * finish_acquisition() is called from receive_transfer() once + * every in-flight transfer has fired its cancellation + * callback. Do not call it here to avoid a double-free of + * the event source. + */ + } + + /* + * Block for up to 10 ms waiting for libusb events. + * + * Pass NULL as the `completed` parameter so libusb actually waits up + * to the full tv deadline. Passing &devc->acq_aborted would cause + * libusb to return immediately every tick once acq_aborted is TRUE + * (because the flag is already set), meaning cancellation callbacks + * would never be processed and transfers_used would never reach zero + * -- causing a hang on manual stop. + */ + tv.tv_sec = 0; + tv.tv_usec = 10000; /* 10 ms */ + libusb_handle_events_timeout_completed(drvc->sr_ctx->libusb_ctx, &tv, + NULL); + + return TRUE; +} + +SR_PRIV int +sipeed_slogic_combo8_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct sr_dev_driver *di; + struct dev_context *devc; + struct drv_context *drvc; + struct sr_usb_dev_inst *usb; + struct libusb_transfer *transfer; + uint8_t *dev_buf; + uint8_t cmd[4]; + uint16_t rate; + size_t num_transfers; + size_t bytes_to_transfer; + size_t step; + size_t rem; + uint64_t samples_in_bytes; + int ret; + + devc = sdi->priv; + di = sdi->driver; + drvc = di->context; + usb = sdi->conn; + + sr_dbg("Samplerate: %" PRIu64 "MHz@%" PRIu64 "ch, samples: %" PRIu64, + devc->cur_samplerate / SR_MHZ(1), devc->cur_samplechannel, + devc->limit_samples); + + /* + * Drain any stale data the device may still be streaming from a + * previous acquisition before starting a new one. + * + * Note: we intentionally do not send CMD_STOP (0xb3) here. On the + * Combo8 that command is unstable and can wedge the device's control + * endpoint, which would make the CMD_START below time out. + */ + if (clear_ep(EP_IN, usb->devhdl) < 0) { + sr_err("Device endpoint not idle after drain attempt; " + "try unplugging and replugging the device."); + return SR_ERR_IO; + } + + devc->acq_aborted = FALSE; + devc->bytes_need_transfer = 0; + devc->bytes_transferring = 0; + devc->bytes_transferred = 0; + devc->transfers_used = 0; + devc->empty_transfer_count = 0; + memset(devc->transfers, 0, sizeof(devc->transfers)); + + devc->transfers_buffer_size = get_buffer_size(devc); + num_transfers = get_number_of_transfers(devc); + devc->timeout = get_timeout(devc); + + sr_dbg("transfers_buffer_size: %zu, num_transfers: %zu, timeout: %" PRIu64 + "ms", + devc->transfers_buffer_size, num_transfers, devc->timeout); + + devc->expand_buf = NULL; + devc->expand_buf_size = 0; + + usb_source_add(sdi->session, drvc->sr_ctx, 10, handle_events, + (void *)sdi); + + /* Compute total bytes to transfer from the sample limit. */ + if (devc->limit_samples > 0) { + samples_in_bytes = + devc->limit_samples * devc->cur_samplechannel / 8; + devc->bytes_need_transfer = + samples_in_bytes / devc->transfers_buffer_size; + devc->bytes_need_transfer += + !!(samples_in_bytes % devc->transfers_buffer_size); + devc->bytes_need_transfer *= devc->transfers_buffer_size; + } else { + devc->bytes_need_transfer = 0; /* Continuous mode */ + } + + /* + * Submit exactly num_transfers concurrent bulk IN transfers, up to + * however many are needed to cover bytes_need_transfer. + */ + while (devc->transfers_used < num_transfers) { + if (devc->bytes_need_transfer > 0 && + (devc->bytes_transferred + devc->bytes_transferring >= + devc->bytes_need_transfer)) + break; + + dev_buf = g_malloc(devc->transfers_buffer_size); + if (!dev_buf) { + sr_err("Failed to allocate memory for USB buffer"); + if (devc->transfers_used == 0) { + sipeed_slogic_combo8_acquisition_stop( + (struct sr_dev_inst *)sdi); + return SR_ERR_MALLOC; + } + break; + } + + transfer = libusb_alloc_transfer(0); + if (!transfer) { + g_free(dev_buf); + sr_err("Failed to allocate libusb transfer"); + if (devc->transfers_used == 0) { + sipeed_slogic_combo8_acquisition_stop( + (struct sr_dev_inst *)sdi); + return SR_ERR_MALLOC; + } + break; + } + + bytes_to_transfer = devc->transfers_buffer_size; + if (devc->bytes_need_transfer > 0) { + rem = devc->bytes_need_transfer - + (devc->bytes_transferred + + devc->bytes_transferring); + if (bytes_to_transfer > rem) + bytes_to_transfer = rem; + } + + libusb_fill_bulk_transfer(transfer, usb->devhdl, + EP_IN | LIBUSB_ENDPOINT_IN, dev_buf, + bytes_to_transfer, receive_transfer, + (void *)sdi, devc->timeout); + transfer->actual_length = 0; + + ret = libusb_submit_transfer(transfer); + if (ret != LIBUSB_SUCCESS) { + sr_warn("Failed to submit transfer: %s", + libusb_error_name(ret)); + g_free(transfer->buffer); + libusb_free_transfer(transfer); + if (devc->transfers_used == 0) { + sipeed_slogic_combo8_acquisition_stop( + (struct sr_dev_inst *)sdi); + return SR_ERR_IO; + } + break; + } + + devc->transfers[devc->transfers_used] = transfer; + devc->bytes_transferring += bytes_to_transfer; + devc->transfers_used++; + } + + sr_dbg("Submitted %zu transfers", devc->transfers_used); + + std_session_send_df_header(sdi); + + /* + * Send the start command as a 4-byte control write: 16-bit sample + * rate (little-endian), channel count, and one padding byte. The + * firmware expects a 4-byte-aligned payload; 500 ms is the timeout + * used by the reference implementation. + */ + rate = (uint16_t)(devc->cur_samplerate / SR_MHZ(1)); + cmd[0] = rate & 0xff; + cmd[1] = rate >> 8; + cmd[2] = (uint8_t)devc->cur_samplechannel; + cmd[3] = 0; + + ret = libusb_control_transfer( + usb->devhdl, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, + CMD_START, 0x0000, 0x0000, cmd, sizeof(cmd), 500); + if (ret < 0) { + sr_err("Unable to send start command: %s", + libusb_error_name(ret)); + sipeed_slogic_combo8_acquisition_stop( + (struct sr_dev_inst *)sdi); + return SR_ERR_IO; + } + + sr_dbg("CMD_START sent successfully"); + + /* + * Pre-allocate the bit-expansion buffer for packed 2ch/4ch modes. + * Maximum expansion factor is 4x (2ch: 1 byte -> 4 bytes). + * Defer this until after CMD_START succeeded so no error path above + * can leak it; on failure, stop the device before returning. + */ + if (devc->cur_samplechannel != 8 && devc->cur_samplechannel > 0) { + step = 8 / devc->cur_samplechannel; + devc->expand_buf_size = devc->transfers_buffer_size * step; + devc->expand_buf = g_malloc(devc->expand_buf_size); + if (!devc->expand_buf) { + sr_err("Failed to allocate bit-expansion buffer " + "(%zu bytes)", + devc->expand_buf_size); + sipeed_slogic_combo8_acquisition_stop( + (struct sr_dev_inst *)sdi); + return SR_ERR_MALLOC; + } + } + + return SR_OK; +} + +SR_PRIV int sipeed_slogic_combo8_acquisition_stop(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + size_t i; + if (!sdi) - return TRUE; + return SR_ERR_ARG; devc = sdi->priv; - if (!devc) - return TRUE; - if (revents == G_IO_IN) { - /* TODO */ + devc->acq_aborted = TRUE; + + /* + * Do not send CMD_STOP here: it is unstable on the Combo8 and can + * wedge the control endpoint. Cancelling the in-flight bulk transfers + * is sufficient to stop the acquisition; any remaining streamed data + * is drained at the start of the next acquisition. + */ + + for (i = 0; i < NUM_CONCURRENT_TRANSFERS; i++) { + if (devc->transfers[i]) + libusb_cancel_transfer(devc->transfers[i]); } - return TRUE; + return SR_OK; } diff --git a/src/hardware/sipeed-slogic-combo8/protocol.h b/src/hardware/sipeed-slogic-combo8/protocol.h index 6c14b0729..d1f3f194f 100644 --- a/src/hardware/sipeed-slogic-combo8/protocol.h +++ b/src/hardware/sipeed-slogic-combo8/protocol.h @@ -1,6 +1,7 @@ /* * This file is part of the libsigrok project. * + * Copyright (C) 2023 taorye * Copyright (C) 2026 Lindolfo Meira * * This program is free software: you can redistribute it and/or modify @@ -22,14 +23,222 @@ #include #include +#include #include #include "libsigrok-internal.h" #define LOG_PREFIX "sipeed-slogic-combo8" +#define USB_CONN "359f.0300" + +#define EP_IN 0x01 +#define SIZE_MAX_EP_HS 512 + +/* + * Number of simultaneous in-flight USB transfers. + * + * The original driver used 1, which meant the host had to ACK each 107 KB + * chunk before the device could send more. At >20 MHz the device's TX buffer + * overflows during that gap, causing the device to stall -- which is the root + * cause of the acquisition hang. + * + * 16 concurrent transfers keeps ~1.7 MB of transfers pipelined at all times, + * eliminating the stall window without stressing the kernel DMA allocator. + * The value is intentionally fixed (not samplerate-scaled) for simplicity and + * to keep total DMA usage predictable and bounded. + */ +#define NUM_CONCURRENT_TRANSFERS 16 + +/* + * Maximum number of consecutive empty (zero-length or timed-out) transfers + * before we consider the device stalled and abort acquisition. + */ +#define MAX_EMPTY_TRANSFERS 32 + +/* + * Protocol command used to begin acquisition. + * + * CMD_STOP (0xb3) is deliberately not used: on the Combo8 it is unstable and + * can wedge the device's control endpoint. Stopping relies on cancelling the + * in-flight bulk transfers, and the start path only drains the endpoint. + */ +#define CMD_START 0xb1 + +/* + * Wall-clock deadline (ms) for draining the endpoint at the start of an + * acquisition. The drain loop exits as soon as a read returns zero bytes + * (device quiet) or this total elapsed time is exceeded. + */ +#define CLEAR_EP_TIMEOUT_MS 500 + +/* + * Per-read timeout (ms) used inside the drain loop. Short so we detect + * "device went quiet" quickly rather than waiting 100 ms per empty read. + */ +#define CLEAR_EP_READ_TIMEOUT_MS 10 + +static const uint64_t samplerates[] = { + SR_MHZ(1), + SR_MHZ(2), + SR_MHZ(4), + SR_MHZ(5), + SR_MHZ(8), + SR_MHZ(10), + SR_MHZ(16), + SR_MHZ(20), + SR_MHZ(32), + /* x 4ch */ + SR_MHZ(40), + SR_MHZ(80), + /* x 2ch */ + SR_MHZ(160), +}; + struct dev_context { + uint64_t limit_samples; + uint64_t cur_samplerate; + uint64_t cur_samplechannel; + + struct libusb_transfer *transfers[NUM_CONCURRENT_TRANSFERS]; + + gboolean acq_aborted; + + uint64_t timeout; + + size_t transfers_buffer_size; + + size_t bytes_need_transfer; + size_t bytes_transferring; + size_t bytes_transferred; + size_t transfers_used; + + /* Consecutive empty (zero-length or timed-out) transfers; triggers + * abort when too high. */ + int empty_transfer_count; + + /* + * Pre-allocated buffer for expanding packed samples (2ch/4ch modes) to + * one sample per byte. Sized for transfers_buffer_size * max_step + * (step=4 for 2ch). Allocated once in acquisition_start, freed in + * finish_acquisition. + */ + uint8_t *expand_buf; + size_t expand_buf_size; }; -SR_PRIV int sipeed_slogic_combo8_receive_data(int fd, int revents, void *cb_data); +/* Channel count derives automatically from samplerate. */ +static inline void devc_set_samplerate(struct dev_context *devc, + uint64_t new_samplerate) +{ + devc->cur_samplerate = new_samplerate; + if (new_samplerate >= SR_MHZ(160)) { + devc->cur_samplechannel = 2; + } else if (new_samplerate >= SR_MHZ(80)) { + devc->cur_samplechannel = 4; + } else { + devc->cur_samplechannel = 8; + } + sr_info("Rebind sample channel to %" PRIu64 "CH", + devc->cur_samplechannel); +} + +SR_PRIV int +sipeed_slogic_combo8_acquisition_start(const struct sr_dev_inst *sdi); +SR_PRIV int sipeed_slogic_combo8_acquisition_stop(struct sr_dev_inst *sdi); + +/* + * Bytes produced per millisecond at the current samplerate and channel count. + * cur_samplechannel bits per sample, 8 bits per byte, 1000 ms per second. + */ +static inline size_t to_bytes_per_ms(struct dev_context *devc) +{ + return (devc->cur_samplerate * devc->cur_samplechannel) / 8 / 1000; +} + +/* + * Per-transfer buffer size: fixed at 210 x 512 = 107,520 bytes (~2.7 ms at + * 40 MB/s). This must be a multiple of SIZE_MAX_EP_HS for USB bulk + * alignment. + * + * We deliberately keep this fixed rather than scaling it with samplerate. + * Each libusb bulk transfer maps to a kernel DMA allocation; on Linux the + * default usbfs memory limit is 16 MB (/sys/module/usbcore/parameters/ + * usbfs_memory_mb). With NUM_CONCURRENT_TRANSFERS = 16 transfers in flight, + * total DMA usage is 16 x 107,520 ~ 1.7 MB -- well within the limit at all + * samplerates. Scaling the buffer size with samplerate would push this over + * the limit at 80-160 MHz (e.g., 16 x 400 KB = 6.4 MB) and cause + * LIBUSB_ERROR_NO_MEM at submission time. + */ +static inline size_t get_buffer_size(struct dev_context *devc) +{ + (void)devc; + return 210 * SIZE_MAX_EP_HS; /* 107,520 bytes */ +} + +static inline size_t get_number_of_transfers(struct dev_context *devc) +{ + (void)devc; + return NUM_CONCURRENT_TRANSFERS; +} + +/* + * Per-transfer timeout in milliseconds: time to fill one transfer buffer at + * the current data rate, plus 25 % headroom. Floor of 50 ms ensures a + * reasonable timeout at very low samplerates. + */ +static inline size_t get_timeout(struct dev_context *devc) +{ + size_t bytes_per_ms = to_bytes_per_ms(devc); + size_t buf_size = get_buffer_size(devc); + size_t timeout; + + if (bytes_per_ms == 0) + bytes_per_ms = 1; + + timeout = buf_size / bytes_per_ms; /* ms to fill one transfer */ + timeout = timeout * 5 / 4; /* +25 % headroom */ + if (timeout < 50) + timeout = 50; /* floor: 50 ms */ + return timeout; +} + +/* + * Drain any stale data from the bulk IN endpoint until the device goes quiet + * or the wall-clock deadline is reached. + * + * Strategy: use a short per-read timeout (CLEAR_EP_READ_TIMEOUT_MS) so that + * the first empty read -- which signals the device has actually stopped + * streaming -- is detected within ~10 ms rather than the original 100 ms. + * A total wall-clock deadline (CLEAR_EP_TIMEOUT_MS) caps the worst case. + * + * Returns 0 if the endpoint was drained cleanly (a read returned 0 bytes), + * -1 if the deadline was reached before the device went quiet. + */ +static inline int clear_ep(uint8_t ep, libusb_device_handle *usbh) +{ + uint8_t tmp[SIZE_MAX_EP_HS]; + int actual_length; + gint64 deadline; + + sr_dbg("Clearing EP: %u", ep); + + deadline = g_get_monotonic_time() + CLEAR_EP_TIMEOUT_MS * 1000; + + do { + actual_length = 0; + libusb_bulk_transfer(usbh, ep | LIBUSB_ENDPOINT_IN, tmp, + sizeof(tmp), &actual_length, + CLEAR_EP_READ_TIMEOUT_MS); + } while (actual_length > 0 && g_get_monotonic_time() < deadline); + + if (actual_length > 0) { + sr_warn("clear_ep: device still streaming after %d ms deadline", + CLEAR_EP_TIMEOUT_MS); + return -1; + } + + sr_dbg("Cleared EP: %u", ep); + return 0; +} #endif From 6150a9bf06a1db23b52bd1841cc8932e1dc3f5cb Mon Sep 17 00:00:00 2001 From: Lindolfo Meira Date: Tue, 11 Aug 2026 17:35:17 -0300 Subject: [PATCH 3/3] sipeed-slogic-combo8: Add configurable channels, triggers, and capture limits Add support for configuring the samplerate, channel count, capture limits and software trigger. The capture mode (channel count) always follows the selected samplerate: lowering the rate widens the capture (160MHz -> 2ch, 80MHz -> 4ch, 40MHz -> 8ch) and raising it narrows it. Manually selecting a channel that requires a bigger mode raises the mode and clamps the samplerate down to that mode's maximum, stating the limit in a warning. The full samplerate range is always listed and the effective rate is reported through a META packet at the start of every acquisition so frontends can stay in sync. Signed-off-by: Lindolfo Meira --- src/hardware/sipeed-slogic-combo8/api.c | 222 +++++++++++++++++-- src/hardware/sipeed-slogic-combo8/protocol.c | 146 +++++++++++- src/hardware/sipeed-slogic-combo8/protocol.h | 72 +++++- 3 files changed, 402 insertions(+), 38 deletions(-) diff --git a/src/hardware/sipeed-slogic-combo8/api.c b/src/hardware/sipeed-slogic-combo8/api.c index 31e76ba9e..a650ed9e1 100644 --- a/src/hardware/sipeed-slogic-combo8/api.c +++ b/src/hardware/sipeed-slogic-combo8/api.c @@ -32,7 +32,16 @@ static const uint32_t drvopts[] = { static const uint32_t devopts[] = { SR_CONF_CONTINUOUS, SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_NUM_LOGIC_CHANNELS | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, + SR_CONF_TRIGGER_MATCH | SR_CONF_LIST, +}; + +static const int32_t trigger_matches[] = { + SR_TRIGGER_ZERO, SR_TRIGGER_ONE, SR_TRIGGER_RISING, + SR_TRIGGER_FALLING, SR_TRIGGER_EDGE, }; static GSList *scan(struct sr_dev_driver *di, GSList *options) @@ -133,7 +142,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) } devc = g_malloc0(sizeof(struct dev_context)); - devc_set_samplerate(devc, samplerates[7]); + devc->capture_ratio = DEFAULT_CAPTURE_RATIO; + devc_set_samplechannel(devc, DEFAULT_SAMPLE_CHANNEL); + devc_set_samplerate(devc, DEFAULT_SAMPLERATE); sdi->priv = devc; devices = g_slist_append(devices, sdi); @@ -184,7 +195,7 @@ static int dev_open(struct sr_dev_inst *sdi) } if (devc->cur_samplerate == 0) - devc_set_samplerate(devc, samplerates[7]); + devc_set_samplerate(devc, DEFAULT_SAMPLERATE); return std_dummy_dev_open(sdi); } @@ -234,9 +245,18 @@ static int config_get(uint32_t key, GVariant **data, case SR_CONF_SAMPLERATE: *data = g_variant_new_uint64(devc->cur_samplerate); break; + case SR_CONF_NUM_LOGIC_CHANNELS: + *data = g_variant_new_int32(devc->cur_samplechannel); + break; case SR_CONF_LIMIT_SAMPLES: *data = g_variant_new_uint64(devc->limit_samples); break; + case SR_CONF_LIMIT_MSEC: + *data = g_variant_new_uint64(devc->limit_msec); + break; + case SR_CONF_CAPTURE_RATIO: + *data = g_variant_new_uint64(devc->capture_ratio); + break; default: return SR_ERR_NA; } @@ -244,14 +264,65 @@ static int config_get(uint32_t key, GVariant **data, return SR_OK; } +/* + * Enable channel index < cur_samplechannel and disable the rest, matching the + * active capture mode. Disables are applied in a first pass and enables in a + * second pass: sr_dev_channel_enable() fires config_channel_set() for every + * state change, and enabling a low channel while the high channels are still + * enabled would re-raise the count while switching to a smaller mode. + */ +static void update_channel_enables(const struct sr_dev_inst *sdi, + struct dev_context *devc) +{ + struct sr_channel *ch; + GSList *l; + size_t idx; + + devc->updating_channels = TRUE; + + for (l = sdi->channels, idx = 0; l; l = l->next, idx++) { + ch = l->data; + if (ch->type != SR_CHANNEL_LOGIC) { + sr_err("Unexpected channel type on channel %zu", idx); + devc->updating_channels = FALSE; + return; + } + if (idx >= devc->cur_samplechannel) + sr_dev_channel_enable(ch, FALSE); + } + for (l = sdi->channels, idx = 0; l; l = l->next, idx++) { + ch = l->data; + if (idx < devc->cur_samplechannel) + sr_dev_channel_enable(ch, TRUE); + } + + devc->updating_channels = FALSE; +} + +/* + * Validate a channel count GVariant against the supported samplechannels[] + * table. Returns the table index, or -1 if not supported. + */ +static int samplechannel_idx(GVariant *data) +{ + int64_t val; + size_t i; + + if (!g_variant_is_of_type(data, G_VARIANT_TYPE_INT32)) + return -1; + val = g_variant_get_int32(data); + for (i = 0; i < ARRAY_SIZE(samplechannels); i++) { + if (samplechannels[i] == val) + return (int)i; + } + return -1; +} + static int config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; - struct sr_channel *ch; - GSList *l; - size_t idx; (void)cg; @@ -261,29 +332,57 @@ static int config_set(uint32_t key, GVariant *data, devc = sdi->priv; switch (key) { - case SR_CONF_SAMPLERATE: - if (std_u64_idx(data, ARRAY_AND_SIZE(samplerates)) < 0) { + case SR_CONF_SAMPLERATE: { + uint64_t new_samplerate; + size_t i; + + new_samplerate = g_variant_get_uint64(data); + if (std_u64_idx(data, ARRAY_AND_SIZE(samplerates)) < 0) return SR_ERR_ARG; - } else { - devc_set_samplerate(devc, g_variant_get_uint64(data)); - idx = 0; - for (l = sdi->channels; l; l = l->next, idx++) { - ch = l->data; - if (ch->type == SR_CHANNEL_LOGIC) { - sr_dev_channel_enable( - ch, (idx < - devc->cur_samplechannel) ? - TRUE : - FALSE); - } else { - return SR_ERR_BUG; - } + + /* + * The capture mode always follows the rate: select the largest + * mode (highest channel count) that can sustain the selected + * rate. This way lowering the rate widens the capture (160M + * -> 2ch, 80M -> 4ch, 40M -> 8ch) and raising it narrows it. + * The mode is only a hint; the device may settle on a lower + * effective rate, which is reflected below. + */ + for (i = ARRAY_SIZE(samplechannels); i-- > 0;) + if (limit_samplerates[i] >= new_samplerate) { + devc_set_samplechannel( + devc, (uint64_t)samplechannels[i]); + break; } - } + devc_set_samplerate(devc, new_samplerate); + update_channel_enables(sdi, devc); + sr_info("Channel mode set to %" PRIu64 "CH to match the selected " + "samplerate (%" PRIu64 "MHz).", + devc->cur_samplechannel, devc->cur_samplerate / SR_MHZ(1)); + break; + } + case SR_CONF_NUM_LOGIC_CHANNELS: + if (samplechannel_idx(data) < 0) + return SR_ERR_ARG; + devc_set_samplechannel(devc, g_variant_get_int32(data)); + devc_set_samplerate(devc, devc->cur_samplerate); + update_channel_enables(sdi, devc); break; case SR_CONF_LIMIT_SAMPLES: devc->limit_samples = g_variant_get_uint64(data); break; + case SR_CONF_LIMIT_MSEC: + devc->limit_msec = g_variant_get_uint64(data); + break; + case SR_CONF_CAPTURE_RATIO: + devc->capture_ratio = g_variant_get_uint64(data); + if (devc->capture_ratio > 100) { + sr_warn("Capture ratio clamped from %" PRIu64 + "%% to 100%%", + devc->capture_ratio); + devc->capture_ratio = 100; + } + break; default: return SR_ERR_NA; } @@ -295,14 +394,30 @@ static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { + struct dev_context *devc; + + devc = sdi ? sdi->priv : NULL; + switch (key) { case SR_CONF_SCAN_OPTIONS: case SR_CONF_DEVICE_OPTIONS: return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); case SR_CONF_SAMPLERATE: + if (!devc) + return SR_ERR_ARG; + /* + * Always list the full range; config_set() picks the capture + * mode (channel count) that can sustain the selected rate. + */ *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates)); break; + case SR_CONF_NUM_LOGIC_CHANNELS: + *data = std_gvar_array_i32(ARRAY_AND_SIZE(samplechannels)); + break; + case SR_CONF_TRIGGER_MATCH: + *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches)); + break; default: return SR_ERR_NA; } @@ -310,6 +425,68 @@ static int config_list(uint32_t key, GVariant **data, return SR_OK; } +static int config_channel_set(const struct sr_dev_inst *sdi, + struct sr_channel *ch, unsigned int changes) +{ + struct dev_context *devc; + struct sr_channel *lch; + GSList *l; + uint64_t new_samplechannel; + size_t i; + + if (!sdi) + return SR_ERR_ARG; + + (void)ch; + + devc = sdi->priv; + + /* Swallow changes made by our own bulk update. */ + if (devc->updating_channels) + return SR_OK; + + if (changes != SR_CHANNEL_SET_ENABLED) + return SR_OK; + + /* + * Only react to channels being enabled. Disabling a channel must not + * shrink the capture mode, and ignoring disable events also prevents + * update_channel_enables() from re-raising the count while it walks the + * channel list disabling the channels above the new count. + */ + if (!ch->enabled) + return SR_OK; + + /* + * Derive the channel count from the highest enabled channel, rounding + * up to the next supported count. Only ever raise the count: toggling + * a channel off must not silently shrink the capture mode. + */ + new_samplechannel = devc->cur_samplechannel; + for (l = sdi->channels; l; l = l->next) { + lch = l->data; + if (lch->type != SR_CHANNEL_LOGIC || !lch->enabled) + continue; + for (i = 0; i < ARRAY_SIZE(samplechannels); i++) { + if ((uint64_t)samplechannels[i] > + (uint64_t)lch->index) { + if ((uint64_t)samplechannels[i] > + new_samplechannel) + new_samplechannel = + (uint64_t)samplechannels[i]; + break; + } + } + } + + if (new_samplechannel != devc->cur_samplechannel) { + devc_set_samplechannel(devc, new_samplechannel); + devc_set_samplerate(devc, devc->cur_samplerate); + } + + return SR_OK; +} + static struct sr_dev_driver sipeed_slogic_combo8_driver_info = { .name = "sipeed-slogic-combo8", .longname = "SiPEED Slogic Combo8", @@ -322,6 +499,7 @@ static struct sr_dev_driver sipeed_slogic_combo8_driver_info = { .config_get = config_get, .config_set = config_set, .config_list = config_list, + .config_channel_set = config_channel_set, .dev_open = dev_open, .dev_close = dev_close, .dev_acquisition_start = sipeed_slogic_combo8_acquisition_start, diff --git a/src/hardware/sipeed-slogic-combo8/protocol.c b/src/hardware/sipeed-slogic-combo8/protocol.c index 8a9ce0ce9..954876979 100644 --- a/src/hardware/sipeed-slogic-combo8/protocol.c +++ b/src/hardware/sipeed-slogic-combo8/protocol.c @@ -89,6 +89,12 @@ static void finish_acquisition(struct sr_dev_inst *sdi) usb_source_remove(sdi->session, drvc->sr_ctx); std_session_send_df_end(sdi); + if (devc->stl) { + soft_trigger_logic_free(devc->stl); + devc->stl = NULL; + } + devc->trigger_fired = TRUE; + if (devc->expand_buf) { g_free(devc->expand_buf); devc->expand_buf = NULL; @@ -102,12 +108,15 @@ static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) struct sr_dev_inst *sdi; struct dev_context *devc; uint8_t *ptr; + uint8_t *free_ptr; uint8_t mask; size_t len; size_t step; size_t expanded_len; size_t bytes_to_transfer; size_t i, j; + int trigger_offset; + int pre_trigger_samples; sdi = transfer->user_data; devc = sdi->priv; @@ -171,6 +180,7 @@ static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) devc->bytes_transferring = 0; if (transfer->actual_length > 0) { + free_ptr = NULL; ptr = transfer->buffer; len = transfer->actual_length; @@ -197,6 +207,7 @@ static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) sr_warn("expand_buf too small or NULL, " "falling back to g_malloc"); ptr = g_malloc(expanded_len); + free_ptr = ptr; if (!ptr) { sr_err("Failed to allocate expansion " "buffer"); @@ -218,14 +229,58 @@ static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) len = expanded_len; } - submit_data(ptr, len, sdi); + /* + * Software trigger handling. Before the trigger fires, all + * data is consumed by the soft-trigger logic, which keeps a + * pre-trigger ring buffer and itself sends the pre-trigger + * data plus SR_DF_TRIGGER once the pattern matches. After it + * fires, deliver the remaining data up to the sample budget. + */ + if (devc->stl) { + if (!devc->trigger_fired) { + trigger_offset = soft_trigger_logic_check( + devc->stl, ptr, (int)len, + &pre_trigger_samples); + if (trigger_offset >= 0) { + devc->samples_sent = + (uint64_t)pre_trigger_samples; + ptr += trigger_offset; + len -= (size_t)trigger_offset; + if (devc->samples_need > 0 && + len > devc->samples_need - + devc->samples_sent) + len = devc->samples_need - + devc->samples_sent; + if (len > 0) { + submit_data(ptr, len, sdi); + devc->samples_sent += len; + } + devc->trigger_fired = TRUE; + } + } else { + if (devc->samples_need > 0 && + len > devc->samples_need - + devc->samples_sent) + len = devc->samples_need - + devc->samples_sent; + if (len > 0) { + submit_data(ptr, len, sdi); + devc->samples_sent += len; + } + } + } else { + submit_data(ptr, len, sdi); + } - /* Free fallback allocation if we couldn't use expand_buf. */ - if (devc->cur_samplechannel != 8 && - devc->cur_samplechannel > 0 && ptr != devc->expand_buf) - g_free(ptr); + if (free_ptr) + g_free(free_ptr); } + /* In triggered captures, stop once the sample budget is met. */ + if (devc->stl && devc->trigger_fired && devc->samples_need > 0 && + devc->samples_sent >= devc->samples_need) + sipeed_slogic_combo8_acquisition_stop(sdi); + /* Determine how many bytes to request in the next submission. */ bytes_to_transfer = 0; if (devc->bytes_need_transfer > 0) { @@ -393,10 +448,31 @@ sipeed_slogic_combo8_acquisition_start(const struct sr_dev_inst *sdi) usb_source_add(sdi->session, drvc->sr_ctx, 10, handle_events, (void *)sdi); + /* Compute the total number of samples to deliver this capture. */ + devc->samples_need = devc->limit_samples; + if (devc->limit_msec > 0) { + devc->samples_need = + devc->limit_msec * devc->cur_samplerate / 1000; + sr_info("Capture limited to %" PRIu64 " samples " + "(%" PRIu64 " ms)", + devc->samples_need, devc->limit_msec); + } + devc->samples_sent = 0; + + /* + * Software trigger: while armed we can't predict when it will fire, + * so the bulk pipeline runs continuously and the sample budget stops + * it once the post-trigger data has been delivered. + */ + devc->stl = NULL; + devc->trigger_fired = TRUE; + if (sr_session_trigger_get(sdi->session)) + devc->trigger_fired = FALSE; + /* Compute total bytes to transfer from the sample limit. */ - if (devc->limit_samples > 0) { + if (devc->trigger_fired && devc->samples_need > 0) { samples_in_bytes = - devc->limit_samples * devc->cur_samplechannel / 8; + devc->samples_need * devc->cur_samplechannel / 8; devc->bytes_need_transfer = samples_in_bytes / devc->transfers_buffer_size; devc->bytes_need_transfer += @@ -477,6 +553,54 @@ sipeed_slogic_combo8_acquisition_start(const struct sr_dev_inst *sdi) std_session_send_df_header(sdi); + /* + * Report the actual samplerate through a META packet. Frontends + * (e.g. PulseView) use this to re-synchronise their channel layout + * after the capture mode has changed, so this must be sent on every + * acquisition, regardless of whether the rate changed. + */ + struct sr_datafeed_meta meta; + struct sr_config *src; + struct sr_datafeed_packet packet; + + src = sr_config_new(SR_CONF_SAMPLERATE, + g_variant_new_uint64(devc->cur_samplerate)); + meta.config = g_slist_append(NULL, src); + + packet.type = SR_DF_META; + packet.payload = &meta; + sr_session_send(sdi, &packet); + + g_slist_free(meta.config); + sr_config_free(src); + + /* + * Set up the software trigger. Done here, after the transfers are + * already submitted but before CMD_START, so no receive callback can + * race ahead of the trigger state. + */ + if (!devc->trigger_fired) { + struct sr_trigger *trigger; + int pre_trigger_samples; + + trigger = sr_session_trigger_get(sdi->session); + pre_trigger_samples = 0; + if (devc->samples_need > 0) + pre_trigger_samples = + (devc->capture_ratio * devc->samples_need) / + 100; + devc->stl = soft_trigger_logic_new(sdi, trigger, + pre_trigger_samples); + if (!devc->stl) { + sr_err("Failed to allocate software trigger logic"); + sipeed_slogic_combo8_acquisition_stop( + (struct sr_dev_inst *)sdi); + return SR_ERR_MALLOC; + } + sr_info("Software trigger armed, %d pre-trigger samples", + pre_trigger_samples); + } + /* * Send the start command as a 4-byte control write: 16-bit sample * rate (little-endian), channel count, and one padding byte. The @@ -495,6 +619,10 @@ sipeed_slogic_combo8_acquisition_start(const struct sr_dev_inst *sdi) if (ret < 0) { sr_err("Unable to send start command: %s", libusb_error_name(ret)); + if (devc->stl) { + soft_trigger_logic_free(devc->stl); + devc->stl = NULL; + } sipeed_slogic_combo8_acquisition_stop( (struct sr_dev_inst *)sdi); return SR_ERR_IO; @@ -516,6 +644,10 @@ sipeed_slogic_combo8_acquisition_start(const struct sr_dev_inst *sdi) sr_err("Failed to allocate bit-expansion buffer " "(%zu bytes)", devc->expand_buf_size); + if (devc->stl) { + soft_trigger_logic_free(devc->stl); + devc->stl = NULL; + } sipeed_slogic_combo8_acquisition_stop( (struct sr_dev_inst *)sdi); return SR_ERR_MALLOC; diff --git a/src/hardware/sipeed-slogic-combo8/protocol.h b/src/hardware/sipeed-slogic-combo8/protocol.h index d1f3f194f..1191175f9 100644 --- a/src/hardware/sipeed-slogic-combo8/protocol.h +++ b/src/hardware/sipeed-slogic-combo8/protocol.h @@ -94,9 +94,23 @@ static const uint64_t samplerates[] = { SR_MHZ(160), }; +/* Channel counts and the maximum samplerate each supports, index-aligned. */ +static const int32_t samplechannels[] = { 2, 4, 8 }; +static const uint64_t limit_samplerates[] = { SR_MHZ(160), SR_MHZ(80), + SR_MHZ(40) }; + +#define DEFAULT_SAMPLE_CHANNEL 8 +#define DEFAULT_SAMPLERATE SR_MHZ(40) + +/* Default pre-trigger ratio in percent; the SR_CONF_CAPTURE_RATIO config key + * overrides this at runtime. */ +#define DEFAULT_CAPTURE_RATIO 0 + struct dev_context { uint64_t limit_samples; + uint64_t limit_msec; uint64_t cur_samplerate; + uint64_t limit_samplerate; uint64_t cur_samplechannel; struct libusb_transfer *transfers[NUM_CONCURRENT_TRANSFERS]; @@ -116,6 +130,23 @@ struct dev_context { * abort when too high. */ int empty_transfer_count; + /* Total samples to deliver this acquisition, derived from + * limit_samples or limit_msec. */ + uint64_t samples_need; + /* Samples already delivered to the session (used with triggers). */ + uint64_t samples_sent; + + /* Software trigger state. */ + struct soft_trigger_logic *stl; + gboolean trigger_fired; + uint64_t capture_ratio; + + /* + * Guard against re-entrant config_channel_set() calls while + * update_channel_enables() bulk-enables/disables channels. + */ + gboolean updating_channels; + /* * Pre-allocated buffer for expanding packed samples (2ch/4ch modes) to * one sample per byte. Sized for transfers_buffer_size * max_step @@ -126,20 +157,43 @@ struct dev_context { size_t expand_buf_size; }; -/* Channel count derives automatically from samplerate. */ +/* + * Set the samplerate, clamping it down to the maximum the current channel + * count supports. The channel count itself is left unchanged. + */ static inline void devc_set_samplerate(struct dev_context *devc, uint64_t new_samplerate) { + if (new_samplerate > devc->limit_samplerate) { + sr_warn("Samplerate clamped to %" PRIu64 "MHz: current setting " + "exceeds the device's %" PRIu64 "CH limit.", + devc->limit_samplerate / SR_MHZ(1), + devc->cur_samplechannel); + new_samplerate = devc->limit_samplerate; + } devc->cur_samplerate = new_samplerate; - if (new_samplerate >= SR_MHZ(160)) { - devc->cur_samplechannel = 2; - } else if (new_samplerate >= SR_MHZ(80)) { - devc->cur_samplechannel = 4; - } else { - devc->cur_samplechannel = 8; +} + +/* + * Set the active channel count and its samplerate limit. Does not touch the + * samplerate itself: callers that raise the channel count beyond what the + * current rate supports must re-apply the rate with devc_set_samplerate() + * afterwards to clamp it down. + */ +static inline void devc_set_samplechannel(struct dev_context *devc, + uint64_t new_samplechannel) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(samplechannels); idx++) { + if ((uint64_t)samplechannels[idx] == new_samplechannel) + break; } - sr_info("Rebind sample channel to %" PRIu64 "CH", - devc->cur_samplechannel); + if (idx >= ARRAY_SIZE(samplechannels)) + return; + + devc->cur_samplechannel = new_samplechannel; + devc->limit_samplerate = limit_samplerates[idx]; } SR_PRIV int