From 11bf53b39154b5ed035181c7b8017e5146a1e73e Mon Sep 17 00:00:00 2001 From: huehuehuehueing <243025451+huehuehuehueing@users.noreply.github.com> Date: Sat, 30 May 2026 18:52:39 -0400 Subject: [PATCH] dreamsourcelab-dslogic: add support for DSLogic Plus Pango (USB 2a0e:0034) Adds support for the DSLogic Plus hardware revision that enumerates as USB ID 2a0e:0034 ("USB-based DSL Instrument v2") and ships with a Pango FPGA bitstream. This revision speaks a newer envelope-style control protocol than the existing DSLogic family; the driver gains a parallel V2 path while leaving the V1 (flat-opcode) path untouched. Architecture ------------ - struct dslogic_protocol_ops vtable in protocol.h with one slot per operation whose wire format differs between protocol versions (fpga_firmware_upload, fpga_config, acquisition_start/stop, set_samplerate/voltage_threshold/trigger/external_clock/clock_edge, security_check). - enum dslogic_protocol_version per profile (DSL_PROTO_V1 or DSL_PROTO_V2). All existing profiles tagged V1; PID 0x0034 tagged V2. - dev_context gains a cached ops pointer resolved at dev_open. - protocol_v1.c (new): thin facades over the existing V1 functions, bound into dslogic_v1_ops. Pure refactor with no wire changes. - protocol_v2.{c,h} (new): the envelope-protocol implementation. Wire format (V2) ---------------- - Three control opcodes only: CMD_CTL_WR=0xb0, CMD_CTL_RD_PRE=0xb1, CMD_CTL_RD=0xb2. The packed struct ctl_header (dest, offset, size) carries the real destination via a DSL_CTL_* enum (HW_STATUS, INTRDY, WORDWIDE, START, STOP, BULK_WR, NVM, I2C_REG, I2C_STATUS, PROG_B, LED, FW_VERSION). - command_ctl_rd_v2 is two-phase: PRE (OUT) writes the header, 10 ms sleep, then RD (IN) collects the requested bytes. - Register R/W (dsl_wr_reg_v2 / dsl_rd_reg_v2) wraps the envelope with I2C_REG (write) / I2C_STATUS (read) destination and a 1-byte address in offset. NVM reads use DSL_CTL_NVM. Bitstream upload (V2) --------------------- Mirrors DSView's dsl_fpga_config sequence: PROG_B low -> LED off -> PROG_B high -> wait FPGA_INIT_B -> INTRDY low -> DSL_CTL_BULK_WR with 3-byte filesize -> bulk transfer bitstream on ep2 OUT -> INTRDY high -> wait GPIF_DONE -> INTRDY low -> wait FPGA_DONE -> LED green -> WORDWIDE high. Security challenge (V2) ----------------------- The 0x0034 firmware gates capture on an 8-step challenge-response over an I2C register block (SEC_CTRL_ADDR=0x73, SEC_DATA_ADDR=0x75). The encryption blob is read from device NVM at SECU_EEP_ADDR=0x3C00 via DSL_CTL_NVM. Implemented as v2_security_check; called from dev_open after the FPGA bitstream is loaded. Mirrors DSView's dsl_secuCheck. FPGA arm (V2) ------------- WORDWIDE -> DSL_CTL_BULK_WR (3-byte word count = sizeof(setting)/2 = 186) -> poll bmSYS_CLR -> bulk transfer struct DSL_setting (372 bytes) on ep2 OUT -> DSL_CTL_INTRDY -> read HW_STATUS once and check bmGPIF_DONE. The DSL_setting layout (with its (register_index << 8) | word_count header encoding) and the samplerate divider math (hw_max=500 MHz, pre_div=5 for DSLogic Plus pgl12) mirror DSView's dsl_fpga_arm. Sample count is shifted right by 4 because the FPGA's minimum capture unit is 16 samples. V2 acquisition stop is two-stage like DSView's: write CTR0_ADDR := bmFORCE_RDY first (soft FPGA abort that releases the GPIF capture engine and resets the green LED to solid), then DSL_CTL_STOP. dev_open changes ---------------- - Extends has_firmware product-string probe to recognise "USB-based DSL Instrument v2" so the V2 device skips the legacy FX2 firmware upload (which would renumerate it to 0x0020 with the V1 firmware). - V2 path performs DSL_CTL_FW_VERSION + DSL_CTL_HW_STATUS reads at the start of dev_open (matches DSView's hw_dev_open + dsl_dev_open initialisation). The pre-existing V1 firmware version probe at bRequest 0xb0 collides with V2's CMD_CTL_WR opcode and is now V1-gated. - After bitstream upload + security check, an initial voltage threshold is written to VTH_ADDR via the new vtable slot. Tested on real PID 0x2a0e:0x0034 hardware: scan, FPGA bitstream upload, security challenge, FPGA arm, sample capture (100 samples / 16 channels at 1 MHz returns 1959 bytes), and clean stop with idle-LED state. Co-authored-by: Larry Hernandez --- Makefile.am | 3 + contrib/60-libsigrok.rules | 2 + src/hardware/dreamsourcelab-dslogic/api.c | 38 +- .../dreamsourcelab-dslogic/protocol.c | 97 ++- .../dreamsourcelab-dslogic/protocol.h | 30 + .../dreamsourcelab-dslogic/protocol_v1.c | 93 +++ .../dreamsourcelab-dslogic/protocol_v2.c | 704 ++++++++++++++++++ .../dreamsourcelab-dslogic/protocol_v2.h | 148 ++++ 8 files changed, 1078 insertions(+), 37 deletions(-) create mode 100644 src/hardware/dreamsourcelab-dslogic/protocol_v1.c create mode 100644 src/hardware/dreamsourcelab-dslogic/protocol_v2.c create mode 100644 src/hardware/dreamsourcelab-dslogic/protocol_v2.h diff --git a/Makefile.am b/Makefile.am index 3b68142f2..8764d9dd3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -353,6 +353,9 @@ if HW_DREAMSOURCELAB_DSLOGIC src_libdrivers_la_SOURCES += \ src/hardware/dreamsourcelab-dslogic/protocol.h \ src/hardware/dreamsourcelab-dslogic/protocol.c \ + src/hardware/dreamsourcelab-dslogic/protocol_v1.c \ + src/hardware/dreamsourcelab-dslogic/protocol_v2.h \ + src/hardware/dreamsourcelab-dslogic/protocol_v2.c \ src/hardware/dreamsourcelab-dslogic/api.c endif if HW_FLUKE_45 diff --git a/contrib/60-libsigrok.rules b/contrib/60-libsigrok.rules index 716178039..4aae732ad 100644 --- a/contrib/60-libsigrok.rules +++ b/contrib/60-libsigrok.rules @@ -110,6 +110,8 @@ ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0003", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0002", ENV{ID_SIGROK}="1" # DreamSourceLab DSLogic Plus ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0020", ENV{ID_SIGROK}="1" +# DreamSourceLab DSLogic Plus (hardware revision) +ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0034", ENV{ID_SIGROK}="1" # DreamSourceLab DSLogic Basic ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0021", ENV{ID_SIGROK}="1" diff --git a/src/hardware/dreamsourcelab-dslogic/api.c b/src/hardware/dreamsourcelab-dslogic/api.c index 6d5b7fc7d..0e3c6e9dc 100644 --- a/src/hardware/dreamsourcelab-dslogic/api.c +++ b/src/hardware/dreamsourcelab-dslogic/api.c @@ -26,23 +26,33 @@ static const struct dslogic_profile supported_device[] = { /* DreamSourceLab DSLogic */ { 0x2a0e, 0x0001, "DreamSourceLab", "DSLogic", NULL, "dreamsourcelab-dslogic-fx2.fw", - 0, "DreamSourceLab", "DSLogic", 256 * 1024 * 1024}, + 0, "DreamSourceLab", "DSLogic", 256 * 1024 * 1024, + DSL_PROTO_V1, &dslogic_v1_ops}, /* DreamSourceLab DSCope */ { 0x2a0e, 0x0002, "DreamSourceLab", "DSCope", NULL, "dreamsourcelab-dscope-fx2.fw", - 0, "DreamSourceLab", "DSCope", 256 * 1024 * 1024}, + 0, "DreamSourceLab", "DSCope", 256 * 1024 * 1024, + DSL_PROTO_V1, &dslogic_v1_ops}, /* DreamSourceLab DSLogic Pro */ { 0x2a0e, 0x0003, "DreamSourceLab", "DSLogic Pro", NULL, "dreamsourcelab-dslogic-pro-fx2.fw", - 0, "DreamSourceLab", "DSLogic", 256 * 1024 * 1024}, + 0, "DreamSourceLab", "DSLogic", 256 * 1024 * 1024, + DSL_PROTO_V1, &dslogic_v1_ops}, /* DreamSourceLab DSLogic Plus */ { 0x2a0e, 0x0020, "DreamSourceLab", "DSLogic Plus", NULL, "dreamsourcelab-dslogic-plus-fx2.fw", - 0, "DreamSourceLab", "DSLogic", 256 * 1024 * 1024}, + 0, "DreamSourceLab", "DSLogic", 256 * 1024 * 1024, + DSL_PROTO_V1, &dslogic_v1_ops}, + /* DreamSourceLab DSLogic Plus (hardware revision, PID 0x0034) */ + { 0x2a0e, 0x0034, "DreamSourceLab", "DSLogic Plus", NULL, + "dreamsourcelab-dslogic-plus-fx2.fw", + 0, "DreamSourceLab", "DSLogic", 256 * 1024 * 1024, + DSL_PROTO_V2, &dslogic_v2_ops}, /* DreamSourceLab DSLogic Basic */ { 0x2a0e, 0x0021, "DreamSourceLab", "DSLogic Basic", NULL, "dreamsourcelab-dslogic-basic-fx2.fw", - 0, "DreamSourceLab", "DSLogic", 256 * 1024}, + 0, "DreamSourceLab", "DSLogic", 256 * 1024, + DSL_PROTO_V1, &dslogic_v1_ops}, ALL_ZERO }; @@ -255,7 +265,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) devc->samplerates = samplerates; devc->num_samplerates = ARRAY_SIZE(samplerates); - has_firmware = usb_match_manuf_prod(devlist[i], "DreamSourceLab", "USB-based Instrument"); + has_firmware = usb_match_manuf_prod(devlist[i], "DreamSourceLab", "USB-based Instrument") + || usb_match_manuf_prod(devlist[i], "DreamSourceLab", "USB-based DSL Instrument v2"); if (has_firmware) { /* Already has the firmware, so fix the new address. */ @@ -298,6 +309,8 @@ static int dev_open(struct sr_dev_inst *sdi) devc = sdi->priv; usb = sdi->conn; + devc->ops = devc->profile->ops; + /* * If the firmware was recently uploaded, wait up to MAX_RENUM_DELAY_MS * milliseconds for the FX2 to renumerate. @@ -352,8 +365,15 @@ static int dev_open(struct sr_dev_inst *sdi) } - if ((ret = dslogic_fpga_firmware_upload(sdi)) != SR_OK) + if ((ret = devc->ops->fpga_firmware_upload(sdi)) != SR_OK) + return ret; + if ((ret = devc->ops->security_check(sdi)) != SR_OK) return ret; + /* DSView writes VTH_ADDR right after dsl_dev_open returns + * (dslogic.c). Without this, the FPGA threshold DAC + * is uninitialised and subsequent arm-sequence status polls may + * stall. Use a sensible default (1.0V on 3.3V logic). */ + (void)devc->ops->set_voltage_threshold(sdi, 1.0, 1.0); if (devc->cur_samplerate == 0) { /* Samplerate hasn't been set; default to the slowest one. */ @@ -362,7 +382,7 @@ static int dev_open(struct sr_dev_inst *sdi) if (devc->cur_threshold == 0.0) { devc->cur_threshold = thresholds[1][0]; - return dslogic_set_voltage_threshold(sdi, devc->cur_threshold); + return devc->ops->set_voltage_threshold(sdi, devc->cur_threshold, devc->cur_threshold); } return SR_OK; @@ -484,7 +504,7 @@ static int config_set(uint32_t key, GVariant *data, return dslogic_fpga_firmware_upload(sdi); } else { g_variant_get(data, "(dd)", &low, &high); - return dslogic_set_voltage_threshold(sdi, (low + high) / 2.0); + return devc->ops->set_voltage_threshold(sdi, low, high); } break; case SR_CONF_EXTERNAL_CLOCK: diff --git a/src/hardware/dreamsourcelab-dslogic/protocol.c b/src/hardware/dreamsourcelab-dslogic/protocol.c index bb98d61a4..e40ebb827 100644 --- a/src/hardware/dreamsourcelab-dslogic/protocol.c +++ b/src/hardware/dreamsourcelab-dslogic/protocol.c @@ -24,6 +24,7 @@ #include #include #include "protocol.h" +#include "protocol_v2.h" #define DS_CMD_GET_FW_VERSION 0xb0 #define DS_CMD_GET_REVID_VERSION 0xb1 @@ -178,7 +179,7 @@ static int command_get_revid_version(struct sr_dev_inst *sdi, uint8_t *revid) return SR_OK; } -static int command_start_acquisition(const struct sr_dev_inst *sdi) +SR_PRIV int command_start_acquisition(const struct sr_dev_inst *sdi) { struct sr_usb_dev_inst *usb; struct dslogic_mode mode; @@ -199,7 +200,7 @@ static int command_start_acquisition(const struct sr_dev_inst *sdi) return SR_OK; } -static int command_stop_acquisition(const struct sr_dev_inst *sdi) +SR_PRIV int command_stop_acquisition(const struct sr_dev_inst *sdi) { struct sr_usb_dev_inst *usb; struct dslogic_mode mode; @@ -425,7 +426,7 @@ static bool set_trigger(const struct sr_dev_inst *sdi, struct fpga_config *cfg) return num_trigger_stages != 0; } -static int fpga_configure(const struct sr_dev_inst *sdi) +SR_PRIV int fpga_configure(const struct sr_dev_inst *sdi) { const struct dev_context *const devc = sdi->priv; const struct sr_usb_dev_inst *const usb = sdi->conn; @@ -601,29 +602,65 @@ SR_PRIV int dslogic_dev_open(struct sr_dev_inst *sdi, struct sr_dev_driver *di) } } - ret = command_get_fw_version(usb->devhdl, &vi); - if (ret != SR_OK) { - sr_err("Failed to get firmware version."); - break; - } - - ret = command_get_revid_version(sdi, &revid); - if (ret != SR_OK) { - sr_err("Failed to get REVID."); - break; - } - /* - * Changes in major version mean incompatible/API changes, so - * bail out if we encounter an incompatible version. - * Different minor versions are OK, they should be compatible. + * The V1 firmware-version probe uses bRequest 0xb0, which + * collides with the V2 envelope opcode CMD_CTL_WR. Only run + * the V1 probe + version check for V1 devices. */ - if (vi.major != DSLOGIC_REQUIRED_VERSION_MAJOR) { - sr_err("Expected firmware version %d.x, " - "got %d.%d.", DSLOGIC_REQUIRED_VERSION_MAJOR, - vi.major, vi.minor); - ret = SR_ERR; - break; + if (devc->profile->protocol_version == DSL_PROTO_V1) { + ret = command_get_fw_version(usb->devhdl, &vi); + if (ret != SR_OK) { + sr_err("Failed to get firmware version."); + break; + } + + ret = command_get_revid_version(sdi, &revid); + if (ret != SR_OK) { + sr_err("Failed to get REVID."); + break; + } + + /* + * Changes in major version mean incompatible/API changes, + * so bail out if we encounter an incompatible version. + * Different minor versions are OK, they should be compatible. + */ + if (vi.major != DSLOGIC_REQUIRED_VERSION_MAJOR) { + sr_err("Expected firmware version %d.x, " + "got %d.%d.", DSLOGIC_REQUIRED_VERSION_MAJOR, + vi.major, vi.minor); + ret = SR_ERR; + break; + } + } else { + /* + * V2 hello reads - DSView's hw_dev_open starts with + * DSL_CTL_FW_VERSION read (dsl.c) and dsl_dev_open + * follows with a DSL_CTL_HW_STATUS read (dsl.c). + * These appear to prime the firmware's state machine; without + * them, later HW_STATUS reads in the arm path stall. We + * don't validate the values - just perform the reads. + */ + { + uint8_t v2_fw_ver[2] = {0, 0}; + uint8_t v2_hw_status = 0; + struct ctl_rd_cmd v2_rd; + + v2_rd.header.dest = DSL_CTL_FW_VERSION; + v2_rd.header.offset = 0; + v2_rd.header.size = 2; + v2_rd.data = v2_fw_ver; + if (command_ctl_rd_v2(usb->devhdl, v2_rd) == SR_OK) + sr_info("V2 firmware version: %u.%u", + v2_fw_ver[0], v2_fw_ver[1]); + + v2_rd.header.dest = DSL_CTL_HW_STATUS; + v2_rd.header.offset = 0; + v2_rd.header.size = 1; + v2_rd.data = &v2_hw_status; + if (command_ctl_rd_v2(usb->devhdl, v2_rd) == SR_OK) + sr_dbg("V2 HW_STATUS: 0x%02x", v2_hw_status); + } } sr_info("Opened device on %d.%d (logical) / %s (physical), " @@ -1050,13 +1087,15 @@ SR_PRIV int dslogic_acquisition_start(const struct sr_dev_inst *sdi) usb_source_add(sdi->session, devc->ctx, timeout, receive_data, drvc); - if ((ret = command_stop_acquisition(sdi)) != SR_OK) + /* Stop any prior acquisition, then arm and start. Matches DSView's order + * at dslogic.c (STOP -> arm -> START). */ + if ((ret = devc->ops->acquisition_stop(sdi)) != SR_OK) return ret; - if ((ret = fpga_configure(sdi)) != SR_OK) + if ((ret = devc->ops->fpga_config(sdi)) != SR_OK) return ret; - if ((ret = command_start_acquisition(sdi)) != SR_OK) + if ((ret = devc->ops->acquisition_start(sdi)) != SR_OK) return ret; sr_dbg("Getting trigger."); @@ -1086,7 +1125,9 @@ SR_PRIV int dslogic_acquisition_start(const struct sr_dev_inst *sdi) SR_PRIV int dslogic_acquisition_stop(struct sr_dev_inst *sdi) { - command_stop_acquisition(sdi); + struct dev_context *devc = sdi->priv; + + devc->ops->acquisition_stop(sdi); abort_acquisition(sdi->priv); return SR_OK; } diff --git a/src/hardware/dreamsourcelab-dslogic/protocol.h b/src/hardware/dreamsourcelab-dslogic/protocol.h index 6af7c8236..cf2f1e42f 100644 --- a/src/hardware/dreamsourcelab-dslogic/protocol.h +++ b/src/hardware/dreamsourcelab-dslogic/protocol.h @@ -29,6 +29,29 @@ #include #include "libsigrok-internal.h" +struct sr_dev_inst; /* forward */ + +enum dslogic_protocol_version { + DSL_PROTO_V1 = 0, /* flat opcodes 0xb0-0xb8 (legacy DSLogic firmware) */ + DSL_PROTO_V2 = 1, /* envelope CMD_CTL_WR/RD with ctl_header (DSView 1.3.2-era firmware) */ +}; + +struct dslogic_protocol_ops { + int (*fpga_firmware_upload)(const struct sr_dev_inst *sdi); + int (*fpga_config)(const struct sr_dev_inst *sdi); + int (*acquisition_start)(const struct sr_dev_inst *sdi); + int (*acquisition_stop)(const struct sr_dev_inst *sdi); + int (*set_samplerate)(const struct sr_dev_inst *sdi, uint64_t rate); + int (*set_voltage_threshold)(const struct sr_dev_inst *sdi, double low, double high); + int (*set_trigger)(const struct sr_dev_inst *sdi); + int (*set_external_clock)(const struct sr_dev_inst *sdi, gboolean ext); + int (*set_clock_edge)(const struct sr_dev_inst *sdi, int edge); + int (*security_check)(const struct sr_dev_inst *sdi); +}; + +extern const struct dslogic_protocol_ops dslogic_v1_ops; +extern const struct dslogic_protocol_ops dslogic_v2_ops; + #define LOG_PREFIX "dreamsourcelab-dslogic" #define USB_INTERFACE 0 @@ -103,10 +126,14 @@ struct dslogic_profile { /* Memory depth in bits. */ uint64_t mem_depth; + + enum dslogic_protocol_version protocol_version; + const struct dslogic_protocol_ops *ops; }; struct dev_context { const struct dslogic_profile *profile; + const struct dslogic_protocol_ops *ops; /* * Since we can't keep track of a DSLogic device after upgrading * the firmware (it renumerates into a different device address @@ -142,11 +169,14 @@ struct dev_context { double cur_threshold; }; +SR_PRIV int fpga_configure(const struct sr_dev_inst *sdi); SR_PRIV int dslogic_fpga_firmware_upload(const struct sr_dev_inst *sdi); SR_PRIV int dslogic_set_voltage_threshold(const struct sr_dev_inst *sdi, double threshold); SR_PRIV int dslogic_dev_open(struct sr_dev_inst *sdi, struct sr_dev_driver *di); SR_PRIV struct dev_context *dslogic_dev_new(void); SR_PRIV int dslogic_acquisition_start(const struct sr_dev_inst *sdi); SR_PRIV int dslogic_acquisition_stop(struct sr_dev_inst *sdi); +SR_PRIV int command_start_acquisition(const struct sr_dev_inst *sdi); +SR_PRIV int command_stop_acquisition(const struct sr_dev_inst *sdi); #endif diff --git a/src/hardware/dreamsourcelab-dslogic/protocol_v1.c b/src/hardware/dreamsourcelab-dslogic/protocol_v1.c new file mode 100644 index 000000000..294b128b9 --- /dev/null +++ b/src/hardware/dreamsourcelab-dslogic/protocol_v1.c @@ -0,0 +1,93 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2026 Larry Hernandez + * + * 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 int v1_fpga_firmware_upload(const struct sr_dev_inst *sdi) +{ + return dslogic_fpga_firmware_upload(sdi); +} + +static int v1_fpga_config(const struct sr_dev_inst *sdi) +{ + return fpga_configure(sdi); +} + +static int v1_acquisition_start(const struct sr_dev_inst *sdi) +{ + return command_start_acquisition(sdi); +} + +static int v1_acquisition_stop(const struct sr_dev_inst *sdi) +{ + return command_stop_acquisition(sdi); +} + +static int v1_set_samplerate(const struct sr_dev_inst *sdi, uint64_t rate) +{ + struct dev_context *devc = sdi->priv; + devc->cur_samplerate = rate; + return SR_OK; /* V1 applies samplerate via fpga_configure on next acq */ +} + +static int v1_set_voltage_threshold(const struct sr_dev_inst *sdi, double low, double high) +{ + (void)low; /* V1 uses a single threshold midpoint */ + return dslogic_set_voltage_threshold(sdi, high); +} + +static int v1_set_trigger(const struct sr_dev_inst *sdi) +{ + (void)sdi; + return SR_OK; /* V1 sets trigger inside fpga_configure */ +} + +static int v1_set_external_clock(const struct sr_dev_inst *sdi, gboolean ext) +{ + struct dev_context *devc = sdi->priv; + devc->external_clock = ext; + return SR_OK; +} + +static int v1_set_clock_edge(const struct sr_dev_inst *sdi, int edge) +{ + struct dev_context *devc = sdi->priv; + devc->clock_edge = edge; + return SR_OK; +} + +static int v1_security_check(const struct sr_dev_inst *sdi) +{ + (void)sdi; + return SR_OK; /* V1 hardware has no security check */ +} + +SR_PRIV const struct dslogic_protocol_ops dslogic_v1_ops = { + .fpga_firmware_upload = v1_fpga_firmware_upload, + .fpga_config = v1_fpga_config, + .acquisition_start = v1_acquisition_start, + .acquisition_stop = v1_acquisition_stop, + .set_samplerate = v1_set_samplerate, + .set_voltage_threshold = v1_set_voltage_threshold, + .set_trigger = v1_set_trigger, + .set_external_clock = v1_set_external_clock, + .set_clock_edge = v1_set_clock_edge, + .security_check = v1_security_check, +}; diff --git a/src/hardware/dreamsourcelab-dslogic/protocol_v2.c b/src/hardware/dreamsourcelab-dslogic/protocol_v2.c new file mode 100644 index 000000000..4aef57653 --- /dev/null +++ b/src/hardware/dreamsourcelab-dslogic/protocol_v2.c @@ -0,0 +1,704 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2013 Bert Vermeulen + * Copyright (C) 2013-2017 DreamSourceLab + * Copyright (C) 2026 Larry Hernandez + * + * V2 (envelope) protocol implementation. Wire format mirrors DSView + * 1.3.2's libsigrok4DSL/hardware/DSL/command.c and dsl.c. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License v3. + */ + +#include +#include +#include +#include +#include +#include "protocol.h" +#include "protocol_v2.h" + +#define V2_USB_TIMEOUT_MS 3000 + +SR_PRIV int command_ctl_wr_v2(libusb_device_handle *devhdl, struct ctl_wr_cmd cmd) +{ + int ret; + + assert(devhdl); + + ret = libusb_control_transfer(devhdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, + CMD_CTL_WR, 0x0000, 0x0000, + (unsigned char *)&cmd, + cmd.header.size + sizeof(struct ctl_header), + V2_USB_TIMEOUT_MS); + if (ret < 0) { + sr_err("CMD_CTL_WR failed (dest=%u offset=%u size=%u): %s", + cmd.header.dest, cmd.header.offset, cmd.header.size, + libusb_error_name(ret)); + return SR_ERR; + } + return SR_OK; +} + +SR_PRIV int command_ctl_rd_v2(libusb_device_handle *devhdl, struct ctl_rd_cmd cmd) +{ + int ret; + + assert(devhdl); + + /* Phase 1: write the header to set up the read. */ + ret = libusb_control_transfer(devhdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, + CMD_CTL_RD_PRE, 0x0000, 0x0000, + (unsigned char *)&cmd, sizeof(struct ctl_header), + V2_USB_TIMEOUT_MS); + if (ret < 0) { + sr_err("CMD_CTL_RD_PRE failed (dest=%u offset=%u size=%u): %s", + cmd.header.dest, cmd.header.offset, cmd.header.size, + libusb_error_name(ret)); + return SR_ERR; + } + + g_usleep(10 * 1000); + + /* Phase 2: read the requested bytes. */ + ret = libusb_control_transfer(devhdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + CMD_CTL_RD, 0x0000, 0x0000, + (unsigned char *)cmd.data, cmd.header.size, + V2_USB_TIMEOUT_MS); + if (ret < 0) { + sr_err("CMD_CTL_RD failed: %s", libusb_error_name(ret)); + return SR_ERR; + } + return SR_OK; +} + +SR_PRIV int dsl_wr_reg_v2(const struct sr_dev_inst *sdi, uint16_t addr, uint8_t value) +{ + struct sr_usb_dev_inst *usb = sdi->conn; + struct ctl_wr_cmd wr_cmd; + + wr_cmd.header.dest = DSL_CTL_I2C_REG; + wr_cmd.header.offset = addr; + wr_cmd.header.size = 1; + wr_cmd.data[0] = value; + return command_ctl_wr_v2(usb->devhdl, wr_cmd); +} + +SR_PRIV int dsl_rd_reg_v2(const struct sr_dev_inst *sdi, uint16_t addr, uint8_t *value) +{ + struct sr_usb_dev_inst *usb = sdi->conn; + struct ctl_rd_cmd rd_cmd; + + rd_cmd.header.dest = DSL_CTL_I2C_STATUS; + rd_cmd.header.offset = addr; + rd_cmd.header.size = 1; + rd_cmd.data = value; + return command_ctl_rd_v2(usb->devhdl, rd_cmd); +} + +SR_PRIV int dsl_rd_nvm_v2(const struct sr_dev_inst *sdi, uint8_t *buf, uint16_t addr, uint8_t len) +{ + struct sr_usb_dev_inst *usb = sdi->conn; + struct ctl_rd_cmd rd_cmd; + + rd_cmd.header.dest = DSL_CTL_NVM; + rd_cmd.header.offset = addr; + rd_cmd.header.size = len; + rd_cmd.data = buf; + return command_ctl_rd_v2(usb->devhdl, rd_cmd); +} + +SR_PRIV int dsl_wait_hw_status_bit_v2(libusb_device_handle *hdl, uint8_t bit_mask, gboolean want_set, unsigned timeout_ms) +{ + uint8_t status; + struct ctl_rd_cmd rd_cmd; + gint64 deadline_us = g_get_monotonic_time() + (gint64)timeout_ms * 1000; + + rd_cmd.header.dest = DSL_CTL_HW_STATUS; + rd_cmd.header.offset = 0; + rd_cmd.header.size = 1; + rd_cmd.data = &status; + + for (;;) { + if (command_ctl_rd_v2(hdl, rd_cmd) != SR_OK) + return SR_ERR; + if (want_set && (status & bit_mask)) + return SR_OK; + if (!want_set && !(status & bit_mask)) + return SR_OK; + if (g_get_monotonic_time() >= deadline_us) { + sr_err("Timeout waiting for HW_STATUS bit 0x%02x (%s)", + bit_mask, want_set ? "set" : "clear"); + return SR_ERR; + } + g_usleep(1000); + } +} + +/* Security challenge-response helpers (mirror DSView dsl.c). */ + +static int v2_secu_reset(const struct sr_dev_inst *sdi) +{ + if (dsl_wr_reg_v2(sdi, SEC_CTRL_ADDR, 0) != SR_OK) return SR_ERR; + if (dsl_wr_reg_v2(sdi, SEC_CTRL_ADDR + 1, 0) != SR_OK) return SR_ERR; + g_usleep(10 * 1000); + if (dsl_wr_reg_v2(sdi, SEC_CTRL_ADDR, 1) != SR_OK) return SR_ERR; + if (dsl_wr_reg_v2(sdi, SEC_CTRL_ADDR + 1, 0) != SR_OK) return SR_ERR; + return SR_OK; +} + +static int v2_secu_write(const struct sr_dev_inst *sdi, uint16_t cmd, uint16_t din) +{ + if (dsl_wr_reg_v2(sdi, SEC_DATA_ADDR, din & 0xff) != SR_OK) return SR_ERR; + if (dsl_wr_reg_v2(sdi, SEC_DATA_ADDR + 1, (din >> 8) & 0xff) != SR_OK) return SR_ERR; + if (dsl_wr_reg_v2(sdi, SEC_CTRL_ADDR, cmd & 0xff) != SR_OK) return SR_ERR; + if (dsl_wr_reg_v2(sdi, SEC_CTRL_ADDR + 1, (cmd >> 8) & 0xff) != SR_OK) return SR_ERR; + return SR_OK; +} + +static gboolean v2_secu_is_ready(const struct sr_dev_inst *sdi) +{ + uint8_t t = 0; + if (dsl_rd_reg_v2(sdi, SEC_CTRL_ADDR, &t) != SR_OK) + return FALSE; + return (t & bmSECU_READY) ? TRUE : FALSE; +} + +static gboolean v2_secu_is_pass(const struct sr_dev_inst *sdi) +{ + uint8_t t = 0; + if (dsl_rd_reg_v2(sdi, SEC_CTRL_ADDR, &t) != SR_OK) + return FALSE; + return (t & bmSECU_PASS) ? TRUE : FALSE; +} + +static uint16_t v2_secu_read(const struct sr_dev_inst *sdi) +{ + uint8_t hi = 0, lo = 0; + if (dsl_rd_reg_v2(sdi, SEC_DATA_ADDR + 1, &hi) != SR_OK) return 0; + if (dsl_rd_reg_v2(sdi, SEC_DATA_ADDR, &lo) != SR_OK) return 0; + return ((uint16_t)hi << 8) | lo; +} + +static int v2_security_check(const struct sr_dev_inst *sdi) +{ + uint16_t encryption[SECU_STEPS]; + int i; + int try_cnt; + + /* "Dessert clear" - DSView writes CTR0_ADDR=0x70 to 0 before the + * encryption read (dsl.c). Without this the FPGA can be left in + * a state where subsequent HW_STATUS reads stall. */ + if (dsl_wr_reg_v2(sdi, 0x70, 0x00) != SR_OK) { + sr_err("Failed CTR0_ADDR dessert-clear."); + return SR_ERR; + } + + if (dsl_rd_nvm_v2(sdi, (uint8_t *)encryption, SECU_EEP_ADDR, SECU_STEPS * 2) != SR_OK) { + sr_err("Failed to read encryption blob from device NVM at 0x%04x.", SECU_EEP_ADDR); + return SR_ERR; + } + + if (v2_secu_reset(sdi) != SR_OK) { + sr_err("Security reset failed."); + return SR_ERR; + } + + if (v2_secu_is_pass(sdi)) { + sr_err("Security state is already 'pass' before challenge; rejected."); + return SR_ERR; + } + + if (v2_secu_write(sdi, SECU_START, 0) != SR_OK) { + sr_err("Security start command failed."); + return SR_ERR; + } + + /* Step counts down from SECU_STEPS-1 to 0, mirrors DSView dsl.c. */ + for (i = SECU_STEPS - 1; i >= 0; i--) { + if (v2_secu_is_pass(sdi)) { + sr_err("Security passed prematurely at step %d.", i); + return SR_ERR; + } + try_cnt = SECU_TRY_CNT; + while (!v2_secu_is_ready(sdi)) { + if (try_cnt-- == 0) { + sr_err("Security ready timeout at step %d.", i); + return SR_ERR; + } + } + if (v2_secu_read(sdi) != 0) { + sr_err("Security read non-zero at step %d.", i); + return SR_ERR; + } + if (v2_secu_write(sdi, SECU_CHECK, encryption[i]) != SR_OK) { + sr_err("Security check write failed at step %d.", i); + return SR_ERR; + } + } + + sr_info("Security check pass!"); + return SR_OK; +} + +/* FPGA arm sequence (mirrors DSView dsl_fpga_arm at dsl.c). */ + +/* + * V2 FPGA bitstream upload (mirrors DSView dsl_fpga_config at dsl.c). + * + * V1 uses a single DS_CMD_CONFIG (0xb3) control transfer + bulk write of the + * bitstream. V2 firmware does not recognise 0xb3 and stalls. The V2 sequence: + * 1) DSL_CTL_PROG_B := ~bmWR_PROG_B (PROG_B low) + * 2) DSL_CTL_LED := off + * 3) DSL_CTL_PROG_B := bmWR_PROG_B (PROG_B high) + * 4) poll HW_STATUS until bmFPGA_INIT_B is set + * 5) DSL_CTL_INTRDY := ~bmWR_INTRDY (INTRDY low) + * 6) DSL_CTL_BULK_WR with 3-byte bitstream-size announce + * 7) bulk transfer the bitstream on ep2 OUT + * 8) DSL_CTL_INTRDY := bmWR_INTRDY (INTRDY high → data end) + * 9) poll HW_STATUS until bmGPIF_DONE is set + */ +static int v2_fpga_firmware_upload(const struct sr_dev_inst *sdi) +{ + struct drv_context *drvc = sdi->driver->context; + struct sr_usb_dev_inst *usb = sdi->conn; + libusb_device_handle *hdl = usb->devhdl; + struct dev_context *devc = sdi->priv; + struct sr_resource bitstream; + struct ctl_wr_cmd wr; + unsigned char *buf; + int transferred; + int result, ret; + const char *name = NULL; + + if (!strcmp(devc->profile->model, "DSLogic Plus")) { + name = "dreamsourcelab-dslogic-plus-fpga.fw"; + } else { + sr_err("v2: no FPGA firmware for model '%s'.", devc->profile->model); + return SR_ERR; + } + + sr_dbg("Uploading FPGA bitstream '%s' via V2 envelope protocol.", name); + if ((result = sr_resource_open(drvc->sr_ctx, &bitstream, + SR_RESOURCE_FIRMWARE, name)) != SR_OK) + return result; + + /* 1) PROG_B low */ + wr.header.dest = DSL_CTL_PROG_B; + wr.header.offset = 0; + wr.header.size = 1; + wr.data[0] = (uint8_t)~bmWR_PROG_B; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 2) LEDs off */ + wr.header.dest = DSL_CTL_LED; + wr.header.size = 1; + wr.data[0] = (uint8_t)(~bmLED_GREEN & ~bmLED_RED); + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 3) PROG_B high */ + wr.header.dest = DSL_CTL_PROG_B; + wr.header.size = 1; + wr.data[0] = bmWR_PROG_B; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 4) Wait bmFPGA_INIT_B set */ + if ((ret = dsl_wait_hw_status_bit_v2(hdl, bmFPGA_INIT_B, TRUE, 3000)) != SR_OK) + goto fail; + + /* 5) INTRDY low */ + wr.header.dest = DSL_CTL_INTRDY; + wr.header.size = 1; + wr.data[0] = (uint8_t)~bmWR_INTRDY; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 6) BULK_WR announce: 3-byte file size */ + wr.header.dest = DSL_CTL_BULK_WR; + wr.header.size = 3; + wr.data[0] = (uint8_t)bitstream.size; + wr.data[1] = (uint8_t)(bitstream.size >> 8); + wr.data[2] = (uint8_t)(bitstream.size >> 16); + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 7) bulk-transfer the bitstream */ + buf = g_malloc(bitstream.size); + if (!buf) { ret = SR_ERR; goto fail; } + { + uint64_t sum = 0; + ssize_t chunk; + while ((chunk = sr_resource_read(drvc->sr_ctx, &bitstream, + buf + sum, bitstream.size - sum)) > 0) + sum += chunk; + if ((int64_t)sum != (int64_t)bitstream.size) { + sr_err("v2 fpga: short read of bitstream (%" PRIu64 "/%" PRIu64 ").", + sum, bitstream.size); + g_free(buf); + ret = SR_ERR; + goto fail; + } + } + ret = libusb_bulk_transfer(hdl, 2 | LIBUSB_ENDPOINT_OUT, + buf, (int)bitstream.size, &transferred, V2_USB_TIMEOUT_MS); + g_free(buf); + if (ret < 0) { + sr_err("v2 fpga: bitstream bulk write failed: %s", libusb_error_name(ret)); + ret = SR_ERR; + goto fail; + } + if (transferred != (int)bitstream.size) { + sr_err("v2 fpga: bitstream short transfer (%d/%" PRIu64 ").", + transferred, bitstream.size); + ret = SR_ERR; + goto fail; + } + + /* 8) INTRDY high (signal data end) */ + wr.header.dest = DSL_CTL_INTRDY; + wr.header.size = 1; + wr.data[0] = bmWR_INTRDY; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 9) Wait GPIF_DONE */ + if ((ret = dsl_wait_hw_status_bit_v2(hdl, bmGPIF_DONE, TRUE, 3000)) != SR_OK) + goto fail; + + /* 10) INTRDY low (dsl.c) */ + wr.header.dest = DSL_CTL_INTRDY; + wr.header.size = 1; + wr.data[0] = (uint8_t)~bmWR_INTRDY; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 11) Wait FPGA_DONE - confirms the FPGA bitstream is live (dsl.c) */ + if ((ret = dsl_wait_hw_status_bit_v2(hdl, bmFPGA_DONE, TRUE, 3000)) != SR_OK) + goto fail; + + /* 12) Turn on the green LED (dsl.c) */ + wr.header.dest = DSL_CTL_LED; + wr.header.size = 1; + wr.data[0] = bmLED_GREEN; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + /* 13) Re-assert WORDWIDE high (dsl.c) */ + wr.header.dest = DSL_CTL_WORDWIDE; + wr.header.size = 1; + wr.data[0] = bmWR_WORDWIDE; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) goto fail; + + sr_info("FPGA configure done: %" PRIu64 " bytes.", bitstream.size); + sr_resource_close(drvc->sr_ctx, &bitstream); + return SR_OK; + +fail: + sr_resource_close(drvc->sr_ctx, &bitstream); + return SR_ERR; +} + +/* + * Integer ceiling division helper - avoids pulling in . + * Returns ceil(a / b) as uint32_t. + */ +static inline uint32_t div_round_up(uint64_t a, uint64_t b) +{ + return (uint32_t)((a + b - 1) / b); +} + +/* + * Build the struct DSL_setting that is bulk-written to the FPGA. + * + * Header field encoding: (register_index << 8) | word_count + * mirrors dsl.c. + * + * Samplerate divider uses hw_max_samplerate = 500 MHz and pre_div = 5, + * which are the correct values for the DSLogic Plus pgl12 16-channel + * mode (DSL_STREAM20x16_3DN2) as confirmed in dsl.h. + * + * Sample count is shifted right by 4 because the FPGA's minimum unit + * is 16 samples (dsl.c: "hardware minimum unit 64" [sic; actually + * 16 because >>4 == /16]). + */ +static void v2_build_default_setting(const struct sr_dev_inst *sdi, + struct DSL_setting *s) +{ + struct dev_context *devc = sdi->priv; + /* DSLogic Plus pgl12 (PID 0x0034) channel mode DSL_STREAM20x16_3DN2 */ + const uint64_t hw_max_samplerate = SR_MHZ(500); + const uint32_t pre_div = 5; + uint32_t tmp_u32; + uint64_t cur_sr; + uint64_t count_units; + int i; + + memset(s, 0, sizeof(*s)); + + /* Sync markers (dsl.c, 1046). */ + s->sync = DSL_SETTING_SYNC; + s->end_sync = DSL_SETTING_END_SYNC; + + /* Header values encode (register_index << 8) | word_count. */ + s->mode_header = 0x0001; /* reg 0, 1 word */ + s->divider_header = 0x0102; /* reg 1, 2 words */ + s->count_header = 0x0302; /* reg 3, 2 words */ + s->trig_pos_header = 0x0502; /* reg 5, 2 words */ + s->trig_glb_header = 0x0701; /* reg 7, 1 word */ + s->dso_count_header = 0x0802; /* reg 8, 2 words */ + s->ch_en_header = 0x0a02; /* reg 0xa, 2 words */ + s->fgain_header = 0x0c01; /* reg 0xc, 1 word */ + s->trig_header = 0x40a0; /* reg 0x40, 0xa0 words */ + + /* mode = 0: logic capture, internal clock, no compression, no trigger. */ + s->mode = 0; + + /* + * Samplerate divider (dsl.c, LOGIC mode branch). + * tmp_u32 = ceil(hw_max / cur_sr) + * div_h = ((tmp_u32 >= pre_div) ? (pre_div-1) : (tmp_u32-1)) << 8 + * tmp_u32 = ceil(tmp_u32 / pre_div) + * div_l = tmp_u32 & 0xffff + * div_h += tmp_u32 >> 16 + */ + cur_sr = devc->cur_samplerate ? devc->cur_samplerate : SR_MHZ(1); + tmp_u32 = div_round_up(hw_max_samplerate, cur_sr); + s->div_h = ((tmp_u32 >= pre_div) ? (pre_div - 1) : (tmp_u32 - 1)) << 8; + tmp_u32 = div_round_up(tmp_u32, pre_div); + s->div_l = tmp_u32 & 0x0000ffff; + s->div_h = (uint16_t)(s->div_h + (tmp_u32 >> 16)); + + /* + * Capture counter (dsl.c, LOGIC mode branch). + * The FPGA's minimum unit is 16 samples (>>4). + */ + count_units = devc->limit_samples >> 4; + s->cnt_l = count_units & 0xffff; + s->cnt_h = (count_units >> 16) & 0xffff; + + /* trig_pos = 0 (no pre-trigger). */ + s->tpos_l = 0; + s->tpos_h = 0; + + /* trig_glb = 0 (no trigger). */ + s->trig_glb = 0; + + /* dso_cnt = 0 (unused in logic mode). */ + s->dso_cnt_l = 0; + s->dso_cnt_h = 0; + + /* All 16 logic channels enabled (dsl.c). */ + s->ch_en_l = 0xffff; + s->ch_en_h = 0x0000; + + /* fgain = 0 (no digital fine gain in logic mode). */ + s->fgain = 0; + + /* + * Trigger arrays - "no trigger" defaults, mirroring dsl.c + * (the i >= 1 loop in the SIMPLE_TRIGGER branch; we apply it to all + * stages since we have no active trigger stage). + * + * memset already zeroed value/edge/count; set mask and logic explicitly. + */ + for (i = 0; i < NUM_TRIGGER_STAGES; i++) { + s->trig_mask0[i] = 0xffff; + s->trig_mask1[i] = 0xffff; + s->trig_value0[i] = 0; + s->trig_value1[i] = 0; + s->trig_edge0[i] = 0; + s->trig_edge1[i] = 0; + s->trig_logic0[i] = 2; /* "always" per DSView */ + s->trig_logic1[i] = 2; + s->trig_count[i] = 0; + } +} + +/* + * Arm the FPGA by sending struct DSL_setting over bulk endpoint 2. + * + * Sequence (dsl.c): + * DSL_CTL_WORDWIDE → DSL_CTL_BULK_WR (3-byte word count) → + * poll bmSYS_CLR → bulk write setting blob on ep2 OUT → + * DSL_CTL_INTRDY → read HW_STATUS once, check bmGPIF_DONE. + * + * No DSL_CTL_STOP is issued here; DSView does not include it in the + * arm sequence. + */ +static int v2_fpga_config(const struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb = sdi->conn; + libusb_device_handle *hdl = usb->devhdl; + struct ctl_wr_cmd wr; + struct ctl_rd_cmd rd; + struct DSL_setting setting; + uint32_t arm_size; + uint8_t rd_data; + int ret, transferred; + + /* 1) Set GPIF to word-wide (16-bit) mode (dsl.c). */ + wr.header.dest = DSL_CTL_WORDWIDE; + wr.header.offset = 0; + wr.header.size = 1; + wr.data[0] = bmWR_WORDWIDE; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) { + sr_err("DSL_CTL_WORDWIDE failed."); + return SR_ERR; + } + + /* + * 2) Send bulk-write control command with 3-byte word count + * (dsl.c). arm_size is in uint16_t words. + */ + arm_size = sizeof(struct DSL_setting) / sizeof(uint16_t); + wr.header.dest = DSL_CTL_BULK_WR; + wr.header.offset = 0; + wr.header.size = 3; + wr.data[0] = (uint8_t)arm_size; + wr.data[1] = (uint8_t)(arm_size >> 8); + wr.data[2] = (uint8_t)(arm_size >> 16); + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) { + sr_err("DSL_CTL_BULK_WR (arm announce) failed."); + return SR_ERR; + } + + /* + * 3) Poll bmSYS_CLR until the firmware asserts it + * (dsl.c) - DSView polls immediately after BULK_WR with + * no delay; the firmware appears to expect this fast cadence. + */ + if (dsl_wait_hw_status_bit_v2(hdl, bmSYS_CLR, TRUE, 3000) != SR_OK) + return SR_ERR; + + /* 4) Build the settings blob and bulk-write it (dsl.c). */ + v2_build_default_setting(sdi, &setting); + transferred = 0; + ret = libusb_bulk_transfer(hdl, 2 | LIBUSB_ENDPOINT_OUT, + (unsigned char *)&setting, + sizeof(struct DSL_setting), + &transferred, V2_USB_TIMEOUT_MS); + if (ret < 0) { + sr_err("Arm FPGA bulk write failed: %s.", libusb_error_name(ret)); + return SR_ERR; + } + if (transferred != (int)sizeof(struct DSL_setting)) { + sr_err("Arm FPGA bulk write short: %d/%zu.", + transferred, sizeof(struct DSL_setting)); + return SR_ERR; + } + + /* 5) Assert INTRDY high to signal end of data (dsl.c). */ + wr.header.dest = DSL_CTL_INTRDY; + wr.header.offset = 0; + wr.header.size = 1; + wr.data[0] = bmWR_INTRDY; + if ((ret = command_ctl_wr_v2(hdl, wr)) != SR_OK) { + sr_err("DSL_CTL_INTRDY failed."); + return SR_ERR; + } + + /* + * 6) Read HW_STATUS once and check bmGPIF_DONE (dsl.c). + * DSView does NOT poll - a single read is the spec. + */ + rd.header.dest = DSL_CTL_HW_STATUS; + rd.header.offset = 0; + rd.header.size = 1; + rd_data = 0; + rd.data = &rd_data; + if (command_ctl_rd_v2(hdl, rd) != SR_OK) + return SR_ERR; + if (rd_data & bmGPIF_DONE) { + sr_info("Arm FPGA done."); + return SR_OK; + } + sr_err("Arm FPGA: bmGPIF_DONE not set after INTRDY (HW_STATUS=0x%02x).", rd_data); + return SR_ERR; +} + +static int v2_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb = sdi->conn; + struct ctl_wr_cmd wr; + + wr.header.dest = DSL_CTL_START; + wr.header.offset = 0; + wr.header.size = 0; + return command_ctl_wr_v2(usb->devhdl, wr); +} + +static int v2_acquisition_stop(const struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb = sdi->conn; + struct ctl_wr_cmd wr; + + /* + * DSView's dsl_dev_acquisition_stop is two-stage (dsl.c): + * write CTR0_ADDR := bmFORCE_RDY first (soft FPGA abort that releases + * the GPIF capture engine and resets the green LED), then send + * DSL_CTL_STOP. Without bmFORCE_RDY, the FPGA stays in capture state + * and the LED flashes after a "completed" acquisition. + */ + (void)dsl_wr_reg_v2(sdi, CTR0_ADDR, bmFORCE_RDY); + + wr.header.dest = DSL_CTL_STOP; + wr.header.offset = 0; + wr.header.size = 0; + return command_ctl_wr_v2(usb->devhdl, wr); +} + +static int v2_set_samplerate(const struct sr_dev_inst *sdi, uint64_t rate) +{ + struct dev_context *devc = sdi->priv; + devc->cur_samplerate = rate; + return SR_OK; +} + +static int v2_set_voltage_threshold(const struct sr_dev_inst *sdi, double low, double high) +{ + /* DSLogic exposes a single threshold via VTH_ADDR; use the midpoint. */ + double mid = (low + high) / 2.0; + uint8_t dac; + + if (mid < 0.0) mid = 0.0; + if (mid > 3.3) mid = 3.3; + dac = (uint8_t)(mid / 3.3 * (1.5 / 2.5) * 255.0); + + return dsl_wr_reg_v2(sdi, VTH_ADDR, dac); +} + +static int v2_set_trigger(const struct sr_dev_inst *sdi) +{ + /* Triggers are encoded into struct DSL_setting at arm time + * (see v2_build_default_setting). Nothing to do at config time. */ + (void)sdi; + return SR_OK; +} + +static int v2_set_external_clock(const struct sr_dev_inst *sdi, gboolean ext) +{ + struct dev_context *devc = sdi->priv; + devc->external_clock = ext; + /* Applied at next arm via the mode bitfield in DSL_setting. */ + return SR_OK; +} + +static int v2_set_clock_edge(const struct sr_dev_inst *sdi, int edge) +{ + struct dev_context *devc = sdi->priv; + devc->clock_edge = edge; + return SR_OK; +} + +SR_PRIV const struct dslogic_protocol_ops dslogic_v2_ops = { + .fpga_firmware_upload = v2_fpga_firmware_upload, + .fpga_config = v2_fpga_config, + .acquisition_start = v2_acquisition_start, + .acquisition_stop = v2_acquisition_stop, + .set_samplerate = v2_set_samplerate, + .set_voltage_threshold = v2_set_voltage_threshold, + .set_trigger = v2_set_trigger, + .set_external_clock = v2_set_external_clock, + .set_clock_edge = v2_set_clock_edge, + .security_check = v2_security_check, +}; diff --git a/src/hardware/dreamsourcelab-dslogic/protocol_v2.h b/src/hardware/dreamsourcelab-dslogic/protocol_v2.h new file mode 100644 index 000000000..099b61627 --- /dev/null +++ b/src/hardware/dreamsourcelab-dslogic/protocol_v2.h @@ -0,0 +1,148 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2013 Bert Vermeulen + * Copyright (C) 2013-2017 DreamSourceLab + * Copyright (C) 2026 Larry Hernandez + * + * Constants, structs, and prototypes for the DSLogic envelope (v2) + * protocol used by newer-firmware DSLogic hardware (PID 2a0e:0034 + * and any later device tagged DSL_PROTO_V2). + * + * Wire format mirrors DSView 1.3.2's libsigrok4DSL/hardware/DSL/. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License v3. + */ + +#ifndef LIBSIGROK_HARDWARE_DREAMSOURCELAB_DSLOGIC_PROTOCOL_V2_H +#define LIBSIGROK_HARDWARE_DREAMSOURCELAB_DSLOGIC_PROTOCOL_V2_H + +#include +#include +#include "protocol.h" + +/* Envelope opcodes (carried in libusb_control_transfer's bRequest). */ +#define CMD_CTL_WR 0xb0 +#define CMD_CTL_RD_PRE 0xb1 +#define CMD_CTL_RD 0xb2 + +/* DSL_CTL destination codes (selector in ctl_header.dest). */ +#define DSL_CTL_FW_VERSION 0 +#define DSL_CTL_REVID_VERSION 1 +#define DSL_CTL_HW_STATUS 2 +#define DSL_CTL_PROG_B 3 +#define DSL_CTL_SYS 4 +#define DSL_CTL_LED 5 +#define DSL_CTL_INTRDY 6 +#define DSL_CTL_WORDWIDE 7 +#define DSL_CTL_START 8 +#define DSL_CTL_STOP 9 +#define DSL_CTL_BULK_WR 10 +#define DSL_CTL_REG 11 +#define DSL_CTL_NVM 12 +#define DSL_CTL_I2C_REG 14 +#define DSL_CTL_I2C_STATUS 15 + +/* Status bits in DSL_CTL_HW_STATUS read byte. */ +#define bmGPIF_DONE (1 << 7) +#define bmFPGA_DONE (1 << 6) +#define bmFPGA_INIT_B (1 << 5) +#define bmSYS_OVERFLOW (1 << 4) +#define bmSYS_CLR (1 << 3) +#define bmSYS_EN (1 << 2) + +/* Bits written via DSL_CTL_INTRDY / DSL_CTL_WORDWIDE / DSL_CTL_PROG_B. */ +#define bmWR_INTRDY (1 << 7) +#define bmWR_WORDWIDE (1 << 0) +#define bmWR_PROG_B (1 << 2) + +/* Bits written via DSL_CTL_LED. */ +#define bmLED_GREEN (1 << 0) +#define bmLED_RED (1 << 1) + +/* I2C-mapped FPGA register addresses. */ +#define VTH_ADDR 0x78 +#define SEC_DATA_ADDR 0x75 +#define SEC_CTRL_ADDR 0x73 +#define CTR0_ADDR 0x70 + +/* CTR0_ADDR bits (mirrors DSView command.h). */ +#define bmFORCE_RDY (1 << 1) + +/* Security check (mirrors DSView command.c). */ +#define bmSECU_READY (1 << 3) +#define bmSECU_PASS (1 << 4) +#define SECU_STEPS 8 +#define SECU_START 0x0513 +#define SECU_CHECK 0x0219 +#define SECU_EEP_ADDR 0x3C00 +#define SECU_TRY_CNT 8 + +/* Trigger / setting blob (mirrors DSView dsl.h). */ +#ifndef NUM_TRIGGER_STAGES +#define NUM_TRIGGER_STAGES 16 +#endif + +#pragma pack(push, 1) + +struct ctl_header { + uint8_t dest; + uint16_t offset; + uint8_t size; +}; + +struct ctl_wr_cmd { + struct ctl_header header; + uint8_t data[60]; +}; + +struct ctl_rd_cmd { + struct ctl_header header; + uint8_t *data; +}; + +struct DSL_setting { + uint32_t sync; + uint16_t mode_header; uint16_t mode; + uint16_t divider_header; uint16_t div_l, div_h; + uint16_t count_header; uint16_t cnt_l, cnt_h; + uint16_t trig_pos_header; uint16_t tpos_l, tpos_h; + uint16_t trig_glb_header; uint16_t trig_glb; + uint16_t dso_count_header; uint16_t dso_cnt_l, dso_cnt_h; + uint16_t ch_en_header; uint16_t ch_en_l, ch_en_h; + uint16_t fgain_header; uint16_t fgain; + + uint16_t trig_header; + uint16_t trig_mask0[NUM_TRIGGER_STAGES]; + uint16_t trig_mask1[NUM_TRIGGER_STAGES]; + uint16_t trig_value0[NUM_TRIGGER_STAGES]; + uint16_t trig_value1[NUM_TRIGGER_STAGES]; + uint16_t trig_edge0[NUM_TRIGGER_STAGES]; + uint16_t trig_edge1[NUM_TRIGGER_STAGES]; + uint16_t trig_logic0[NUM_TRIGGER_STAGES]; + uint16_t trig_logic1[NUM_TRIGGER_STAGES]; + uint32_t trig_count[NUM_TRIGGER_STAGES]; + + uint32_t end_sync; +}; + +#pragma pack(pop) + +/* Sync markers used by DSL_setting (mirrors DSView dsl.c constants). */ +#define DSL_SETTING_SYNC 0xf5a5f5a5 +#define DSL_SETTING_END_SYNC 0xfa5afa5a + +/* Transport primitives. */ +SR_PRIV int command_ctl_wr_v2(libusb_device_handle *devhdl, struct ctl_wr_cmd cmd); +SR_PRIV int command_ctl_rd_v2(libusb_device_handle *devhdl, struct ctl_rd_cmd cmd); + +/* Register / NVM access (envelope wrappers). */ +SR_PRIV int dsl_wr_reg_v2(const struct sr_dev_inst *sdi, uint16_t addr, uint8_t value); +SR_PRIV int dsl_rd_reg_v2(const struct sr_dev_inst *sdi, uint16_t addr, uint8_t *value); +SR_PRIV int dsl_rd_nvm_v2(const struct sr_dev_inst *sdi, uint8_t *buf, uint16_t addr, uint8_t len); + +/* HW status polling helper. */ +SR_PRIV int dsl_wait_hw_status_bit_v2(libusb_device_handle *hdl, uint8_t bit_mask, gboolean want_set, unsigned timeout_ms); + +#endif