From 3b5b123cc03902900bdfc7428e8817b2c0927d81 Mon Sep 17 00:00:00 2001 From: Patrick Plenefisch Date: Wed, 1 Mar 2023 17:25:28 -0500 Subject: [PATCH 01/20] tektronix-tds: Initial driver skeleton. --- Makefile.am | 6 + configure.ac | 1 + src/hardware/tektronix-tds/api.c | 154 ++++++++++++++++++++++++++ src/hardware/tektronix-tds/protocol.c | 43 +++++++ src/hardware/tektronix-tds/protocol.h | 35 ++++++ 5 files changed, 239 insertions(+) create mode 100644 src/hardware/tektronix-tds/api.c create mode 100644 src/hardware/tektronix-tds/protocol.c create mode 100644 src/hardware/tektronix-tds/protocol.h diff --git a/Makefile.am b/Makefile.am index 63b26d6fc..7ea17b947 100644 --- a/Makefile.am +++ b/Makefile.am @@ -650,6 +650,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/sysclk-sla5032/protocol.c \ src/hardware/sysclk-sla5032/api.c endif +if HW_TEKTRONIX_TDS +src_libdrivers_la_SOURCES += \ + src/hardware/tektronix-tds/protocol.h \ + src/hardware/tektronix-tds/protocol.c \ + src/hardware/tektronix-tds/api.c +endif if HW_TELEINFO src_libdrivers_la_SOURCES += \ src/hardware/teleinfo/protocol.h \ diff --git a/configure.ac b/configure.ac index c8c51eb94..db27e88da 100644 --- a/configure.ac +++ b/configure.ac @@ -359,6 +359,7 @@ SR_DRIVER([serial LCR], [serial-lcr], [serial_comm]) SR_DRIVER([Siglent SDS], [siglent-sds]) SR_DRIVER([Sysclk LWLA], [sysclk-lwla], [libusb]) SR_DRIVER([Sysclk SLA5032], [sysclk-sla5032], [libusb]) +SR_DRIVER([Tektronix TDS], [tektronix-tds]) SR_DRIVER([Teleinfo], [teleinfo], [serial_comm]) SR_DRIVER([Testo], [testo], [libusb]) SR_DRIVER([Tondaj SL-814], [tondaj-sl-814], [serial_comm]) diff --git a/src/hardware/tektronix-tds/api.c b/src/hardware/tektronix-tds/api.c new file mode 100644 index 000000000..2136595db --- /dev/null +++ b/src/hardware/tektronix-tds/api.c @@ -0,0 +1,154 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Patrick Plenefisch + * + * 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 tektronix_tds_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 tektronix_tds_driver_info = { + .name = "tektronix-tds", + .longname = "Tektronix TDS", + .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(tektronix_tds_driver_info); diff --git a/src/hardware/tektronix-tds/protocol.c b/src/hardware/tektronix-tds/protocol.c new file mode 100644 index 000000000..b07aa1cda --- /dev/null +++ b/src/hardware/tektronix-tds/protocol.c @@ -0,0 +1,43 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Patrick Plenefisch + * + * 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 tektronix_tds_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/tektronix-tds/protocol.h b/src/hardware/tektronix-tds/protocol.h new file mode 100644 index 000000000..9496c0d2a --- /dev/null +++ b/src/hardware/tektronix-tds/protocol.h @@ -0,0 +1,35 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Patrick Plenefisch + * + * 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_TEKTRONIX_TDS_PROTOCOL_H +#define LIBSIGROK_HARDWARE_TEKTRONIX_TDS_PROTOCOL_H + +#include +#include +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "tektronix-tds" + +struct dev_context { +}; + +SR_PRIV int tektronix_tds_receive_data(int fd, int revents, void *cb_data); + +#endif From 5afb6ffe5fa6da383eb127bf6308e6ba22044faf Mon Sep 17 00:00:00 2001 From: Patrick Plenefisch Date: Sun, 9 Apr 2023 16:26:45 -0400 Subject: [PATCH 02/20] udev: add Tektronix TDS 2024 USB indentification rule --- contrib/60-libsigrok.rules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/60-libsigrok.rules b/contrib/60-libsigrok.rules index 398ee4040..a0435ec86 100644 --- a/contrib/60-libsigrok.rules +++ b/contrib/60-libsigrok.rules @@ -313,6 +313,9 @@ ATTRS{idVendor}=="2961", ATTRS{idProduct}=="6689", ENV{ID_SIGROK}="1" # Sysclk SLA5032 ("32CH 500M" mode) ATTRS{idVendor}=="2961", ATTRS{idProduct}=="66b0", ENV{ID_SIGROK}="1" +# Tektronix +ATTRS{idVendor}=="0699", ATTRS{idProduct}=="036a", ENV{ID_SIGROK}="1" + # Testo 435 ATTRS{idVendor}=="128d", ATTRS{idProduct}=="0003", ENV{ID_SIGROK}="1" From ff1fe699af766937228068fa33cf48cd86626c89 Mon Sep 17 00:00:00 2001 From: Patrick Plenefisch Date: Sun, 9 Apr 2023 16:32:00 -0400 Subject: [PATCH 03/20] tektronix-tds: Add driver for select Tektronix TDS/TPS/TBS scopes --- src/hardware/tektronix-tds/api.c | 880 ++++++++++++++++++++++++-- src/hardware/tektronix-tds/protocol.c | 788 ++++++++++++++++++++++- src/hardware/tektronix-tds/protocol.h | 179 +++++- 3 files changed, 1797 insertions(+), 50 deletions(-) diff --git a/src/hardware/tektronix-tds/api.c b/src/hardware/tektronix-tds/api.c index 2136595db..eb10757e1 100644 --- a/src/hardware/tektronix-tds/api.c +++ b/src/hardware/tektronix-tds/api.c @@ -18,78 +18,749 @@ */ #include +#include +#include +#include +#include "libsigrok-internal.h" #include "protocol.h" +#include "scpi.h" + +/** + * Documentation for the SCPI commands can be found in + * https://download.tek.com/manual/TBS1000-B-EDU-TDS2000-B-C-TDS1000-B-C-EDU-TDS200-TPS2000-B-Programmer-077044403_RevB.pdf + * and is referred to as "doc page $PDF_PAGE/$PRINTED_PAGE" + */ + + +/** + * Missing semi-important features: + * bandwidth limiting ch:bandwidth + * chanel invert ch:invert + * volt/amp configuration ch::yunit + * pulse triggering + * ext trigger coupling + * peak-detect mode (data retreival) + * + * Missing less important features: + * capture/savefiles + * screenshots + * fine adjust of vdivs + * video triggering + */ + static struct sr_dev_driver tektronix_tds_driver_info; -static GSList *scan(struct sr_dev_driver *di, GSList *options) +static const uint32_t scanopts[] = {SR_CONF_CONN, SR_CONF_SERIALCOMM}; + +static const uint32_t drvopts[] = {SR_CONF_OSCILLOSCOPE}; + +static const uint32_t devopts[] = { + SR_CONF_TIMEBASE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_SLOPE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_LEVEL | SR_CONF_GET | SR_CONF_SET, + SR_CONF_HORIZ_TRIGGERPOS | SR_CONF_GET | SR_CONF_SET, + SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, + SR_CONF_NUM_HDIV | SR_CONF_GET, + SR_CONF_SAMPLERATE | SR_CONF_GET, + SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_AVERAGING | SR_CONF_GET | SR_CONF_SET, + SR_CONF_AVG_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_BUFFERSIZE | SR_CONF_GET, + SR_CONF_DATA_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_PEAK_DETECTION | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg_analog[] = { + SR_CONF_NUM_VDIV | SR_CONF_GET, + SR_CONF_VDIV | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_COUPLING | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_PROBE_FACTOR | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, +}; + +// TODO: Compensation adjusts the vdivs, but PulseView doesn't pick up +// any such dynamic changes, nor does it seem to support fine-adjust, +// so for now all possible compensation vdivs are present + +// validated in doc page 75/2-57 +static const uint64_t vdivs[][2] = { + /* millivolts */ + {2, 1000}, + {5, 1000}, + {10, 1000}, + {20, 1000}, + {50, 1000}, + {100, 1000}, + {200, 1000}, + {500, 1000}, + /* volts */ + {1, 1}, + {2, 1}, + {5, 1}, + /* >1x compensation only */ + {10, 1}, + {20, 1}, + {50, 1}, + /* >10x compensation only */ + {100, 1}, + {200, 1}, + {500, 1}, + /* >100x compensation only */ + {1000, 1}, + {2000, 1}, + {5000, 1}, +}; + +// everyone uses the same voltrange, only modified by compensation ranges (also the same) +#define VOLTRANGE_2m_5V 0, 0 + +static const uint64_t timebases[][2] = { + /* nanoseconds */ + {25, 10000000000}, + {5, 1000000000}, + {10, 1000000000}, + {25, 1000000000}, + {50, 1000000000}, + {100, 1000000000}, + {250, 1000000000}, + {500, 1000000000}, + /* microseconds */ + {1, 1000000}, + {25, 10000000}, + {5, 1000000}, + {10, 1000000}, + {25, 1000000}, + {50, 1000000}, + {100, 1000000}, + {250, 1000000}, + {500, 1000000}, + /* milliseconds */ + {1, 1000}, + {25, 10000}, + {5, 1000}, + {10, 1000}, + {25, 1000}, + {50, 1000}, + {100, 1000}, + {250, 1000}, + {500, 1000}, + /* seconds */ + {1, 1}, + {25, 10}, + {5, 1}, + {10, 1}, + {25, 1}, + {50, 1}, +}; +// Timebase limits are forward index, and reverse index +#define TIMEBASE_2n5_50s 0, 0 +#define TIMEBASE_5ns_50s 1, 0 +#define TIMEBASE_10n_50s 2, 0 +#define TIMEBASE_5ns_5s 1, 3 + +// validated in doc page 71/2-53 +static const char *coupling[] = { + "AC", + "DC", + "GND", +}; + +// validated in doc page 74/2-53 +static const uint64_t probe_factor_new[] = {1, 10, 20, 50, 100, 500, 1000}; +// Only for tds200, tds2000, tds1000 +static const uint64_t probe_factor_old[] = {1, 10, 100, 1000}; + +static const char *trigger_slopes[] = { + "r", + "f", +}; + +// validated in doc page 60/2-42 +static const uint64_t averages[] = {4, 16, 64, 128}; + +enum series_support_matrix +{ + T_S_Remainder, + TDS224, + TPS_2k, +}; + +// Must be in same order as the enum values DRIVER_CAPTURE_MODE +static const char *data_sources[] = { + [CAPTURE_LIVE] = "Live", + [CAPTURE_ONE_SHOT] = "One Shot", + [CAPTURE_DISPLAY] = "Memory+Live", + [CAPTURE_MEMORY] = "Memory", +}; + +// Note, CH3 and 4 should be last so that 4ch vs 2ch scopes +// can simply truncate this list by two +// validated in doc page 214/2-196 +static const char *trigger_sources_models_T_S_Remainder[] = { + "Ext", "Ext /5", "AC Line", + "CH1", "CH2", + /* 4ch only: */ "CH3", "CH4", +}; +static const char *trigger_sources_models_TDS224[] = { + "AC Line", + "CH1", "CH2", + /* 4ch only: */ "CH3", "CH4" +}; +static const char *trigger_sources_models_TPS_2k[] = { + "Ext", "Ext /5", "Ext /10", + "CH1", "CH2", + /* 4ch only: */ "CH3", "CH4", +}; + +#define DEVICE_SPEC(id_name, channels, sa_per_s, bw, probe_factors, \ + time_range, volt_range, trigger_sources) \ + { \ + id_name, channels, SA_##sa_per_s, BW_##bw, \ + ARRAY_AND_SIZE(probe_factor_##probe_factors), \ + TIMEBASE_##time_range, VOLTRANGE_##volt_range, \ + (trigger_sources_models_##trigger_sources), \ + ARRAY_SIZE(trigger_sources_models_##trigger_sources) - \ + 4 + channels \ + } + +/* This table was generated from the documentation: + * + * TBS1000B/EDU: https://download.tek.com/manual/TBS1000B-User-Manual-077088602-RevA.pdf + * TBS1000: https://download.tek.com/manual/TBS1000-Oscilloscope-User-Manual_077076001.pdf + * TDS2000C/TDS1000C-EDU: https://download.tek.com/manual/TDS2000C-and-TDS1000C-EDU-Oscilloscope-User-Manual-EN_077082600.pdf + * TDS2000B/TDS1000B: https://download.tek.com/manual/071181702web.pdf + * TDS2000/TDS1000: https://download.tek.com/manual/TDS2000_TDS1000_User_071106400_Revision_A.pdf + * TDS200: https://download.tek.com/manual/071039803.pdf + * TPS2000B:https://download.tek.com/manual/TPS2000B-Digital-Oscilloscope-User-Manual-077137901.pdf + * TPS2000: https://download.tek.com/manual/071144105web.pdf + * + * All specs can be found in Appendix A's of the linked pdfs + * EDU series are badge-only, and respond as if they are non-EDU products + */ +static const struct device_spec device_models[] = { + + // TBS original-series + DEVICE_SPEC("TBS 1022", 2, 500M, 25MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1042", 2, 500M, 40MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1062", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1064", 4, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1102", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1104", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1152", 2, 1G, 150MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1154", 4, 1G, 150MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + + // TBS B-series + DEVICE_SPEC("TBS 1032B", 2, 500M, 30MHz, new, 10n_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1052B", 2, 1G, 50MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1072B", 2, 1G, 70MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1102B", 2, 2G, 100MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1152B", 2, 2G, 150MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1202B", 2, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + + // TDS 200-series, only supports 5ns-5s/div + DEVICE_SPEC("TDS 210", 2, 1G , 60MHz, old, 5ns_5s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 220", 2, 1G, 100MHz, old, 5ns_5s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 224", 4, 1G, 100MHz, old, 5ns_5s, 2m_5V, TDS224), + + // TDS original-series + DEVICE_SPEC("TDS 1002", 2, 1G, 60MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1012", 2, 1G, 100MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + + DEVICE_SPEC("TDS 2002", 2, 1G, 60MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2012", 2, 1G, 100MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2014", 4, 1G, 100MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2022", 2, 2G, 200MHz, old, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2024", 4, 2G, 200MHz, old, 2n5_50s, 2m_5V, T_S_Remainder), + + // TDS B-series + DEVICE_SPEC("TDS 1001B", 2, 500M, 40MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1002B", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1012B", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + + DEVICE_SPEC("TDS 2002B", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2004B", 4, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2012B", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2014B", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2022B", 2, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2024B", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + + // TDS C-series + DEVICE_SPEC("TDS 1001C", 2, 500M, 40MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1002C", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1012C", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + + DEVICE_SPEC("TDS 2001C", 2, 500M, 50MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2002C", 2, 1G, 70MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2004C", 4, 1G, 70MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2012C", 2, 2G, 100MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2014C", 4, 2G, 100MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2022C", 2, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2024C", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + + // TPS original-series + DEVICE_SPEC("TPS 2012", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2014", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2024", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, TPS_2k), + + // TPS B-series + DEVICE_SPEC("TPS 2012B", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2014B", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2024B", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, TPS_2k), +}; + +static const char *TEKTRONIX = "Tektronix"; + +static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) { - struct drv_context *drvc; - GSList *devices; + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_scpi_hw_info *hw_info; + const struct device_spec *device; + struct sr_channel *ch; + unsigned int i; + gchar *channel_name; + + sdi = NULL; + devc = NULL; + hw_info = NULL; + + if (sr_scpi_get_hw_id(scpi, &hw_info) != SR_OK) { + sr_info("Couldn't get IDN response, retrying."); + sr_scpi_close(scpi); + sr_scpi_open(scpi); + if (sr_scpi_get_hw_id(scpi, &hw_info) != SR_OK) { + sr_info("Couldn't get IDN response."); + goto error; + } + } + + if (g_ascii_strcasecmp(hw_info->manufacturer, TEKTRONIX) != 0) + goto error; + + device = NULL; + for (i = 0; i < ARRAY_SIZE(device_models); i++) { + if (g_ascii_strcasecmp(hw_info->model, device_models[i].model) != 0) + continue; + device = &device_models[i]; + break; + } + + if (!device) { + sr_dbg("Found Tektronix device not supported by the tds/tps/tbs driver: %s", + hw_info->model); + goto error; + } + + sdi = g_malloc0(sizeof(*sdi)); + sdi->vendor = g_strdup(TEKTRONIX); + sdi->model = g_strdup(hw_info->model); + sdi->version = g_strdup(hw_info->firmware_version); + sdi->serial_num = g_strdup(hw_info->serial_number); + sdi->conn = scpi; + sdi->driver = &tektronix_tds_driver_info; + sdi->inst_type = SR_INST_SCPI; - (void)options; + devc = g_malloc0(sizeof(*devc)); + devc->model = device; + sdi->priv = devc; - devices = NULL; - drvc = di->context; - drvc->instances = NULL; + // give us a buffer on our buffer + devc->buffer = g_malloc(TEK_BUFFER_SIZE + 1000); + devc->limit_frames = 1; + devc->capture_mode = CAPTURE_MEMORY; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ + sr_scpi_hw_info_free(hw_info); - return devices; + devc->analog_groups = g_malloc0( + sizeof(struct sr_channel_group *) * device->channels); + + for (i = 0; i < (unsigned)device->channels; i++) { + channel_name = g_strdup_printf("CH%d", i + 1); + ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_name); + + devc->analog_groups[i] = sr_channel_group_new( + sdi, channel_name, NULL); + devc->analog_groups[i]->channels = g_slist_append(NULL, ch); + } + + return sdi; + +error: + sr_scpi_hw_info_free(hw_info); + g_free(devc); + sr_dev_inst_free(sdi); + + return NULL; +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + return sr_scpi_scan(di->context, options, probe_device); } static int dev_open(struct sr_dev_inst *sdi) { - (void)sdi; + int ret; + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; - /* TODO: get handle from sdi->conn and open it. */ + if ((ret = sr_scpi_open(scpi)) < 0) { + sr_err("Failed to open SCPI device: %s.", sr_strerror(ret)); + return SR_ERR; + } + + if ((ret = tektronix_tds_get_dev_cfg(sdi)) < 0) { + sr_err("Failed to get device config: %s.", sr_strerror(ret)); + return SR_ERR; + } + + sr_info( + "Opened Tektronix device '%s' with %d channels, %dMHz bandwidth, and %dMSa/s", + devc->model->model, devc->model->channels, devc->model->bandwidth, + devc->model->sample_rate); 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; + return sr_scpi_close(sdi->conn); } static int config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; + struct sr_channel *ch; + const char *tmp_str; + int analog_channel = -1; + float smallest_diff = INFINITY; + int idx = -1; + unsigned i; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + + /* If a channel group is specified, it must be a valid one. */ + if (cg && !g_slist_find(sdi->channel_groups, cg)) { + sr_err("Invalid channel group specified."); + return SR_ERR; + } - (void)sdi; - (void)data; - (void)cg; + if (cg) { + ch = g_slist_nth_data(cg->channels, 0); + if (!ch) + return SR_ERR; + if (ch->type == SR_CHANNEL_ANALOG) { + if (ch->name[2] < '1' || ch->name[2] > '4') + return SR_ERR; + analog_channel = ch->name[2] - '1'; + } + } - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_NUM_HDIV: + *data = g_variant_new_int32(TEK_NUM_HDIV); + break; + case SR_CONF_NUM_VDIV: + *data = g_variant_new_int32(TEK_NUM_VDIV); + break; + case SR_CONF_LIMIT_FRAMES: + *data = g_variant_new_uint64(devc->limit_frames); + break; + case SR_CONF_DATA_SOURCE: + *data = g_variant_new_string(data_sources[devc->capture_mode]); + break; + case SR_CONF_SAMPLERATE: + tektronix_tds_get_dev_cfg_horizontal(sdi); + *data = g_variant_new_uint64(MIN( + TEK_BUFFER_SIZE / (devc->timebase * (float)TEK_NUM_HDIV), + devc->model->sample_rate * 1000000.0)); + break; + case SR_CONF_TRIGGER_SOURCE: + *data = g_variant_new_string(devc->trigger_source); + break; + case SR_CONF_TRIGGER_SLOPE: + if (!g_ascii_strncasecmp(devc->trigger_slope, "RISE", 4)) { + tmp_str = "r"; + } else if (!g_ascii_strncasecmp(devc->trigger_slope, "FALL", 4)) { + tmp_str = "f"; + } else { + sr_dbg("Unknown trigger slope: '%s'.", devc->trigger_slope); + return SR_ERR_NA; + } + *data = g_variant_new_string(tmp_str); + break; + case SR_CONF_TRIGGER_LEVEL: + *data = g_variant_new_double(devc->trigger_level); + break; + case SR_CONF_HORIZ_TRIGGERPOS: + *data = g_variant_new_double(devc->horiz_triggerpos); + break; + case SR_CONF_CAPTURE_RATIO: + *data = g_variant_new_uint64(devc->horiz_triggerpos * 100); + break; + case SR_CONF_TIMEBASE: + for (i = devc->model->timebase_start; + i < ARRAY_SIZE(timebases) - devc->model->timebase_stop; + i++) { + float tb, diff; + + tb = (float)timebases[i][0] / timebases[i][1]; + diff = fabs(devc->timebase - tb); + if (diff < smallest_diff) { + smallest_diff = diff; + idx = i; + } + } + if (idx < devc->model->timebase_start) { + sr_dbg("Negative timebase index: %d.", idx); + return SR_ERR_NA; + } + *data = g_variant_new("(tt)", timebases[idx][0], timebases[idx][1]); + break; + case SR_CONF_VDIV: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + for (i = devc->model->voltrange_start; + i < ARRAY_SIZE(vdivs) - devc->model->voltrange_stop; i++) { + float vdiv = (float)vdivs[i][0] / vdivs[i][1]; + float diff = fabsf(devc->vdiv[analog_channel] - vdiv); + if (diff < smallest_diff) { + smallest_diff = diff; + idx = i; + } + } + if (idx < devc->model->voltrange_start) { + sr_dbg("Negative vdiv index: %d.", idx); + return SR_ERR_NA; + } + *data = g_variant_new("(tt)", vdivs[idx][0], vdivs[idx][1]); + break; + case SR_CONF_COUPLING: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + *data = g_variant_new_string(devc->coupling[analog_channel]); + break; + case SR_CONF_PROBE_FACTOR: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + *data = g_variant_new_uint64(devc->attenuation[analog_channel]); + break; + case SR_CONF_ENABLED: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + *data = g_variant_new_boolean( + devc->analog_channels[analog_channel]); + break; + case SR_CONF_PEAK_DETECTION: + *data = g_variant_new_boolean(devc->peak_enabled); + break; + case SR_CONF_AVERAGING: + *data = g_variant_new_boolean(devc->average_enabled); + break; + case SR_CONF_AVG_SAMPLES: + *data = g_variant_new_uint64(devc->average_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) { - int ret; - - (void)sdi; - (void)data; - (void)cg; + struct dev_context *devc; + uint64_t p; + double t_dbl; + int i; + int ret, idx; + const char *tmp_str; + char cmd4[4]; + gboolean b; + + devc = sdi->priv; + + /* If a channel group is specified, it must be a valid one. */ + if (cg && !g_slist_find(sdi->channel_groups, cg)) { + sr_err("Invalid channel group specified."); + return SR_ERR; + } ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_LIMIT_FRAMES: + devc->limit_frames = g_variant_get_uint64(data); + sr_info("Getting frames limit of %li", g_variant_get_uint64(data)); + break; + case SR_CONF_TRIGGER_SLOPE: + if ((idx = std_str_idx(data, ARRAY_AND_SIZE(trigger_slopes))) < 0) + return SR_ERR_ARG; + g_free(devc->trigger_slope); + devc->trigger_slope = g_strdup( + (trigger_slopes[idx][0] == 'r') ? "RISE" : "FALL"); + return tektronix_tds_config_set( + sdi, "TRIG:MAI:EDGE:SLO %s", devc->trigger_slope); + case SR_CONF_CAPTURE_RATIO: + t_dbl = g_variant_get_uint64(data) / 100.0; + if (t_dbl < 0.0 || t_dbl > 1.0) { + sr_err("Invalid horiz. trigger position: %g.", t_dbl); + return SR_ERR; + } + /* Fall through */ + case SR_CONF_HORIZ_TRIGGERPOS: + if (key == SR_CONF_HORIZ_TRIGGERPOS) + t_dbl = g_variant_get_double(data); + devc->horiz_triggerpos = t_dbl; + /* We have the trigger offset as a percentage of the frame, but + * need to express this in seconds. */ + t_dbl = -(devc->horiz_triggerpos - 0.5) * devc->timebase * TEK_NUM_HDIV; + return tektronix_tds_config_set(sdi, "hor:mai:pos %.3e", t_dbl); + case SR_CONF_TRIGGER_LEVEL: + if (!strcmp(devc->trigger_source, "AC Line")) + sr_err("Can't set level on AC line trigger, ignoring"); + return SR_ERR; + + t_dbl = g_variant_get_double(data); + ret = tektronix_tds_config_set(sdi, "TRIG:MAI:LEV %.3e", t_dbl); + if (ret == SR_OK) + devc->trigger_level = t_dbl; + break; + case SR_CONF_TIMEBASE: + if ((idx = std_u64_tuple_idx(data, timebases, + ARRAY_SIZE(timebases) - devc->model->timebase_stop)) < 0) + return SR_ERR_ARG; + if (idx < devc->model->timebase_start) + return SR_ERR_ARG; + devc->timebase = (float)timebases[idx][0] / timebases[idx][1]; + ret = tektronix_tds_config_set( + sdi, "hor:sca %.1e", devc->timebase); + if (ret == SR_OK) + tektronix_tds_get_dev_cfg_horizontal(sdi); + return ret; + case SR_CONF_TRIGGER_SOURCE: + if ((idx = std_str_idx(data, devc->model->trigger_sources, + devc->model->num_trigger_sources)) < 0) + return SR_ERR_ARG; + g_free(devc->trigger_source); + devc->trigger_source = g_strdup(devc->model->trigger_sources[idx]); + if (!strcmp(devc->trigger_source, "AC Line")) { + // ONLY set edge trigger, as only edge trigger supports this + // TODO: raise error when not edge source + return tektronix_tds_config_set( + sdi, "TRIG:mai:edge:sou line"); + } else if (!strcmp(devc->trigger_source, "Ext /5")) + tmp_str = "EXT5"; + else if (!strcmp(devc->trigger_source, "Ext /10")) + tmp_str = "EXT10"; + else + tmp_str = (char *)devc->trigger_source; + // Note: pulse and video triggering isn't set here + return tektronix_tds_config_set( + sdi, "TRIG:mai:edge:sou %s", tmp_str); + case SR_CONF_VDIV: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + if ((idx = std_u64_tuple_idx(data, ARRAY_AND_SIZE(vdivs))) < 0) + return SR_ERR_ARG; + devc->vdiv[i] = (float)vdivs[idx][0] / vdivs[idx][1]; + ret = tektronix_tds_config_set( + sdi, "CH%d:SCA %.2e", i + 1, (double)devc->vdiv[i]); + return ret; + case SR_CONF_COUPLING: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + if ((idx = std_str_idx(data, ARRAY_AND_SIZE(coupling))) < 0) + return SR_ERR_ARG; + g_free(devc->coupling[i]); + devc->coupling[i] = g_strdup(coupling[idx]); + strncpy(cmd4, devc->coupling[i], 3); + cmd4[3] = 0; + return tektronix_tds_config_set( + sdi, "CH%d:COUP %s", i + 1, cmd4); + case SR_CONF_PROBE_FACTOR: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + if ((idx = std_u64_idx(data, devc->model->probe_factors, + devc->model->num_probe_factors)) < 0) + return SR_ERR_ARG; + p = g_variant_get_uint64(data); + devc->attenuation[i] = devc->model->probe_factors[idx]; + ret = tektronix_tds_config_set( + sdi, "CH%d:PROBE %" PRIu64, i + 1, p); + if (ret == SR_OK) + tektronix_tds_get_dev_cfg_vertical(sdi); + return ret; + case SR_CONF_ENABLED: + sr_dbg("configuring channel"); + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + b = g_variant_get_boolean(data); + devc->analog_channels[i] = b; + ret = tektronix_tds_config_set( + sdi, "SEL:CH%d %s", i + 1, b ? "ON" : "OFF"); + return ret; + case SR_CONF_DATA_SOURCE: + if ((idx = std_str_idx(data, ARRAY_AND_SIZE(data_sources))) < 0) + return SR_ERR_ARG; + devc->capture_mode = idx; + break; + case SR_CONF_PEAK_DETECTION: + // TODO: you can configure peak detect mode, but the data isn't + // parsed yet + devc->peak_enabled = g_variant_get_boolean(data); + if (devc->peak_enabled) + ret = tektronix_tds_config_set(sdi, "acq:mode peak"); + else + ret = tektronix_tds_config_set(sdi, "acq:mode sam"); + devc->average_enabled = FALSE; + sr_dbg("%s peak detect", + devc->peak_enabled ? "Enabling" : "Disabling"); + break; + case SR_CONF_AVERAGING: + devc->average_enabled = g_variant_get_boolean(data); + if (devc->average_enabled) + ret = tektronix_tds_config_set(sdi, "acq:mode ave"); + else + ret = tektronix_tds_config_set(sdi, "acq:mode sam"); + devc->peak_enabled = FALSE; + sr_dbg("%s averaging", + devc->average_enabled ? "Enabling" : "Disabling"); + break; + case SR_CONF_AVG_SAMPLES: + devc->average_samples = g_variant_get_uint64(data); + sr_dbg("Setting averaging rate to %d", devc->average_samples); + ret = tektronix_tds_config_set( + sdi, "acq:numav %d", devc->average_samples); + break; default: - ret = SR_ERR_NA; + return SR_ERR_NA; } return ret; @@ -98,44 +769,165 @@ static int config_set(uint32_t key, GVariant *data, static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; - (void)sdi; - (void)data; - (void)cg; + devc = (sdi) ? sdi->priv : NULL; - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + if (!cg) + return STD_CONFIG_LIST( + key, data, sdi, cg, scanopts, drvopts, devopts); + if (!devc) + return SR_ERR_ARG; + if (std_cg_idx(cg, devc->analog_groups, devc->model->channels) < 0) + return SR_ERR_ARG; + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_analog)); + return SR_OK; + break; + case SR_CONF_COUPLING: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + *data = g_variant_new_strv(ARRAY_AND_SIZE(coupling)); + break; + case SR_CONF_PROBE_FACTOR: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + *data = std_gvar_array_u64(devc->model->probe_factors, + devc->model->num_probe_factors); + break; + case SR_CONF_VDIV: + if (!devc) + /* Can't know this until we have the exact model. */ + return SR_ERR_ARG; + if (!cg) + return SR_ERR_CHANNEL_GROUP; + *data = std_gvar_tuple_array(ARRAY_AND_SIZE(vdivs)); + break; + case SR_CONF_TIMEBASE: + if (!devc) + /* Can't know this until we have the exact model. */ + return SR_ERR_ARG; + *data = std_gvar_tuple_array( + &timebases[devc->model->timebase_start], + ARRAY_SIZE(timebases) - devc->model->timebase_start - + devc->model->timebase_stop); + break; + case SR_CONF_TRIGGER_SOURCE: + if (!devc) + /* Can't know this until we have the exact model. */ + return SR_ERR_ARG; + *data = g_variant_new_strv(devc->model->trigger_sources, + devc->model->num_trigger_sources); + break; + case SR_CONF_TRIGGER_SLOPE: + *data = g_variant_new_strv(ARRAY_AND_SIZE(trigger_slopes)); + break; + case SR_CONF_DATA_SOURCE: + *data = g_variant_new_strv(ARRAY_AND_SIZE(data_sources)); + break; + case SR_CONF_NUM_HDIV: + *data = g_variant_new_int32(TEK_NUM_HDIV); + break; + case SR_CONF_NUM_VDIV: + *data = g_variant_new_int32(TEK_NUM_VDIV); + break; + case SR_CONF_AVG_SAMPLES: + *data = std_gvar_array_u64(ARRAY_AND_SIZE(averages)); + break; default: return SR_ERR_NA; } - return ret; + return SR_OK; } static int dev_acquisition_start(const struct sr_dev_inst *sdi) { - /* TODO: configure hardware, reset acquisition state, set up - * callbacks and send header packet. */ + struct sr_scpi_dev_inst *scpi; + struct dev_context *devc; + struct sr_channel *ch; + GSList *l; + char *response = NULL; + + scpi = sdi->conn; + devc = sdi->priv; + + devc->num_frames = 0; + + for (l = sdi->channels; l; l = l->next) { + ch = l->data; + if (ch->enabled) + devc->enabled_channels = g_slist_append( + devc->enabled_channels, ch); + if (ch->enabled != devc->analog_channels[ch->index]) { + /* Enabled channel is currently disabled, or vice versa. */ + if (tektronix_tds_config_set(sdi, "SEL:CH%d %s", + ch->index + 1, + ch->enabled ? "ON" : "OFF") != SR_OK) + return SR_ERR; + devc->analog_channels[ch->index] = ch->enabled; + } + } + if (!devc->enabled_channels) + return SR_ERR; - (void)sdi; + // Set view to main, but don't check the status as TBS1000B doesn't + // support this command. This ensures the timebase is correct + tektronix_tds_config_set(sdi, "hor:view mai"); + + tektronix_tds_get_dev_cfg_horizontal(sdi); + + if (sr_scpi_get_bool(scpi, "acq:state?", &devc->prior_state_running) != SR_OK) + return SR_ERR; + + if (sr_scpi_get_string(scpi, "acq:stopa?", &response) != SR_OK) + return SR_ERR; + + devc->prior_state_single = + (g_ascii_strncasecmp("sequence", response, 3) == 0); + + // these models are slow, and TDS2xxxB takes ~1.5 seconds to begin + // transmitting, so poll slowly However, not too slow, as that makes + // some weird transmission timeouts appear + sr_scpi_source_add(sdi->session, scpi, G_IO_IN, 500, + tektronix_tds_receive, (void *)sdi); + + std_session_send_df_header(sdi); + + devc->channel_entry = devc->enabled_channels; + + if (tektronix_tds_capture_start(sdi) != SR_OK) + return SR_ERR; + + /* Start of first frame. */ + std_session_send_df_frame_begin(sdi); return SR_OK; } static int dev_acquisition_stop(struct sr_dev_inst *sdi) { - /* TODO: stop acquisition. */ + struct dev_context *devc; + struct sr_scpi_dev_inst *scpi; + + devc = sdi->priv; + + std_session_send_df_end(sdi); - (void)sdi; + g_slist_free(devc->enabled_channels); + devc->enabled_channels = NULL; + scpi = sdi->conn; + sr_scpi_source_remove(sdi->session, scpi); return SR_OK; } static struct sr_dev_driver tektronix_tds_driver_info = { + // Awkward name, but there isn't a proper name for it .name = "tektronix-tds", - .longname = "Tektronix TDS", + .longname = "Tektronix TDS/TBS/TPS", .api_version = 1, .init = std_init, .cleanup = std_cleanup, diff --git a/src/hardware/tektronix-tds/protocol.c b/src/hardware/tektronix-tds/protocol.c index b07aa1cda..292f83e86 100644 --- a/src/hardware/tektronix-tds/protocol.c +++ b/src/hardware/tektronix-tds/protocol.c @@ -18,13 +18,80 @@ */ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scpi.h" #include "protocol.h" -SR_PRIV int tektronix_tds_receive_data(int fd, int revents, void *cb_data) +struct tek_enum_parser { + int enum_value; + const char *name; +}; + +static const struct tek_enum_parser parse_table_data_encoding[] = { + {ENC_ASCII, "ASC"}, {ENC_BINARY, "BIN"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_data_format[] = { + {FMT_RI, "RI"}, {FMT_RP, "RP"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_data_ordering[] = { + {ORDER_LSB, "LSB"}, {ORDER_MSB, "MSB"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_point_format[] = { + {PT_FMT_ENV, "ENV"}, {PT_FMT_Y, "Y"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_xunits[] = { + {XU_SECOND, "s"}, {XU_HZ, "Hz"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_yunits[] = {{YU_UNKNOWN, "U"}, + {YU_UNKNOWN_MASK, "?"}, {YU_VOLTS, "Volts"}, {YU_DECIBELS, "dB"}, + + // select models only: + {YU_AMPS, "A"}, {YU_AA, "AA"}, {YU_VA, "VA"}, {YU_VV, "VV"}, {0, NULL}}; + +#define TEK_PRE_HEADER_FIELDS 16 + +static int tektronix_tds_read_header(struct sr_dev_inst *sdi); + +/* Revert all settings, if requested. */ +SR_PRIV int tektronix_tds_capture_finish(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) + return SR_ERR; + + devc->acquire_status = WAIT_DONE; + + sr_dbg("Setting exiting setttings back"); + + if (devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_DISPLAY) { + if (tektronix_tds_config_set(sdi, "ACQ:stopa runstop") != SR_OK) + return SR_ERR; + + if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != SR_OK) + return SR_ERR; + } + + return SR_OK; +} + +SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) { - const struct sr_dev_inst *sdi; + struct sr_dev_inst *sdi; struct dev_context *devc; + struct sr_scpi_dev_inst *scpi; + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; + struct sr_channel *ch; + int len, i; + (void)fd; sdi = cb_data; @@ -34,10 +101,721 @@ SR_PRIV int tektronix_tds_receive_data(int fd, int revents, void *cb_data) devc = sdi->priv; if (!devc) return TRUE; + scpi = sdi->conn; + ch = devc->channel_entry->data; - if (revents == G_IO_IN) { - /* TODO */ - } + if (revents == G_IO_IN || TRUE) { // this is always 0 for some reason + + // no data yet + sr_dbg("Waiting for data..."); + if (sr_scpi_read_begin(scpi) != SR_OK) + return TRUE; + + sr_dbg("New block with header expected."); + len = tektronix_tds_read_header(sdi); + if (len == 0) + /* Still reading the header. */ + return TRUE; + if (len == -1) { + sr_err("Read error, aborting capture."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + + devc->acquire_status = WAIT_DONE; + + // streaming data back is pretty fast, at least once the scope + // eventually starts sending it our way + devc->num_block_read = 0; + + sr_dbg("Requesting block: %d bytes.", TEK_BUFFER_SIZE + 1); + len = sr_scpi_read_data( + scpi, (char *)devc->buffer, TEK_BUFFER_SIZE + 1); + if (len == -1) { + sr_err("Read error, aborting capture."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + sr_dbg("Received block: %d bytes.", len); + devc->num_block_read = len; + + // ensure the terminating newline is read + while (devc->num_block_read < TEK_BUFFER_SIZE + 1) { + sr_dbg("Requesting: %d bytes.", + TEK_BUFFER_SIZE + 1 - devc->num_block_read); + len = sr_scpi_read_data(scpi, + (char *)devc->buffer + devc->num_block_read, + TEK_BUFFER_SIZE + 1 - devc->num_block_read); + if (len == -1) { + sr_err("Read error, aborting capture."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + sr_dbg("Received block: %d bytes.", len); + devc->num_block_read += len; + } + sr_dbg("Transfer has been completed."); + if (!sr_scpi_read_complete(scpi)) { + sr_err("Read should have been completed."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + + // We have received the entire 2.5k buffer now, so process it, + // ignoring the trailing \n + len = TEK_BUFFER_SIZE; + + float vdiv = devc->vdiv[ch->index]; + GArray *float_data; + static GArray *data; + float voltage, vdivlog; + int digits; + + data = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); + g_array_append_vals(data, devc->buffer, len); + float_data = g_array_new(FALSE, FALSE, sizeof(float)); + for (i = 0; i < len; i++) { + voltage = (float)g_array_index(data, int8_t, i) - + devc->wavepre.y_off; + voltage = ((devc->wavepre.y_mult * voltage) + + devc->wavepre.y_zero); + g_array_append_val(float_data, voltage); + } + vdivlog = log10f(vdiv); + digits = -(int)vdivlog + (vdivlog < 0.0); + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); + analog.meaning->channels = g_slist_append(NULL, ch); + analog.num_samples = float_data->len; + analog.data = ((float *)float_data->data); + if (devc->wavepre.y_unit == YU_VOLTS) { + analog.meaning->mq = SR_MQ_VOLTAGE; + analog.meaning->unit = SR_UNIT_VOLT; + } else if (devc->wavepre.y_unit == YU_AMPS) { + analog.meaning->mq = SR_MQ_CURRENT; + analog.meaning->unit = SR_UNIT_AMPERE; + } else if (devc->wavepre.y_unit == YU_DECIBELS) { + analog.meaning->mq = SR_MQ_POWER; + analog.meaning->unit = SR_UNIT_DECIBEL_MW; + } else { + analog.meaning->mq = 0; + analog.meaning->unit = SR_UNIT_UNITLESS; + } + analog.meaning->mqflags = 0; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_dbg("Computing using trigger point %.6f", devc->horiz_triggerpos); + // only the first packet provides trigger information, all others + // are "after" the correct timebase + if (devc->channel_entry != devc->enabled_channels) { + sr_session_send(sdi, &packet); + } else if (devc->horiz_triggerpos > 0) { + // This will round to (potentially) twice the expected margin + // on-device (% -> s -> %) vs our expectation (%) + analog.num_samples = float_data->len * devc->horiz_triggerpos; + sr_dbg("First batch has %d", analog.num_samples); + sr_session_send(sdi, &packet); + std_session_send_df_trigger(sdi); + if (devc->horiz_triggerpos < 1) { + analog.data = ((float *)float_data->data) + analog.num_samples; + analog.num_samples = float_data->len - analog.num_samples; + sr_dbg("second batch has %d", analog.num_samples); + sr_session_send(sdi, &packet); + } + } else { // trigger == 0 + std_session_send_df_trigger(sdi); + sr_session_send(sdi, &packet); + } + g_slist_free(analog.meaning->channels); + g_array_free(data, TRUE); + + if (devc->channel_entry->next) { + sr_dbg("Doing another channel"); + /* We got the frame for this channel, now get the next channel. */ + devc->channel_entry = devc->channel_entry->next; + tektronix_tds_channel_start(sdi); + } else { + /* Done with this frame. */ + std_session_send_df_frame_end(sdi); + if (++devc->num_frames == devc->limit_frames) { + /* Last frame, stop capture. */ + sdi->driver->dev_acquisition_stop(sdi); + tektronix_tds_capture_finish(sdi); + } else { + sr_dbg("Doing another frame"); + /* Get the next frame, starting with the first channel. */ + devc->channel_entry = devc->enabled_channels; + tektronix_tds_capture_start(sdi); + + /* Start of next frame. */ + std_session_send_df_frame_begin(sdi); + } + } + } return TRUE; } + +/* Start reading data from the current channel. */ +SR_PRIV int tektronix_tds_channel_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_channel *ch; + + if (!(devc = sdi->priv)) + return SR_ERR; + + ch = devc->channel_entry->data; + + sr_dbg("Configure reading data from channel %s.", ch->name); + + if (sr_scpi_send(sdi->conn, "DAT:SOU CH%d", ch->index + 1) != SR_OK) + return SR_ERR; + + // wait for trigger (asynchronous) + if (devc->acquire_status == WAIT_CAPTURE && + (devc->num_frames > 0 || devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_ONE_SHOT || + devc->prior_state_running)) + if (sr_scpi_send(sdi->conn, "*WAI") != SR_OK) + return SR_ERR; + devc->acquire_status = WAIT_CHANNEL; + + sr_dbg("Requesting waveform"); + if (sr_scpi_send(sdi->conn, "WAVF?") != SR_OK) + return SR_ERR; + + devc->num_block_read = 0; + + return SR_OK; +} + +/* Start capturing a new frameset. */ +SR_PRIV int tektronix_tds_capture_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) + return SR_ERR; + + // Force our capture settings to 1 byte, msb, binary + if (tektronix_tds_config_set(sdi, "dat:enc RIB") != SR_OK) + return SR_ERR; + if (tektronix_tds_config_set(sdi, "dat:wid 1") != SR_OK) + return SR_ERR; + + devc->acquire_status = WAIT_CAPTURE; + + if (devc->num_frames == 0) { + // if we aren't requesting memory, create a new capture + // if we are requesting memory, but it was already running, + // convert to single-shot so we can synchronize channels + if (devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_ONE_SHOT || + devc->prior_state_running) { + sr_dbg("Triggering restart"); + // stop before setting single sequence mode, so that we + // can get the same waveform data per channel + if (!devc->prior_state_single) { + if (tektronix_tds_config_set( + sdi, "ACQ:STATE STOP") != SR_OK) + return SR_ERR; + if (tektronix_tds_config_set( + sdi, "ACQ:stopa seq") != SR_OK) + return SR_ERR; + } + if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != + SR_OK) + return SR_ERR; + } + } else { + // If you are requesting multiple frames, all capture modes reset + if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != SR_OK) + return SR_ERR; + } + + if (tektronix_tds_channel_start(sdi) != SR_OK) + return SR_ERR; + + sr_dbg("Starting data capture for curves."); + + return SR_OK; +} + +static int parse_scpi_int(const char *data, int *out_err, int default_value) +{ + int value = default_value; + struct sr_rational ret_rational; + int ret; + + ret = sr_parse_rational(data, &ret_rational); + if (ret == SR_OK && (ret_rational.p % ret_rational.q) == 0) { + value = ret_rational.p / ret_rational.q; + } else { + sr_dbg("get_int: non-integer rational=%" PRId64 "/%" PRIu64, + ret_rational.p, ret_rational.q); + *out_err = SR_ERR_DATA; + } + + return value; +} + +static float parse_scpi_float(const char *data, int *out_err, float default_value) +{ + float value = default_value; + + if (sr_atof_ascii(data, &value) != SR_OK) + *out_err = SR_ERR_DATA; + + return value; +} + +static const char *parse_scpi_string(char *data, int *out_err) +{ + (void)out_err; + return sr_scpi_unquote_string(data); +} + +static int parse_scpi_enum(const char *data, + const struct tek_enum_parser *parser_table, int *out_err, int default_value) +{ + while (parser_table->name) { + if (g_ascii_strcasecmp(parser_table->name, data) == 0) { + return parser_table->enum_value; + } + parser_table++; + } + *out_err = SR_ERR_DATA; + return default_value; +} + +static const char *render_scpi_enum( + int value, const struct tek_enum_parser *parser_table, int *out_err) +{ + while (parser_table->name) { + if (value == parser_table->enum_value) { + return parser_table->name; + } + parser_table++; + } + *out_err = SR_ERR_DATA; + return "NULL"; +} +static int parse_scpi_blockstart(const char *data, int *out_err) +{ + int len, i; + int ret = 0; + if (data[0] != '#' || data[1] < '0' || data[1] > '9') { + sr_err("block header invalid: %.2s", + data); + goto err; + } + len = data[1] - '0'; + for(i = 0; i < len; ++i) { + if (data[2+i] < '0' || data[2+i] > '9') + goto err; + ret = ret * 10 + (data[2+i] - '0'); + } + return ret; +err: + *out_err = SR_ERR_DATA; + return -1; +} + + +static void check_expected_value( + const char* name, int actual, int expected, int* out_err, + const struct tek_enum_parser *parser_table) +{ + if (actual != expected) { + *out_err = SR_ERR_DATA; + if (parser_table == NULL) + sr_err( + "Error validating data header. Field '%s' expected %d, but found %d", + name, expected, actual); + else { + sr_err( + "Error validating data header. Field '%s' expected %s, but found %s", + name, + render_scpi_enum(expected, parser_table, out_err), + render_scpi_enum(actual, parser_table, out_err)); + } + } +} + +static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) +{ + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; + char *buf = (char *)devc->buffer; + char *fields[TEK_PRE_HEADER_FIELDS + 1]; // one extra for block + int i = 0; + int ret = SR_OK; + int pt_off; + const char *wfid; + int bit_width; + int byte_width; + int blocklength; + enum TEK_POINT_FORMAT pt_format; + enum TEK_DATA_ORDERING ordering; + enum TEK_DATA_FORMAT format; + enum TEK_DATA_ENCODING encoding; + (void)scpi; + + sr_dbg("Parsing header of size %d", (int)(end_buf - buf)); + sr_spew("Line as receved: %.*s", (int)(end_buf - buf - 1), buf); + + // Parse in 3 steps: + // 1. find all semicolons, and replace with null bytes + // 2. Put next char reference into array + // 3. Parse each type based on array index + fields[i++] = buf; + while (buf < end_buf) { + if (*buf == ';') { + *buf = 0; // turn into list of C strings + fields[i++] = buf + 1; + } + buf++; + } + sr_spew("Expected 17 indexes, found %d in header", i); + /* + BYT_Nr ; + BIT_Nr ; + ENCdg { ASC | BIN }; + BN_Fmt { RI | RP }; + BYT_Or { LSB | MSB }; + NR_Pt ; + WFID ; + PT_FMT {ENV | Y}; + XINcr ; + PT_Off ; + XZERo ; + XUNit; + YMUlt ; + YZEro ; + YOFF ; + YUNit ; + #..block + */ + + i = 0; + byte_width = parse_scpi_int(fields[i++], &ret, 1); + bit_width = parse_scpi_int(fields[i++], &ret, 8); + encoding = parse_scpi_enum( + fields[i++], parse_table_data_encoding, &ret, ENC_ASCII); + format = parse_scpi_enum( + fields[i++], parse_table_data_format, &ret, FMT_RI); + ordering = parse_scpi_enum( + fields[i++], parse_table_data_ordering, &ret, ORDER_LSB); + devc->wavepre.num_pts = parse_scpi_int(fields[i++], &ret, -1); + wfid = parse_scpi_string(fields[i++], &ret); + pt_format = parse_scpi_enum( + fields[i++], parse_table_point_format, &ret, PT_FMT_Y); + devc->wavepre.x_incr = parse_scpi_float(fields[i++], &ret, 1); + pt_off = parse_scpi_int(fields[i++], &ret, 0); + devc->wavepre.x_zero = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.x_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), + parse_table_xunits, &ret, XU_SECOND); + devc->wavepre.y_mult = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_zero = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_off = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), + parse_table_yunits, &ret, YU_UNKNOWN); + blocklength = parse_scpi_blockstart(fields[i++], &ret); + + sr_dbg("Expected 17 values, parsed %d in header with ret=%i", i, ret); + if (i != TEK_PRE_HEADER_FIELDS + 1 && ret == SR_OK) + ret = SR_ERR; + + // expensive, so avoid + if (sr_log_loglevel_get() >= SR_LOG_SPEW) + sr_spew("Line is parsed as: %d;%d;%s;%s;%s;%i;\"%s\";%s;%.2e;%i;%.2e;\"%s\";%.2e;%.2e;%.2e;\"%s\";#.%d... ", + byte_width, bit_width, + render_scpi_enum(encoding, parse_table_data_encoding, &ret), + render_scpi_enum(format, parse_table_data_format, &ret), + render_scpi_enum(ordering, parse_table_data_ordering, &ret), + devc->wavepre.num_pts, wfid, + render_scpi_enum(pt_format, parse_table_point_format, &ret), + devc->wavepre.x_incr, pt_off, devc->wavepre.x_zero, + render_scpi_enum( + devc->wavepre.x_unit, parse_table_xunits, &ret), + devc->wavepre.y_mult, devc->wavepre.y_zero, + devc->wavepre.y_off, + render_scpi_enum(devc->wavepre.y_unit, + parse_table_yunits, &ret), + blocklength); + + // check that settings weren't tampered with + check_expected_value("byte width", byte_width, 1, &ret, NULL); + check_expected_value("bit size", bit_width, 8, &ret, NULL); + check_expected_value("data encoding", encoding, ENC_BINARY, &ret, parse_table_data_encoding); + check_expected_value("data format", format, FMT_RI, &ret, parse_table_data_format); + check_expected_value("data encoding", ordering, ORDER_MSB, &ret, parse_table_data_ordering); + check_expected_value("number of points", devc->wavepre.num_pts, TEK_BUFFER_SIZE, &ret, NULL); + // this value is ENV when in peak detect mode + check_expected_value("point format", pt_format, PT_FMT_Y, &ret, parse_table_point_format); + + check_expected_value("point offset", pt_off, 0, &ret, NULL); + check_expected_value("block length", blocklength, TEK_BUFFER_SIZE, &ret, NULL); + return ret; +} + +/* Read the header of a data block. */ +static int tektronix_tds_read_header(struct sr_dev_inst *sdi) +{ + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; + char *buf = (char *)devc->buffer; + int ret; + + // header is variable, but at least 100 bytes, and likely no more than + // 175 bytes. Typical values are around 150 + + int attempt = 100; + int found = 0; + + // Find all 16 fields by locating their semicolons. + // In theory the string values could contain semicolons to throw us off + // but I think we are safe based on the docs + while (found < TEK_PRE_HEADER_FIELDS) { + /* Read header from device. */ + ret = sr_scpi_read_data(scpi, buf, attempt); + if (ret < attempt) { + sr_err("Read error while reading data header: %i of %i", + ret, attempt); + return SR_ERR; + } + for (int i = 0; i < ret; i++, buf++) { + if (*buf == ';') { + found++; + } + } + attempt = TEK_PRE_HEADER_FIELDS - found; + if (attempt > 1) + attempt *= 2; + } + + // read block header prefix (# + ) + ret = sr_scpi_read_data(scpi, buf, 2); + if (ret < 2) { + sr_err("Read error while reading block header: %i of %i", + ret, 2); + return SR_ERR; + } + if (buf[0] != '#' || buf[1] < '0' || buf[1] > '9') { + sr_err("block header invalid: %.2s", + buf); + return SR_ERR; + } + attempt = buf[1] - '0'; + buf+=2; + // read block header size + ret = sr_scpi_read_data(scpi, buf, attempt); + if (ret < attempt) { + sr_err("Read error while reading block header: %i of %i", + ret, attempt); + return SR_ERR; + } + buf +=attempt; + + if (tektronix_tds_parse_header(sdi, buf + 1) != SR_OK) + ret = -1; + return ret; +} +/* Send a configuration setting. */ +SR_PRIV int tektronix_tds_config_set( + const struct sr_dev_inst *sdi, const char *format, ...) +{ + va_list args; + int ret; + + va_start(args, format); + ret = sr_scpi_send_variadic(sdi->conn, format, args); + va_end(args); + + return ret; +} + +SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + char *cmd; + int i; + int res; + + devc = sdi->priv; + + /* Vertical gain. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:SCA?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->vdiv[i]); + g_free(cmd); + if (res != SR_OK) + return SR_ERR; + } + sr_dbg("Current vertical gain:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->vdiv[i]); + + /* Vertical offset. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:POS?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->vert_offset[i]); + g_free(cmd); + if (res != SR_OK) + return SR_ERR; + } + sr_dbg("Current vertical offset:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->vert_offset[i]); + + return SR_OK; +} + +SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_channel *ch; + char *cmd, *response; + int i; + int res; + + devc = sdi->priv; + + /* Analog channel state. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("SELECT:CH%i?", i + 1); + res = sr_scpi_get_bool(sdi->conn, cmd, &devc->analog_channels[i]); + g_free(cmd); + if (res != SR_OK) + return SR_ERR; + ch = g_slist_nth_data(sdi->channels, i); + ch->enabled = devc->analog_channels[i]; + } + sr_dbg("Current analog channel state:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %s", i + 1, devc->analog_channels[i] ? "On" : "Off"); + + /* Probe attenuation. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:PROBE?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->attenuation[i]); + g_free(cmd); + if (res != SR_OK) + return SR_ERR; + } + sr_dbg("Current probe attenuation:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->attenuation[i]); + + /* Vertical gain and offset. */ + if (tektronix_tds_get_dev_cfg_vertical(sdi) != SR_OK) + return SR_ERR; + + if (tektronix_tds_get_dev_cfg_horizontal(sdi) != SR_OK) + return SR_ERR; + + /* Coupling. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:COUP?", i + 1); + g_free(devc->coupling[i]); + devc->coupling[i] = NULL; + res = sr_scpi_get_string(sdi->conn, cmd, &devc->coupling[i]); + g_free(cmd); + if (res != SR_OK) + return SR_ERR; + } + + sr_dbg("Current coupling:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %s", i + 1, devc->coupling[i]); + + /* Trigger source. edge, pulse, and video are always the same, it + * appears */ + response = NULL; + g_free(devc->trigger_source); + if (sr_scpi_get_string(sdi->conn, "TRIG:MAI:edge:sou?", + &devc->trigger_source) != SR_OK) + return SR_ERR; + sr_dbg("Current trigger source: %s.", devc->trigger_source); + + /* Horizontal trigger position. */ + if (sr_scpi_get_float(sdi->conn, "hor:pos?", &devc->horiz_triggerpos) != + SR_OK) + return SR_ERR; + + // triggerpos is in timeunits, convert back to percentage + devc->horiz_triggerpos = + (-devc->horiz_triggerpos / (devc->timebase * 10)) + 0.5; + + sr_dbg("Current horizontal trigger position %.10f.", + devc->horiz_triggerpos); + + /* Trigger slope. */ + g_free(devc->trigger_slope); + devc->trigger_slope = NULL; + res = sr_scpi_get_string( + sdi->conn, "trig:mai:edge:slope?", &devc->trigger_slope); + if (res != SR_OK) + return SR_ERR; + sr_dbg("Current trigger slope: %s.", devc->trigger_slope); + + /* Trigger level. */ + res = sr_scpi_get_float(sdi->conn, "trig:mai:lev?", &devc->trigger_level); + if (res != SR_OK) + return SR_ERR; + sr_dbg("Current trigger level: %g.", devc->trigger_level); + + /* Averaging/peak detection */ + response = NULL; + if (sr_scpi_get_string(sdi->conn, "acq:mod?", &response) != SR_OK) + return SR_ERR; + devc->average_enabled = g_ascii_strncasecmp(response, "average", 3) == 0; + devc->peak_enabled = g_ascii_strncasecmp(response, "peak", 3) == 0; + sr_dbg("Acquisition mode: %s.", response); + g_free(response); + + if (sr_scpi_get_int(sdi->conn, "acq:numav?", &devc->average_samples) != SR_OK) + return SR_ERR; + sr_dbg("Averaging samples: %i.", devc->average_samples); + + return SR_OK; +} + +SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + float fvalue; + int memory_depth; + + devc = sdi->priv; + + /* Get the timebase. */ + if (sr_scpi_get_float(sdi->conn, "hor:sca?", &devc->timebase) != SR_OK) + return SR_ERR; + sr_dbg("Current timebase: %g.", devc->timebase); + + /* Get the record size. A sanity check as it should be 2500 */ + if (sr_scpi_get_int(sdi->conn, "hor:reco?", &memory_depth) != SR_OK) + return SR_ERR; + + if (memory_depth != TEK_BUFFER_SIZE) { + sr_err("A Tek 2k5 device should have that much memory. Expecting: 2500 bytes, found %d bytes", + memory_depth); + return SR_ERR; + } + + fvalue = TEK_BUFFER_SIZE / (devc->timebase * (float)TEK_NUM_HDIV); + if (devc->model->sample_rate * 1000000.0 < fvalue) + sr_dbg("Current samplerate: %i MSa/s (limited by device).", + devc->model->sample_rate); + else + sr_dbg("Current samplerate: %ld Sa/s.", (long)fvalue); + + // TODO: peak detect mode is half of this + sr_dbg("Current memory depth: %d.", TEK_BUFFER_SIZE); + return SR_OK; +} diff --git a/src/hardware/tektronix-tds/protocol.h b/src/hardware/tektronix-tds/protocol.h index 9496c0d2a..f239a889d 100644 --- a/src/hardware/tektronix-tds/protocol.h +++ b/src/hardware/tektronix-tds/protocol.h @@ -27,9 +27,186 @@ #define LOG_PREFIX "tektronix-tds" +/* Mostly for general information, but also used for debug messages */ +enum bandwidth +{ + BW_25MHz = 25, + BW_30MHz = 30, + BW_40MHz = 40, + BW_45MHz = 45, + BW_50MHz = 50, + BW_60MHz = 60, + BW_70MHz = 70, + BW_100MHz = 100, + BW_150MHz = 150, + BW_200MHz = 200, +}; + +enum samplerate +{ + SA_500M = 500, + SA_1G = 1000, + SA_2G = 2000, +}; + +/* Describes model-specific features. See DEVICE_SPEC() macro in api.c for the + * "constructor" */ +struct device_spec { + const char *model; + int channels; + + enum samplerate sample_rate; + enum bandwidth bandwidth; + + const uint64_t *probe_factors; + int num_probe_factors; + + int timebase_start; + int timebase_stop; + + int voltrange_start; + int voltrange_stop; + + const char **trigger_sources; + int num_trigger_sources; +}; + +/* Values that are the same for all models */ +#define TEK_BUFFER_SIZE 2500 +// all scopes have -5 to +5 hdivs +// and -4 to +4 vdivs +#define TEK_NUM_HDIV 10 +#define TEK_NUM_VDIV 8 +#define MAX_ANALOG_CHANNELS 4 + +/* Wave data information */ + +enum TEK_DATA_ENCODING +{ + ENC_ASCII, + ENC_BINARY +}; + +enum TEK_DATA_FORMAT +{ + FMT_RI, + FMT_RP +}; + +enum TEK_DATA_ORDERING +{ + ORDER_LSB, + ORDER_MSB +}; + +enum TEK_POINT_FORMAT +{ + PT_FMT_ENV, + PT_FMT_Y +}; + +enum TEK_X_UNITS +{ + XU_SECOND, + XU_HZ +}; + +enum TEK_Y_UNITS +{ + YU_UNKNOWN, + YU_UNKNOWN_MASK, + YU_VOLTS, + YU_DECIBELS, + + // TBS1000B/EDU, TBS1000, TDS2000C, TDS1000C-EDU, TDS2000B, + // TDS1000B, TPS2000B, and TPS2000 Series only: + YU_AMPS, + YU_VV, + YU_VA, + YU_AA +}; + +struct most_recent_wave_preamble { + // Xn = XZEro + XINcr (n - PT_OFf) + float x_zero; // (in xunis) + float x_incr; // seconds per point or herts per point + enum TEK_X_UNITS x_unit; + + // value_in_YUNits = ((curve_in_dl - YOFF_in_dl) * YMUlt) + + // YZERO_in_YUNits + float y_mult; // (in yunits) + float y_off; // (in digitizer levels) + float y_zero; // (in yunits) + enum TEK_Y_UNITS y_unit; + + int num_pts; +}; + +enum DRIVER_CAPTURE_MODE +{ + CAPTURE_LIVE, // reset trigger, re-enable at end + CAPTURE_ONE_SHOT, // reset trigger, no clear + CAPTURE_DISPLAY, // no reset, re-enable at end + CAPTURE_MEMORY, // no reset, no clear +}; + +enum wait_events +{ + WAIT_CAPTURE, + WAIT_CHANNEL, + WAIT_DONE, +}; + struct dev_context { + /* Core information */ + struct sr_channel_group **analog_groups; + const struct device_spec *model; + + /* Current & configured channel settings */ + gboolean analog_channels[MAX_ANALOG_CHANNELS]; + float vdiv[MAX_ANALOG_CHANNELS]; + float vert_offset[MAX_ANALOG_CHANNELS]; + float attenuation[MAX_ANALOG_CHANNELS]; + char *coupling[MAX_ANALOG_CHANNELS]; + + /* Current & configured device settings */ + float timebase; + char *trigger_source; + float horiz_triggerpos; + char *trigger_slope; + float trigger_level; + + /* Current & configured acquisition settings */ + gboolean average_enabled; + int average_samples; + gboolean peak_enabled; + enum DRIVER_CAPTURE_MODE capture_mode; + + /* Acquisition state */ + enum wait_events acquire_status; + struct most_recent_wave_preamble wavepre; + gboolean prior_state_running; + gboolean prior_state_single; + + uint64_t limit_frames; + uint64_t num_frames; + GSList *enabled_channels; + GSList *channel_entry; + + /* Acq buffers used for reading from the scope and sending data to app. + */ + unsigned char *buffer; + int num_block_read; }; -SR_PRIV int tektronix_tds_receive_data(int fd, int revents, void *cb_data); +SR_PRIV int tektronix_tds_config_set( + const struct sr_dev_inst *sdi, const char *format, ...); +SR_PRIV int tektronix_tds_capture_start(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_channel_start(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_capture_finish(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data); +SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi); #endif From fd3524672de15f91bfe4da5c5b72cd23ae5d55a6 Mon Sep 17 00:00:00 2001 From: Patrick Plenefisch Date: Sun, 9 Apr 2023 16:37:27 -0400 Subject: [PATCH 04/20] tektronix-tds: Return correct values for digits and buffer size --- src/hardware/tektronix-tds/api.c | 3 +++ src/hardware/tektronix-tds/protocol.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hardware/tektronix-tds/api.c b/src/hardware/tektronix-tds/api.c index eb10757e1..f3943498c 100644 --- a/src/hardware/tektronix-tds/api.c +++ b/src/hardware/tektronix-tds/api.c @@ -469,6 +469,9 @@ static int config_get(uint32_t key, GVariant **data, case SR_CONF_NUM_VDIV: *data = g_variant_new_int32(TEK_NUM_VDIV); break; + case SR_CONF_BUFFERSIZE: + *data = g_variant_new_uint64(TEK_BUFFER_SIZE); + break; case SR_CONF_LIMIT_FRAMES: *data = g_variant_new_uint64(devc->limit_frames); break; diff --git a/src/hardware/tektronix-tds/protocol.c b/src/hardware/tektronix-tds/protocol.c index 292f83e86..72dd59003 100644 --- a/src/hardware/tektronix-tds/protocol.c +++ b/src/hardware/tektronix-tds/protocol.c @@ -187,7 +187,7 @@ SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) g_array_append_val(float_data, voltage); } vdivlog = log10f(vdiv); - digits = -(int)vdivlog + (vdivlog < 0.0); + digits = -(int)vdivlog + (vdivlog < 0.0) + 3 /* 8-bit resolution*/ - 1; sr_analog_init(&analog, &encoding, &meaning, &spec, digits); analog.meaning->channels = g_slist_append(NULL, ch); analog.num_samples = float_data->len; From c495784b04eabc2c4c044f9a919aa5c745deeca9 Mon Sep 17 00:00:00 2001 From: Patrick Plenefisch Date: Sun, 9 Apr 2023 17:15:10 -0400 Subject: [PATCH 05/20] tektronix-tds: Add mutex for multi-threaded clients --- src/hardware/tektronix-tds/api.c | 15 ++++++- src/hardware/tektronix-tds/protocol.c | 62 +++++++++++++++++++-------- src/hardware/tektronix-tds/protocol.h | 1 + 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/hardware/tektronix-tds/api.c b/src/hardware/tektronix-tds/api.c index f3943498c..348b703e7 100644 --- a/src/hardware/tektronix-tds/api.c +++ b/src/hardware/tektronix-tds/api.c @@ -363,6 +363,7 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) sdi->inst_type = SR_INST_SCPI; devc = g_malloc0(sizeof(*devc)); + g_rec_mutex_init(&devc->mutex); devc->model = device; sdi->priv = devc; @@ -426,6 +427,14 @@ static int dev_open(struct sr_dev_inst *sdi) static int dev_close(struct sr_dev_inst *sdi) { + struct dev_context *devc; + + devc = (sdi) ? sdi->priv : NULL; + if (devc) { + g_rec_mutex_clear(&devc->mutex); + g_free(devc->buffer); + } + return sr_scpi_close(sdi->conn); } @@ -875,6 +884,8 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) } if (!devc->enabled_channels) return SR_ERR; + + g_rec_mutex_lock(&devc->mutex); // Set view to main, but don't check the status as TBS1000B doesn't // support this command. This ensures the timebase is correct @@ -901,8 +912,10 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) devc->channel_entry = devc->enabled_channels; - if (tektronix_tds_capture_start(sdi) != SR_OK) + if (tektronix_tds_capture_start(sdi) != SR_OK) { + g_rec_mutex_unlock(&devc->mutex); return SR_ERR; + } /* Start of first frame. */ std_session_send_df_frame_begin(sdi); diff --git a/src/hardware/tektronix-tds/protocol.c b/src/hardware/tektronix-tds/protocol.c index 72dd59003..7060d5deb 100644 --- a/src/hardware/tektronix-tds/protocol.c +++ b/src/hardware/tektronix-tds/protocol.c @@ -69,13 +69,17 @@ SR_PRIV int tektronix_tds_capture_finish(const struct sr_dev_inst *sdi) if (devc->capture_mode == CAPTURE_LIVE || devc->capture_mode == CAPTURE_DISPLAY) { if (tektronix_tds_config_set(sdi, "ACQ:stopa runstop") != SR_OK) - return SR_ERR; + goto err; if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != SR_OK) - return SR_ERR; + goto err; } + g_rec_mutex_unlock(&devc->mutex); return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; } SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) @@ -631,11 +635,18 @@ SR_PRIV int tektronix_tds_config_set( { va_list args; int ret; + struct dev_context *devc; + + devc = sdi->priv; + + g_rec_mutex_lock(&devc->mutex); va_start(args, format); ret = sr_scpi_send_variadic(sdi->conn, format, args); va_end(args); + g_rec_mutex_unlock(&devc->mutex); + return ret; } @@ -647,6 +658,7 @@ SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi) int res; devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); /* Vertical gain. */ for (i = 0; i < devc->model->channels; i++) { @@ -654,7 +666,7 @@ SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi) res = sr_scpi_get_float(sdi->conn, cmd, &devc->vdiv[i]); g_free(cmd); if (res != SR_OK) - return SR_ERR; + goto err; } sr_dbg("Current vertical gain:"); for (i = 0; i < devc->model->channels; i++) @@ -666,13 +678,17 @@ SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi) res = sr_scpi_get_float(sdi->conn, cmd, &devc->vert_offset[i]); g_free(cmd); if (res != SR_OK) - return SR_ERR; + goto err; } sr_dbg("Current vertical offset:"); for (i = 0; i < devc->model->channels; i++) sr_dbg("CH%d %g", i + 1, devc->vert_offset[i]); + g_rec_mutex_unlock(&devc->mutex); return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; } SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) @@ -684,6 +700,7 @@ SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) int res; devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); /* Analog channel state. */ for (i = 0; i < devc->model->channels; i++) { @@ -691,7 +708,7 @@ SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) res = sr_scpi_get_bool(sdi->conn, cmd, &devc->analog_channels[i]); g_free(cmd); if (res != SR_OK) - return SR_ERR; + goto err; ch = g_slist_nth_data(sdi->channels, i); ch->enabled = devc->analog_channels[i]; } @@ -705,7 +722,7 @@ SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) res = sr_scpi_get_float(sdi->conn, cmd, &devc->attenuation[i]); g_free(cmd); if (res != SR_OK) - return SR_ERR; + goto err; } sr_dbg("Current probe attenuation:"); for (i = 0; i < devc->model->channels; i++) @@ -713,10 +730,10 @@ SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) /* Vertical gain and offset. */ if (tektronix_tds_get_dev_cfg_vertical(sdi) != SR_OK) - return SR_ERR; + goto err; if (tektronix_tds_get_dev_cfg_horizontal(sdi) != SR_OK) - return SR_ERR; + goto err; /* Coupling. */ for (i = 0; i < devc->model->channels; i++) { @@ -726,7 +743,7 @@ SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) res = sr_scpi_get_string(sdi->conn, cmd, &devc->coupling[i]); g_free(cmd); if (res != SR_OK) - return SR_ERR; + goto err; } sr_dbg("Current coupling:"); @@ -739,13 +756,13 @@ SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) g_free(devc->trigger_source); if (sr_scpi_get_string(sdi->conn, "TRIG:MAI:edge:sou?", &devc->trigger_source) != SR_OK) - return SR_ERR; + goto err; sr_dbg("Current trigger source: %s.", devc->trigger_source); /* Horizontal trigger position. */ if (sr_scpi_get_float(sdi->conn, "hor:pos?", &devc->horiz_triggerpos) != SR_OK) - return SR_ERR; + goto err; // triggerpos is in timeunits, convert back to percentage devc->horiz_triggerpos = @@ -760,29 +777,33 @@ SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) res = sr_scpi_get_string( sdi->conn, "trig:mai:edge:slope?", &devc->trigger_slope); if (res != SR_OK) - return SR_ERR; + goto err; sr_dbg("Current trigger slope: %s.", devc->trigger_slope); /* Trigger level. */ res = sr_scpi_get_float(sdi->conn, "trig:mai:lev?", &devc->trigger_level); if (res != SR_OK) - return SR_ERR; + goto err; sr_dbg("Current trigger level: %g.", devc->trigger_level); /* Averaging/peak detection */ response = NULL; if (sr_scpi_get_string(sdi->conn, "acq:mod?", &response) != SR_OK) - return SR_ERR; + goto err; devc->average_enabled = g_ascii_strncasecmp(response, "average", 3) == 0; devc->peak_enabled = g_ascii_strncasecmp(response, "peak", 3) == 0; sr_dbg("Acquisition mode: %s.", response); g_free(response); if (sr_scpi_get_int(sdi->conn, "acq:numav?", &devc->average_samples) != SR_OK) - return SR_ERR; + goto err; sr_dbg("Averaging samples: %i.", devc->average_samples); + g_rec_mutex_unlock(&devc->mutex); return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; } SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi) @@ -792,20 +813,21 @@ SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi) int memory_depth; devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); /* Get the timebase. */ if (sr_scpi_get_float(sdi->conn, "hor:sca?", &devc->timebase) != SR_OK) - return SR_ERR; + goto err; sr_dbg("Current timebase: %g.", devc->timebase); /* Get the record size. A sanity check as it should be 2500 */ if (sr_scpi_get_int(sdi->conn, "hor:reco?", &memory_depth) != SR_OK) - return SR_ERR; + goto err; if (memory_depth != TEK_BUFFER_SIZE) { sr_err("A Tek 2k5 device should have that much memory. Expecting: 2500 bytes, found %d bytes", memory_depth); - return SR_ERR; + goto err; } fvalue = TEK_BUFFER_SIZE / (devc->timebase * (float)TEK_NUM_HDIV); @@ -817,5 +839,9 @@ SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi) // TODO: peak detect mode is half of this sr_dbg("Current memory depth: %d.", TEK_BUFFER_SIZE); + g_rec_mutex_unlock(&devc->mutex); return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; } diff --git a/src/hardware/tektronix-tds/protocol.h b/src/hardware/tektronix-tds/protocol.h index f239a889d..98e66a51d 100644 --- a/src/hardware/tektronix-tds/protocol.h +++ b/src/hardware/tektronix-tds/protocol.h @@ -161,6 +161,7 @@ struct dev_context { /* Core information */ struct sr_channel_group **analog_groups; const struct device_spec *model; + GRecMutex mutex; /* Current & configured channel settings */ gboolean analog_channels[MAX_ANALOG_CHANNELS]; From 18afa676ef9afa320fd34a74748b05f90b3a25bd Mon Sep 17 00:00:00 2001 From: Patrick Plenefisch Date: Sun, 9 Apr 2023 17:24:05 -0400 Subject: [PATCH 06/20] tektronix-tds: Reorder protocol.c functions to classic C ordering --- src/hardware/tektronix-tds/protocol.c | 1370 ++++++++++++------------- 1 file changed, 685 insertions(+), 685 deletions(-) diff --git a/src/hardware/tektronix-tds/protocol.c b/src/hardware/tektronix-tds/protocol.c index 7060d5deb..7579332bd 100644 --- a/src/hardware/tektronix-tds/protocol.c +++ b/src/hardware/tektronix-tds/protocol.c @@ -52,264 +52,580 @@ static const struct tek_enum_parser parse_table_yunits[] = {{YU_UNKNOWN, "U"}, #define TEK_PRE_HEADER_FIELDS 16 -static int tektronix_tds_read_header(struct sr_dev_inst *sdi); - -/* Revert all settings, if requested. */ -SR_PRIV int tektronix_tds_capture_finish(const struct sr_dev_inst *sdi) +static int parse_scpi_int(const char *data, int *out_err, int default_value) { - struct dev_context *devc; + int value = default_value; + struct sr_rational ret_rational; + int ret; - if (!(devc = sdi->priv)) - return SR_ERR; + ret = sr_parse_rational(data, &ret_rational); + if (ret == SR_OK && (ret_rational.p % ret_rational.q) == 0) { + value = ret_rational.p / ret_rational.q; + } else { + sr_dbg("get_int: non-integer rational=%" PRId64 "/%" PRIu64, + ret_rational.p, ret_rational.q); + *out_err = SR_ERR_DATA; + } - devc->acquire_status = WAIT_DONE; + return value; +} - sr_dbg("Setting exiting setttings back"); +static float parse_scpi_float(const char *data, int *out_err, float default_value) +{ + float value = default_value; - if (devc->capture_mode == CAPTURE_LIVE || - devc->capture_mode == CAPTURE_DISPLAY) { - if (tektronix_tds_config_set(sdi, "ACQ:stopa runstop") != SR_OK) - goto err; + if (sr_atof_ascii(data, &value) != SR_OK) + *out_err = SR_ERR_DATA; - if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != SR_OK) - goto err; - } + return value; +} - g_rec_mutex_unlock(&devc->mutex); - return SR_OK; -err: - g_rec_mutex_unlock(&devc->mutex); - return SR_ERR; +static const char *parse_scpi_string(char *data, int *out_err) +{ + (void)out_err; + return sr_scpi_unquote_string(data); } -SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) +static int parse_scpi_enum(const char *data, + const struct tek_enum_parser *parser_table, int *out_err, int default_value) { - struct sr_dev_inst *sdi; - struct dev_context *devc; + while (parser_table->name) { + if (g_ascii_strcasecmp(parser_table->name, data) == 0) { + return parser_table->enum_value; + } + parser_table++; + } + *out_err = SR_ERR_DATA; + return default_value; +} - struct sr_scpi_dev_inst *scpi; - struct sr_datafeed_packet packet; - struct sr_datafeed_analog analog; - struct sr_analog_encoding encoding; - struct sr_analog_meaning meaning; - struct sr_analog_spec spec; - struct sr_channel *ch; +static const char *render_scpi_enum( + int value, const struct tek_enum_parser *parser_table, int *out_err) +{ + while (parser_table->name) { + if (value == parser_table->enum_value) { + return parser_table->name; + } + parser_table++; + } + *out_err = SR_ERR_DATA; + return "NULL"; +} +static int parse_scpi_blockstart(const char *data, int *out_err) +{ int len, i; + int ret = 0; + if (data[0] != '#' || data[1] < '0' || data[1] > '9') { + sr_err("block header invalid: %.2s", + data); + goto err; + } + len = data[1] - '0'; + for(i = 0; i < len; ++i) { + if (data[2+i] < '0' || data[2+i] > '9') + goto err; + ret = ret * 10 + (data[2+i] - '0'); + } + return ret; +err: + *out_err = SR_ERR_DATA; + return -1; +} - (void)fd; - sdi = cb_data; - if (!sdi) - return TRUE; +static void check_expected_value( + const char* name, int actual, int expected, int* out_err, + const struct tek_enum_parser *parser_table) +{ + if (actual != expected) { + *out_err = SR_ERR_DATA; + if (parser_table == NULL) + sr_err( + "Error validating data header. Field '%s' expected %d, but found %d", + name, expected, actual); + else { + sr_err( + "Error validating data header. Field '%s' expected %s, but found %s", + name, + render_scpi_enum(expected, parser_table, out_err), + render_scpi_enum(actual, parser_table, out_err)); + } + } +} - devc = sdi->priv; - if (!devc) - return TRUE; - scpi = sdi->conn; - ch = devc->channel_entry->data; +static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) +{ + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; + char *buf = (char *)devc->buffer; + char *fields[TEK_PRE_HEADER_FIELDS + 1]; // one extra for block + int i = 0; + int ret = SR_OK; + int pt_off; + const char *wfid; + int bit_width; + int byte_width; + int blocklength; + enum TEK_POINT_FORMAT pt_format; + enum TEK_DATA_ORDERING ordering; + enum TEK_DATA_FORMAT format; + enum TEK_DATA_ENCODING encoding; + (void)scpi; - if (revents == G_IO_IN || TRUE) { // this is always 0 for some reason + sr_dbg("Parsing header of size %d", (int)(end_buf - buf)); + sr_spew("Line as receved: %.*s", (int)(end_buf - buf - 1), buf); - // no data yet - sr_dbg("Waiting for data..."); - if (sr_scpi_read_begin(scpi) != SR_OK) - return TRUE; + // Parse in 3 steps: + // 1. find all semicolons, and replace with null bytes + // 2. Put next char reference into array + // 3. Parse each type based on array index + fields[i++] = buf; + while (buf < end_buf) { + if (*buf == ';') { + *buf = 0; // turn into list of C strings + fields[i++] = buf + 1; + } + buf++; + } + sr_spew("Expected 17 indexes, found %d in header", i); + /* + BYT_Nr ; + BIT_Nr ; + ENCdg { ASC | BIN }; + BN_Fmt { RI | RP }; + BYT_Or { LSB | MSB }; + NR_Pt ; + WFID ; + PT_FMT {ENV | Y}; + XINcr ; + PT_Off ; + XZERo ; + XUNit; + YMUlt ; + YZEro ; + YOFF ; + YUNit ; + #..block + */ - sr_dbg("New block with header expected."); - len = tektronix_tds_read_header(sdi); - if (len == 0) - /* Still reading the header. */ - return TRUE; + i = 0; + byte_width = parse_scpi_int(fields[i++], &ret, 1); + bit_width = parse_scpi_int(fields[i++], &ret, 8); + encoding = parse_scpi_enum( + fields[i++], parse_table_data_encoding, &ret, ENC_ASCII); + format = parse_scpi_enum( + fields[i++], parse_table_data_format, &ret, FMT_RI); + ordering = parse_scpi_enum( + fields[i++], parse_table_data_ordering, &ret, ORDER_LSB); + devc->wavepre.num_pts = parse_scpi_int(fields[i++], &ret, -1); + wfid = parse_scpi_string(fields[i++], &ret); + pt_format = parse_scpi_enum( + fields[i++], parse_table_point_format, &ret, PT_FMT_Y); + devc->wavepre.x_incr = parse_scpi_float(fields[i++], &ret, 1); + pt_off = parse_scpi_int(fields[i++], &ret, 0); + devc->wavepre.x_zero = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.x_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), + parse_table_xunits, &ret, XU_SECOND); + devc->wavepre.y_mult = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_zero = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_off = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), + parse_table_yunits, &ret, YU_UNKNOWN); + blocklength = parse_scpi_blockstart(fields[i++], &ret); - if (len == -1) { - sr_err("Read error, aborting capture."); - std_session_send_df_frame_end(sdi); - sdi->driver->dev_acquisition_stop(sdi); - return TRUE; - } + sr_dbg("Expected 17 values, parsed %d in header with ret=%i", i, ret); + if (i != TEK_PRE_HEADER_FIELDS + 1 && ret == SR_OK) + ret = SR_ERR; - devc->acquire_status = WAIT_DONE; + // expensive, so avoid + if (sr_log_loglevel_get() >= SR_LOG_SPEW) + sr_spew("Line is parsed as: %d;%d;%s;%s;%s;%i;\"%s\";%s;%.2e;%i;%.2e;\"%s\";%.2e;%.2e;%.2e;\"%s\";#.%d... ", + byte_width, bit_width, + render_scpi_enum(encoding, parse_table_data_encoding, &ret), + render_scpi_enum(format, parse_table_data_format, &ret), + render_scpi_enum(ordering, parse_table_data_ordering, &ret), + devc->wavepre.num_pts, wfid, + render_scpi_enum(pt_format, parse_table_point_format, &ret), + devc->wavepre.x_incr, pt_off, devc->wavepre.x_zero, + render_scpi_enum( + devc->wavepre.x_unit, parse_table_xunits, &ret), + devc->wavepre.y_mult, devc->wavepre.y_zero, + devc->wavepre.y_off, + render_scpi_enum(devc->wavepre.y_unit, + parse_table_yunits, &ret), + blocklength); + + // check that settings weren't tampered with + check_expected_value("byte width", byte_width, 1, &ret, NULL); + check_expected_value("bit size", bit_width, 8, &ret, NULL); + check_expected_value("data encoding", encoding, ENC_BINARY, &ret, parse_table_data_encoding); + check_expected_value("data format", format, FMT_RI, &ret, parse_table_data_format); + check_expected_value("data encoding", ordering, ORDER_MSB, &ret, parse_table_data_ordering); + check_expected_value("number of points", devc->wavepre.num_pts, TEK_BUFFER_SIZE, &ret, NULL); + // this value is ENV when in peak detect mode + check_expected_value("point format", pt_format, PT_FMT_Y, &ret, parse_table_point_format); - // streaming data back is pretty fast, at least once the scope - // eventually starts sending it our way - devc->num_block_read = 0; + check_expected_value("point offset", pt_off, 0, &ret, NULL); + check_expected_value("block length", blocklength, TEK_BUFFER_SIZE, &ret, NULL); + return ret; +} - sr_dbg("Requesting block: %d bytes.", TEK_BUFFER_SIZE + 1); - len = sr_scpi_read_data( - scpi, (char *)devc->buffer, TEK_BUFFER_SIZE + 1); - if (len == -1) { - sr_err("Read error, aborting capture."); - std_session_send_df_frame_end(sdi); - sdi->driver->dev_acquisition_stop(sdi); - return TRUE; - } - sr_dbg("Received block: %d bytes.", len); - devc->num_block_read = len; - - // ensure the terminating newline is read - while (devc->num_block_read < TEK_BUFFER_SIZE + 1) { - sr_dbg("Requesting: %d bytes.", - TEK_BUFFER_SIZE + 1 - devc->num_block_read); - len = sr_scpi_read_data(scpi, - (char *)devc->buffer + devc->num_block_read, - TEK_BUFFER_SIZE + 1 - devc->num_block_read); - if (len == -1) { - sr_err("Read error, aborting capture."); - std_session_send_df_frame_end(sdi); - sdi->driver->dev_acquisition_stop(sdi); - return TRUE; - } - sr_dbg("Received block: %d bytes.", len); - devc->num_block_read += len; - } - sr_dbg("Transfer has been completed."); - if (!sr_scpi_read_complete(scpi)) { - sr_err("Read should have been completed."); - std_session_send_df_frame_end(sdi); - sdi->driver->dev_acquisition_stop(sdi); - return TRUE; - } +/* Read the header of a data block. */ +static int tektronix_tds_read_header(struct sr_dev_inst *sdi) +{ + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; + char *buf = (char *)devc->buffer; + int ret; - // We have received the entire 2.5k buffer now, so process it, - // ignoring the trailing \n - len = TEK_BUFFER_SIZE; + // header is variable, but at least 100 bytes, and likely no more than + // 175 bytes. Typical values are around 150 - float vdiv = devc->vdiv[ch->index]; - GArray *float_data; - static GArray *data; - float voltage, vdivlog; - int digits; + int attempt = 100; + int found = 0; - data = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); - g_array_append_vals(data, devc->buffer, len); - float_data = g_array_new(FALSE, FALSE, sizeof(float)); - for (i = 0; i < len; i++) { - voltage = (float)g_array_index(data, int8_t, i) - - devc->wavepre.y_off; - voltage = ((devc->wavepre.y_mult * voltage) + - devc->wavepre.y_zero); - g_array_append_val(float_data, voltage); - } - vdivlog = log10f(vdiv); - digits = -(int)vdivlog + (vdivlog < 0.0) + 3 /* 8-bit resolution*/ - 1; - sr_analog_init(&analog, &encoding, &meaning, &spec, digits); - analog.meaning->channels = g_slist_append(NULL, ch); - analog.num_samples = float_data->len; - analog.data = ((float *)float_data->data); - if (devc->wavepre.y_unit == YU_VOLTS) { - analog.meaning->mq = SR_MQ_VOLTAGE; - analog.meaning->unit = SR_UNIT_VOLT; - } else if (devc->wavepre.y_unit == YU_AMPS) { - analog.meaning->mq = SR_MQ_CURRENT; - analog.meaning->unit = SR_UNIT_AMPERE; - } else if (devc->wavepre.y_unit == YU_DECIBELS) { - analog.meaning->mq = SR_MQ_POWER; - analog.meaning->unit = SR_UNIT_DECIBEL_MW; - } else { - analog.meaning->mq = 0; - analog.meaning->unit = SR_UNIT_UNITLESS; + // Find all 16 fields by locating their semicolons. + // In theory the string values could contain semicolons to throw us off + // but I think we are safe based on the docs + while (found < TEK_PRE_HEADER_FIELDS) { + /* Read header from device. */ + ret = sr_scpi_read_data(scpi, buf, attempt); + if (ret < attempt) { + sr_err("Read error while reading data header: %i of %i", + ret, attempt); + return SR_ERR; } - analog.meaning->mqflags = 0; - packet.type = SR_DF_ANALOG; - packet.payload = &analog; - sr_dbg("Computing using trigger point %.6f", devc->horiz_triggerpos); - // only the first packet provides trigger information, all others - // are "after" the correct timebase - if (devc->channel_entry != devc->enabled_channels) { - sr_session_send(sdi, &packet); - } else if (devc->horiz_triggerpos > 0) { - // This will round to (potentially) twice the expected margin - // on-device (% -> s -> %) vs our expectation (%) - analog.num_samples = float_data->len * devc->horiz_triggerpos; - sr_dbg("First batch has %d", analog.num_samples); - sr_session_send(sdi, &packet); - std_session_send_df_trigger(sdi); - if (devc->horiz_triggerpos < 1) { - analog.data = ((float *)float_data->data) + analog.num_samples; - analog.num_samples = float_data->len - analog.num_samples; - sr_dbg("second batch has %d", analog.num_samples); - sr_session_send(sdi, &packet); + for (int i = 0; i < ret; i++, buf++) { + if (*buf == ';') { + found++; } - } else { // trigger == 0 - std_session_send_df_trigger(sdi); - sr_session_send(sdi, &packet); } - g_slist_free(analog.meaning->channels); - g_array_free(data, TRUE); - - if (devc->channel_entry->next) { - sr_dbg("Doing another channel"); - /* We got the frame for this channel, now get the next channel. */ - devc->channel_entry = devc->channel_entry->next; - tektronix_tds_channel_start(sdi); - } else { - /* Done with this frame. */ - std_session_send_df_frame_end(sdi); - if (++devc->num_frames == devc->limit_frames) { - /* Last frame, stop capture. */ - sdi->driver->dev_acquisition_stop(sdi); - tektronix_tds_capture_finish(sdi); - } else { - sr_dbg("Doing another frame"); - /* Get the next frame, starting with the first channel. */ - devc->channel_entry = devc->enabled_channels; - tektronix_tds_capture_start(sdi); + attempt = TEK_PRE_HEADER_FIELDS - found; + if (attempt > 1) + attempt *= 2; + } - /* Start of next frame. */ - std_session_send_df_frame_begin(sdi); - } - } + // read block header prefix (# + ) + ret = sr_scpi_read_data(scpi, buf, 2); + if (ret < 2) { + sr_err("Read error while reading block header: %i of %i", + ret, 2); + return SR_ERR; } - return TRUE; + if (buf[0] != '#' || buf[1] < '0' || buf[1] > '9') { + sr_err("block header invalid: %.2s", + buf); + return SR_ERR; + } + attempt = buf[1] - '0'; + buf+=2; + // read block header size + ret = sr_scpi_read_data(scpi, buf, attempt); + if (ret < attempt) { + sr_err("Read error while reading block header: %i of %i", + ret, attempt); + return SR_ERR; + } + buf +=attempt; + + if (tektronix_tds_parse_header(sdi, buf + 1) != SR_OK) + ret = -1; + return ret; } -/* Start reading data from the current channel. */ -SR_PRIV int tektronix_tds_channel_start(const struct sr_dev_inst *sdi) +/* Send a configuration setting. */ +SR_PRIV int tektronix_tds_config_set( + const struct sr_dev_inst *sdi, const char *format, ...) { + va_list args; + int ret; struct dev_context *devc; - struct sr_channel *ch; - if (!(devc = sdi->priv)) - return SR_ERR; + devc = sdi->priv; - ch = devc->channel_entry->data; + g_rec_mutex_lock(&devc->mutex); - sr_dbg("Configure reading data from channel %s.", ch->name); + va_start(args, format); + ret = sr_scpi_send_variadic(sdi->conn, format, args); + va_end(args); - if (sr_scpi_send(sdi->conn, "DAT:SOU CH%d", ch->index + 1) != SR_OK) - return SR_ERR; + g_rec_mutex_unlock(&devc->mutex); - // wait for trigger (asynchronous) - if (devc->acquire_status == WAIT_CAPTURE && - (devc->num_frames > 0 || devc->capture_mode == CAPTURE_LIVE || - devc->capture_mode == CAPTURE_ONE_SHOT || - devc->prior_state_running)) - if (sr_scpi_send(sdi->conn, "*WAI") != SR_OK) - return SR_ERR; - devc->acquire_status = WAIT_CHANNEL; + return ret; +} - sr_dbg("Requesting waveform"); - if (sr_scpi_send(sdi->conn, "WAVF?") != SR_OK) - return SR_ERR; +SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + float fvalue; + int memory_depth; - devc->num_block_read = 0; + devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); + + /* Get the timebase. */ + if (sr_scpi_get_float(sdi->conn, "hor:sca?", &devc->timebase) != SR_OK) + goto err; + sr_dbg("Current timebase: %g.", devc->timebase); + + /* Get the record size. A sanity check as it should be 2500 */ + if (sr_scpi_get_int(sdi->conn, "hor:reco?", &memory_depth) != SR_OK) + goto err; + + if (memory_depth != TEK_BUFFER_SIZE) { + sr_err("A Tek 2k5 device should have that much memory. Expecting: 2500 bytes, found %d bytes", + memory_depth); + goto err; + } + + fvalue = TEK_BUFFER_SIZE / (devc->timebase * (float)TEK_NUM_HDIV); + if (devc->model->sample_rate * 1000000.0 < fvalue) + sr_dbg("Current samplerate: %i MSa/s (limited by device).", + devc->model->sample_rate); + else + sr_dbg("Current samplerate: %ld Sa/s.", (long)fvalue); + // TODO: peak detect mode is half of this + sr_dbg("Current memory depth: %d.", TEK_BUFFER_SIZE); + g_rec_mutex_unlock(&devc->mutex); return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; } -/* Start capturing a new frameset. */ -SR_PRIV int tektronix_tds_capture_start(const struct sr_dev_inst *sdi) + +SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi) { struct dev_context *devc; + char *cmd; + int i; + int res; - if (!(devc = sdi->priv)) - return SR_ERR; + devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); - // Force our capture settings to 1 byte, msb, binary - if (tektronix_tds_config_set(sdi, "dat:enc RIB") != SR_OK) - return SR_ERR; - if (tektronix_tds_config_set(sdi, "dat:wid 1") != SR_OK) - return SR_ERR; + /* Vertical gain. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:SCA?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->vdiv[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + sr_dbg("Current vertical gain:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->vdiv[i]); + + /* Vertical offset. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:POS?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->vert_offset[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + sr_dbg("Current vertical offset:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->vert_offset[i]); + + g_rec_mutex_unlock(&devc->mutex); + return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; +} + +SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_channel *ch; + char *cmd, *response; + int i; + int res; + + devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); + + /* Analog channel state. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("SELECT:CH%i?", i + 1); + res = sr_scpi_get_bool(sdi->conn, cmd, &devc->analog_channels[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + ch = g_slist_nth_data(sdi->channels, i); + ch->enabled = devc->analog_channels[i]; + } + sr_dbg("Current analog channel state:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %s", i + 1, devc->analog_channels[i] ? "On" : "Off"); + + /* Probe attenuation. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:PROBE?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->attenuation[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + sr_dbg("Current probe attenuation:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->attenuation[i]); + + /* Vertical gain and offset. */ + if (tektronix_tds_get_dev_cfg_vertical(sdi) != SR_OK) + goto err; + + if (tektronix_tds_get_dev_cfg_horizontal(sdi) != SR_OK) + goto err; + + /* Coupling. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:COUP?", i + 1); + g_free(devc->coupling[i]); + devc->coupling[i] = NULL; + res = sr_scpi_get_string(sdi->conn, cmd, &devc->coupling[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + + sr_dbg("Current coupling:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %s", i + 1, devc->coupling[i]); + + /* Trigger source. edge, pulse, and video are always the same, it + * appears */ + response = NULL; + g_free(devc->trigger_source); + if (sr_scpi_get_string(sdi->conn, "TRIG:MAI:edge:sou?", + &devc->trigger_source) != SR_OK) + goto err; + sr_dbg("Current trigger source: %s.", devc->trigger_source); + + /* Horizontal trigger position. */ + if (sr_scpi_get_float(sdi->conn, "hor:pos?", &devc->horiz_triggerpos) != + SR_OK) + goto err; + + // triggerpos is in timeunits, convert back to percentage + devc->horiz_triggerpos = + (-devc->horiz_triggerpos / (devc->timebase * 10)) + 0.5; + + sr_dbg("Current horizontal trigger position %.10f.", + devc->horiz_triggerpos); + + /* Trigger slope. */ + g_free(devc->trigger_slope); + devc->trigger_slope = NULL; + res = sr_scpi_get_string( + sdi->conn, "trig:mai:edge:slope?", &devc->trigger_slope); + if (res != SR_OK) + goto err; + sr_dbg("Current trigger slope: %s.", devc->trigger_slope); + + /* Trigger level. */ + res = sr_scpi_get_float(sdi->conn, "trig:mai:lev?", &devc->trigger_level); + if (res != SR_OK) + goto err; + sr_dbg("Current trigger level: %g.", devc->trigger_level); + + /* Averaging/peak detection */ + response = NULL; + if (sr_scpi_get_string(sdi->conn, "acq:mod?", &response) != SR_OK) + goto err; + devc->average_enabled = g_ascii_strncasecmp(response, "average", 3) == 0; + devc->peak_enabled = g_ascii_strncasecmp(response, "peak", 3) == 0; + sr_dbg("Acquisition mode: %s.", response); + g_free(response); + + if (sr_scpi_get_int(sdi->conn, "acq:numav?", &devc->average_samples) != SR_OK) + goto err; + sr_dbg("Averaging samples: %i.", devc->average_samples); + + g_rec_mutex_unlock(&devc->mutex); + return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; +} + +/* Revert all settings, if requested. */ +SR_PRIV int tektronix_tds_capture_finish(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) + return SR_ERR; + + devc->acquire_status = WAIT_DONE; + + sr_dbg("Setting exiting setttings back"); + + if (devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_DISPLAY) { + if (tektronix_tds_config_set(sdi, "ACQ:stopa runstop") != SR_OK) + goto err; + + if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != SR_OK) + goto err; + } + + g_rec_mutex_unlock(&devc->mutex); + return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; +} + +/* Start reading data from the current channel. */ +SR_PRIV int tektronix_tds_channel_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_channel *ch; + + if (!(devc = sdi->priv)) + return SR_ERR; + + ch = devc->channel_entry->data; + + sr_dbg("Configure reading data from channel %s.", ch->name); + + if (sr_scpi_send(sdi->conn, "DAT:SOU CH%d", ch->index + 1) != SR_OK) + return SR_ERR; + + // wait for trigger (asynchronous) + if (devc->acquire_status == WAIT_CAPTURE && + (devc->num_frames > 0 || devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_ONE_SHOT || + devc->prior_state_running)) + if (sr_scpi_send(sdi->conn, "*WAI") != SR_OK) + return SR_ERR; + devc->acquire_status = WAIT_CHANNEL; + + sr_dbg("Requesting waveform"); + if (sr_scpi_send(sdi->conn, "WAVF?") != SR_OK) + return SR_ERR; + + devc->num_block_read = 0; + + return SR_OK; +} + +/* Start capturing a new frameset. */ +SR_PRIV int tektronix_tds_capture_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) + return SR_ERR; + + // Force our capture settings to 1 byte, msb, binary + if (tektronix_tds_config_set(sdi, "dat:enc RIB") != SR_OK) + return SR_ERR; + if (tektronix_tds_config_set(sdi, "dat:wid 1") != SR_OK) + return SR_ERR; devc->acquire_status = WAIT_CAPTURE; @@ -349,499 +665,183 @@ SR_PRIV int tektronix_tds_capture_start(const struct sr_dev_inst *sdi) return SR_OK; } -static int parse_scpi_int(const char *data, int *out_err, int default_value) +SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) { - int value = default_value; - struct sr_rational ret_rational; - int ret; - - ret = sr_parse_rational(data, &ret_rational); - if (ret == SR_OK && (ret_rational.p % ret_rational.q) == 0) { - value = ret_rational.p / ret_rational.q; - } else { - sr_dbg("get_int: non-integer rational=%" PRId64 "/%" PRIu64, - ret_rational.p, ret_rational.q); - *out_err = SR_ERR_DATA; - } + struct sr_dev_inst *sdi; + struct dev_context *devc; - return value; -} + struct sr_scpi_dev_inst *scpi; + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; + struct sr_channel *ch; + int len, i; -static float parse_scpi_float(const char *data, int *out_err, float default_value) -{ - float value = default_value; + (void)fd; - if (sr_atof_ascii(data, &value) != SR_OK) - *out_err = SR_ERR_DATA; + sdi = cb_data; + if (!sdi) + return TRUE; - return value; -} - -static const char *parse_scpi_string(char *data, int *out_err) -{ - (void)out_err; - return sr_scpi_unquote_string(data); -} - -static int parse_scpi_enum(const char *data, - const struct tek_enum_parser *parser_table, int *out_err, int default_value) -{ - while (parser_table->name) { - if (g_ascii_strcasecmp(parser_table->name, data) == 0) { - return parser_table->enum_value; - } - parser_table++; - } - *out_err = SR_ERR_DATA; - return default_value; -} - -static const char *render_scpi_enum( - int value, const struct tek_enum_parser *parser_table, int *out_err) -{ - while (parser_table->name) { - if (value == parser_table->enum_value) { - return parser_table->name; - } - parser_table++; - } - *out_err = SR_ERR_DATA; - return "NULL"; -} -static int parse_scpi_blockstart(const char *data, int *out_err) -{ - int len, i; - int ret = 0; - if (data[0] != '#' || data[1] < '0' || data[1] > '9') { - sr_err("block header invalid: %.2s", - data); - goto err; - } - len = data[1] - '0'; - for(i = 0; i < len; ++i) { - if (data[2+i] < '0' || data[2+i] > '9') - goto err; - ret = ret * 10 + (data[2+i] - '0'); - } - return ret; -err: - *out_err = SR_ERR_DATA; - return -1; -} - - -static void check_expected_value( - const char* name, int actual, int expected, int* out_err, - const struct tek_enum_parser *parser_table) -{ - if (actual != expected) { - *out_err = SR_ERR_DATA; - if (parser_table == NULL) - sr_err( - "Error validating data header. Field '%s' expected %d, but found %d", - name, expected, actual); - else { - sr_err( - "Error validating data header. Field '%s' expected %s, but found %s", - name, - render_scpi_enum(expected, parser_table, out_err), - render_scpi_enum(actual, parser_table, out_err)); - } - } -} - -static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) -{ - struct sr_scpi_dev_inst *scpi = sdi->conn; - struct dev_context *devc = sdi->priv; - char *buf = (char *)devc->buffer; - char *fields[TEK_PRE_HEADER_FIELDS + 1]; // one extra for block - int i = 0; - int ret = SR_OK; - int pt_off; - const char *wfid; - int bit_width; - int byte_width; - int blocklength; - enum TEK_POINT_FORMAT pt_format; - enum TEK_DATA_ORDERING ordering; - enum TEK_DATA_FORMAT format; - enum TEK_DATA_ENCODING encoding; - (void)scpi; - - sr_dbg("Parsing header of size %d", (int)(end_buf - buf)); - sr_spew("Line as receved: %.*s", (int)(end_buf - buf - 1), buf); - - // Parse in 3 steps: - // 1. find all semicolons, and replace with null bytes - // 2. Put next char reference into array - // 3. Parse each type based on array index - fields[i++] = buf; - while (buf < end_buf) { - if (*buf == ';') { - *buf = 0; // turn into list of C strings - fields[i++] = buf + 1; - } - buf++; - } - sr_spew("Expected 17 indexes, found %d in header", i); - /* - BYT_Nr ; - BIT_Nr ; - ENCdg { ASC | BIN }; - BN_Fmt { RI | RP }; - BYT_Or { LSB | MSB }; - NR_Pt ; - WFID ; - PT_FMT {ENV | Y}; - XINcr ; - PT_Off ; - XZERo ; - XUNit; - YMUlt ; - YZEro ; - YOFF ; - YUNit ; - #..block - */ - - i = 0; - byte_width = parse_scpi_int(fields[i++], &ret, 1); - bit_width = parse_scpi_int(fields[i++], &ret, 8); - encoding = parse_scpi_enum( - fields[i++], parse_table_data_encoding, &ret, ENC_ASCII); - format = parse_scpi_enum( - fields[i++], parse_table_data_format, &ret, FMT_RI); - ordering = parse_scpi_enum( - fields[i++], parse_table_data_ordering, &ret, ORDER_LSB); - devc->wavepre.num_pts = parse_scpi_int(fields[i++], &ret, -1); - wfid = parse_scpi_string(fields[i++], &ret); - pt_format = parse_scpi_enum( - fields[i++], parse_table_point_format, &ret, PT_FMT_Y); - devc->wavepre.x_incr = parse_scpi_float(fields[i++], &ret, 1); - pt_off = parse_scpi_int(fields[i++], &ret, 0); - devc->wavepre.x_zero = parse_scpi_float(fields[i++], &ret, 0); - devc->wavepre.x_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), - parse_table_xunits, &ret, XU_SECOND); - devc->wavepre.y_mult = parse_scpi_float(fields[i++], &ret, 0); - devc->wavepre.y_zero = parse_scpi_float(fields[i++], &ret, 0); - devc->wavepre.y_off = parse_scpi_float(fields[i++], &ret, 0); - devc->wavepre.y_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), - parse_table_yunits, &ret, YU_UNKNOWN); - blocklength = parse_scpi_blockstart(fields[i++], &ret); - - sr_dbg("Expected 17 values, parsed %d in header with ret=%i", i, ret); - if (i != TEK_PRE_HEADER_FIELDS + 1 && ret == SR_OK) - ret = SR_ERR; - - // expensive, so avoid - if (sr_log_loglevel_get() >= SR_LOG_SPEW) - sr_spew("Line is parsed as: %d;%d;%s;%s;%s;%i;\"%s\";%s;%.2e;%i;%.2e;\"%s\";%.2e;%.2e;%.2e;\"%s\";#.%d... ", - byte_width, bit_width, - render_scpi_enum(encoding, parse_table_data_encoding, &ret), - render_scpi_enum(format, parse_table_data_format, &ret), - render_scpi_enum(ordering, parse_table_data_ordering, &ret), - devc->wavepre.num_pts, wfid, - render_scpi_enum(pt_format, parse_table_point_format, &ret), - devc->wavepre.x_incr, pt_off, devc->wavepre.x_zero, - render_scpi_enum( - devc->wavepre.x_unit, parse_table_xunits, &ret), - devc->wavepre.y_mult, devc->wavepre.y_zero, - devc->wavepre.y_off, - render_scpi_enum(devc->wavepre.y_unit, - parse_table_yunits, &ret), - blocklength); - - // check that settings weren't tampered with - check_expected_value("byte width", byte_width, 1, &ret, NULL); - check_expected_value("bit size", bit_width, 8, &ret, NULL); - check_expected_value("data encoding", encoding, ENC_BINARY, &ret, parse_table_data_encoding); - check_expected_value("data format", format, FMT_RI, &ret, parse_table_data_format); - check_expected_value("data encoding", ordering, ORDER_MSB, &ret, parse_table_data_ordering); - check_expected_value("number of points", devc->wavepre.num_pts, TEK_BUFFER_SIZE, &ret, NULL); - // this value is ENV when in peak detect mode - check_expected_value("point format", pt_format, PT_FMT_Y, &ret, parse_table_point_format); - - check_expected_value("point offset", pt_off, 0, &ret, NULL); - check_expected_value("block length", blocklength, TEK_BUFFER_SIZE, &ret, NULL); - return ret; -} - -/* Read the header of a data block. */ -static int tektronix_tds_read_header(struct sr_dev_inst *sdi) -{ - struct sr_scpi_dev_inst *scpi = sdi->conn; - struct dev_context *devc = sdi->priv; - char *buf = (char *)devc->buffer; - int ret; - - // header is variable, but at least 100 bytes, and likely no more than - // 175 bytes. Typical values are around 150 - - int attempt = 100; - int found = 0; - - // Find all 16 fields by locating their semicolons. - // In theory the string values could contain semicolons to throw us off - // but I think we are safe based on the docs - while (found < TEK_PRE_HEADER_FIELDS) { - /* Read header from device. */ - ret = sr_scpi_read_data(scpi, buf, attempt); - if (ret < attempt) { - sr_err("Read error while reading data header: %i of %i", - ret, attempt); - return SR_ERR; - } - for (int i = 0; i < ret; i++, buf++) { - if (*buf == ';') { - found++; - } - } - attempt = TEK_PRE_HEADER_FIELDS - found; - if (attempt > 1) - attempt *= 2; - } - - // read block header prefix (# + ) - ret = sr_scpi_read_data(scpi, buf, 2); - if (ret < 2) { - sr_err("Read error while reading block header: %i of %i", - ret, 2); - return SR_ERR; - } - if (buf[0] != '#' || buf[1] < '0' || buf[1] > '9') { - sr_err("block header invalid: %.2s", - buf); - return SR_ERR; - } - attempt = buf[1] - '0'; - buf+=2; - // read block header size - ret = sr_scpi_read_data(scpi, buf, attempt); - if (ret < attempt) { - sr_err("Read error while reading block header: %i of %i", - ret, attempt); - return SR_ERR; - } - buf +=attempt; - - if (tektronix_tds_parse_header(sdi, buf + 1) != SR_OK) - ret = -1; - return ret; -} -/* Send a configuration setting. */ -SR_PRIV int tektronix_tds_config_set( - const struct sr_dev_inst *sdi, const char *format, ...) -{ - va_list args; - int ret; - struct dev_context *devc; - - devc = sdi->priv; - - g_rec_mutex_lock(&devc->mutex); - - va_start(args, format); - ret = sr_scpi_send_variadic(sdi->conn, format, args); - va_end(args); - - g_rec_mutex_unlock(&devc->mutex); - - return ret; -} - -SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi) -{ - struct dev_context *devc; - char *cmd; - int i; - int res; - - devc = sdi->priv; - g_rec_mutex_lock(&devc->mutex); - - /* Vertical gain. */ - for (i = 0; i < devc->model->channels; i++) { - cmd = g_strdup_printf("CH%d:SCA?", i + 1); - res = sr_scpi_get_float(sdi->conn, cmd, &devc->vdiv[i]); - g_free(cmd); - if (res != SR_OK) - goto err; - } - sr_dbg("Current vertical gain:"); - for (i = 0; i < devc->model->channels; i++) - sr_dbg("CH%d %g", i + 1, devc->vdiv[i]); - - /* Vertical offset. */ - for (i = 0; i < devc->model->channels; i++) { - cmd = g_strdup_printf("CH%d:POS?", i + 1); - res = sr_scpi_get_float(sdi->conn, cmd, &devc->vert_offset[i]); - g_free(cmd); - if (res != SR_OK) - goto err; - } - sr_dbg("Current vertical offset:"); - for (i = 0; i < devc->model->channels; i++) - sr_dbg("CH%d %g", i + 1, devc->vert_offset[i]); - - g_rec_mutex_unlock(&devc->mutex); - return SR_OK; -err: - g_rec_mutex_unlock(&devc->mutex); - return SR_ERR; -} - -SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) -{ - struct dev_context *devc; - struct sr_channel *ch; - char *cmd, *response; - int i; - int res; - - devc = sdi->priv; - g_rec_mutex_lock(&devc->mutex); - - /* Analog channel state. */ - for (i = 0; i < devc->model->channels; i++) { - cmd = g_strdup_printf("SELECT:CH%i?", i + 1); - res = sr_scpi_get_bool(sdi->conn, cmd, &devc->analog_channels[i]); - g_free(cmd); - if (res != SR_OK) - goto err; - ch = g_slist_nth_data(sdi->channels, i); - ch->enabled = devc->analog_channels[i]; - } - sr_dbg("Current analog channel state:"); - for (i = 0; i < devc->model->channels; i++) - sr_dbg("CH%d %s", i + 1, devc->analog_channels[i] ? "On" : "Off"); - - /* Probe attenuation. */ - for (i = 0; i < devc->model->channels; i++) { - cmd = g_strdup_printf("CH%d:PROBE?", i + 1); - res = sr_scpi_get_float(sdi->conn, cmd, &devc->attenuation[i]); - g_free(cmd); - if (res != SR_OK) - goto err; - } - sr_dbg("Current probe attenuation:"); - for (i = 0; i < devc->model->channels; i++) - sr_dbg("CH%d %g", i + 1, devc->attenuation[i]); - - /* Vertical gain and offset. */ - if (tektronix_tds_get_dev_cfg_vertical(sdi) != SR_OK) - goto err; - - if (tektronix_tds_get_dev_cfg_horizontal(sdi) != SR_OK) - goto err; - - /* Coupling. */ - for (i = 0; i < devc->model->channels; i++) { - cmd = g_strdup_printf("CH%d:COUP?", i + 1); - g_free(devc->coupling[i]); - devc->coupling[i] = NULL; - res = sr_scpi_get_string(sdi->conn, cmd, &devc->coupling[i]); - g_free(cmd); - if (res != SR_OK) - goto err; - } - - sr_dbg("Current coupling:"); - for (i = 0; i < devc->model->channels; i++) - sr_dbg("CH%d %s", i + 1, devc->coupling[i]); - - /* Trigger source. edge, pulse, and video are always the same, it - * appears */ - response = NULL; - g_free(devc->trigger_source); - if (sr_scpi_get_string(sdi->conn, "TRIG:MAI:edge:sou?", - &devc->trigger_source) != SR_OK) - goto err; - sr_dbg("Current trigger source: %s.", devc->trigger_source); + devc = sdi->priv; + if (!devc) + return TRUE; + scpi = sdi->conn; + ch = devc->channel_entry->data; - /* Horizontal trigger position. */ - if (sr_scpi_get_float(sdi->conn, "hor:pos?", &devc->horiz_triggerpos) != - SR_OK) - goto err; + if (revents == G_IO_IN || TRUE) { // this is always 0 for some reason - // triggerpos is in timeunits, convert back to percentage - devc->horiz_triggerpos = - (-devc->horiz_triggerpos / (devc->timebase * 10)) + 0.5; + // no data yet + sr_dbg("Waiting for data..."); + if (sr_scpi_read_begin(scpi) != SR_OK) + return TRUE; - sr_dbg("Current horizontal trigger position %.10f.", - devc->horiz_triggerpos); + sr_dbg("New block with header expected."); + len = tektronix_tds_read_header(sdi); + if (len == 0) + /* Still reading the header. */ + return TRUE; - /* Trigger slope. */ - g_free(devc->trigger_slope); - devc->trigger_slope = NULL; - res = sr_scpi_get_string( - sdi->conn, "trig:mai:edge:slope?", &devc->trigger_slope); - if (res != SR_OK) - goto err; - sr_dbg("Current trigger slope: %s.", devc->trigger_slope); + if (len == -1) { + sr_err("Read error, aborting capture."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } - /* Trigger level. */ - res = sr_scpi_get_float(sdi->conn, "trig:mai:lev?", &devc->trigger_level); - if (res != SR_OK) - goto err; - sr_dbg("Current trigger level: %g.", devc->trigger_level); + devc->acquire_status = WAIT_DONE; - /* Averaging/peak detection */ - response = NULL; - if (sr_scpi_get_string(sdi->conn, "acq:mod?", &response) != SR_OK) - goto err; - devc->average_enabled = g_ascii_strncasecmp(response, "average", 3) == 0; - devc->peak_enabled = g_ascii_strncasecmp(response, "peak", 3) == 0; - sr_dbg("Acquisition mode: %s.", response); - g_free(response); + // streaming data back is pretty fast, at least once the scope + // eventually starts sending it our way + devc->num_block_read = 0; - if (sr_scpi_get_int(sdi->conn, "acq:numav?", &devc->average_samples) != SR_OK) - goto err; - sr_dbg("Averaging samples: %i.", devc->average_samples); + sr_dbg("Requesting block: %d bytes.", TEK_BUFFER_SIZE + 1); + len = sr_scpi_read_data( + scpi, (char *)devc->buffer, TEK_BUFFER_SIZE + 1); + if (len == -1) { + sr_err("Read error, aborting capture."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + sr_dbg("Received block: %d bytes.", len); + devc->num_block_read = len; - g_rec_mutex_unlock(&devc->mutex); - return SR_OK; -err: - g_rec_mutex_unlock(&devc->mutex); - return SR_ERR; -} + // ensure the terminating newline is read + while (devc->num_block_read < TEK_BUFFER_SIZE + 1) { + sr_dbg("Requesting: %d bytes.", + TEK_BUFFER_SIZE + 1 - devc->num_block_read); + len = sr_scpi_read_data(scpi, + (char *)devc->buffer + devc->num_block_read, + TEK_BUFFER_SIZE + 1 - devc->num_block_read); + if (len == -1) { + sr_err("Read error, aborting capture."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + sr_dbg("Received block: %d bytes.", len); + devc->num_block_read += len; + } + sr_dbg("Transfer has been completed."); + if (!sr_scpi_read_complete(scpi)) { + sr_err("Read should have been completed."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } -SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi) -{ - struct dev_context *devc; - float fvalue; - int memory_depth; + // We have received the entire 2.5k buffer now, so process it, + // ignoring the trailing \n + len = TEK_BUFFER_SIZE; - devc = sdi->priv; - g_rec_mutex_lock(&devc->mutex); + float vdiv = devc->vdiv[ch->index]; + GArray *float_data; + static GArray *data; + float voltage, vdivlog; + int digits; - /* Get the timebase. */ - if (sr_scpi_get_float(sdi->conn, "hor:sca?", &devc->timebase) != SR_OK) - goto err; - sr_dbg("Current timebase: %g.", devc->timebase); + data = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); + g_array_append_vals(data, devc->buffer, len); + float_data = g_array_new(FALSE, FALSE, sizeof(float)); + for (i = 0; i < len; i++) { + voltage = (float)g_array_index(data, int8_t, i) - + devc->wavepre.y_off; + voltage = ((devc->wavepre.y_mult * voltage) + + devc->wavepre.y_zero); + g_array_append_val(float_data, voltage); + } + vdivlog = log10f(vdiv); + digits = -(int)vdivlog + (vdivlog < 0.0) + 3 /* 8-bit resolution*/ - 1; + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); + analog.meaning->channels = g_slist_append(NULL, ch); + analog.num_samples = float_data->len; + analog.data = ((float *)float_data->data); + if (devc->wavepre.y_unit == YU_VOLTS) { + analog.meaning->mq = SR_MQ_VOLTAGE; + analog.meaning->unit = SR_UNIT_VOLT; + } else if (devc->wavepre.y_unit == YU_AMPS) { + analog.meaning->mq = SR_MQ_CURRENT; + analog.meaning->unit = SR_UNIT_AMPERE; + } else if (devc->wavepre.y_unit == YU_DECIBELS) { + analog.meaning->mq = SR_MQ_POWER; + analog.meaning->unit = SR_UNIT_DECIBEL_MW; + } else { + analog.meaning->mq = 0; + analog.meaning->unit = SR_UNIT_UNITLESS; + } + analog.meaning->mqflags = 0; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_dbg("Computing using trigger point %.6f", devc->horiz_triggerpos); + // only the first packet provides trigger information, all others + // are "after" the correct timebase + if (devc->channel_entry != devc->enabled_channels) { + sr_session_send(sdi, &packet); + } else if (devc->horiz_triggerpos > 0) { + // This will round to (potentially) twice the expected margin + // on-device (% -> s -> %) vs our expectation (%) + analog.num_samples = float_data->len * devc->horiz_triggerpos; + sr_dbg("First batch has %d", analog.num_samples); + sr_session_send(sdi, &packet); + std_session_send_df_trigger(sdi); + if (devc->horiz_triggerpos < 1) { + analog.data = ((float *)float_data->data) + analog.num_samples; + analog.num_samples = float_data->len - analog.num_samples; + sr_dbg("second batch has %d", analog.num_samples); + sr_session_send(sdi, &packet); + } + } else { // trigger == 0 + std_session_send_df_trigger(sdi); + sr_session_send(sdi, &packet); + } + g_slist_free(analog.meaning->channels); + g_array_free(data, TRUE); - /* Get the record size. A sanity check as it should be 2500 */ - if (sr_scpi_get_int(sdi->conn, "hor:reco?", &memory_depth) != SR_OK) - goto err; + if (devc->channel_entry->next) { + sr_dbg("Doing another channel"); + /* We got the frame for this channel, now get the next channel. */ + devc->channel_entry = devc->channel_entry->next; + tektronix_tds_channel_start(sdi); + } else { + /* Done with this frame. */ + std_session_send_df_frame_end(sdi); + if (++devc->num_frames == devc->limit_frames) { + /* Last frame, stop capture. */ + sdi->driver->dev_acquisition_stop(sdi); + tektronix_tds_capture_finish(sdi); + } else { + sr_dbg("Doing another frame"); + /* Get the next frame, starting with the first channel. */ + devc->channel_entry = devc->enabled_channels; + tektronix_tds_capture_start(sdi); - if (memory_depth != TEK_BUFFER_SIZE) { - sr_err("A Tek 2k5 device should have that much memory. Expecting: 2500 bytes, found %d bytes", - memory_depth); - goto err; + /* Start of next frame. */ + std_session_send_df_frame_begin(sdi); + } + } } - - fvalue = TEK_BUFFER_SIZE / (devc->timebase * (float)TEK_NUM_HDIV); - if (devc->model->sample_rate * 1000000.0 < fvalue) - sr_dbg("Current samplerate: %i MSa/s (limited by device).", - devc->model->sample_rate); - else - sr_dbg("Current samplerate: %ld Sa/s.", (long)fvalue); - - // TODO: peak detect mode is half of this - sr_dbg("Current memory depth: %d.", TEK_BUFFER_SIZE); - g_rec_mutex_unlock(&devc->mutex); - return SR_OK; -err: - g_rec_mutex_unlock(&devc->mutex); - return SR_ERR; + return TRUE; } From 4fc4d3e2eef1968c7171a20ccdd3922ad2d09d56 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 8 Apr 2023 09:15:41 +0200 Subject: [PATCH 07/20] scpi: rename "current channel name" variable (false friends) Rename the actual_channel_name identifier to curr_channel_name. This was introduced in commit 17a82e83ff4c2. --- src/scpi.h | 2 +- src/scpi/scpi.c | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/scpi.h b/src/scpi.h index 634c27271..117b756b2 100644 --- a/src/scpi.h +++ b/src/scpi.h @@ -112,7 +112,7 @@ struct sr_scpi_dev_inst { /* Only used for quirk workarounds, notably the Rigol DS1000 series. */ uint64_t firmware_version; GMutex scpi_mutex; - char *actual_channel_name; + char *curr_channel_name; gboolean no_opc_command; }; diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 8d24ceea9..90c584a0a 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -605,7 +605,7 @@ SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi) scpi->free(scpi->priv); g_free(scpi->priv); - g_free(scpi->actual_channel_name); + g_free(scpi->curr_channel_name); g_free(scpi); } @@ -1316,10 +1316,10 @@ SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, /* Select channel. */ channel_cmd = sr_scpi_cmd_get(cmdtable, channel_command); if (channel_cmd && channel_name && - g_strcmp0(channel_name, scpi->actual_channel_name)) { + g_strcmp0(channel_name, scpi->curr_channel_name)) { sr_spew("sr_scpi_cmd(): new channel = %s", channel_name); - g_free(scpi->actual_channel_name); - scpi->actual_channel_name = g_strdup(channel_name); + g_free(scpi->curr_channel_name); + scpi->curr_channel_name = g_strdup(channel_name); ret = scpi_send(scpi, channel_cmd, channel_name); if (ret != SR_OK) return ret; @@ -1361,10 +1361,10 @@ SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi, /* Select channel. */ channel_cmd = sr_scpi_cmd_get(cmdtable, channel_command); if (channel_cmd && channel_name && - g_strcmp0(channel_name, scpi->actual_channel_name)) { + g_strcmp0(channel_name, scpi->curr_channel_name)) { sr_spew("sr_scpi_cmd_get(): new channel = %s", channel_name); - g_free(scpi->actual_channel_name); - scpi->actual_channel_name = g_strdup(channel_name); + g_free(scpi->curr_channel_name); + scpi->curr_channel_name = g_strdup(channel_name); ret = scpi_send(scpi, channel_cmd, channel_name); if (ret != SR_OK) return ret; From 5abb5795c3c16694c1596e64ace71562f7013a13 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 8 Apr 2023 16:30:59 +0200 Subject: [PATCH 08/20] scpi: reduce indentation in continuation lines (function signatures) Whitespace change exclusively, no change in behaviour. Reduce the level of indentation of continuation lines in common SCPI code's header and source files. See a whitespace ignoring diff for review. A TAB is sufficient to represent a level of indentation. More than this, up to four of them, or additional SPACE after TAB is excessive. Drop it. --- src/scpi.h | 52 +++++++++++++++++++------------------- src/scpi/scpi.c | 66 ++++++++++++++++++++++++------------------------- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/scpi.h b/src/scpi.h index 117b756b2..c3127769b 100644 --- a/src/scpi.h +++ b/src/scpi.h @@ -117,21 +117,21 @@ struct sr_scpi_dev_inst { }; SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, - struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)); + struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)); SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, - const char *resource, const char *serialcomm); + const char *resource, const char *serialcomm); SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi, - char **connection_id); + char **connection_id); SR_PRIV int sr_scpi_source_add(struct sr_session *session, - struct sr_scpi_dev_inst *scpi, int events, int timeout, - sr_receive_data_callback cb, void *cb_data); + struct sr_scpi_dev_inst *scpi, int events, int timeout, + sr_receive_data_callback cb, void *cb_data); SR_PRIV int sr_scpi_source_remove(struct sr_session *session, - struct sr_scpi_dev_inst *scpi); + struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, - const char *format, ...); + const char *format, ...); SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi, - const char *format, va_list args); + const char *format, va_list args); SR_PRIV int sr_scpi_read_begin(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen); SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, char *buf, int len); @@ -140,43 +140,43 @@ SR_PRIV int sr_scpi_close(struct sr_scpi_dev_inst *scpi); SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi, - GString *response, gint64 abs_timeout_us); + GString *response, gint64 abs_timeout_us); SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, - const char *command, char **scpi_response); + const char *command, char **scpi_response); SR_PRIV int sr_scpi_get_bool(struct sr_scpi_dev_inst *scpi, - const char *command, gboolean *scpi_response); + const char *command, gboolean *scpi_response); SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, - const char *command, int *scpi_response); + const char *command, int *scpi_response); SR_PRIV int sr_scpi_get_float(struct sr_scpi_dev_inst *scpi, - const char *command, float *scpi_response); + const char *command, float *scpi_response); SR_PRIV int sr_scpi_get_double(struct sr_scpi_dev_inst *scpi, - const char *command, double *scpi_response); + const char *command, double *scpi_response); SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response); + const char *command, GArray **scpi_response); SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response); + const char *command, GArray **scpi_response); SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, - const char *command, GString **scpi_response); + const char *command, GString **scpi_response); SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, - const char *command, GByteArray **scpi_response); + const char *command, GByteArray **scpi_response); SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, - struct sr_scpi_hw_info **scpi_response); + struct sr_scpi_hw_info **scpi_response); SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info); SR_PRIV const char *sr_scpi_unquote_string(char *s); SR_PRIV const char *sr_vendor_alias(const char *raw_vendor); SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, - int command); + int command); SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - int command, ...); + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + int command, ...); SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - GVariant **gvar, const GVariantType *gvtype, int command, ...); + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + GVariant **gvar, const GVariantType *gvtype, int command, ...); /*--- GPIB only functions ---------------------------------------------------*/ diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 90c584a0a..06dd85ed5 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -107,8 +107,8 @@ static const struct sr_scpi_dev_inst *scpi_devs[] = { }; static struct sr_dev_inst *sr_scpi_scan_resource(struct drv_context *drvc, - const char *resource, const char *serialcomm, - struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) + const char *resource, const char *serialcomm, + struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) { struct sr_scpi_dev_inst *scpi; struct sr_dev_inst *sdi; @@ -144,7 +144,7 @@ static struct sr_dev_inst *sr_scpi_scan_resource(struct drv_context *drvc, * @return SR_OK on success, SR_ERR on failure. */ static int scpi_send_variadic(struct sr_scpi_dev_inst *scpi, - const char *format, va_list args) + const char *format, va_list args) { va_list args_copy; char *buf; @@ -232,7 +232,7 @@ static int scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen) * @return read length on success, SR_ERR* on failure. */ static int scpi_read_response(struct sr_scpi_dev_inst *scpi, - GString *response, gint64 abs_timeout_us) + GString *response, gint64 abs_timeout_us) { int len, space; @@ -268,7 +268,7 @@ static int scpi_read_response(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR on failure. */ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, - const char *command, GString **scpi_response) + const char *command, GString **scpi_response) { int ret; GString *response; @@ -312,7 +312,7 @@ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, } SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, - struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) + struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) { GSList *resources, *l, *devices; struct sr_dev_inst *sdi; @@ -364,7 +364,7 @@ SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, } SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, - const char *resource, const char *serialcomm) + const char *resource, const char *serialcomm) { struct sr_scpi_dev_inst *scpi = NULL; const struct sr_scpi_dev_inst *scpi_dev; @@ -419,7 +419,7 @@ SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi) * @return SR_OK on success, SR_ERR on failure. */ SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi, - char **connection_id) + char **connection_id) { return scpi->connection_id(scpi, connection_id); } @@ -438,8 +438,8 @@ SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi, * SR_ERR_MALLOC upon memory allocation errors. */ SR_PRIV int sr_scpi_source_add(struct sr_session *session, - struct sr_scpi_dev_inst *scpi, int events, int timeout, - sr_receive_data_callback cb, void *cb_data) + struct sr_scpi_dev_inst *scpi, int events, int timeout, + sr_receive_data_callback cb, void *cb_data) { return scpi->source_add(session, scpi->priv, events, timeout, cb, cb_data); } @@ -455,7 +455,7 @@ SR_PRIV int sr_scpi_source_add(struct sr_session *session, * internal errors. */ SR_PRIV int sr_scpi_source_remove(struct sr_session *session, - struct sr_scpi_dev_inst *scpi) + struct sr_scpi_dev_inst *scpi) { return scpi->source_remove(session, scpi->priv); } @@ -469,7 +469,7 @@ SR_PRIV int sr_scpi_source_remove(struct sr_session *session, * @return SR_OK on success, SR_ERR on failure. */ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, - const char *format, ...) + const char *format, ...) { va_list args; int ret; @@ -493,7 +493,7 @@ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR on failure. */ SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi, - const char *format, va_list args) + const char *format, va_list args) { int ret; @@ -526,7 +526,7 @@ SR_PRIV int sr_scpi_read_begin(struct sr_scpi_dev_inst *scpi) * @return Number of bytes read, or SR_ERR upon failure. */ SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, - char *buf, int maxlen) + char *buf, int maxlen) { int ret; @@ -550,7 +550,7 @@ SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, * @return Number of bytes read, or SR_ERR upon failure. */ SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, - char *buf, int maxlen) + char *buf, int maxlen) { int ret; @@ -622,7 +622,7 @@ SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi) * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, - const char *command, char **scpi_response) + const char *command, char **scpi_response) { GString *response; @@ -662,7 +662,7 @@ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, * @return read length on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi, - GString *response, gint64 abs_timeout_us) + GString *response, gint64 abs_timeout_us) { int ret; @@ -674,7 +674,7 @@ SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi, } SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, - const char *command, GString **scpi_response) + const char *command, GString **scpi_response) { int ret; @@ -696,7 +696,7 @@ SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_bool(struct sr_scpi_dev_inst *scpi, - const char *command, gboolean *scpi_response) + const char *command, gboolean *scpi_response) { int ret; char *response; @@ -728,7 +728,7 @@ SR_PRIV int sr_scpi_get_bool(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, - const char *command, int *scpi_response) + const char *command, int *scpi_response) { int ret; struct sr_rational ret_rational; @@ -765,7 +765,7 @@ SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_float(struct sr_scpi_dev_inst *scpi, - const char *command, float *scpi_response) + const char *command, float *scpi_response) { int ret; char *response; @@ -797,7 +797,7 @@ SR_PRIV int sr_scpi_get_float(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_double(struct sr_scpi_dev_inst *scpi, - const char *command, double *scpi_response) + const char *command, double *scpi_response) { int ret; char *response; @@ -857,7 +857,7 @@ SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi) * error or upon no response. */ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response) + const char *command, GArray **scpi_response) { int ret; float tmp; @@ -917,7 +917,7 @@ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, * error or upon no response. */ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response) + const char *command, GArray **scpi_response) { int tmp, ret; char *response; @@ -976,7 +976,7 @@ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, * error or upon no response. */ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, - const char *command, GByteArray **scpi_response) + const char *command, GByteArray **scpi_response) { int ret; GString* response; @@ -1137,7 +1137,7 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, * @return SR_OK upon success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, - struct sr_scpi_hw_info **scpi_response) + struct sr_scpi_hw_info **scpi_response) { int num_tokens, ret; char *response; @@ -1274,7 +1274,7 @@ SR_PRIV const char *sr_vendor_alias(const char *raw_vendor) } SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, - int command) + int command) { unsigned int i; const char *cmd; @@ -1294,9 +1294,9 @@ SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, } SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - int command, ...) + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + int command, ...) { struct sr_scpi_dev_inst *scpi; va_list args; @@ -1335,9 +1335,9 @@ SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, } SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - GVariant **gvar, const GVariantType *gvtype, int command, ...) + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + GVariant **gvar, const GVariantType *gvtype, int command, ...) { struct sr_scpi_dev_inst *scpi; va_list args; From d5139236d65d14575b9a6ed48dbc9eb668fcaae7 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 9 Apr 2023 14:10:39 +0200 Subject: [PATCH 09/20] scpi: rephrase *OPC? checker routine, stricter success check Rename retry count and delay parameters which exclusively were used in the "*OPC?" result getter. Prefer while-not-done over counted for loop. Only return success when SCPI communication was successful and the value is true-ish (communication was not checked explicitly before, instead relied on internal implementation details of the bool getter). --- src/scpi/scpi.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 06dd85ed5..7cb97ab56 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -27,8 +27,8 @@ #define LOG_PREFIX "scpi" -#define SCPI_READ_RETRIES 100 -#define SCPI_READ_RETRY_TIMEOUT_US (10 * 1000) +#define SCPI_OPC_RETRY_COUNT 100 +#define SCPI_OPC_RETRY_DELAY_US (10 * 1000) static const char *scpi_vendors[][2] = { { "Agilent Technologies", "Agilent" }, @@ -828,15 +828,18 @@ SR_PRIV int sr_scpi_get_double(struct sr_scpi_dev_inst *scpi, */ SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi) { - unsigned int i; + unsigned int retries; gboolean opc; + int ret; - for (i = 0; i < SCPI_READ_RETRIES; i++) { + retries = SCPI_OPC_RETRY_COUNT; + while (retries--) { opc = FALSE; - sr_scpi_get_bool(scpi, SCPI_CMD_OPC, &opc); - if (opc) + ret = sr_scpi_get_bool(scpi, SCPI_CMD_OPC, &opc); + if (ret == SR_OK && opc) return SR_OK; - g_usleep(SCPI_READ_RETRY_TIMEOUT_US); + if (retries) + g_usleep(SCPI_OPC_RETRY_DELAY_US); } return SR_ERR; From 8afe9e3109d4594f00e93e406bd132d452348ff2 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 9 Apr 2023 19:51:28 +0200 Subject: [PATCH 10/20] scpi: rephrase device instance creation (resource match, error path) Rephrase the scpi_dev_inst_new() routine for code style and robustness. Don't hide assignments in declaration blocks. Unobfuscate string compare and reduce indentation in the prefix match loop. Don't mix function call and result check and flow control in a single statement. Don't access uninitialized data in error paths (resource release call). --- src/scpi/scpi.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 7cb97ab56..549cc7f0d 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -366,31 +366,33 @@ SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, const char *resource, const char *serialcomm) { - struct sr_scpi_dev_inst *scpi = NULL; + size_t i; const struct sr_scpi_dev_inst *scpi_dev; + struct sr_scpi_dev_inst *scpi; gchar **params; - unsigned i; + int ret; for (i = 0; i < ARRAY_SIZE(scpi_devs); i++) { scpi_dev = scpi_devs[i]; - if (!strncmp(resource, scpi_dev->prefix, strlen(scpi_dev->prefix))) { - sr_dbg("Opening %s device %s.", scpi_dev->name, resource); - scpi = g_malloc(sizeof(*scpi)); - *scpi = *scpi_dev; - scpi->priv = g_malloc0(scpi->priv_size); - scpi->read_timeout_us = 1000 * 1000; - params = g_strsplit(resource, "/", 0); - if (scpi->dev_inst_new(scpi->priv, drvc, resource, - params, serialcomm) != SR_OK) { - sr_scpi_free(scpi); - scpi = NULL; - } - g_strfreev(params); - break; + if (strncmp(resource, scpi_dev->prefix, strlen(scpi_dev->prefix)) != 0) + continue; + sr_dbg("Opening %s device %s.", scpi_dev->name, resource); + scpi = g_malloc0(sizeof(*scpi)); + *scpi = *scpi_dev; + scpi->priv = g_malloc0(scpi->priv_size); + scpi->read_timeout_us = 1000 * 1000; + params = g_strsplit(resource, "/", 0); + ret = scpi->dev_inst_new(scpi->priv, drvc, + resource, params, serialcomm); + g_strfreev(params); + if (ret != SR_OK) { + sr_scpi_free(scpi); + scpi = NULL; } + return scpi; } - return scpi; + return NULL; } /** From c7da8bf46d8b1a2fdca87cceb148b00f235f25cf Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 9 Apr 2023 12:41:40 +0200 Subject: [PATCH 11/20] scpi: add optional pause between reads when no receive data is seen Avoid tight read loops when a SCPI device is busy creating the very response that the host is trying to receive. Add a new 'read_pause_us' member next to the existing 'read_timeout_us'. Default to zero for maximum backwards compatibility. Pause between read calls when an attempt sees zero receive data. Adjust the internal scpi_get_data() and the public sr_scpi_get_block() routines, which other SCPI routines are built upon. --- src/scpi.h | 9 ++++++++- src/scpi/scpi.c | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/scpi.h b/src/scpi.h index c3127769b..0d57aad51 100644 --- a/src/scpi.h +++ b/src/scpi.h @@ -107,7 +107,14 @@ struct sr_scpi_dev_inst { int (*read_complete)(void *priv); int (*close)(struct sr_scpi_dev_inst *scpi); void (*free)(void *priv); - unsigned int read_timeout_us; + /* + * Read times out when 'read_timeout_us' passes while no data + * at all became available. Read is paused for 'read_pause_us' + * when a read attempt got zero receive data (avoid tight loops + * when devices are busy creating the response). + */ + int64_t read_timeout_us; + int64_t read_pause_us; void *priv; /* Only used for quirk workarounds, notably the Rigol DS1000 series. */ uint64_t firmware_version; diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 549cc7f0d..01a8ec095 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -306,6 +306,8 @@ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, return ret; if (ret > 0) timeout = g_get_monotonic_time() + scpi->read_timeout_us; + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); } return SR_OK; @@ -381,6 +383,7 @@ SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, *scpi = *scpi_dev; scpi->priv = g_malloc0(scpi->priv_size); scpi->read_timeout_us = 1000 * 1000; + scpi->read_pause_us = 0; params = g_strsplit(resource, "/", 0); ret = scpi->dev_inst_new(scpi->priv, drvc, resource, params, serialcomm); @@ -1022,6 +1025,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_string_free(response, TRUE); return ret; } + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); } while (response->len < 2); /* @@ -1078,6 +1083,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_string_free(response, TRUE); return ret; } + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); } memcpy(buf, &response->str[2], llen); @@ -1115,6 +1122,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_string_free(response, TRUE); return ret; } + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); if (ret > 0) timeout = g_get_monotonic_time() + scpi->read_timeout_us; } while (response->len < (unsigned long)(datalen)); From 95c02a0c6a50e00b1c99ff47aa5266b309a093c9 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 9 Apr 2023 12:50:14 +0200 Subject: [PATCH 12/20] scpi: minor nits in common text response read routine Adjust the internal scpi_get_data() routine which collects a SCPI response message. Add in/out decorations to Doxygen comments. Group instructions that execute a logical sequence which belongs together (eliminate clutter). Use size types for string buffer lengths. --- src/scpi/scpi.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 01a8ec095..ea05b71a8 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -261,9 +261,9 @@ static int scpi_read_response(struct sr_scpi_dev_inst *scpi, * Send a SCPI command, receive the reply and store the reply in * scpi_response, without mutex. * - * @param scpi Previously initialised SCPI device structure. - * @param command The SCPI command to send to the device. - * @param scpi_response Pointer where to store the SCPI response. + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device. + * @param[out] scpi_response Pointer where to store the SCPI response. * * @return SR_OK on success, SR_ERR on failure. */ @@ -272,7 +272,7 @@ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, { int ret; GString *response; - int space; + size_t space; gint64 timeout; /* Optionally send caller provided command. */ @@ -286,22 +286,19 @@ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, return SR_ERR; /* Keep reading until completion or until timeout. */ - timeout = g_get_monotonic_time() + scpi->read_timeout_us; - response = *scpi_response; - + timeout = g_get_monotonic_time() + scpi->read_timeout_us; while (!sr_scpi_read_complete(scpi)) { /* Resize the buffer when free space drops below a threshold. */ space = response->allocated_len - response->len; if (space < 128) { - int oldlen = response->len; + size_t oldlen = response->len; g_string_set_size(response, oldlen + 1024); g_string_set_size(response, oldlen); } /* Read another chunk of the response. */ ret = scpi_read_response(scpi, response, timeout); - if (ret < 0) return ret; if (ret > 0) From 9e507aa5350db760fcdbf2a22ced9a0265f9dac5 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 9 Apr 2023 13:06:01 +0200 Subject: [PATCH 13/20] scpi: separate helper to make free space in receive buffer Move code which keeps growing a string buffer before accumulating more receive data out of the scpi_read_data() routine and into a separate helper. The block reader wants to share this logic. --- src/scpi/scpi.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index ea05b71a8..968695bbe 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -221,6 +221,29 @@ static int scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen) return scpi->read_data(scpi->priv, buf, maxlen); } +/** + * Make sure a string buffer contains free space, chunked resize. + * + * Allocates more space ('add_space' in addition to existing content) + * when free buffer space is below 'threshold'. + * + * @param[in] buf The buffer where free space is desired. + * @param[in] threshold Minimum amount of free space accepted. + * @param[in] add_space Chunk to ensure when buffer gets extended. + */ +static void scpi_make_string_space(GString *buf, + size_t threshold, size_t add_space) +{ + size_t have_space, prev_size; + + have_space = buf->allocated_len - buf->len; + if (have_space < threshold) { + prev_size = buf->len; + g_string_set_size(buf, prev_size + add_space); + g_string_set_size(buf, prev_size); + } +} + /** * Do a non-blocking read of up to the allocated length, and * check if a timeout has occured, without mutex. @@ -272,7 +295,6 @@ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, { int ret; GString *response; - size_t space; gint64 timeout; /* Optionally send caller provided command. */ @@ -290,12 +312,7 @@ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, timeout = g_get_monotonic_time() + scpi->read_timeout_us; while (!sr_scpi_read_complete(scpi)) { /* Resize the buffer when free space drops below a threshold. */ - space = response->allocated_len - response->len; - if (space < 128) { - size_t oldlen = response->len; - g_string_set_size(response, oldlen + 1024); - g_string_set_size(response, oldlen); - } + scpi_make_string_space(response, 128, 1024); /* Read another chunk of the response. */ ret = scpi_read_response(scpi, response, timeout); From 92b4959253718957001d5d0cbc8b65ad346677dc Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 9 Apr 2023 12:11:37 +0200 Subject: [PATCH 14/20] scpi_tcp: don't put colon into connection id, breaks conn re-use SCPI transports construct connection ID strings for display purposes. The SCPI over TCP implementation did present the essential details (IP address, TCP port), but yielded an output format which does not work as an SR_CONF_CONN value. The colon separator even breaks CLI option parsing. Separate IP address and TCP port by means of a slash. Generate a connection ID text which matches all other transports, and also works for the creation of new device instances. --- src/scpi/scpi_tcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scpi/scpi_tcp.c b/src/scpi/scpi_tcp.c index 29e7a4394..baef50e0d 100644 --- a/src/scpi/scpi_tcp.c +++ b/src/scpi/scpi_tcp.c @@ -120,7 +120,7 @@ static int scpi_tcp_connection_id(struct sr_scpi_dev_inst *scpi, { struct scpi_tcp *tcp = scpi->priv; - *connection_id = g_strdup_printf("%s/%s:%s", + *connection_id = g_strdup_printf("%s/%s/%s", scpi->prefix, tcp->address, tcp->port); return SR_OK; From c7bd0ad290aa6afa909330d84325c47a2135d513 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 8 Apr 2023 18:09:45 +0200 Subject: [PATCH 15/20] scpi: add "block ends" transport method, needed for SCPI over serial The SCPI protocol usually communicates requests (commands, queries) and responses in ASCII format. But also supports the concept of data blocks which carry raw byte sequences. Checking for CR or LF characters in response text does not combine well with transmission of raw bytes. Introduce a "block ends" method for SCPI transports. Common code is aware when a data block is received, and where it ends. That's when transports can re-consider their end-of-message condition for the remaining reception of that very response. The transport method sees all previously accumulated receive data that follows the data block. Implement that "block ends" method in the serial transport, which motivated the introduction of the method. Void falsely detected "is complete" conditions that originate from seeing CR or LF in byte data. Other transports derive their respective end-of-message condition from other information which is not affected (EOI signals, VISA status bits, TCP with length specs in headers before data, USB transfer flags, etc). --- src/scpi.h | 3 +++ src/scpi/scpi.c | 23 +++++++++++++++++++++++ src/scpi/scpi_serial.c | 14 ++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/scpi.h b/src/scpi.h index 0d57aad51..64f9c5dcf 100644 --- a/src/scpi.h +++ b/src/scpi.h @@ -104,6 +104,7 @@ struct sr_scpi_dev_inst { int (*read_begin)(void *priv); int (*read_data)(void *priv, char *buf, int maxlen); int (*write_data)(void *priv, char *buf, int len); + int (*block_ends)(void *priv, char *buf, size_t len); int (*read_complete)(void *priv); int (*close)(struct sr_scpi_dev_inst *scpi); void (*free)(void *priv); @@ -142,6 +143,8 @@ SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi, SR_PRIV int sr_scpi_read_begin(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen); SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, char *buf, int len); +SR_PRIV int sr_scpi_block_ends(struct sr_scpi_dev_inst *scpi, + char *buf, size_t len); SR_PRIV int sr_scpi_read_complete(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_close(struct sr_scpi_dev_inst *scpi); SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi); diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 968695bbe..d4cb40abd 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -580,6 +580,29 @@ SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, return ret; } +/** + * Re-check whether remaining payload data contains "end of message". + * + * This executes after a binary block has completed, and is passed + * the remaining previously accumulated receive data. It is only + * important to those physical transports where "end of message" is + * communicated by means of payload data, not in handshake signals + * that are outside of payload bytes. + * + * @param scpi Previously initialized SCPI device structure. + * @param buf Buffer position after binary block. + * @param len Number of bytes that were received after the block. + * + * @return zero upon success, non-zero upon failure. + */ +SR_PRIV int sr_scpi_block_ends(struct sr_scpi_dev_inst *scpi, + char *buf, size_t len) +{ + if (scpi->block_ends) + return scpi->block_ends(scpi->priv, buf, len); + return SR_OK; +} + /** * Check whether a complete SCPI response has been received. * diff --git a/src/scpi/scpi_serial.c b/src/scpi/scpi_serial.c index 067838adc..6bfcea84f 100644 --- a/src/scpi/scpi_serial.c +++ b/src/scpi/scpi_serial.c @@ -203,6 +203,19 @@ static int scpi_serial_read_data(void *priv, char *buf, int maxlen) return ret; } +static int scpi_serial_block_ends(void *priv, char *buf, size_t remlen) +{ + struct scpi_serial *sscpi; + + sscpi = priv; + + sscpi->got_newline = FALSE; + while (remlen--) + sscpi->got_newline |= *buf++ == '\n'; + + return SR_OK; +} + static int scpi_serial_read_complete(void *priv) { struct scpi_serial *sscpi = priv; @@ -238,6 +251,7 @@ SR_PRIV const struct sr_scpi_dev_inst scpi_serial_dev = { .send = scpi_serial_send, .read_begin = scpi_serial_read_begin, .read_data = scpi_serial_read_data, + .block_ends = scpi_serial_block_ends, .read_complete = scpi_serial_read_complete, .close = scpi_serial_close, .free = scpi_serial_free, From b313131e8473ade06d1b98ce5784b611bf5300e8 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 8 Apr 2023 18:49:39 +0200 Subject: [PATCH 16/20] scpi: comment and style nits in common data block getter Fix typos in comments, and update comments which went stale in earlier commits. Mention that "indefinite length block" is not supported in the routine's Doxygen comment for callers' awareness. Address tiny style nits while we are here: Asterisk placement in pointer declarations. Braces around complex multi-line statements. Drop empty lines in groups of statements that belong together. Un-clutter their comments, discuss the whole group in one text. --- src/scpi/scpi.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index d4cb40abd..90be16b40 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -1008,11 +1008,15 @@ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, /** * Send a SCPI command, read the reply, parse it as binary data with a - * "definite length block" header and store the as an result in scpi_response. + * "definite length block" header and store the result in scpi_response. * * Callers must free the allocated memory (unless it's NULL) regardless of * the routine's return code. See @ref g_byte_array_free(). * + * The "indefinite length block" is not supported by this implementation. + * Because not all supported physical transports signal the end-of-message + * condition out of band. + * * @param[in] scpi Previously initialised SCPI device structure. * @param[in] command The SCPI command to send to the device (can be NULL). * @param[out] scpi_response Pointer where to store the parsed result. @@ -1024,7 +1028,7 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, const char *command, GByteArray **scpi_response) { int ret; - GString* response; + GString *response; gsize oldlen; char buf[10]; long llen; @@ -1035,11 +1039,12 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_mutex_lock(&scpi->scpi_mutex); - if (command) + if (command) { if (scpi_send(scpi, command) != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); return SR_ERR; } + } if (sr_scpi_read_begin(scpi) != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); @@ -1047,14 +1052,13 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, } /* - * Assume an initial maximum length, optionally gets adjusted below. - * Prepare a NULL return value for when error paths will be taken. + * Get (the first chunk of) the response. + * + * Start with an arbitrary initial buffer size. Which gets + * extended as subsequent reads require more buffer space. */ response = g_string_sized_new(1024); - timeout = g_get_monotonic_time() + scpi->read_timeout_us; - - /* Get (the first chunk of) the response. */ do { ret = scpi_read_response(scpi, response, timeout); if (ret < 0) { @@ -1071,8 +1075,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, * The length spec consists of a '#' marker, one digit which * specifies the character count of the length spec, and the * respective number of characters which specify the data block's - * length. Raw data bytes follow (thus one must no longer assume - * that the received input stream would be an ASCIIZ string). + * length. Raw data bytes follow, thus one must no longer assume + * that the received input stream would be an ASCII string. * * Get the data block length, and strip off the length spec from * the input buffer, leaving just the data bytes. From 2089d679c602b1f8755f4e2c15a040dd30aa6477 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 8 Apr 2023 19:07:38 +0200 Subject: [PATCH 17/20] scpi: rephrase common "get data block" routine Rephrase the sr_scpi_get_block() routine to increase readability and robustness. Address data type issues (signedness). Rename variables to better reflect their purpose. Concentrate more comments before larger instruction sequences. Mention instructions' purpose first before more detailled discussion, to speedup reader's navigation in the complex logic. Reword the discussion of definite and indefinite length blocks. Add more diagnostics. Although the text diff is huge, the spirit of the previous implementation is kept. Rephrase loops when the motivation for their phrasing was uncertain: Replace "if (cond) do { ... } while (cond)" with just "while (cond)". Which reduces nesting and indentation. Use "while" instead of "do while" when it's obvious that the body does initially execute. It's believed that behaviour remains identical. This commit also changes behaviour: Re-arm timeouts more often after receive data was seen. Which helps slow transports, yet does not harm fast transports which already accumulated the input data for the next stage of processing. Check for zero length in more locations (accept phrases like "#10" as well). There is excessive diagnostics. Needs more adjustment (wording, levels). --- src/scpi/scpi.c | 158 ++++++++++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 66 deletions(-) diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 90be16b40..4776985ee 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -1029,24 +1029,30 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, { int ret; GString *response; - gsize oldlen; + size_t prev_size; char buf[10]; - long llen; - long datalen; + unsigned long ul_value; + size_t digits_count, bytes_count; gint64 timeout; *scpi_response = NULL; g_mutex_lock(&scpi->scpi_mutex); + /* Optionally send a caller provided command. */ if (command) { - if (scpi_send(scpi, command) != SR_OK) { + ret = scpi_send(scpi, command); + sr_dbg("SCPI get block, sent command, ret %d.", ret); + if (ret != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); return SR_ERR; } } - if (sr_scpi_read_begin(scpi) != SR_OK) { + /* Start reading a reponse message. */ + ret = sr_scpi_read_begin(scpi); + sr_dbg("SCPI get block, read begin, ret %d.", ret); + if (ret != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); return SR_ERR; } @@ -1059,8 +1065,9 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, */ response = g_string_sized_new(1024); timeout = g_get_monotonic_time() + scpi->read_timeout_us; - do { + while (response->len < 2) { ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, initial read, ret %d.", ret); if (ret < 0) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); @@ -1068,18 +1075,43 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, } if (ret == 0 && scpi->read_pause_us) g_usleep(scpi->read_pause_us); - } while (response->len < 2); + } /* + * Get the data block length, then strip off that length detail + * from the input buffer, leaving just the data bytes which were + * received so far. Then read more bytes when necessary. + * * SCPI protocol data blocks are preceeded with a length spec. * The length spec consists of a '#' marker, one digit which * specifies the character count of the length spec, and the * respective number of characters which specify the data block's * length. Raw data bytes follow, thus one must no longer assume - * that the received input stream would be an ASCII string. + * that the received input stream would be an ASCII string, or + * that CR or LF characters had a meaning within that block which + * corresponds to message termination. Example: + * #210abcdefghij + * + * Zero length is legal but unsupported in this implementation. + * It is referred to as "indefinite length block", the number of + * bytes is unknown or is not communicated in advance. The end + * of the bytes sequence depends on the physical transport's + * end-of-message condition. Which is only reliably available + * in those transports which communicate message length or end + * out of band, separate from the response text or raw bytes. + * The use of indefinite length blocks in firmware is considered + * rare. Whether or not a trailing LF character must be seen as + * part of the raw bytes sequence is yet to get determined. Emit + * a warning for users' and developers' awareness when we see + * this response format. Examples: + * #0bytes_of_unknown_length_with_uncertain_end_condition + * #10not_supported_either * - * Get the data block length, and strip off the length spec from - * the input buffer, leaving just the data bytes. + * The SCPI 1999.0 specification (see page 220 and following in + * the "HCOPy" description) references IEEE 488.2, especially + * section 8.7.9 for DEFINITE LENGTH and section 8.7.10 for + * INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE DATA. The latter + * with a leading "#0" length and a trailing "NL^END" marker. */ if (response->str[0] != '#') { g_mutex_unlock(&scpi->scpi_mutex); @@ -1088,26 +1120,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, } buf[0] = response->str[1]; buf[1] = '\0'; - ret = sr_atol(buf, &llen); - /* - * The form "#0..." is legal, and does not mean "empty response", - * but means that the number of data bytes is not known (or was - * not communicated) at this time. Instead the block ends at an - * "END MESSAGE" termination sequence. Which translates to active - * EOI while a text line termination is sent (CR or LF, and this - * text line termination is not part of the block's data value). - * Since this kind of #0... response is considered rare, and - * depends on specific support in physical transports underneath - * the SCPI layer, let's flag the condition and bail out with an - * error here, until it's found to be a genuine issue in the field. - * - * The SCPI 1999.0 specification (see page 220 and following in - * the "HCOPy" description) references IEEE 488.2, especially - * section 8.7.9 for DEFINITE LENGTH and section 8.7.10 for - * INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE DATA. The latter - * with a leading "#0" length and a trailing "NL^END" marker. - */ - if (ret == SR_OK && !llen) { + ret = sr_atoul_base(buf, &ul_value, NULL, 10); + if (ret == SR_OK && !ul_value) { sr_err("unsupported INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE"); ret = SR_ERR_NA; } @@ -1116,8 +1130,10 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_string_free(response, TRUE); return ret; } + digits_count = ul_value; - while (response->len < (unsigned long)(2 + llen)) { + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (response->len < 2 + digits_count) { ret = scpi_read_response(scpi, response, timeout); if (ret < 0) { g_mutex_unlock(&scpi->scpi_mutex); @@ -1127,54 +1143,64 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, if (ret == 0 && scpi->read_pause_us) g_usleep(scpi->read_pause_us); } - - memcpy(buf, &response->str[2], llen); - buf[llen] = '\0'; - ret = sr_atol(buf, &datalen); - if ((ret != SR_OK) || (datalen == 0)) { + memcpy(buf, &response->str[2], digits_count); + buf[digits_count] = '\0'; + ret = sr_atoul_base(buf, &ul_value, NULL, 10); + if (ret == SR_OK && !ul_value) { + sr_err("unsupported INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE"); + ret = SR_ERR_NA; + } + if (ret != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); return ret; } - g_string_erase(response, 0, 2 + llen); + bytes_count = ul_value; + + sr_spew("SCPI get block, text %*s, digits %zu, number %zu.", + (int)(2 + digits_count), response->str, + digits_count, bytes_count); + sr_dbg("SCPI get block, bytes count %zu.", bytes_count); + g_string_erase(response, 0, 2 + digits_count); /* - * Re-allocate the buffer size to the now known length - * and keep reading more chunks of response data. + * Re-allocate the buffer size to the now known bytes count. + * Keep reading more chunks of response data as necessary. + * + * Do not stall here for incomplete reads. Truncate the data + * and return the partial response upon timeouts (bug 1323). */ - oldlen = response->len; - g_string_set_size(response, datalen); - g_string_set_size(response, oldlen); - - if (oldlen < (unsigned long)(datalen)) { - do { - oldlen = response->len; - ret = scpi_read_response(scpi, response, timeout); - - /* On timeout truncate the buffer and send the partial response - * instead of getting stuck on timeouts... - */ - if (ret == SR_ERR_TIMEOUT) { - datalen = oldlen; - break; - } - if (ret < 0) { - g_mutex_unlock(&scpi->scpi_mutex); - g_string_free(response, TRUE); - return ret; - } - if (ret == 0 && scpi->read_pause_us) - g_usleep(scpi->read_pause_us); - if (ret > 0) - timeout = g_get_monotonic_time() + scpi->read_timeout_us; - } while (response->len < (unsigned long)(datalen)); + prev_size = response->len; + g_string_set_size(response, bytes_count); + g_string_set_size(response, prev_size); + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (response->len < bytes_count) { + prev_size = response->len; + ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, read block, ret %d.", ret); + if (ret == SR_ERR_TIMEOUT) { + sr_dbg("SCPI get block, timeout, had %zu, cap to %zu..", + response->len, prev_size); + bytes_count = prev_size; + break; + } + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); + if (ret > 0) + timeout = g_get_monotonic_time() + scpi->read_timeout_us; } g_mutex_unlock(&scpi->scpi_mutex); /* Convert received data to byte array. */ *scpi_response = g_byte_array_new_take( - (guint8*)g_string_free(response, FALSE), datalen); + (guint8 *)g_string_free(response, FALSE), bytes_count); + sr_dbg("SCPI get block, got block, return bytes %zu.", bytes_count); return SR_OK; } From 0c7d5e68c9b34d9ce64a390593d2c23c6bee98bf Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 8 Apr 2023 19:30:58 +0200 Subject: [PATCH 18/20] scpi: read end-of-message after data block reception The previous sr_scpi_get_block() implementation immediately returned after grabbing the data block. Which could have worked by coincidence when fast transports queue large chunks of receive data early, or could have gone unnoticed because there are few callers (hameg, lecroy only). Keep reading after the data block until the underlying transport finds the response message's end. Skipping this step could result in seeing "empty responses" for the next request, and synchronization loss for the remaining session when responses no longer correspond to callers' requests. Probability to reproduce depends on the type and speed of the involved transport, and how the device firmware chunks responses. --- src/scpi/scpi.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 4776985ee..8a7fef302 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -1034,6 +1034,7 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, unsigned long ul_value; size_t digits_count, bytes_count; gint64 timeout; + size_t trail_count; *scpi_response = NULL; @@ -1164,14 +1165,15 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_string_erase(response, 0, 2 + digits_count); /* - * Re-allocate the buffer size to the now known bytes count. + * Re-allocate the buffer size to the now known bytes count + * (and include some more space for response termination). * Keep reading more chunks of response data as necessary. * * Do not stall here for incomplete reads. Truncate the data * and return the partial response upon timeouts (bug 1323). */ prev_size = response->len; - g_string_set_size(response, bytes_count); + g_string_set_size(response, bytes_count + 16); g_string_set_size(response, prev_size); timeout = g_get_monotonic_time() + scpi->read_timeout_us; while (response->len < bytes_count) { @@ -1195,6 +1197,29 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, timeout = g_get_monotonic_time() + scpi->read_timeout_us; } + /* + * Depending on underlying physical transports the data block + * could be followed by more data which signals end-of-message. + * Keep reading until the transport detects the response's + * completion. Tell transports that binary mode has ended. + */ + trail_count = response->len - bytes_count; + sr_dbg("SCPI get block, block ends, trail length %zu.", trail_count); + sr_scpi_block_ends(scpi, &response->str[bytes_count], trail_count); + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (!sr_scpi_read_complete(scpi)) { + scpi_make_string_space(response, 4, 16); + ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, read end-of-message, ret %d.", ret); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); + } + g_mutex_unlock(&scpi->scpi_mutex); /* Convert received data to byte array. */ From 912a4718a80e1343d1af9a1f929838e56c52e4ad Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 9 Apr 2023 19:14:50 +0200 Subject: [PATCH 19/20] scpi: add "get text then block" reader (Tektronix TDS2000) SCPI responses are expected to either come in pure text format (ASCII), _or_ raw bytes (data blocks). Tektronix TDS2000 was observed to respond with the unusual combination of a data block that is preceeded(!) by variable length(!) text fields which are separated by semicolon. This is the consequence of the "WAVF?" request being an aggregate, sending several properties including the "CURVE" response in the same message. Introduce a rather specific "get (an optional) text then data block" reader routine, which uses caller provided logic to identify the position of the data block in the response. Tested with TDS 220. The implementation re-uses the previously existing "get block" routine and extends it to optionally get a leading text. While lots of more diagnostics are added. Two very thin public wrapper routines provide the "get block" and "get (text and) block" semantics. There is excessive diagnostics. Levels and wording needs adjustment. --- src/scpi.h | 19 ++++++ src/scpi/scpi.c | 177 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 163 insertions(+), 33 deletions(-) diff --git a/src/scpi.h b/src/scpi.h index 64f9c5dcf..69df59fd8 100644 --- a/src/scpi.h +++ b/src/scpi.h @@ -124,6 +124,21 @@ struct sr_scpi_dev_inst { gboolean no_opc_command; }; +/** + * Callback to find a data block after optional leading response text. + * + * @param[in] cb_data Caller provided context information. + * @param[in] rsp_data Previously accumulated response data. + * @param[in] rsp_dlen Length of accumulated response data. + * @param[out] block_off Position of block start in response. + * + * @returns Non-zero positive when more input data is required. + * SR_OK when the block start position was determined. + * SR_ERR* when fatal errors were seen. + */ +typedef int (*sr_scpi_block_find_cb)(void *cb_data, + const char *rsp_data, size_t rsp_dlen, size_t *block_off); + SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)); SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, @@ -170,6 +185,10 @@ SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, const char *command, GString **scpi_response); SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, const char *command, GByteArray **scpi_response); +SR_PRIV int sr_scpi_get_text_then_block(struct sr_scpi_dev_inst *scpi, + const char *command, + sr_scpi_block_find_cb cb_func, void *cb_data, + GString **scpi_text_response, GByteArray **scpi_block_response); SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, struct sr_scpi_hw_info **scpi_response); SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info); diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 8a7fef302..a3a109460 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -1006,37 +1006,27 @@ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, return ret; } -/** - * Send a SCPI command, read the reply, parse it as binary data with a - * "definite length block" header and store the result in scpi_response. - * - * Callers must free the allocated memory (unless it's NULL) regardless of - * the routine's return code. See @ref g_byte_array_free(). - * - * The "indefinite length block" is not supported by this implementation. - * Because not all supported physical transports signal the end-of-message - * condition out of band. - * - * @param[in] scpi Previously initialised SCPI device structure. - * @param[in] command The SCPI command to send to the device (can be NULL). - * @param[out] scpi_response Pointer where to store the parsed result. - * - * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing - * error or upon no response. - */ -SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, - const char *command, GByteArray **scpi_response) +/* Internal, serves both sr_scpi_get_block(), sr_scpi_get_text_then_block(). */ +static int scpi_get_block_int(struct sr_scpi_dev_inst *scpi, + const char *command, + sr_scpi_block_find_cb cb_func, void *cb_data, + GString **text_response, GByteArray **block_response) { int ret; GString *response; size_t prev_size; + size_t blk_off; char buf[10]; unsigned long ul_value; size_t digits_count, bytes_count; gint64 timeout; size_t trail_count; + uint8_t *blk_bytes; - *scpi_response = NULL; + if (text_response) + *text_response = NULL; + if (block_response) + *block_response = NULL; g_mutex_lock(&scpi->scpi_mutex); @@ -1059,16 +1049,68 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, } /* - * Get (the first chunk of) the response. - * - * Start with an arbitrary initial buffer size. Which gets - * extended as subsequent reads require more buffer space. + * Get an optional leading text before the data block when the + * caller provided routine can determine the boundary between + * the text and the block parts of the response. Absence of a + * boundary finder assumes that the response is just a block. */ response = g_string_sized_new(1024); timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (cb_func) { + /* Extend buffer space in sensible chunks. */ + scpi_make_string_space(response, 128, 1024); + /* Accumulate more receive data. */ + ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, text read, ret %d, len %zu.", + ret, response->len); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret == 0) { + if (scpi->read_pause_us) + g_usleep(scpi->read_pause_us); + continue; + } + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + /* + * Run caller's routine to find the start of the block. + * Negative return is a fatal error. Zero return found + * the offset. Non-zero positive return needs more data. + */ + blk_off = 0; + ret = cb_func(cb_data, response->str, response->len, &blk_off); + sr_dbg("SCPI get block, text check, ret %d, off %zu.", + ret, blk_off); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret > 0) + continue; + /* Block boundary detected. Grab text before the block. */ + if (blk_off > response->len) + blk_off = response->len; + if (text_response) + *text_response = g_string_new_len(response->str, blk_off); + g_string_erase(response, 0, blk_off); + break; + } + + /* + * Get (the first chunk of) the block part of the response. + * + * When we get here, there either is no text before the block, + * or the text before the block was already taken above. Either + * way, remaining code exclusively deals with the data block. + * Get enough receive data to determine the block's length. + */ while (response->len < 2) { ret = scpi_read_response(scpi, response, timeout); - sr_dbg("SCPI get block, initial read, ret %d.", ret); + sr_dbg("SCPI get block, block start read, ret %d, len %zu.", + ret, response->len); if (ret < 0) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); @@ -1136,6 +1178,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, timeout = g_get_monotonic_time() + scpi->read_timeout_us; while (response->len < 2 + digits_count) { ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, block len read, ret %d, len %zu.", + ret, response->len); if (ret < 0) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); @@ -1179,7 +1223,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, while (response->len < bytes_count) { prev_size = response->len; ret = scpi_read_response(scpi, response, timeout); - sr_dbg("SCPI get block, read block, ret %d.", ret); + sr_dbg("SCPI get block, read block, ret %d, len %zu.", + ret, response->len); if (ret == SR_ERR_TIMEOUT) { sr_dbg("SCPI get block, timeout, had %zu, cap to %zu..", response->len, prev_size); @@ -1191,10 +1236,12 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_string_free(response, TRUE); return ret; } - if (ret == 0 && scpi->read_pause_us) - g_usleep(scpi->read_pause_us); - if (ret > 0) - timeout = g_get_monotonic_time() + scpi->read_timeout_us; + if (ret == 0) { + if (scpi->read_pause_us) + g_usleep(scpi->read_pause_us); + continue; + } + timeout = g_get_monotonic_time() + scpi->read_timeout_us; } /* @@ -1223,13 +1270,77 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_mutex_unlock(&scpi->scpi_mutex); /* Convert received data to byte array. */ - *scpi_response = g_byte_array_new_take( - (guint8 *)g_string_free(response, FALSE), bytes_count); sr_dbg("SCPI get block, got block, return bytes %zu.", bytes_count); + if (block_response) { + blk_bytes = (uint8_t *)g_string_free(response, FALSE); + *block_response = g_byte_array_new_take(blk_bytes, bytes_count); + } else { + g_string_free(response, TRUE); + } return SR_OK; } +/** + * Send a SCPI command, read the reply, parse it as binary data with a + * "definite length block" header, while optional text can precede the + * binary block. + * + * Callers must provide a routine which determines the position in the + * response message where the block starts. In the absence of a block + * finding routine the response is assumed to be a block only without + * leading text. Common SCPI support code will keep reading until the + * caller's routine either finds the start of the block, or communicates + * an error condition. Callers must not modify the input text during + * block search, the routine can get invoked arbitrary number of times. + * + * Callers must free the allocated memory (unless it's #NULL) regardless of + * the routine's exit code. See @ref g_string_free(), @ref g_byte_array_free(). + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[in] cb_func Caller provided routine to find the start of the block. + * @param[in] cb_data Context for caller provided callback routine. + * @param[out] text_response Pointer where to store the text result. + * @param[out] block_response Pointer where to store the bytes result. + * + * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing + * error or upon no response. + */ +SR_PRIV int sr_scpi_get_text_then_block(struct sr_scpi_dev_inst *scpi, + const char *command, + sr_scpi_block_find_cb cb_func, void *cb_data, + GString **text_response, GByteArray **block_response) +{ + return scpi_get_block_int(scpi, command, cb_func, cb_data, + text_response, block_response); +} + +/** + * Send a SCPI command, read the reply, parse it as binary data with a + * "definite length block" header and store the result in scpi_response. + * + * Callers must free the allocated memory (unless it's NULL) regardless of + * the routine's return code. See @ref g_byte_array_free(). + * + * The "indefinite length block" is not supported by this implementation. + * Because not all supported physical transports signal the end-of-message + * condition out of band. + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[out] scpi_response Pointer where to store the parsed result. + * + * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing + * error or upon no response. + */ +SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, + const char *command, GByteArray **scpi_response) +{ + return scpi_get_block_int(scpi, command, NULL, NULL, + NULL, scpi_response); +} + /** * Send the *IDN? SCPI command, receive the reply, parse it and store the * reply as a sr_scpi_hw_info structure in the supplied scpi_response pointer. From 4ea2dce71e784704940861db4035271050d1099f Mon Sep 17 00:00:00 2001 From: Patrick Plenefisch Date: Sun, 21 May 2023 17:12:56 -0400 Subject: [PATCH 20/20] tektronix-tds: Set large timeout and use new scpi text+block parser --- src/hardware/tektronix-tds/api.c | 4 + src/hardware/tektronix-tds/protocol.c | 189 +++++++++++--------------- 2 files changed, 82 insertions(+), 111 deletions(-) diff --git a/src/hardware/tektronix-tds/api.c b/src/hardware/tektronix-tds/api.c index 348b703e7..92d182159 100644 --- a/src/hardware/tektronix-tds/api.c +++ b/src/hardware/tektronix-tds/api.c @@ -326,6 +326,10 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) devc = NULL; hw_info = NULL; + // Slow devices (like TDS 220) need lots more time, though even fast + // deviecs require ~8 seconds for data xfer + scpi->read_timeout_us = 30 * 1000 * 1000; + if (sr_scpi_get_hw_id(scpi, &hw_info) != SR_OK) { sr_info("Couldn't get IDN response, retrying."); sr_scpi_close(scpi); diff --git a/src/hardware/tektronix-tds/protocol.c b/src/hardware/tektronix-tds/protocol.c index 7579332bd..f1cd15d9e 100644 --- a/src/hardware/tektronix-tds/protocol.c +++ b/src/hardware/tektronix-tds/protocol.c @@ -111,27 +111,6 @@ static const char *render_scpi_enum( *out_err = SR_ERR_DATA; return "NULL"; } -static int parse_scpi_blockstart(const char *data, int *out_err) -{ - int len, i; - int ret = 0; - if (data[0] != '#' || data[1] < '0' || data[1] > '9') { - sr_err("block header invalid: %.2s", - data); - goto err; - } - len = data[1] - '0'; - for(i = 0; i < len; ++i) { - if (data[2+i] < '0' || data[2+i] > '9') - goto err; - ret = ret * 10 + (data[2+i] - '0'); - } - return ret; -err: - *out_err = SR_ERR_DATA; - return -1; -} - static void check_expected_value( const char* name, int actual, int expected, int* out_err, @@ -153,11 +132,12 @@ static void check_expected_value( } } -static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) +static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, GString *buffer) { struct sr_scpi_dev_inst *scpi = sdi->conn; struct dev_context *devc = sdi->priv; - char *buf = (char *)devc->buffer; + gchar *buf = buffer->str; + gchar *end_buf = &buffer->str[buffer->len]; char *fields[TEK_PRE_HEADER_FIELDS + 1]; // one extra for block int i = 0; int ret = SR_OK; @@ -165,7 +145,6 @@ static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) const char *wfid; int bit_width; int byte_width; - int blocklength; enum TEK_POINT_FORMAT pt_format; enum TEK_DATA_ORDERING ordering; enum TEK_DATA_FORMAT format; @@ -231,15 +210,14 @@ static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) devc->wavepre.y_off = parse_scpi_float(fields[i++], &ret, 0); devc->wavepre.y_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), parse_table_yunits, &ret, YU_UNKNOWN); - blocklength = parse_scpi_blockstart(fields[i++], &ret); - sr_dbg("Expected 17 values, parsed %d in header with ret=%i", i, ret); - if (i != TEK_PRE_HEADER_FIELDS + 1 && ret == SR_OK) + sr_dbg("Expected 16 values, parsed %d in header with ret=%i", i, ret); + if (i != TEK_PRE_HEADER_FIELDS && ret == SR_OK) ret = SR_ERR; // expensive, so avoid if (sr_log_loglevel_get() >= SR_LOG_SPEW) - sr_spew("Line is parsed as: %d;%d;%s;%s;%s;%i;\"%s\";%s;%.2e;%i;%.2e;\"%s\";%.2e;%.2e;%.2e;\"%s\";#.%d... ", + sr_spew("Line is parsed as: %d;%d;%s;%s;%s;%i;\"%s\";%s;%.2e;%i;%.2e;\"%s\";%.2e;%.2e;%.2e;\"%s\";%c... ", byte_width, bit_width, render_scpi_enum(encoding, parse_table_data_encoding, &ret), render_scpi_enum(format, parse_table_data_format, &ret), @@ -253,7 +231,8 @@ static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) devc->wavepre.y_off, render_scpi_enum(devc->wavepre.y_unit, parse_table_yunits, &ret), - blocklength); + '#' + ); // check that settings weren't tampered with check_expected_value("byte width", byte_width, 1, &ret, NULL); @@ -266,70 +245,89 @@ static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, char *end_buf) check_expected_value("point format", pt_format, PT_FMT_Y, &ret, parse_table_point_format); check_expected_value("point offset", pt_off, 0, &ret, NULL); - check_expected_value("block length", blocklength, TEK_BUFFER_SIZE, &ret, NULL); return ret; } -/* Read the header of a data block. */ -static int tektronix_tds_read_header(struct sr_dev_inst *sdi) +/* Find all 16 fields by locating their semicolons. + * In theory the string values could contain semicolons to throw us off + * but I think we are safe based on the docs + */ +static int tektronix_tds_count_headers(void *cb_data, + const char *rsp_data, size_t rsp_dlen, size_t *block_off) +{ + const char *rsp_start = rsp_data; + size_t found_fields = 0; + + (void)cb_data; + + /* Zero input length is acceptable here. */ + if (rsp_data == NULL || block_off == NULL) + return SR_ERR_ARG; + + while (rsp_dlen--> 0) { + if (*rsp_data++ == ';') { + found_fields++; + + // if we found all fields, return the end of the header + if (found_fields == TEK_PRE_HEADER_FIELDS) { + // found all fields, this is the start of the block + *block_off = rsp_data - rsp_start; + return SR_OK; + } + } + } + + return 1; /* Need more receive data. */ +} + + +/* Read the header and prepare the data of a data block. */ +static int tektronix_tds_read_header_and_data(struct sr_dev_inst *sdi) { struct sr_scpi_dev_inst *scpi = sdi->conn; struct dev_context *devc = sdi->priv; char *buf = (char *)devc->buffer; int ret; + GByteArray *block = NULL; + GString *text; + + ret = sr_scpi_get_text_then_block(scpi, NULL, + tektronix_tds_count_headers, NULL, &text, &block); + + + if (ret != SR_OK) { + sr_err("Read error while reading data: %d", + ret); + ret = 1; // hack to avoid loosing data. We didn't start reading yet + goto err; + } // header is variable, but at least 100 bytes, and likely no more than // 175 bytes. Typical values are around 150 - int attempt = 100; - int found = 0; - - // Find all 16 fields by locating their semicolons. - // In theory the string values could contain semicolons to throw us off - // but I think we are safe based on the docs - while (found < TEK_PRE_HEADER_FIELDS) { - /* Read header from device. */ - ret = sr_scpi_read_data(scpi, buf, attempt); - if (ret < attempt) { - sr_err("Read error while reading data header: %i of %i", - ret, attempt); - return SR_ERR; - } - for (int i = 0; i < ret; i++, buf++) { - if (*buf == ';') { - found++; - } - } - attempt = TEK_PRE_HEADER_FIELDS - found; - if (attempt > 1) - attempt *= 2; + if (tektronix_tds_parse_header(sdi, text) != SR_OK) { + goto err; } - // read block header prefix (# + ) - ret = sr_scpi_read_data(scpi, buf, 2); - if (ret < 2) { - sr_err("Read error while reading block header: %i of %i", - ret, 2); - return SR_ERR; - } - if (buf[0] != '#' || buf[1] < '0' || buf[1] > '9') { - sr_err("block header invalid: %.2s", - buf); - return SR_ERR; - } - attempt = buf[1] - '0'; - buf+=2; - // read block header size - ret = sr_scpi_read_data(scpi, buf, attempt); - if (ret < attempt) { - sr_err("Read error while reading block header: %i of %i", - ret, attempt); - return SR_ERR; + // copy points to our buffer + if (block->len != TEK_BUFFER_SIZE) { + sr_err("Expected full buffer, got one of size %d instead", + block->len); + ret = SR_ERR; + goto err; } - buf +=attempt; + memcpy(buf, block->data, block->len); + devc->num_block_read = block->len; + + g_byte_array_free(block, TRUE); + g_string_free(text, TRUE); - if (tektronix_tds_parse_header(sdi, buf + 1) != SR_OK) - ret = -1; + return SR_OK; +err: + if (block != NULL) + g_byte_array_free(block, TRUE); + if (text != NULL) + g_string_free(text, TRUE); return ret; } @@ -695,14 +693,12 @@ SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) // no data yet sr_dbg("Waiting for data..."); - if (sr_scpi_read_begin(scpi) != SR_OK) + len = tektronix_tds_read_header_and_data(sdi); + if (len == 1) + /* Still reading the header. */ return TRUE; sr_dbg("New block with header expected."); - len = tektronix_tds_read_header(sdi); - if (len == 0) - /* Still reading the header. */ - return TRUE; if (len == -1) { sr_err("Read error, aborting capture."); @@ -715,36 +711,7 @@ SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) // streaming data back is pretty fast, at least once the scope // eventually starts sending it our way - devc->num_block_read = 0; - sr_dbg("Requesting block: %d bytes.", TEK_BUFFER_SIZE + 1); - len = sr_scpi_read_data( - scpi, (char *)devc->buffer, TEK_BUFFER_SIZE + 1); - if (len == -1) { - sr_err("Read error, aborting capture."); - std_session_send_df_frame_end(sdi); - sdi->driver->dev_acquisition_stop(sdi); - return TRUE; - } - sr_dbg("Received block: %d bytes.", len); - devc->num_block_read = len; - - // ensure the terminating newline is read - while (devc->num_block_read < TEK_BUFFER_SIZE + 1) { - sr_dbg("Requesting: %d bytes.", - TEK_BUFFER_SIZE + 1 - devc->num_block_read); - len = sr_scpi_read_data(scpi, - (char *)devc->buffer + devc->num_block_read, - TEK_BUFFER_SIZE + 1 - devc->num_block_read); - if (len == -1) { - sr_err("Read error, aborting capture."); - std_session_send_df_frame_end(sdi); - sdi->driver->dev_acquisition_stop(sdi); - return TRUE; - } - sr_dbg("Received block: %d bytes.", len); - devc->num_block_read += len; - } sr_dbg("Transfer has been completed."); if (!sr_scpi_read_complete(scpi)) { sr_err("Read should have been completed.");