]> sigrok.org Git - libsigrok.git/commitdiff
output/csv: use intermediate time_t var, silence compiler warning master github/master
authorGerhard Sittig <redacted>
Mon, 1 Jan 2024 13:37:50 +0000 (14:37 +0100)
committerGerhard Sittig <redacted>
Wed, 3 Jan 2024 19:21:36 +0000 (20:21 +0100)
There are platforms where timeval and time_t disagree on the width of
the data type of the field which holds seconds. Passing a pointer to an
unexpected type results in warnings (and probably unreliable execution).

Assign the value which is gotten from a timeval to an intermediate
time_t variable, so that the ctime() invocation becomes portable.

153 files changed:
.gitignore
HACKING
Makefile.am
README
README.devices
bindings/cxx/ConfigKey_methods.cpp
bindings/cxx/classes.cpp
bindings/cxx/include/libsigrokcxx/libsigrokcxx.hpp
bindings/cxx/libsigrokcxx.pc.in
bindings/python/sigrok/core/classes.i
bindings/ruby/classes.i
configure.ac
contrib/60-libsigrok.rules
include/libsigrok/libsigrok.h
include/libsigrok/proto.h
include/libsigrok/version.h.in
m4/ax_cxx_compile_stdcxx.m4
m4/sigrok.m4
src/analog.c
src/binary_helpers.c
src/bt/bt_bluez.c
src/device.c
src/dmm/bm52x.c
src/dmm/bm85x.c
src/dmm/bm86x.c
src/dmm/eev121gw.c
src/dmm/metex14.c
src/dmm/mm38xr.c
src/drivers.c
src/hardware/agilent-dmm/protocol.c
src/hardware/arachnid-labs-re-load-pro/api.c
src/hardware/asix-omega-rtm-cli/api.c
src/hardware/asix-omega-rtm-cli/protocol.c
src/hardware/asix-omega-rtm-cli/protocol.h
src/hardware/asix-sigma/api.c
src/hardware/asix-sigma/protocol.h
src/hardware/atorch/api.c [new file with mode: 0644]
src/hardware/atorch/protocol.c [new file with mode: 0644]
src/hardware/atorch/protocol.h [new file with mode: 0644]
src/hardware/atten-pps3xxx/api.c
src/hardware/baylibre-acme/protocol.c
src/hardware/chronovu-la/api.c
src/hardware/dcttech-usbrelay/api.c
src/hardware/demo/api.c
src/hardware/devantech-eth008/api.c [new file with mode: 0644]
src/hardware/devantech-eth008/protocol.c [new file with mode: 0644]
src/hardware/devantech-eth008/protocol.h [new file with mode: 0644]
src/hardware/dreamsourcelab-dslogic/api.c
src/hardware/fluke-dmm/api.c
src/hardware/fluke-dmm/protocol.c
src/hardware/fluke-dmm/protocol.h
src/hardware/fx2lafw/api.c
src/hardware/fx2lafw/protocol.c
src/hardware/fx2lafw/protocol.h
src/hardware/greatfet/api.c [new file with mode: 0644]
src/hardware/greatfet/protocol.c [new file with mode: 0644]
src/hardware/greatfet/protocol.h [new file with mode: 0644]
src/hardware/gwinstek-gds-800/api.c
src/hardware/gwinstek-gpd/api.c
src/hardware/hameg-hmo/protocol.c
src/hardware/hantek-4032l/api.c
src/hardware/hantek-6xxx/api.c
src/hardware/hantek-dso/api.c
src/hardware/hantek-dso/protocol.c
src/hardware/hp-3457a/api.c
src/hardware/hp-3478a/api.c
src/hardware/hp-3478a/protocol.c
src/hardware/hp-3478a/protocol.h
src/hardware/hp-59306a/api.c
src/hardware/hung-chang-dso-2100/api.c
src/hardware/icstation-usbrelay/api.c [new file with mode: 0644]
src/hardware/icstation-usbrelay/protocol.c [new file with mode: 0644]
src/hardware/icstation-usbrelay/protocol.h [new file with mode: 0644]
src/hardware/itech-it8500/api.c
src/hardware/juntek-jds6600/api.c [new file with mode: 0644]
src/hardware/juntek-jds6600/protocol.c [new file with mode: 0644]
src/hardware/juntek-jds6600/protocol.h [new file with mode: 0644]
src/hardware/kingst-la2016/api.c
src/hardware/kingst-la2016/protocol.c
src/hardware/kingst-la2016/protocol.h
src/hardware/korad-kaxxxxp/api.c
src/hardware/korad-kaxxxxp/protocol.c
src/hardware/korad-kaxxxxp/protocol.h
src/hardware/lecroy-xstream/protocol.c
src/hardware/maynuo-m97/api.c
src/hardware/microchip-pickit2/api.c
src/hardware/microchip-pickit2/protocol.h
src/hardware/mooshimeter-dmm/api.c
src/hardware/motech-lps-30x/api.c
src/hardware/openbench-logic-sniffer/api.c
src/hardware/openbench-logic-sniffer/protocol.c
src/hardware/openbench-logic-sniffer/protocol.h
src/hardware/pipistrello-ols/protocol.c
src/hardware/raspberrypi-pico/api.c [new file with mode: 0644]
src/hardware/raspberrypi-pico/protocol.c [new file with mode: 0644]
src/hardware/raspberrypi-pico/protocol.h [new file with mode: 0644]
src/hardware/rdtech-dps/api.c
src/hardware/rdtech-dps/protocol.c
src/hardware/rdtech-dps/protocol.h
src/hardware/rdtech-tc/api.c
src/hardware/rdtech-tc/protocol.c
src/hardware/rdtech-tc/protocol.h
src/hardware/rdtech-um/api.c
src/hardware/rdtech-um/protocol.c
src/hardware/rdtech-um/protocol.h
src/hardware/rigol-dg/api.c
src/hardware/rigol-ds/api.c
src/hardware/scpi-dmm/api.c
src/hardware/scpi-dmm/protocol.c
src/hardware/scpi-pps/api.c
src/hardware/scpi-pps/profiles.c
src/hardware/scpi-pps/protocol.h
src/hardware/serial-dmm/protocol.c
src/hardware/siglent-sds/api.c
src/hardware/siglent-sds/protocol.c
src/hardware/uni-t-dmm/api.c
src/hardware/yokogawa-dlm/api.c
src/hardware/yokogawa-dlm/protocol.c
src/hardware/zeroplus-logic-cube/api.c
src/hardware/zeroplus-logic-cube/protocol.c
src/hardware/zeroplus-logic-cube/protocol.h
src/hwdriver.c
src/input/csv.c
src/input/feed_queue.c
src/input/input.c
src/input/logicport.c
src/input/protocoldata.c [new file with mode: 0644]
src/input/stf.c
src/input/trace32_ad.c
src/input/vcd.c
src/input/wav.c
src/libsigrok-internal.h
src/log.c
src/output/csv.c
src/output/srzip.c
src/resource.c
src/scpi/scpi.c
src/scpi/scpi_serial.c
src/scpi/scpi_tcp.c
src/serial.c
src/serial_bt.c
src/serial_hid.c
src/serial_tcpraw.c [new file with mode: 0644]
src/std.c
src/strutil.c
src/sw_limits.c
src/tcp.c [new file with mode: 0644]
src/usb.c
src/version.c
tests/analog.c
tests/conv.c
tests/strutil.c
tests/version.c

index e943562f81b4ab9fb310c18913c9b78684f90457..235d51c2c95773d6ec2bd1046df274cbb4653b58 100644 (file)
 /*.kdev4
 /Makefile.am.user
 
+# clangd language server cruft
+.cache/
+/compile_commands.json
+
 # Configure/build cruft
 *.[ao]
 *.l[ao]
@@ -26,6 +30,7 @@
 /config.*
 /doxy/
 /include/libsigrok/version.h
+/include/libsigrok/git-version.h
 /libsigrok-*.tar.*
 /libsigrok.pc
 /libtool
diff --git a/HACKING b/HACKING
index 9d71c55c11d0fa240b7eb3b5f32b83e0b20a095c..2d9abe954e565aa5075853c5477b3c1dfde0e2f4 100644 (file)
--- a/HACKING
+++ b/HACKING
@@ -79,6 +79,9 @@ Random notes
  - Generally avoid assigning values to variables at declaration time,
    especially so for complex and/or run-time dependent values.
 
+ - Separate assignments from control flow. Example: Avoid the pattern
+   if (var = func()) {...}  as it complicates review and maintenance.
+
  - Consistently use g_*malloc() / g_*malloc0(). Do not use standard
    malloc()/calloc() if it can be avoided (sometimes other libs such
    as libftdi can return malloc()'d memory, for example).
@@ -95,6 +98,9 @@ Random notes
    Do use g_try_malloc() or g_try_malloc0() for large (>= 1MB) allocations
    and check the return value.
 
+ - Endianness conversion: Prefer the common helpers that are provided in
+   libsigrok-internal.h, such as read_u16be() etc.
+
  - You should never print any messages (neither to stdout nor stderr nor
    elsewhere) "manually" via e.g. printf() or g_log() or similar functions.
    Only sr_err()/sr_warn()/sr_info()/sr_dbg()/sr_spew() should be used.
index 280cf64d29d915199a0b3092f9ffd5a96d5d3f7b..62aca8ac95ad0830b9b35971fab21b28c8c6e546 100644 (file)
@@ -25,6 +25,8 @@ GNUMAKEFLAGS = --no-print-directory
 # distutils/setuptools cause trouble on distcheck. Disable for now.
 DISTCHECK_CONFIGURE_FLAGS = --disable-python
 
+CLEAN_EXTRA =
+
 FIRMWARE_DIR = $(datadir)/sigrok-firmware
 
 local_includes = -Iinclude -I$(srcdir)/include -I$(srcdir)/src -I. @RPC_CFLAGS@
@@ -69,11 +71,15 @@ libsigrok_la_SOURCES = \
        src/version.c \
        src/error.c \
        src/std.c \
-       src/sw_limits.c
+       src/sw_limits.c \
+       src/tcp.c
 
 # Support code, shared among input and driver modules
 libsigrok_la_SOURCES += \
-       src/minilzo/minilzo.c
+       src/minilzo/minilzo.c \
+       src/minilzo/minilzo.h \
+       src/minilzo/lzoconf.h \
+       src/minilzo/lzodefs.h
 
 # Input modules
 libsigrok_la_SOURCES += \
@@ -83,6 +89,7 @@ libsigrok_la_SOURCES += \
        src/input/chronovu_la8.c \
        src/input/csv.c \
        src/input/logicport.c \
+       src/input/protocoldata.c \
        src/input/raw_analog.c \
        src/input/saleae.c \
        src/input/trace32_ad.c \
@@ -145,6 +152,7 @@ libsigrok_la_SOURCES += \
        src/serial_hid_cp2110.c \
        src/serial_hid_victor.c \
        src/serial_libsp.c \
+       src/serial_tcpraw.c \
        src/scpi/scpi_serial.c
 else
 libsigrok_la_SOURCES += \
@@ -256,6 +264,12 @@ src_libdrivers_la_SOURCES += \
        src/hardware/asix-sigma/protocol.c \
        src/hardware/asix-sigma/api.c
 endif
+if HW_ATORCH
+src_libdrivers_la_SOURCES += \
+       src/hardware/atorch/protocol.h \
+       src/hardware/atorch/protocol.c \
+       src/hardware/atorch/api.c
+endif
 if HW_ATTEN_PPS3XXX
 src_libdrivers_la_SOURCES += \
        src/hardware/atten-pps3xxx/protocol.h \
@@ -321,6 +335,12 @@ src_libdrivers_la_SOURCES += \
        src/hardware/demo/protocol.c \
        src/hardware/demo/api.c
 endif
+if HW_DEVANTECH_ETH008
+src_libdrivers_la_SOURCES += \
+       src/hardware/devantech-eth008/protocol.h \
+       src/hardware/devantech-eth008/protocol.c \
+       src/hardware/devantech-eth008/api.c
+endif
 if HW_DREAMSOURCELAB_DSLOGIC
 src_libdrivers_la_SOURCES += \
        src/hardware/dreamsourcelab-dslogic/protocol.h \
@@ -357,6 +377,12 @@ src_libdrivers_la_SOURCES += \
        src/hardware/gmc-mh-1x-2x/protocol.c \
        src/hardware/gmc-mh-1x-2x/api.c
 endif
+if HW_GREATFET
+src_libdrivers_la_SOURCES += \
+       src/hardware/greatfet/protocol.h \
+       src/hardware/greatfet/protocol.c \
+       src/hardware/greatfet/api.c
+endif
 if HW_GWINSTEK_GDS_800
 src_libdrivers_la_SOURCES += \
        src/hardware/gwinstek-gds-800/protocol.h \
@@ -417,6 +443,12 @@ src_libdrivers_la_SOURCES += \
        src/hardware/hung-chang-dso-2100/protocol.c \
        src/hardware/hung-chang-dso-2100/api.c
 endif
+if HW_ICSTATION_USBRELAY
+src_libdrivers_la_SOURCES += \
+       src/hardware/icstation-usbrelay/protocol.h \
+       src/hardware/icstation-usbrelay/protocol.c \
+       src/hardware/icstation-usbrelay/api.c
+endif
 if HW_IKALOGIC_SCANALOGIC2
 src_libdrivers_la_SOURCES += \
        src/hardware/ikalogic-scanalogic2/protocol.h \
@@ -441,6 +473,12 @@ src_libdrivers_la_SOURCES += \
        src/hardware/itech-it8500/protocol.c \
        src/hardware/itech-it8500/api.c
 endif
+if HW_JUNTEK_JDS6600
+src_libdrivers_la_SOURCES += \
+       src/hardware/juntek-jds6600/protocol.h \
+       src/hardware/juntek-jds6600/protocol.c \
+       src/hardware/juntek-jds6600/api.c
+endif
 if HW_KECHENG_KC_330B
 src_libdrivers_la_SOURCES += \
        src/hardware/kecheng-kc-330b/protocol.h \
@@ -549,6 +587,12 @@ src_libdrivers_la_SOURCES += \
        src/hardware/pipistrello-ols/protocol.c \
        src/hardware/pipistrello-ols/api.c
 endif
+if HW_RASPBERRYPI_PICO
+src_libdrivers_la_SOURCES += \
+       src/hardware/raspberrypi-pico/protocol.h \
+       src/hardware/raspberrypi-pico/protocol.c \
+       src/hardware/raspberrypi-pico/api.c
+endif
 if HW_RDTECH_DPS
 src_libdrivers_la_SOURCES += \
        src/hardware/rdtech-dps/protocol.h \
@@ -716,6 +760,35 @@ nodist_library_include_HEADERS = \
        include/libsigrok/version.h
 noinst_HEADERS = src/libsigrok-internal.h
 
+$(builddir)/src/version.lo: $(builddir)/include/libsigrok/git-version.h
+
+# Create the git-version.h file even for non-versioned source trees,
+# to reduce complexity in the library code. Re-create the header file
+# when branches change, when revisions change, or upon re-configuration.
+# Use the verbatim tagged version number when applicable, or append the
+# "-git-<hash>[-dirty]" suffix for non-tagged source trees.
+if VCS_IS_GIT
+
+$(builddir)/include/libsigrok/git-version.h: Makefile $(VERSION_GITVERSION_DEPS)
+       $(AM_V_GEN) \
+               HASH=`git -C "$(srcdir)" describe --match "@VERSION_TAG_MATCH@" --always --dirty` && \
+               HASH=`echo "$${HASH}" | sed 's/@VERSION_TAG_MATCH@-//'` && \
+               SUFFIX=`git -C "$(srcdir)" describe --match "@VERSION_TAG_MATCH@" --exact-match > /dev/null 2> /dev/null || echo "-$${HASH}"` && \
+               echo "#undef SR_PACKAGE_VERSION_STRING_SUFFIX" > $@ && \
+               echo "#define SR_PACKAGE_VERSION_STRING_SUFFIX \"$${SUFFIX}\"" >> $@
+
+else
+
+$(builddir)/include/libsigrok/git-version.h:
+       $(AM_V_GEN)echo '#define SR_PACKAGE_VERSION_STRING_SUFFIX ""' > $@
+
+endif
+
+version-clean:
+       rm -f $(builddir)/include/libsigrok/git-version.h
+
+CLEAN_EXTRA += version-clean
+
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = libsigrok.pc
 
@@ -758,7 +831,11 @@ EXTRA_DIST = \
        contrib/vnd.sigrok.session.xml \
        contrib/60-libsigrok.rules \
        contrib/61-libsigrok-plugdev.rules \
-       contrib/61-libsigrok-uaccess.rules
+       contrib/61-libsigrok-uaccess.rules \
+       src/minilzo/COPYING \
+       src/minilzo/Makefile \
+       src/minilzo/README.LZO \
+       src/minilzo/testmini.c
 
 if HAVE_CHECK
 TESTS = tests/main
@@ -789,7 +866,6 @@ tests_main_LDADD = libsigrok.la $(SR_EXTRA_LIBS) $(TESTS_LIBS)
 BUILD_EXTRA =
 INSTALL_EXTRA =
 UNINSTALL_EXTRA =
-CLEAN_EXTRA =
 
 libsigrok-uninstall:
        -rmdir $(DESTDIR)$(includedir)/libsigrok
diff --git a/README b/README
index 768c8ca2b6612d1850d69f8f25e95c830850aef7..8686a262889351fae0e088883be6b8157bcdd4c1 100644 (file)
--- a/README
+++ b/README
@@ -49,6 +49,7 @@ Requirements for the C library:
  - libgpib (optional, used by some drivers)
  - libieee1284 (optional, used by some drivers)
  - libgio >= 2.32.0 (optional, used by some drivers)
+ - nettle (optional, used by some drivers)
  - check >= 0.9.4 (optional, only needed to run unit tests)
  - doxygen (optional, only needed for the C API docs)
  - graphviz (optional, only needed for the C API docs)
@@ -63,7 +64,7 @@ Requirements for the C++ bindings:
  - doxygen (required for building the bindings, not only for C++ API docs!)
  - graphviz (optional, only needed for the C++ API docs)
  - Python (2 or 3) executable (development files are not needed)
- - glibmm-2.4 (>= 2.32.0)
+ - glibmm-2.4 (>= 2.32.0) or glibmm-2.68 (>= 2.68.0)
 
 Requirements for the Python bindings:
 
index 226ec06ba59691b2c37a3548987d963d79ae3080..7563a57c5e1160a140068e30c3f05c8c234566e5 100644 (file)
@@ -63,6 +63,10 @@ The following drivers/devices require a firmware upload upon connection:
    These can be extracted from the vendor's Windows drivers using a tool
    from our 'sigrok-util' repository/project.
 
+ - kingst-la2016: The Kingst LA series of logic analyzers needs MCU firmware
+   and FPGA netlists. The 'sigrok-util' repository contains a script to
+   extract these files from the vendor software.
+
  - lecroy-logicstudio: The LeCroy LogicStudio requires FPGA bitstream files.
    These can be extracted from the vendor's Windows software using a tool
    from our 'sigrok-util' repository/project.
@@ -197,7 +201,7 @@ Formal syntax for serial communication:
    path may contain slashes
    path and serno are "greedy" (span to the end of the spec)
  - Bluetooth Classic and Bluetooth Low Energy (BLE):
-   conn=bt/<conn>/<addr>
+   conn=bt/<conn>/<addr>[/param=value]
    conn can be: rfcomm, ble122, nrf51, cc254x
    addr can be "dense" or separated, bt/cc254x/0123456789ab or
      bt/rfcomm/11-22-33-44-55-66 or bt/ble122/88:6b:12:34:56:78
@@ -205,6 +209,8 @@ Formal syntax for serial communication:
      from a string that separates fields by colon, e.g. in the "--driver
      <name>:conn=<spec>" example, that is why the dense form and the use
      of dashes for separation are supported)
+   additional parameter keywords can be: channel, handle_rx, handle_tx,
+     handle_cccd, value_cccd, mtu
 
 Some of the drivers implement a default for the connection. Some of the
 drivers can auto-detect USB connected devices.
@@ -288,6 +294,19 @@ rules shipped by the system will be broken.
 Please consult the udev docs for details.
 
 
+Assigning drivers to devices (Windows, Zadig)
+---------------------------------------------
+
+On Windows systems it may be necessary to assign drivers to devices
+before libusb based applications can access them. It may be necessary
+to re-run this driver assignment after firmware upload in case the
+device changes its USB identification as a consequence of loading the
+firmware image.
+
+The https://sigrok.org/wiki/Windows wiki page discusses this subject,
+and other platform specific aspects.
+
+
 Non-default drivers for commodity chips
 ---------------------------------------
 
@@ -347,19 +366,27 @@ Examples (sigrok-cli):
 
  $ sigrok-cli --driver uni-t-ut61e-ser:conn=/dev/ttyUSB0 ...
  $ sigrok-cli --driver voltcraft-vc820-ser:conn=/dev/ttyS0 ...
-
-When using any of the UT-D04 USB/HID cables you have to use the respective
-driver _without_ the '-ser' drivername suffix (internally all of these models
-are handled by the 'uni-t-dmm' driver).
-
-You also need to specify the USB vendor/device IDs of the cable.
-Autodetection is not possible here, since various other products use the
-USB VID/PID of those cables too, and there is no way to distinguish them.
-
-Since the UT-D04 cables are USB based (but don't use a USB-to-serial chip)
-there is no need to specify a serial port via 'conn', of course.
-However, the user running the frontend does also need to have permissions
-to access the respective USB device (see above).
+ $ sigrok-cli --driver uni-t-ut61e-ser:conn=hid/cp2110
+
+Using any of the UT-D04 et al USB/HID cables can be done in two different
+ways: Use transparent serial over HID support in libsigrok, by giving the
+-ser driver a conn=hid/... serial port spec. This re-uses the 'serial-dmm'
+driver, results in better coverage of these code paths, and reduces
+maintenance overhead. Or by running non-ser drivers and passing USB
+specific connection details. When the driver _without_ the '-ser' suffix
+is used, the models are handled by the 'uni-t-dmm' driver. These duplicate
+drivers only exist for historical reasons, the redundancy may result in
+differences of behaviour between the two implementations. When in doubt,
+check if the '-ser' driver works for you.
+
+In the USB specific driver case you need to specify the cable's vendor
+and product IDs. Autodetection is not possible here, since various other
+products use the USB VID/PID of those cables too, and there is no way to
+distinguish them. The sigrok software errs on the safe side, and won't
+communicate to serial ports unless explicitly instructed by the user.
+
+The user running the frontend does also need to have permissions to
+access the respective USB device (see above).
 
 Examples (sigrok-cli):
 
@@ -390,6 +417,18 @@ See also: http://erste.de/UT61/index.html
   done
 
 
+UNI-T UT-D04 cable issue on Windows
+-----------------------------------
+
+There have been reports that CH9325 based cables are not detected on
+Windows out of the box when they are assigned to libwdi drivers. Though
+they may be usable in that case when the USB address is manually specified.
+This can happen when some "USB to serial" driver is assigned which does not
+provide a genuine COM port that enumerates naturally. Manually assigning a
+"USB input device" driver can improve HIDAPI compatibility and make the
+cable show up in sigrok's serial port enumeration.
+
+
 Enabling multimeter / data logger measurement output
 ----------------------------------------------------
 
index 7f8c201ed5a8f1e649b4f4e9a452b5c8344e1dd3..d899ddcc2a18d2053470a346051e6f4e5ccca27f 100644 (file)
@@ -70,6 +70,50 @@ static inline double stod( const std::string& str )
 }
 #endif
 
+#ifndef HAVE_STOUL
+
+/* Fallback implementation of stoul. */
+
+#include <cerrno>
+#include <cstdlib>
+#include <limits>
+#include <stdexcept>
+
+static inline unsigned long stoul(const std::string &str)
+{
+       char *endptr;
+       unsigned long ret;
+       errno = 0;
+       ret = std::strtoul(str.c_str(), &endptr, 10);
+       if (endptr == str.c_str())
+               throw std::invalid_argument("stoul");
+       /*
+        * TODO Convert to a larger/wider intermediate data type?
+        * Because after conversion into the very target type, the
+        * range check is assumed to be ineffective.
+        */
+       if (errno == ERANGE ||
+               ret < std::numeric_limits<unsigned long>::min() ||
+               ret > std::numeric_limits<unsigned long>::max())
+               throw std::out_of_range("stoul");
+       return ret;
+}
+#endif
+
+// Conversion from text to uint32_t, including a range check.
+// This is sigrok specific, _not_ part of any C++ standard library.
+static uint32_t stou32(const std::string &str)
+{
+       unsigned long ret;
+       errno = 0;
+       ret = stoul(str);
+       if (errno == ERANGE)
+               throw std::out_of_range("stou32");
+       if (ret > std::numeric_limits<uint32_t>::max())
+               throw std::out_of_range("stou32");
+       return ret;
+}
+
 Glib::VariantBase ConfigKey::parse_string(std::string value, enum sr_datatype dt)
 {
        GVariant *variant;
@@ -109,6 +153,13 @@ Glib::VariantBase ConfigKey::parse_string(std::string value, enum sr_datatype dt
                                throw Error(SR_ERR_ARG);
                        }
                        break;
+               case SR_T_UINT32:
+                       try {
+                               variant = g_variant_new_uint32(stou32(value));
+                       } catch (invalid_argument&) {
+                               throw Error(SR_ERR_ARG);
+                       }
+                       break;
                default:
                        throw Error(SR_ERR_BUG);
        }
index f9f79273495026f52c61989b74fca89d1b99e50a..d5477847653f85acb6f637b48de4a3ce677a9659 100644 (file)
@@ -287,12 +287,12 @@ shared_ptr<UserDevice> Context::create_user_device(
                default_delete<UserDevice>{}};
 }
 
-shared_ptr<Packet> Context::create_header_packet(Glib::TimeVal start_time)
+shared_ptr<Packet> Context::create_header_packet(Glib::DateTime start_time)
 {
        auto header = g_new(struct sr_datafeed_header, 1);
        header->feed_version = 1;
-       header->starttime.tv_sec = start_time.tv_sec;
-       header->starttime.tv_usec = start_time.tv_usec;
+       header->starttime.tv_sec = start_time.to_unix();
+       header->starttime.tv_usec = start_time.get_microsecond();
        auto packet = g_new(struct sr_datafeed_packet, 1);
        packet->type = SR_DF_HEADER;
        packet->payload = header;
@@ -1154,11 +1154,10 @@ int Header::feed_version() const
        return _structure->feed_version;
 }
 
-Glib::TimeVal Header::start_time() const
+Glib::DateTime Header::start_time() const
 {
-       return Glib::TimeVal(
-               _structure->starttime.tv_sec,
-               _structure->starttime.tv_usec);
+       Glib::DateTime time = Glib::DateTime::create_now_utc(_structure->starttime.tv_sec);
+       return time.add_seconds(_structure->starttime.tv_usec / 1.0e6);
 }
 
 Meta::Meta(const struct sr_datafeed_meta *structure) :
@@ -1566,6 +1565,8 @@ Glib::VariantBase Option::parse_string(string value)
                dt = SR_T_FLOAT;
        } else if (g_variant_is_of_type(tmpl, G_VARIANT_TYPE_INT32)) {
                dt = SR_T_INT32;
+       } else if (g_variant_is_of_type(tmpl, G_VARIANT_TYPE_UINT32)) {
+               dt = SR_T_UINT32;
        } else {
                throw Error(SR_ERR_BUG);
        }
index 97e54e1731a88d1da3eb597c9d1c49cb20bd7750..3bebdd234936c246b7a559ad3569ce509d79629a 100644 (file)
@@ -274,7 +274,7 @@ public:
        std::shared_ptr<UserDevice> create_user_device(
                std::string vendor, std::string model, std::string version);
        /** Create a header packet. */
-       std::shared_ptr<Packet> create_header_packet(Glib::TimeVal start_time);
+       std::shared_ptr<Packet> create_header_packet(Glib::DateTime start_time);
        /** Create a meta packet. */
        std::shared_ptr<Packet> create_meta_packet(
                std::map<const ConfigKey *, Glib::VariantBase> config);
@@ -711,7 +711,7 @@ public:
        /* Feed version number. */
        int feed_version() const;
        /* Start time of this session. */
-       Glib::TimeVal start_time() const;
+       Glib::DateTime start_time() const;
 private:
        explicit Header(const struct sr_datafeed_header *structure);
        ~Header();
@@ -791,9 +791,22 @@ public:
        /** Samples are stored in big-endian order. */
        bool is_bigendian() const;
        /**
-        * Number of significant digits after the decimal point if positive,
-        * or number of non-significant digits before the decimal point if negative
-        * (refers to the value we actually read on the wire).
+        * Number of significant digits after the decimal point, if positive.
+        * When negative, exponent with reversed polarity that is necessary to
+        * express the value with all digits without a decimal point.
+        * Refers to the value we actually read on the wire.
+        *
+        * Examples:
+        *
+        * | On the wire | Exponential notation | Exp. not. (normalized) | digits |
+        * |-------------|----------------------|------------------------|--------|
+        * |  12.34 MOhm |   1.234 * 10^7   Ohm |      1234 * 10^4   Ohm |     -4 |
+        * | 1.2345 MOhm |  1.2345 * 10^6   Ohm |     12345 * 10^2   Ohm |     -2 |
+        * |  123.4 kOhm |   1.234 * 10^5   Ohm |      1234 * 10^2   Ohm |     -2 |
+        * |   1234  Ohm |   1.234 * 10^3   Ohm |      1234 * 10^0   Ohm |      0 |
+        * |  12.34  Ohm |   1.234 * 10^1   Ohm |      1234 * 10^-2  Ohm |      2 |
+        * | 0.0123  Ohm |    1.23 * 10^-2  Ohm |       123 * 10^-4  Ohm |      4 |
+        * |  1.234 pF   |   1.234 * 10^-12 F   |      1234 * 10^-15 F   |     15 |
         */
        int digits() const;
        /** TBD */
index 10a92f2dce314b88dcbd2af7b49d9323f51485a3..7d2723fc89a972090a56d9bd6533038450c934f9 100644 (file)
@@ -6,7 +6,7 @@ includedir=@includedir@
 Name: libsigrokcxx
 Description: C++ bindings for libsigrok
 URL: http://www.sigrok.org
-Requires: libsigrok glibmm-2.4
+Requires: libsigrok @SR_GLIBMM_REQUIRES@
 Version: @SR_PACKAGE_VERSION@
 Libs: -L${libdir} -lsigrokcxx
 Libs.private: -lm
index eb557d0375335f561eb1e05d91f3536c38620c2f..d22db1c150f2cca2f244bb12a16e904762590f4c 100644 (file)
@@ -134,7 +134,7 @@ typedef guint pyg_flags_type;
 
         auto arglist = Py_BuildValue("(OO)", log_obj, string_obj);
 
-        auto result = PyEval_CallObject($input, arglist);
+        auto result = PyObject_CallObject($input, arglist);
 
         Py_XDECREF(arglist);
         Py_XDECREF(log_obj);
@@ -177,7 +177,7 @@ typedef guint pyg_flags_type;
     $1 = [=] () {
         const auto gstate = PyGILState_Ensure();
 
-        const auto result = PyEval_CallObject($input, nullptr);
+        const auto result = PyObject_CallObject($input, nullptr);
         const bool completed = !PyErr_Occurred();
         const bool valid_result = (completed && result == Py_None);
 
@@ -221,7 +221,7 @@ typedef guint pyg_flags_type;
 
         auto arglist = Py_BuildValue("(OO)", device_obj, packet_obj);
 
-        auto result = PyEval_CallObject($input, arglist);
+        auto result = PyObject_CallObject($input, arglist);
 
         Py_XDECREF(arglist);
         Py_XDECREF(device_obj);
@@ -340,6 +340,8 @@ Glib::VariantBase python_to_variant_by_key(PyObject *input, const sigrok::Config
         return Glib::Variant<double>::create(PyFloat_AsDouble(input));
     else if (type == SR_T_INT32 && PyInt_Check(input))
         return Glib::Variant<gint32>::create(PyInt_AsLong(input));
+    else if (type == SR_T_UINT32 && PyInt_Check(input))
+        return Glib::Variant<guint32>::create(PyInt_AsLong(input));
     else if ((type == SR_T_RATIONAL_VOLT) && PyTuple_Check(input) && (PyTuple_Size(input) == 2)) {
         PyObject *numObj = PyTuple_GetItem(input, 0);
         PyObject *denomObj = PyTuple_GetItem(input, 1);
@@ -369,6 +371,8 @@ Glib::VariantBase python_to_variant_by_option(PyObject *input,
         return Glib::Variant<double>::create(PyFloat_AsDouble(input));
     else if (type == G_VARIANT_TYPE_INT32 && PyInt_Check(input))
         return Glib::Variant<gint32>::create(PyInt_AsLong(input));
+    else if (type == G_VARIANT_TYPE_UINT32 && PyInt_Check(input))
+        return Glib::Variant<guint32>::create(PyInt_AsLong(input));
     else
         throw sigrok::Error(SR_ERR_ARG);
 }
index 13496a86bb98fc64fc181300aaf2031b19e2ef76..fbd7c36c95d9c57d008c3d128eb11be622269ac4 100644 (file)
@@ -236,6 +236,8 @@ Glib::VariantBase ruby_to_variant_by_key(VALUE input, const sigrok::ConfigKey *k
         return Glib::Variant<double>::create(RFLOAT_VALUE(input));
     else if (type == SR_T_INT32 && RB_TYPE_P(input, T_FIXNUM))
         return Glib::Variant<gint32>::create(NUM2INT(input));
+    else if (type == SR_T_UINT32 && RB_TYPE_P(input, T_FIXNUM))
+        return Glib::Variant<guint32>::create(NUM2UINT(input));
     else
         throw sigrok::Error(SR_ERR_ARG);
 }
@@ -261,6 +263,8 @@ Glib::VariantBase ruby_to_variant_by_option(VALUE input, std::shared_ptr<sigrok:
         return Glib::Variant<double>::create(RFLOAT_VALUE(input));
     else if (variant.is_of_type(Glib::VARIANT_TYPE_INT32) && RB_TYPE_P(input, T_FIXNUM))
         return Glib::Variant<gint32>::create(NUM2INT(input));
+    else if (variant.is_of_type(Glib::VARIANT_TYPE_UINT32) && RB_TYPE_P(input, T_FIXNUM))
+        return Glib::Variant<guint32>::create(NUM2UINT(input));
     else
         throw sigrok::Error(SR_ERR_ARG);
 }
index 424b00022729e4ba70671bfdc17518c3b8646ed0..5c30a8165f383484c07c0eab6210d085b7a376a7 100644 (file)
@@ -225,6 +225,20 @@ AM_CONDITIONAL([NEED_RPC], [test "x$sr_cv_have_rpc" = xyes])
 # Check for compiler support of 128 bit integers
 AC_CHECK_TYPES([__int128_t, __uint128_t], [], [], [])
 
+AC_CACHE_CHECK([for poll], [sr_cv_have_poll],
+       [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <poll.h>]],
+                       [[(void) poll(0, 0, -1);]])],
+               [sr_cv_have_poll=yes], [sr_cv_have_poll=no])])
+AS_IF([test "x$sr_cv_have_poll" = xyes],
+       [AC_DEFINE([HAVE_POLL], [1],
+               [Specifies whether we have the poll(2) function.])])
+AC_CACHE_CHECK([for select], [sr_cv_have_select],
+       [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/select.h>]],
+                       [[(void) select(0, 0, 0, 0, 0);]])],
+               [sr_cv_have_select=yes], [sr_cv_have_select=no])])
+AS_IF([test "x$sr_cv_have_select" = xyes],
+       [AC_DEFINE([HAVE_SELECT], [1],
+               [Specifies whether we have the select(2) function.])])
 
 #######################
 ##  miniLZO related  ##
@@ -295,6 +309,7 @@ SR_DRIVER([Appa 55II], [appa-55ii], [serial_comm])
 SR_DRIVER([Arachnid Labs Re:load Pro], [arachnid-labs-re-load-pro], [serial_comm])
 SR_DRIVER([ASIX SIGMA/SIGMA2], [asix-sigma], [libftdi])
 SR_DRIVER([ASIX OMEGA RTM CLI], [asix-omega-rtm-cli])
+SR_DRIVER([Atorch], [atorch], [serial_comm])
 SR_DRIVER([Atten PPS3xxx], [atten-pps3xxx], [serial_comm])
 SR_DRIVER([BayLibre ACME], [baylibre-acme], [sys_timerfd_h])
 SR_DRIVER([BeagleLogic], [beaglelogic], [sys_mman_h sys_ioctl_h])
@@ -305,12 +320,14 @@ SR_DRIVER([Colead SLM], [colead-slm], [serial_comm])
 SR_DRIVER([Conrad DIGI 35 CPU], [conrad-digi-35-cpu], [serial_comm])
 SR_DRIVER([dcttech usbrelay], [dcttech-usbrelay], [libhidapi])
 SR_DRIVER([demo], [demo])
+SR_DRIVER([Devantech ETH008], [devantech-eth008], [serial_comm])
 SR_DRIVER([DreamSourceLab DSLogic], [dreamsourcelab-dslogic], [libusb])
 SR_DRIVER([Fluke 45], [fluke-45])
 SR_DRIVER([Fluke DMM], [fluke-dmm], [serial_comm])
 SR_DRIVER([FTDI LA], [ftdi-la], [libusb libftdi])
 SR_DRIVER([fx2lafw], [fx2lafw], [libusb])
 SR_DRIVER([GMC MH 1x/2x], [gmc-mh-1x-2x], [serial_comm])
+SR_DRIVER([Great Scott Gadgets GreatFET One], [greatfet], [libusb])
 SR_DRIVER([GW Instek GDS-800], [gwinstek-gds-800], [serial_comm])
 SR_DRIVER([GW Instek GPD], [gwinstek-gpd], [serial_comm])
 SR_DRIVER([Hameg HMO], [hameg-hmo])
@@ -321,10 +338,12 @@ SR_DRIVER([HP 3457A], [hp-3457a])
 SR_DRIVER([HP 3478A], [hp-3478a], [libgpib])
 SR_DRIVER([hp-59306a], [hp-59306a])
 SR_DRIVER([Hung-Chang DSO-2100], [hung-chang-dso-2100], [libieee1284])
+SR_DRIVER([ICStation USBRelay], [icstation-usbrelay], [serial_comm])
 SR_DRIVER([Ikalogic Scanalogic-2], [ikalogic-scanalogic2], [libusb])
 SR_DRIVER([Ikalogic Scanaplus], [ikalogic-scanaplus], [libftdi])
 SR_DRIVER([IPDBG LA], [ipdbg-la])
 SR_DRIVER([ITECH IT8500], [itech-it8500], [serial_comm])
+SR_DRIVER([JUNTEK JDS6600], [juntek-jds6600], [serial_comm])
 SR_DRIVER([Kecheng KC-330B], [kecheng-kc-330b], [libusb])
 SR_DRIVER([KERN scale], [kern-scale], [serial_comm])
 SR_DRIVER([Kingst LA2016], [kingst-la2016], [libusb])
@@ -343,6 +362,7 @@ SR_DRIVER([Norma DMM], [norma-dmm], [serial_comm])
 SR_DRIVER([OpenBench Logic Sniffer], [openbench-logic-sniffer], [serial_comm])
 SR_DRIVER([PCE PCE-322A], [pce-322a], [serial_comm])
 SR_DRIVER([Pipistrello-OLS], [pipistrello-ols], [libftdi])
+SR_DRIVER([RaspberryPI PICO], [raspberrypi-pico], [serial_comm])
 SR_DRIVER([RDTech DPSxxxx/DPHxxxx], [rdtech-dps], [serial_comm])
 SR_DRIVER([RDTech UMXX], [rdtech-um], [serial_comm])
 SR_DRIVER([RDTech TCXX], [rdtech-tc], [serial_comm libnettle])
@@ -401,14 +421,41 @@ AC_ARG_ENABLE([java],
 
 sr_cxx_missing=
 
-# Check if the C++ compiler supports the C++11 standard.
-AX_CXX_COMPILE_STDCXX([11], [noext], [optional])
-AS_IF([test "x$HAVE_CXX11" != x1],
+# Check if the C++ compiler supports at least the C++11 standard.
+# Get the highest of the available C++17/C++14/C++11 standards.
+# This transparently amends CXXFLAGS to pick the detected standard.
+HAVE_MODERN_CXX=
+AS_IF([test "x$HAVE_MODERN_CXX" = x],
+       [AX_CXX_COMPILE_STDCXX([17], [noext], [optional])])
+AS_IF([test "x$HAVE_CXX17" = x1],
+       [HAVE_MODERN_CXX=yes])
+AS_IF([test "x$HAVE_MODERN_CXX" = x],
+       [AX_CXX_COMPILE_STDCXX([14], [noext], [optional])])
+AS_IF([test "x$HAVE_CXX14" = x1],
+       [HAVE_MODERN_CXX=yes])
+AS_IF([test "x$HAVE_MODERN_CXX" = x],
+       [AX_CXX_COMPILE_STDCXX([11], [noext], [optional])])
+AS_IF([test "x$HAVE_CXX11" = x1],
+       [HAVE_MODERN_CXX=yes])
+AS_IF([test "x$HAVE_MODERN_CXX" = x],
        [SR_APPEND([sr_cxx_missing], [', '], ['C++11'])])
 
 # The C++ bindings need glibmm.
-SR_PKG_CHECK([glibmm], [SR_PKGLIBS_CXX], [glibmm-2.4 >= 2.32.0])
+# Prefer glibmm-2.4 for backwards compatibility.
+# Accept glibmm-2.68 when glibmm-2.4 is not available.
+sr_have_glibmm=no
 AS_IF([test "x$sr_have_glibmm" != xyes],
+       [SR_PKG_CHECK([glibmm24], [SR_PKGLIBS_CXX], [glibmm-2.4 >= 2.32.0])])
+AS_IF([test "x$sr_have_glibmm24" = xyes],
+       [SR_APPEND([SR_GLIBMM_REQUIRES], ['glibmm-2.4 >= 2.32.0'])
+       sr_have_glibmm=yes])
+AS_IF([test "x$sr_have_glibmm" != xyes],
+       [SR_PKG_CHECK([glibmm268], [SR_PKGLIBS_CXX], [glibmm-2.68 >= 2.68.0])])
+AS_IF([test "x$sr_have_glibmm268" = xyes],
+       [SR_APPEND([SR_GLIBMM_REQUIRES], ['glibmm-2.68 >= 2.68.0'])
+       sr_have_glibmm=yes])
+AS_IF([test "x$sr_have_glibmm" = xyes],
+       [AC_SUBST(SR_GLIBMM_REQUIRES)],
        [SR_APPEND([sr_cxx_missing], [', '], [glibmm])])
 
 # The C++ bindings use Doxygen to parse libsigrok symbols.
@@ -438,6 +485,16 @@ AM_COND_IF([BINDINGS_CXX], [
        AS_IF([test "x$sr_cv_have_stoi_stod" = xyes],
                [AC_DEFINE([HAVE_STOI_STOD], [1],
                        [Specifies whether we have the stoi and stod functions.])])
+       # In theory std::stoul() should have identical availability
+       # as std::stoi() and std::stod() have. All of them are C++11.
+       # But we play it safe here, and check support individually.
+       AC_CACHE_CHECK([for stoul], [sr_cv_have_stoul],
+               [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <string>]],
+                               [[(void) std::stoul("1");]])],
+                       [sr_cv_have_stoul=yes], [sr_cv_have_stoul=no])])
+       AS_IF([test "x$sr_cv_have_stoul" = xyes],
+               [AC_DEFINE([HAVE_STOUL], [1],
+                       [Specifies whether we have the std::stoul function.])])
 ])
 
 #######################
@@ -630,9 +687,6 @@ AC_CHECK_FUNCS([ftdi_tciflush ftdi_tcoflush ftdi_tcioflush])
 LIBS=$sr_save_libs
 CFLAGS=$sr_save_cflags
 
-AM_COND_IF([NEED_USB], [AS_CASE([$sr_have_libusb_os_handle:$host_os], [no:mingw*],
-       [AC_MSG_ERROR([Windows builds require the event-abstraction branch of libusb])])])
-
 sr_glib_version=`$PKG_CONFIG --modversion glib-2.0 2>&AS_MESSAGE_LOG_FD`
 sr_libzip_version=`$PKG_CONFIG --modversion libzip 2>&AS_MESSAGE_LOG_FD`
 sr_zlib_version=`$PKG_CONFIG --modversion zlib 2>&AS_MESSAGE_LOG_FD`
@@ -660,6 +714,7 @@ cat >&AS_MESSAGE_FD <<_EOF
 
 libsigrok configuration summary:
  - Package version................. $SR_PACKAGE_VERSION
+ - Version string suffix .......... $SR_PACKAGE_VERSION_STRING_SUFFIX
  - Library ABI version............. $SR_LIB_VERSION
  - Prefix.......................... $prefix
  - Building on..................... $build
index f6ae259f4f9666b4f2acf01485bc0716cabb1eea..b6ac3ba045f35587287c4be08cce2d5987593ff3 100644 (file)
@@ -49,7 +49,9 @@ ATTRS{idVendor}=="0957", ATTRS{idProduct}=="1735", ENV{ID_SIGROK}="1"
 
 # ASIX SIGMA
 # ASIX SIGMA2
+# ASIX OMEGA
 ATTRS{idVendor}=="a600", ATTRS{idProduct}=="a000", ENV{ID_SIGROK}="1"
+ATTRS{idVendor}=="a600", ATTRS{idProduct}=="a004", ENV{ID_SIGROK}="1"
 
 # Braintechnology USB-LPS
 ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0498", ENV{ID_SIGROK}="1"
@@ -111,6 +113,10 @@ ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0020", ENV{ID_SIGROK}="1"
 # DreamSourceLab DSLogic Basic
 ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0021", ENV{ID_SIGROK}="1"
 
+# Great Scott Gadgets
+# GreatFET One
+ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="60e6", ENV{ID_SIGROK}="1"
+
 # GW-Instek GDM-9061 (USBTMC mode)
 ATTRS{idVendor}=="2184", ATTRS{idProduct}=="0059", ENV{ID_SIGROK}="1"
 
@@ -248,6 +254,9 @@ ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0117", ENV{ID_SIGROK}="1"
 ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0118", ENV{ID_SIGROK}="1"
 ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0119", ENV{ID_SIGROK}="1"
 
+# Rohde&Schwarz HMC series power supply (previously branded Hameg) VCP/USBTMC mode
+ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0135", ENV{ID_SIGROK}="1"
+
 # Sainsmart DDS120 / Rocktech BM102, renumerates as 1d50:608e "sigrok fx2lafw", Serial: Sainsmart DDS120
 ATTRS{idVendor}=="8102", ATTRS{idProduct}=="8102", ENV{ID_SIGROK}="1"
 
@@ -263,13 +272,22 @@ ATTRS{idVendor}=="0925", ATTRS{idProduct}=="3881", ENV{ID_SIGROK}="1"
 # Saleae Logic16
 ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1001", ENV{ID_SIGROK}="1"
 
+ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1003", ENV{ID_SIGROK}="1"
+
+# Saleae Logic 8, currently unsupported by libsigrok
+#ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1004", ENV{ID_SIGROK}="1"
+
+# Saleae Logic Pro 8
+ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1005", ENV{ID_SIGROK}="1"
+
 # Saleae Logic Pro 16
 ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1006", ENV{ID_SIGROK}="1"
 
 # Siglent USBTMC devices.
 # f4ec:ee3a: E.g. SDS1052DL+ scope
-# f4ec:ee38: E.g. SDS1104X-E scope
+# f4ec:ee38: E.g. SDS1104X-E scope or SDM3055 Multimeter
 # f4ed:ee3a: E.g. SDS1202X-E scope or SDG1010 waveform generator
+ATTRS{idVendor}=="f4ec", ATTRS{idProduct}=="ee38", ENV{ID_SIGROK}="1"
 ATTRS{idVendor}=="f4ec", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1"
 ATTRS{idVendor}=="f4ed", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1"
 
@@ -324,6 +342,9 @@ ATTRS{idVendor}=="04fc", ATTRS{idProduct}=="0201", ENV{ID_SIGROK}="1"
 # Victor 86C
 ATTRS{idVendor}=="1244", ATTRS{idProduct}=="d237", ENV{ID_SIGROK}="1"
 
+# Voltcraft DSO2020, renumerates as 1d50:608e "sigrok fx2lafw", Serial: Hantek 6022BE
+ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="2020", ENV{ID_SIGROK}="1"
+
 # YiXingDianZi MDSO
 ATTRS{idVendor}=="d4a2", ATTRS{idProduct}=="5660", ENV{ID_SIGROK}="1"
 
index 81a831b3b1814d2bc1b46504ce009b6395fc3dd6..6c5dea08ab0d3ca3eeaa7fe45f1761876f1da353 100644 (file)
@@ -153,6 +153,7 @@ enum sr_datatype {
        SR_T_DOUBLE_RANGE,
        SR_T_INT32,
        SR_T_MQ,
+       SR_T_UINT32,
 
        /* Update sr_variant_type_get() (hwdriver.c) upon changes! */
 };
@@ -528,9 +529,22 @@ struct sr_analog_encoding {
        gboolean is_float;
        gboolean is_bigendian;
        /**
-        * Number of significant digits after the decimal point if positive,
-        * or number of non-significant digits before the decimal point if
-        * negative (refers to the value we actually read on the wire).
+        * Number of significant digits after the decimal point, if positive.
+        * When negative, exponent with reversed polarity that is necessary to
+        * express the value with all digits without a decimal point.
+        * Refers to the value we actually read on the wire.
+        *
+        * Examples:
+        *
+        * | Disp. value | Exp. notation       | Exp. not. normalized | digits |
+        * |-------------|---------------------|----------------------|--------|
+        * |  12.34 MOhm |  1.234 * 10^7   Ohm |    1234 * 10^4   Ohm |     -4 |
+        * | 1.2345 MOhm | 1.2345 * 10^6   Ohm |   12345 * 10^2   Ohm |     -2 |
+        * |  123.4 kOhm |  1.234 * 10^5   Ohm |    1234 * 10^2   Ohm |     -2 |
+        * |   1234  Ohm |  1.234 * 10^3   Ohm |    1234 * 10^0   Ohm |      0 |
+        * |  12.34  Ohm |  1.234 * 10^1   Ohm |    1234 * 10^-2  Ohm |      2 |
+        * | 0.0123  Ohm |   1.23 * 10^-2  Ohm |     123 * 10^-4  Ohm |      4 |
+        * |  1.234 pF   |  1.234 * 10^-12 F   |    1234 * 10^-15 F   |     15 |
         */
        int8_t digits;
        gboolean is_digits_decimal;
@@ -547,10 +561,22 @@ struct sr_analog_meaning {
 
 struct sr_analog_spec {
        /**
-        * Number of significant digits after the decimal point if positive,
-        * or number of non-significant digits before the decimal point if
-        * negative (refers to vendor specifications/datasheet or actual
-        * device display).
+        * Number of significant digits after the decimal point, if positive.
+        * When negative, exponent with reversed polarity that is necessary to
+        * express the value with all digits without a decimal point.
+        * Refers to vendor specifications/datasheet or actual device display.
+        *
+        * Examples:
+        *
+        * | On the wire | Exp. notation       | Exp. not. normalized | spec_digits |
+        * |-------------|---------------------|----------------------|-------------|
+        * |  12.34 MOhm |  1.234 * 10^7   Ohm |    1234 * 10^4   Ohm |          -4 |
+        * | 1.2345 MOhm | 1.2345 * 10^6   Ohm |   12345 * 10^2   Ohm |          -2 |
+        * |  123.4 kOhm |  1.234 * 10^5   Ohm |    1234 * 10^2   Ohm |          -2 |
+        * |   1234  Ohm |  1.234 * 10^3   Ohm |    1234 * 10^0   Ohm |           0 |
+        * |  12.34  Ohm |  1.234 * 10^1   Ohm |    1234 * 10^-2  Ohm |           2 |
+        * | 0.0123  Ohm |   1.23 * 10^-2  Ohm |     123 * 10^-4  Ohm |           4 |
+        * |  1.234 pF   |  1.234 * 10^-12 F   |    1234 * 10^-15 F   |          15 |
         */
        int8_t spec_digits;
 };
@@ -784,6 +810,26 @@ enum sr_configkey {
         */
        SR_CONF_FORCE_DETECT,
 
+       /**
+        * Override builtin probe names from user specs.
+        *
+        * Users may want to override the names which are assigned to
+        * probes during scan (these usually match the vendor's labels
+        * on the device). This avoids the interactive tedium of
+        * changing channel names after device creation and before
+        * protocol decoder attachment. Think of IEEE488 recorders or
+        * parallel computer bus loggers. The scan option eliminates
+        * the issue of looking up previously assigned names before
+        * renaming a channel (see sigrok-cli -C), which depends on
+        * the device as well as the application, and is undesirable.
+        * The scan option is limited to those drivers which implement
+        * support for it, but works identically across those drivers.
+        *
+        * The value is a string, either a comma separated list of
+        * probe names, or an alias for a typical set of names.
+        */
+       SR_CONF_PROBE_NAMES,
+
        /* Update sr_key_info_config[] (hwdriver.c) upon changes! */
 
        /*--- Device (or channel group) configuration -----------------------*/
@@ -1083,6 +1129,14 @@ enum sr_configkey {
         */
        SR_CONF_RESISTANCE_TARGET,
 
+       /**
+        * Over-current protection (OCP) delay
+        * @arg type: double (time)
+        * @arg get: get current delay
+        * @arg set: set new delay
+        */
+       SR_CONF_OVER_CURRENT_PROTECTION_DELAY,
+
        /* Update sr_key_info_config[] (hwdriver.c) upon changes! */
 
        /*--- Special stuff -------------------------------------------------*/
index 41d9cfcc672931f62bac06a648d24a171bcceb76..2c3b255972d7f2a5655f8c5055c4db438b82b834 100644 (file)
@@ -166,7 +166,7 @@ SR_API const char *sr_input_name_get(const struct sr_input_module *imod);
 SR_API const char *sr_input_description_get(const struct sr_input_module *imod);
 SR_API const char *const *sr_input_extensions_get(
                const struct sr_input_module *imod);
-SR_API const struct sr_input_module *sr_input_find(char *id);
+SR_API const struct sr_input_module *sr_input_find(const char *id);
 SR_API const struct sr_option **sr_input_options_get(const struct sr_input_module *imod);
 SR_API void sr_input_options_free(const struct sr_option **options);
 SR_API struct sr_input *sr_input_new(const struct sr_input_module *imod,
@@ -253,6 +253,10 @@ SR_API uint64_t sr_parse_timestring(const char *timestring);
 SR_API gboolean sr_parse_boolstring(const char *boolstring);
 SR_API int sr_parse_period(const char *periodstr, uint64_t *p, uint64_t *q);
 SR_API int sr_parse_voltage(const char *voltstr, uint64_t *p, uint64_t *q);
+SR_API char **sr_parse_probe_names(const char *spec,
+       const char **dflt_names, size_t dflt_count,
+       size_t max_count, size_t *ret_count);
+SR_API void sr_free_probe_names(char **names);
 SR_API int sr_sprintf_ascii(char *buf, const char *format, ...);
 SR_API int sr_vsprintf_ascii(char *buf, const char *format, va_list args);
 SR_API int sr_snprintf_ascii(char *buf, size_t buf_size,
@@ -260,6 +264,11 @@ SR_API int sr_snprintf_ascii(char *buf, size_t buf_size,
 SR_API int sr_vsnprintf_ascii(char *buf, size_t buf_size,
                const char *format, va_list args);
 SR_API int sr_parse_rational(const char *str, struct sr_rational *ret);
+SR_API char *sr_text_trim_spaces(char *s);
+SR_API char *sr_text_next_line(char *s, size_t l, char **next, size_t *taken);
+SR_API char *sr_text_next_word(char *s, char **next);
+
+SR_API int sr_next_power_of_two(size_t value, size_t *bits, size_t *power);
 
 /*--- version.c -------------------------------------------------------------*/
 
index 1c977bd854766e5a828092024309baa2a775d84b..385097e81e58b7837972817560c94674d04d1987 100644 (file)
 #ifndef LIBSIGROK_VERSION_H
 #define LIBSIGROK_VERSION_H
 
+/*
+ * Only libsigrok library builds, and only parts of the library build,
+ * need to reference the git-version.h header file. Which contains the
+ * version suffix, which is relevant to local development, but is not
+ * applicable to release builds. Application builds need not bother with
+ * internal library version details, and always can get this information
+ * in text form for display purposes from the library at runtime.
+ */
+#if defined WANT_LIBSIGROK_GIT_VERSION_H
+#  include <libsigrok/git-version.h>
+#else
+#  undef SR_PACKAGE_VERSION_STRING_SUFFIX
+#  define SR_PACKAGE_VERSION_STRING_SUFFIX ""
+#endif
+
 /**
  * @file
  *
 #undef SR_PACKAGE_VERSION_MICRO
 
 /** The libsigrok package version ("major.minor.micro") as string. */
-#undef SR_PACKAGE_VERSION_STRING
+#define SR_PACKAGE_VERSION_STRING_PREFIX
+
+/** The libsigrok package version with git commit suffix. */
+#define SR_PACKAGE_VERSION_STRING (SR_PACKAGE_VERSION_STRING_PREFIX SR_PACKAGE_VERSION_STRING_SUFFIX)
 
 /*
  * Library/libtool version macros (can be used for conditional compilation).
index 2c18e49c56c0bc732a4bd6534ae03f871d42c6db..a3d964c699aac74685d2407f3f01ad077d1a15a1 100644 (file)
@@ -1,5 +1,5 @@
 # ===========================================================================
-#   http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html
+#  https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html
 # ===========================================================================
 #
 # SYNOPSIS
 #
 #   Check for baseline language coverage in the compiler for the specified
 #   version of the C++ standard.  If necessary, add switches to CXX and
-#   CXXCPP to enable support.  VERSION may be '11' (for the C++11 standard)
-#   or '14' (for the C++14 standard).
+#   CXXCPP to enable support.  VERSION may be '11', '14', '17', or '20' for
+#   the respective C++ standard version.
 #
 #   The second argument, if specified, indicates whether you insist on an
 #   extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
 #   -std=c++11).  If neither is specified, you get whatever works, with
-#   preference for an extended mode.
+#   preference for no added switch, and then for an extended mode.
 #
 #   The third argument, if specified 'mandatory' or if left unspecified,
 #   indicates that baseline support for the specified C++ standard is
 #   Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
 #   Copyright (c) 2015 Paul Norman <penorman@mac.com>
 #   Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu>
+#   Copyright (c) 2016, 2018 Krzesimir Nowak <qdlacz@gmail.com>
+#   Copyright (c) 2019 Enji Cooper <yaneurabeya@gmail.com>
+#   Copyright (c) 2020 Jason Merrill <jason@redhat.com>
+#   Copyright (c) 2021 Jörn Heusipp <osmanx@problemloesungsmaschine.de>
 #
 #   Copying and distribution of this file, with or without modification, are
 #   permitted in any medium without royalty provided the copyright notice
 #   and this notice are preserved.  This file is offered as-is, without any
 #   warranty.
 
-#serial 4
+#serial 15
 
 dnl  This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro
 dnl  (serial version number 13).
 
 AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl
-  m4_if([$1], [11], [],
-        [$1], [14], [],
-        [$1], [17], [m4_fatal([support for C++17 not yet implemented in AX_CXX_COMPILE_STDCXX])],
+  m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"],
+        [$1], [14], [ax_cxx_compile_alternatives="14 1y"],
+        [$1], [17], [ax_cxx_compile_alternatives="17 1z"],
+        [$1], [20], [ax_cxx_compile_alternatives="20"],
         [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl
   m4_if([$2], [], [],
         [$2], [ext], [],
@@ -59,18 +64,21 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl
         [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])])
   AC_LANG_PUSH([C++])dnl
   ac_success=no
-  AC_CACHE_CHECK(whether $CXX supports C++$1 features by default,
-  ax_cv_cxx_compile_cxx$1,
-  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
-    [ax_cv_cxx_compile_cxx$1=yes],
-    [ax_cv_cxx_compile_cxx$1=no])])
-  if test x$ax_cv_cxx_compile_cxx$1 = xyes; then
-    ac_success=yes
-  fi
+
+  m4_if([$2], [], [dnl
+    AC_CACHE_CHECK(whether $CXX supports C++$1 features by default,
+                  ax_cv_cxx_compile_cxx$1,
+      [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+        [ax_cv_cxx_compile_cxx$1=yes],
+        [ax_cv_cxx_compile_cxx$1=no])])
+    if test x$ax_cv_cxx_compile_cxx$1 = xyes; then
+      ac_success=yes
+    fi])
 
   m4_if([$2], [noext], [], [dnl
   if test x$ac_success = xno; then
-    for switch in -std=gnu++$1 -std=gnu++0x; do
+    for alternative in ${ax_cxx_compile_alternatives}; do
+      switch="-std=gnu++${alternative}"
       cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
       AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
                      $cachevar,
@@ -96,22 +104,27 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl
     dnl HP's aCC needs +std=c++11 according to:
     dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf
     dnl Cray's crayCC needs "-h std=c++11"
-    for switch in -std=c++$1 -std=c++0x +std=c++$1 "-h std=c++$1"; do
-      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
-      AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
-                     $cachevar,
-        [ac_save_CXX="$CXX"
-         CXX="$CXX $switch"
-         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
-          [eval $cachevar=yes],
-          [eval $cachevar=no])
-         CXX="$ac_save_CXX"])
-      if eval test x\$$cachevar = xyes; then
-        CXX="$CXX $switch"
-        if test -n "$CXXCPP" ; then
-          CXXCPP="$CXXCPP $switch"
+    for alternative in ${ax_cxx_compile_alternatives}; do
+      for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do
+        cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
+        AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
+                       $cachevar,
+          [ac_save_CXX="$CXX"
+           CXX="$CXX $switch"
+           AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+            [eval $cachevar=yes],
+            [eval $cachevar=no])
+           CXX="$ac_save_CXX"])
+        if eval test x\$$cachevar = xyes; then
+          CXX="$CXX $switch"
+          if test -n "$CXXCPP" ; then
+            CXXCPP="$CXXCPP $switch"
+          fi
+          ac_success=yes
+          break
         fi
-        ac_success=yes
+      done
+      if test x$ac_success = xyes; then
         break
       fi
     done
@@ -140,7 +153,6 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11],
   _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
 )
 
-
 dnl  Test body for checking C++14 support
 
 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14],
@@ -148,6 +160,23 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14],
   _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
 )
 
+dnl  Test body for checking C++17 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17],
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_17
+)
+
+dnl  Test body for checking C++20 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20],
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_17
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_20
+)
+
 
 dnl  Tests for new features in C++11
 
@@ -160,7 +189,11 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[
 
 #error "This is not a C++ compiler"
 
-#elif __cplusplus < 201103L
+// MSVC always sets __cplusplus to 199711L in older versions; newer versions
+// only set it correctly if /Zc:__cplusplus is specified as well as a
+// /std:c++NN switch:
+// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
+#elif __cplusplus < 201103L && !defined _MSC_VER
 
 #error "This is not a C++11 compiler"
 
@@ -185,11 +218,13 @@ namespace cxx11
 
     struct Base
     {
+      virtual ~Base() {}
       virtual void f() {}
     };
 
     struct Derived : public Base
     {
+      virtual ~Derived() override {}
       virtual void f() override {}
     };
 
@@ -449,7 +484,7 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[
 
 #error "This is not a C++ compiler"
 
-#elif __cplusplus < 201402L
+#elif __cplusplus < 201402L && !defined _MSC_VER
 
 #error "This is not a C++14 compiler"
 
@@ -518,7 +553,7 @@ namespace cxx14
 
   }
 
-  namespace test_digit_seperators
+  namespace test_digit_separators
   {
 
     constexpr auto ten_million = 100'000'000;
@@ -560,3 +595,415 @@ namespace cxx14
 #endif  // __cplusplus >= 201402L
 
 ]])
+
+
+dnl  Tests for new features in C++17
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[
+
+// If the compiler admits that it is not ready for C++17, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201703L && !defined _MSC_VER
+
+#error "This is not a C++17 compiler"
+
+#else
+
+#include <initializer_list>
+#include <utility>
+#include <type_traits>
+
+namespace cxx17
+{
+
+  namespace test_constexpr_lambdas
+  {
+
+    constexpr int foo = [](){return 42;}();
+
+  }
+
+  namespace test::nested_namespace::definitions
+  {
+
+  }
+
+  namespace test_fold_expression
+  {
+
+    template<typename... Args>
+    int multiply(Args... args)
+    {
+      return (args * ... * 1);
+    }
+
+    template<typename... Args>
+    bool all(Args... args)
+    {
+      return (args && ...);
+    }
+
+  }
+
+  namespace test_extended_static_assert
+  {
+
+    static_assert (true);
+
+  }
+
+  namespace test_auto_brace_init_list
+  {
+
+    auto foo = {5};
+    auto bar {5};
+
+    static_assert(std::is_same<std::initializer_list<int>, decltype(foo)>::value);
+    static_assert(std::is_same<int, decltype(bar)>::value);
+  }
+
+  namespace test_typename_in_template_template_parameter
+  {
+
+    template<template<typename> typename X> struct D;
+
+  }
+
+  namespace test_fallthrough_nodiscard_maybe_unused_attributes
+  {
+
+    int f1()
+    {
+      return 42;
+    }
+
+    [[nodiscard]] int f2()
+    {
+      [[maybe_unused]] auto unused = f1();
+
+      switch (f1())
+      {
+      case 17:
+        f1();
+        [[fallthrough]];
+      case 42:
+        f1();
+      }
+      return f1();
+    }
+
+  }
+
+  namespace test_extended_aggregate_initialization
+  {
+
+    struct base1
+    {
+      int b1, b2 = 42;
+    };
+
+    struct base2
+    {
+      base2() {
+        b3 = 42;
+      }
+      int b3;
+    };
+
+    struct derived : base1, base2
+    {
+        int d;
+    };
+
+    derived d1 {{1, 2}, {}, 4};  // full initialization
+    derived d2 {{}, {}, 4};      // value-initialized bases
+
+  }
+
+  namespace test_general_range_based_for_loop
+  {
+
+    struct iter
+    {
+      int i;
+
+      int& operator* ()
+      {
+        return i;
+      }
+
+      const int& operator* () const
+      {
+        return i;
+      }
+
+      iter& operator++()
+      {
+        ++i;
+        return *this;
+      }
+    };
+
+    struct sentinel
+    {
+      int i;
+    };
+
+    bool operator== (const iter& i, const sentinel& s)
+    {
+      return i.i == s.i;
+    }
+
+    bool operator!= (const iter& i, const sentinel& s)
+    {
+      return !(i == s);
+    }
+
+    struct range
+    {
+      iter begin() const
+      {
+        return {0};
+      }
+
+      sentinel end() const
+      {
+        return {5};
+      }
+    };
+
+    void f()
+    {
+      range r {};
+
+      for (auto i : r)
+      {
+        [[maybe_unused]] auto v = i;
+      }
+    }
+
+  }
+
+  namespace test_lambda_capture_asterisk_this_by_value
+  {
+
+    struct t
+    {
+      int i;
+      int foo()
+      {
+        return [*this]()
+        {
+          return i;
+        }();
+      }
+    };
+
+  }
+
+  namespace test_enum_class_construction
+  {
+
+    enum class byte : unsigned char
+    {};
+
+    byte foo {42};
+
+  }
+
+  namespace test_constexpr_if
+  {
+
+    template <bool cond>
+    int f ()
+    {
+      if constexpr(cond)
+      {
+        return 13;
+      }
+      else
+      {
+        return 42;
+      }
+    }
+
+  }
+
+  namespace test_selection_statement_with_initializer
+  {
+
+    int f()
+    {
+      return 13;
+    }
+
+    int f2()
+    {
+      if (auto i = f(); i > 0)
+      {
+        return 3;
+      }
+
+      switch (auto i = f(); i + 4)
+      {
+      case 17:
+        return 2;
+
+      default:
+        return 1;
+      }
+    }
+
+  }
+
+  namespace test_template_argument_deduction_for_class_templates
+  {
+
+    template <typename T1, typename T2>
+    struct pair
+    {
+      pair (T1 p1, T2 p2)
+        : m1 {p1},
+          m2 {p2}
+      {}
+
+      T1 m1;
+      T2 m2;
+    };
+
+    void f()
+    {
+      [[maybe_unused]] auto p = pair{13, 42u};
+    }
+
+  }
+
+  namespace test_non_type_auto_template_parameters
+  {
+
+    template <auto n>
+    struct B
+    {};
+
+    B<5> b1;
+    B<'a'> b2;
+
+  }
+
+  namespace test_structured_bindings
+  {
+
+    int arr[2] = { 1, 2 };
+    std::pair<int, int> pr = { 1, 2 };
+
+    auto f1() -> int(&)[2]
+    {
+      return arr;
+    }
+
+    auto f2() -> std::pair<int, int>&
+    {
+      return pr;
+    }
+
+    struct S
+    {
+      int x1 : 2;
+      volatile double y1;
+    };
+
+    S f3()
+    {
+      return {};
+    }
+
+    auto [ x1, y1 ] = f1();
+    auto& [ xr1, yr1 ] = f1();
+    auto [ x2, y2 ] = f2();
+    auto& [ xr2, yr2 ] = f2();
+    const auto [ x3, y3 ] = f3();
+
+  }
+
+  namespace test_exception_spec_type_system
+  {
+
+    struct Good {};
+    struct Bad {};
+
+    void g1() noexcept;
+    void g2();
+
+    template<typename T>
+    Bad
+    f(T*, T*);
+
+    template<typename T1, typename T2>
+    Good
+    f(T1*, T2*);
+
+    static_assert (std::is_same_v<Good, decltype(f(g1, g2))>);
+
+  }
+
+  namespace test_inline_variables
+  {
+
+    template<class T> void f(T)
+    {}
+
+    template<class T> inline T g(T)
+    {
+      return T{};
+    }
+
+    template<> inline void f<>(int)
+    {}
+
+    template<> int g<>(int)
+    {
+      return 5;
+    }
+
+  }
+
+}  // namespace cxx17
+
+#endif  // __cplusplus < 201703L && !defined _MSC_VER
+
+]])
+
+
+dnl  Tests for new features in C++20
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 202002L && !defined _MSC_VER
+
+#error "This is not a C++20 compiler"
+
+#else
+
+#include <version>
+
+namespace cxx20
+{
+
+// As C++20 supports feature test macros in the standard, there is no
+// immediate need to actually test for feature availability on the
+// Autoconf side.
+
+}  // namespace cxx20
+
+#endif  // __cplusplus < 202002L && !defined _MSC_VER
+
+]])
index 2ca2444dfddf813abe3d26b94ed3d4d64c5d412f..5dc296090623b85c6ab1bbed484f8c448f582d53 100644 (file)
@@ -52,34 +52,48 @@ m4_define([_SR_PKG_VERSION_SET],
 [dnl
 m4_assert([$# >= 6])[]dnl
 $1=$4
+dnl Check if we can get version control details. Re-configure when
+dnl branches change (when HEAD starts pointing somewhere else).
+dnl Track individual revisions at compile time, and only as a local
+dnl dependency of just a part of the library build. In other words:
+dnl Don't re-configure and re-build everything just because a commit
+dnl happened). Checks for tagged sources also happen at compile time.
 sr_git_deps=
-# Check if we can get revision information from git.
 sr_head=`git -C "$srcdir" rev-parse --verify --short HEAD 2>&AS_MESSAGE_LOG_FD`
-
-AS_IF([test "$?" = 0 && test "x$sr_head" != x], [dnl
-       test ! -f "$srcdir/.git/HEAD" \
-               || sr_git_deps="$sr_git_deps \$(top_srcdir)/.git/HEAD"
-
+AS_IF([test "$?" = 0 && test -n "$sr_head"], [dnl
+       test ! -f "$srcdir/.git/HEAD" || \
+               sr_git_deps="$sr_git_deps ${ac_abs_confdir}/.git/HEAD"
        sr_head_name=`git -C "$srcdir" rev-parse --symbolic-full-name HEAD 2>&AS_MESSAGE_LOG_FD`
-       AS_IF([test "$?" = 0 && test -f "$srcdir/.git/$sr_head_name"],
-               [sr_git_deps="$sr_git_deps \$(top_srcdir)/.git/$sr_head_name"])
-
-       # Append the revision hash unless we are exactly on a tagged release.
-       git -C "$srcdir" describe --match "$3$4" \
-               --exact-match >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD \
-               || $1="[$]$1-git-$sr_head"
+       AS_IF([test "$?" = 0 && test -f "$srcdir/.git/$sr_head_name"], [dnl
+               sr_head_file="${ac_abs_confdir}/.git/$sr_head_name"
+               AC_SUBST(VERSION_HEAD_FILE, [$sr_head_file])
+       ])
+       sr_hash=`git -C "$srcdir" describe --match "$3$4" --always --dirty`
+       sr_hash=`echo "$sr_hash" | sed 's/$3$4-//'` && \
+       $1_STRING_SUFFIX=`git -C "$srcdir" describe --match "$3$4" --exact-match > /dev/null 2> /dev/null || echo "-$sr_hash"`
+       AS_IF([test -n "$1_STRING_SUFFIX"], [$1="[$]$1-git"])
 ])
-# Use $(wildcard) so that things do not break if for whatever
-# reason these files do not exist anymore at make time.
+AM_CONDITIONAL([VCS_IS_GIT], [test -n "$sr_git_deps"])
+# Use $(wildcard) so that things do not break if for whatever reason
+# these files do not exist anymore at make time.
 AS_IF([test -n "$sr_git_deps"],
-       [SR_APPEND([CONFIG_STATUS_DEPENDENCIES], ["\$(wildcard$sr_git_deps)"])])
+       [SR_APPEND([CONFIG_STATUS_DEPENDENCIES], ["\$(wildcard $sr_git_deps)"])])
 AC_SUBST([CONFIG_STATUS_DEPENDENCIES])[]dnl
+AS_IF([test -n "$sr_git_deps$sr_head_file"],
+       [SR_APPEND([VERSION_GITVERSION_DEPS], ["\$(wildcard $sr_git_deps $sr_head_file)"])])
+AC_SUBST(VERSION_GITVERSION_DEPS)[]dnl
+AC_SUBST(VERSION_SOURCE_DIR, [${ac_abs_confdir}])
+AC_SUBST(VERSION_TAG_PREFIX, [$3])[]dnl
+AC_SUBST(VERSION_TAG_NUMBER, [$4])[]dnl
+AC_SUBST(VERSION_TAG_MATCH, [$3$4])[]dnl
+dnl End of git version control details gathering.
+dnl
 AC_SUBST([$1])[]dnl
 dnl
 AC_DEFINE([$1_MAJOR], [$5], [Major version number of $2.])[]dnl
 AC_DEFINE([$1_MINOR], [$6], [Minor version number of $2.])[]dnl
 m4_ifval([$7], [AC_DEFINE([$1_MICRO], [$7], [Micro version number of $2.])])[]dnl
-AC_DEFINE_UNQUOTED([$1_STRING], ["[$]$1"], [Version of $2.])[]dnl
+AC_DEFINE_UNQUOTED([$1_STRING_PREFIX], ["[$]$1"], [Version of $2.])[]dnl
 ])
 
 ## SR_PKG_VERSION_SET(var-prefix, version-triple)
index f5cc628500138f5a5dd901b3240629abbaf8dbfb..76ea659caf99ec54a33813a023c2d6a6f51279c8 100644 (file)
@@ -492,8 +492,7 @@ SR_API int sr_analog_unit_to_string(const struct sr_datafeed_analog *analog,
                if (analog->meaning->mqflags & mq_strings[i].value)
                        g_string_append(buf, mq_strings[i].str);
 
-       *result = buf->str;
-       g_string_free(buf, FALSE);
+       *result = g_string_free(buf, FALSE);
 
        return SR_OK;
 }
index 86c4c5838fce2bf470524bd3c2a0b8859a741514..14196110475e00c34d66bdb32d194587437af307 100644 (file)
@@ -22,7 +22,8 @@
 #include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
 
-SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const void *data, size_t length)
+SR_PRIV int bv_get_value_len(float *out, const struct binary_value_spec *spec,
+       const uint8_t *data, size_t length)
 {
        float value;
 
@@ -37,17 +38,15 @@ SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const
                break
 
        switch (spec->type) {
-               VALUE_TYPE(BVT_UINT8, R8, sizeof(uint8_t));
+       VALUE_TYPE(BVT_UINT8, read_u8, sizeof(uint8_t));
 
-               VALUE_TYPE(BVT_BE_UINT16, RB16, sizeof(uint16_t));
-               VALUE_TYPE(BVT_BE_UINT32, RB32, sizeof(uint32_t));
-               VALUE_TYPE(BVT_BE_UINT64, RB64, sizeof(uint64_t));
-               VALUE_TYPE(BVT_BE_FLOAT, RBFL, sizeof(float));
+       VALUE_TYPE(BVT_BE_UINT16, read_u16be, sizeof(uint16_t));
+       VALUE_TYPE(BVT_BE_UINT24, read_u24be, 3 * sizeof(uint8_t));
+       VALUE_TYPE(BVT_BE_UINT32, read_u32be, sizeof(uint32_t));
 
-               VALUE_TYPE(BVT_LE_UINT16, RL16, sizeof(uint16_t));
-               VALUE_TYPE(BVT_LE_UINT32, RL32, sizeof(uint32_t));
-               VALUE_TYPE(BVT_LE_UINT64, RL64, sizeof(uint64_t));
-               VALUE_TYPE(BVT_LE_FLOAT, RLFL, sizeof(float));
+       VALUE_TYPE(BVT_LE_UINT16, read_u16le, sizeof(uint16_t));
+       VALUE_TYPE(BVT_LE_UINT24, read_u24le, 3 * sizeof(uint8_t));
+       VALUE_TYPE(BVT_LE_UINT32, read_u32le, sizeof(uint32_t));
 
        default:
                return SR_ERR_ARG;
@@ -55,51 +54,46 @@ SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const
 
 #undef VALUE_TYPE
 
-       *out = value * spec->scale;
+       if (out)
+               *out = value;
        return SR_OK;
 }
 
-SR_PRIV int bv_send_analog_channel(const struct sr_dev_inst *sdi, struct sr_channel *ch,
-                                  const struct binary_analog_channel *bac, const void *data, size_t length)
+SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec,
+       const uint8_t *data)
 {
-       int err;
-       struct sr_analog_encoding encoding;
-       struct sr_analog_meaning meaning;
-       struct sr_analog_spec spec;
-       struct sr_datafeed_analog analog;
-       struct sr_datafeed_packet packet = {
-               .type = SR_DF_ANALOG,
-               .payload = &analog,
-       };
        float value;
+       const uint8_t *ptr;
 
-       err = bv_get_value(&value, &bac->spec, data, length);
-       if (err != SR_OK)
-               goto err_out;
+       ptr = &data[spec->offset];
 
-       err = sr_analog_init(&analog, &encoding, &meaning, &spec, bac->digits);
-       if (err != SR_OK)
-               goto err_out;
-
-       meaning.mq = bac->mq;
-       meaning.unit = bac->unit;
-       meaning.mqflags = 0;
-       meaning.channels = g_slist_append(NULL, ch);
-
-       spec.spec_digits = bac->digits;
-
-       analog.data = &value;
-       analog.num_samples = 1;
-
-       err = sr_session_send(sdi, &packet);
-       if (err != SR_OK)
-               goto err_free;
+       switch (spec->type) {
+       case BVT_UINT8:
+               value = read_u8(ptr);
+               break;
+       case BVT_BE_UINT16:
+               value = read_u16be(ptr);
+               break;
+       case BVT_BE_UINT24:
+               value = read_u24be(ptr);
+               break;
+       case BVT_BE_UINT32:
+               value = read_u32be(ptr);
+               break;
+       case BVT_LE_UINT16:
+               value = read_u16le(ptr);
+               break;
+       case BVT_LE_UINT24:
+               value = read_u24le(ptr);
+               break;
+       case BVT_LE_UINT32:
+               value = read_u32le(ptr);
+               break;
+       default:
+               return SR_ERR_ARG;
+       }
 
+       if (out)
+               *out = value;
        return SR_OK;
-
-err_free:
-       g_slist_free(meaning.channels);
-
-err_out:
-       return err;
 }
index d0a3b7c847605ca2d427fb8e93f9e36eea02ded0..dc3a2636597dde2a525a1d094e618d255826e42e 100644 (file)
@@ -74,6 +74,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <glib.h>
+#include <inttypes.h>
 #include <poll.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -96,9 +97,6 @@
 #define CONNECT_RFCOMM_TRIES   3
 #define CONNECT_RFCOMM_RETRY_MS        100
 
-/* Silence warning about (currently) unused routine. */
-#define WITH_WRITE_TYPE_HANDLE 0
-
 /* {{{ compat decls */
 /*
  * The availability of conversion helpers in <bluetooth/bluetooth.h>
@@ -239,6 +237,7 @@ struct sr_bt_desc {
        uint16_t write_handle;
        uint16_t cccd_handle;
        uint16_t cccd_value;
+       uint16_t ble_mtu;
        /* Internal state. */
        int devid;
        int fd;
@@ -249,10 +248,8 @@ static int sr_bt_desc_open(struct sr_bt_desc *desc, int *id_ref);
 static void sr_bt_desc_close(struct sr_bt_desc *desc);
 static int sr_bt_check_socket_usable(struct sr_bt_desc *desc);
 static ssize_t sr_bt_write_type(struct sr_bt_desc *desc, uint8_t type);
-#if WITH_WRITE_TYPE_HANDLE
 static ssize_t sr_bt_write_type_handle(struct sr_bt_desc *desc,
        uint8_t type, uint16_t handle);
-#endif
 static ssize_t sr_bt_write_type_handle_bytes(struct sr_bt_desc *desc,
        uint8_t type, uint16_t handle, const uint8_t *data, size_t len);
 static ssize_t sr_bt_char_write_req(struct sr_bt_desc *desc,
@@ -365,7 +362,8 @@ SR_PRIV int sr_bt_config_rfcomm(struct sr_bt_desc *desc, size_t channel)
 
 SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc,
        uint16_t read_handle, uint16_t write_handle,
-       uint16_t cccd_handle, uint16_t cccd_value)
+       uint16_t cccd_handle, uint16_t cccd_value,
+       uint16_t ble_mtu)
 {
 
        if (!desc)
@@ -375,6 +373,7 @@ SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc,
        desc->write_handle = write_handle;
        desc->cccd_handle = cccd_handle;
        desc->cccd_value = cccd_value;
+       desc->ble_mtu = ble_mtu;
 
        return 0;
 }
@@ -902,10 +901,15 @@ SR_PRIV int sr_bt_check_notify(struct sr_bt_desc *desc)
 {
        uint8_t buf[1024];
        ssize_t rdlen;
+       const uint8_t *bufptr;
+       size_t buflen;
        uint8_t packet_type;
        uint16_t packet_handle;
        uint8_t *packet_data;
        size_t packet_dlen;
+       const char *type_text;
+       int ret;
+       uint16_t mtu;
 
        if (!desc)
                return -1;
@@ -913,57 +917,113 @@ SR_PRIV int sr_bt_check_notify(struct sr_bt_desc *desc)
        if (sr_bt_check_socket_usable(desc) < 0)
                return -2;
 
-       /* Get another message from the Bluetooth socket. */
+       /*
+        * Get another message from the Bluetooth socket.
+        *
+        * TODO Can we assume that every "message" comes in a separate
+        * read(2) call, or can data combine at the caller's? Need we
+        * loop over the received content until all was consumed?
+        */
        rdlen = sr_bt_read(desc, buf, sizeof(buf));
-       if (rdlen < 0)
+       if (rdlen < 0) {
+               sr_dbg("check notifiy, read error, %zd", rdlen);
                return -2;
-       if (!rdlen)
+       }
+       if (!rdlen) {
+               if (0) sr_spew("check notifiy, empty read");
                return 0;
+       }
+       bufptr = &buf[0];
+       buflen = (size_t)rdlen;
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *txt;
+               txt = sr_hexdump_new(bufptr, buflen);
+               sr_spew("check notifiy, read succes, length %zd, data: %s",
+                       rdlen, txt->str);
+               sr_hexdump_free(txt);
+       }
 
-       /* Get header fields and references to the payload data. */
-       packet_type = 0x00;
+       /*
+        * Get header fields and references to the payload data. Notice
+        * that the first 16bit item after the packet type often is the
+        * handle, but need not always be. That is why the read position
+        * is kept, so that individual packet type handlers can either
+        * read _their_ layout strictly sequentially, or can conveniently
+        * access what a common preparation step has provided to them.
+        */
        packet_handle = 0x0000;
        packet_data = NULL;
        packet_dlen = 0;
-       if (rdlen >= 1)
-               packet_type = buf[0];
-       if (rdlen >= 3) {
-               packet_handle = bt_get_le16(&buf[1]);
-               packet_data = &buf[3];
-               packet_dlen = rdlen - 3;
+       packet_type = read_u8_inc_len(&bufptr, &buflen);
+       if (buflen >= sizeof(uint16_t)) {
+               packet_handle = read_u16le(bufptr);
+               packet_data = (void *)&bufptr[sizeof(uint16_t)];
+               packet_dlen = buflen - sizeof(uint16_t);
+               if (!packet_dlen)
+                       packet_data = NULL;
        }
+       if (0) sr_spew("check notifiy, prep, hdl %" PRIu16 ", data %p len %zu",
+               packet_handle, packet_data, packet_dlen);
 
        /* Dispatch according to the message type. */
        switch (packet_type) {
+       case BLE_ATT_EXCHANGE_MTU_REQ:
+               type_text = "MTU exchange request";
+               if (buflen < sizeof(uint16_t)) {
+                       sr_dbg("%s, invalid (size)", type_text);
+                       break;
+               }
+               mtu = read_u16le_inc_len(&bufptr, &buflen);
+               sr_dbg("%s, peripheral value %" PRIu16, type_text, mtu);
+               if (desc->ble_mtu) {
+                       mtu = desc->ble_mtu;
+                       sr_dbg("%s, central value %" PRIu16, type_text, mtu);
+                       sr_bt_write_type_handle(desc,
+                               BLE_ATT_EXCHANGE_MTU_RESP, mtu);
+                       break;
+               }
+               sr_warn("Unhandled BLE %s.", type_text);
+               break;
        case BLE_ATT_ERROR_RESP:
-               sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "error response");
+               type_text = "error response";
+               if (!buflen) {
+                       sr_dbg("%s, no payload", type_text);
+                       break;
+               }
                /* EMPTY */
+               sr_dbg("%s, not handled here", type_text);
                break;
        case BLE_ATT_WRITE_RESP:
-               sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "write response");
-               /* EMPTY */
+               type_text = "write response";
+               sr_dbg("%s, note taken", type_text);
                break;
        case BLE_ATT_HANDLE_INDICATION:
-               sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "handle indication");
+               type_text = "handle indication";
+               sr_dbg("%s, data len %zu", type_text, packet_dlen);
                sr_bt_write_type(desc, BLE_ATT_HANDLE_CONFIRMATION);
+               sr_spew("%s, confirmation sent", type_text);
                if (packet_handle != desc->read_handle)
                        return -4;
-               if (!packet_data)
-                       return -4;
                if (!desc->data_cb)
                        return 0;
-               return desc->data_cb(desc->data_cb_data, packet_data, packet_dlen);
+               ret = desc->data_cb(desc->data_cb_data,
+                       packet_data, packet_dlen);
+               sr_spew("%s, data cb ret %d", type_text, ret);
+               return ret;
        case BLE_ATT_HANDLE_NOTIFICATION:
-               sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "handle notification");
+               type_text = "handle notification";
+               sr_dbg("%s, data len %zu", type_text, packet_dlen);
                if (packet_handle != desc->read_handle)
                        return -4;
-               if (!packet_data)
-                       return -4;
                if (!desc->data_cb)
                        return 0;
-               return desc->data_cb(desc->data_cb_data, packet_data, packet_dlen);
+               ret = desc->data_cb(desc->data_cb_data,
+                       packet_data, packet_dlen);
+               sr_spew("%s, data cb ret %d", type_text, ret);
+               return ret;
        default:
-               sr_spew("unsupported type 0x%02x", packet_type);
+               sr_dbg("unhandled type 0x%02x, len %zu",
+                       packet_type, buflen);
                return -3;
        }
 
@@ -1013,13 +1073,11 @@ static ssize_t sr_bt_write_type(struct sr_bt_desc *desc, uint8_t type)
        return 0;
 }
 
-#if WITH_WRITE_TYPE_HANDLE
 static ssize_t sr_bt_write_type_handle(struct sr_bt_desc *desc,
        uint8_t type, uint16_t handle)
 {
        return sr_bt_write_type_handle_bytes(desc, type, handle, NULL, 0);
 }
-#endif
 
 static ssize_t sr_bt_write_type_handle_bytes(struct sr_bt_desc *desc,
        uint8_t type, uint16_t handle, const uint8_t *data, size_t len)
index 5a144d97ebccf3b160117b64f17c3be604e9367f..5c681378fab01a99a33befa74dc0df3754e0a1ae 100644 (file)
@@ -67,7 +67,7 @@ SR_PRIV struct sr_channel *sr_channel_new(struct sr_dev_inst *sdi,
        ch->index = index;
        ch->type = type;
        ch->enabled = enabled;
-       if (name)
+       if (name && *name)
                ch->name = g_strdup(name);
 
        sdi->channels = g_slist_append(sdi->channels, ch);
@@ -120,6 +120,8 @@ SR_API int sr_dev_channel_name_set(struct sr_channel *channel,
 {
        if (!channel)
                return SR_ERR_ARG;
+       if (!name || !*name)
+               return SR_ERR_ARG;
 
        g_free(channel->name);
        channel->name = g_strdup(name);
@@ -255,6 +257,62 @@ SR_PRIV gboolean sr_channel_lists_differ(GSList *l1, GSList *l2)
        return FALSE;
 }
 
+/**
+ * Allocate and initialize a new channel group, and add it to sdi.
+ *
+ * @param[in] sdi The device instance the channel group is connected to.
+ *                Optional, can be NULL.
+ * @param[in] name @copydoc sr_channel_group::name
+ * @param[in] priv @copydoc sr_channel_group::priv
+ *
+ * @return A pointer to a new struct sr_channel_group, NULL upon error.
+ *
+ * @private
+ */
+SR_PRIV struct sr_channel_group *sr_channel_group_new(struct sr_dev_inst *sdi,
+       const char *name, void *priv)
+{
+       struct sr_channel_group *cg;
+
+       cg = g_malloc0(sizeof(*cg));
+       if (name && *name)
+               cg->name = g_strdup(name);
+       cg->priv = priv;
+
+       if (sdi)
+               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+
+       return cg;
+}
+
+/**
+ * Release a previously allocated struct sr_channel_group.
+ *
+ * @param[in] cg Pointer to struct sr_channel_group.
+ *
+ * @private
+ */
+SR_PRIV void sr_channel_group_free(struct sr_channel_group *cg)
+{
+       if (!cg)
+               return;
+
+       g_free(cg->name);
+       g_slist_free(cg->channels);
+       g_free(cg->priv);
+       g_free(cg);
+}
+
+/**
+ * Wrapper around sr_channel_group_free(), suitable for glib iterators.
+ *
+ * @private
+ */
+SR_PRIV void sr_channel_group_free_cb(void *cg)
+{
+       return sr_channel_group_free(cg);
+}
+
 /**
  * Determine whether the specified device instance has the specified
  * capability.
@@ -433,7 +491,8 @@ SR_API int sr_dev_inst_channel_add(struct sr_dev_inst *sdi, int index, int type,
        if (!sdi || sdi->inst_type != SR_INST_USER || index < 0)
                return SR_ERR_ARG;
 
-       sr_channel_new(sdi, index, type, TRUE, name);
+       if (!sr_channel_new(sdi, index, type, TRUE, name))
+               return SR_ERR_DATA;
 
        return SR_OK;
 }
@@ -448,7 +507,6 @@ SR_API int sr_dev_inst_channel_add(struct sr_dev_inst *sdi, int index, int type,
 SR_PRIV void sr_dev_inst_free(struct sr_dev_inst *sdi)
 {
        struct sr_channel *ch;
-       struct sr_channel_group *cg;
        GSList *l;
 
        if (!sdi)
@@ -459,15 +517,7 @@ SR_PRIV void sr_dev_inst_free(struct sr_dev_inst *sdi)
                sr_channel_free(ch);
        }
        g_slist_free(sdi->channels);
-
-       for (l = sdi->channel_groups; l; l = l->next) {
-               cg = l->data;
-               g_free(cg->name);
-               g_slist_free(cg->channels);
-               g_free(cg->priv);
-               g_free(cg);
-       }
-       g_slist_free(sdi->channel_groups);
+       g_slist_free_full(sdi->channel_groups, sr_channel_group_free_cb);
 
        if (sdi->session)
                sr_session_dev_remove(sdi->session, sdi);
@@ -519,6 +569,15 @@ SR_PRIV void sr_usb_dev_inst_free(struct sr_usb_dev_inst *usb)
        g_free(usb);
 }
 
+/**
+ * Wrapper for g_slist_free_full() convenience.
+ *
+ * @private
+ */
+SR_PRIV void sr_usb_dev_inst_free_cb(gpointer p)
+{
+       sr_usb_dev_inst_free(p);
+}
 #endif
 
 #ifdef HAVE_SERIAL_COMM
index 89c393a92348634423502cb858c50e6ce35612a5..e5379f12f734388b09e22e0656d56318f4ce19eb 100644 (file)
  *
  * Brymen BM52x serial protocol parser. The USB protocol (for the cable)
  * and the packet description (for the meter) were retrieved from:
- * http://brymen.com/product-html/Download2.html
- * http://brymen.com/product-html/PD02BM520s_protocolDL.html
- * http://brymen.com/product-html/images/DownloadList/ProtocolList/BM520-BM520s_List/BM520-BM520s-10000-count-professional-dual-display-mobile-logging-DMMs-protocol.zip
+ * http://www.brymen.com/Download2.html
+ * http://www.brymen.com/PD02BM520s_protocolDL.html
+ * http://www.brymen.com/images/DownloadList/ProtocolList/BM520-BM520s_List/BM520-BM520s-10000-count-professional-dual-display-mobile-logging-DMMs-protocol.zip
+ * http://web.archive.org/web/20180119175327/http://brymen.com/product-html/images/DownloadList/ProtocolList/BM520-BM520s_List/BM520-BM520s-10000-count-professional-dual-display-mobile-logging-DMMs-protocol.zip
  *
  * This parser was initially created for BM520s devices and tested with
  * BM525s. The Brymen BM820s family of devices uses the same protocol,
@@ -1362,7 +1363,7 @@ SR_PRIV int brymen_bm52x_config_get(void *st, uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        struct brymen_bm52x_state *state;
-       char text[20];
+       char text[32];
 
        state = st;
 
index f6528885304a3a1f41284ff790b835c5bdfed692..ebab59fc5fd61f066a332392fc237bf841b38d57 100644 (file)
  *
  * Protocol parser for Brymen BM850s DMM packets. The USB protocol (for the
  * cable) and the packet description (for the meter) were retrieved from:
- * http://brymen.com/product-html/Download2.html
- * http://brymen.com/product-html/PD02BM850s_protocolDL.html
- * http://brymen.com/product-html/images/DownloadList/ProtocolList/BM850-BM850a-BM850s_List/BM850-BM850a-BM850s-500000-count-DMM-protocol-BC85X-BC85Xa.zip
+ * http://www.brymen.com/Download2.html
+ * http://www.brymen.com/PD02BM850s_protocolDL.html
+ * http://www.brymen.com/images/DownloadList/ProtocolList/BM850-BM850a-BM850s_List/BM850-BM850a-BM850s-500000-count-DMM-protocol-BC85X-BC85Xa.zip
+ * http://web.archive.org/web/20180119175500/http://brymen.com/product-html/images/DownloadList/ProtocolList/BM850-BM850a-BM850s_List/BM850-BM850a-BM850s-500000-count-DMM-protocol-BC85X-BC85Xa.zip
  *
  * Implementor's notes on the protocol:
  * - The BM85x devices require a low RTS pulse after COM port open and
index b38c3123d3d55a616c21616ddc2e72443523f1fa..c160a88c033fc452b0e740a94d196700ba81818d 100644 (file)
  *
  * Brymen BM86x serial protocol parser. The USB protocol (for the cable)
  * and the packet description (for the meter) were retrieved from:
- * http://brymen.com/product-html/Download2.html
- * http://brymen.com/product-html/PD02BM860s_protocolDL.html
- * http://brymen.com/product-html/images/DownloadList/ProtocolList/BM860-BM860s_List/BM860-BM860s-500000-count-dual-display-DMMs-protocol.pdf
+ * http://www.brymen.com/Download2.html
+ * http://www.brymen.com/PD02BM860s_protocolDL.html
+ * http://www.brymen.com/images/DownloadList/ProtocolList/BM860-BM860s_List/BM860-BM860s-500000-count-dual-display-DMMs-protocol.pdf
+ * http://web.archive.org/web/20191231053213/http://www.brymen.com/images/DownloadList/ProtocolList/BM860-BM860s_List/BM860-BM860s-500000-count-dual-display-DMMs-protocol.pdf
  */
 
 #include <config.h>
@@ -59,6 +60,22 @@ SR_PRIV gboolean sr_brymen_bm86x_packet_valid(const uint8_t *buf)
        if (buf[19] != 0x86)
                return FALSE;
 
+       /*
+        * 2021-05 update: The devices that we have seen in the field do
+        * provide four bytes which we can synchronize to. Which happens
+        * to match the bm52x and bm82x devices' protocol. This condition
+        * is not documented by the vendor, but improves reliability of
+        * the re-synchronization for slight offsets, which were seen
+        * in the field, and which would have kept failing in an earlier
+        * implementation.
+        */
+       if (buf[16] != 0x86)
+               return FALSE;
+       if (buf[17] != 0x86)
+               return FALSE;
+       if (buf[18] != 0x86)
+               return FALSE;
+
        return TRUE;
 }
 
@@ -286,6 +303,8 @@ static void brymen_bm86x_parse(const uint8_t *buf, float *floatval,
                        NULL, &temp_unit, NULL, 0x80);
                ret = brymen_bm86x_parse_digits(&buf[9], 4, txtbuf,
                        floatval, NULL, &digits, 0x10);
+               if (ret != SR_OK)
+                       return;
 
                /* SI unit. */
                if (buf[14] & 0x08) {
index 7dfba8d0dfbec0078857600718b6fa4c8a3f8d11..ac7622078c2dafa55f6d233f6cd2edebb479bd79 100644 (file)
@@ -762,6 +762,8 @@ static int sr_eev121gw_parse(const uint8_t *buf, float *floatval,
                ser_mon = FIELD_NL(raw_serial, SERIAL_MONTH);
                ser_nr = FIELD_NL(raw_serial, SERIAL_NUMBER);
                sr_spew("Packet: Y-M %x-%x, nr %x.", ser_year, ser_mon, ser_nr);
+       } else {
+               (void)raw_serial;       /* Silence compiler warning. */
        }
 
        switch (display) {
index d93052a85b84bc7cbfbe50b5b3c266df50dbf665..1c751bd5f794d2682cfacdec72f1f171b3d23ef9 100644 (file)
@@ -195,8 +195,6 @@ static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
 {
        int factor;
 
-       (void)exponent;
-
        /* Factors */
        factor = 0;
        if (info->is_pico)
@@ -212,6 +210,7 @@ static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
        if (info->is_mega)
                factor += 6;
        *floatval *= powf(10, factor);
+       *exponent += factor;
 
        /* Measurement modes */
        if (info->is_volt) {
index 1e4ecd5abdc8a0163bf2090cbd74796cd1c39dfa..8f51adda1e81d7b98ff650b77b4da8185783f2d8 100644 (file)
@@ -341,7 +341,6 @@ SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval,
        struct sr_datafeed_analog *analog, void *info)
 {
        gboolean is_overload, is_bad_jack;
-       int exponent;
        int digits;
        struct meterman_info mi;
 
@@ -350,6 +349,8 @@ SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval,
        if (meterman_38xr_decode(buf, &mi) != SR_OK)
                return SR_ERR;
 
+       digits = 0;
+
        if (mi.meas_mode != MEAS_MODE_CONTINUITY) {
                is_overload = mi.reading == METERMAN_DIGITS_OVERLOAD;
                is_bad_jack = mi.reading == METERMAN_DIGITS_BAD_INPUT_JACK;
@@ -440,18 +441,17 @@ SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval,
        if (mi.rflag_h == 0x0a || mi.peakstatus == 0x0b)
                analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
        if (mi.meas_mode != MEAS_MODE_CONTINUITY) {
-               digits = decimal_digits[mi.meas_mode][mi.rangecode];
-               exponent = units_exponents[mi.meas_mode][mi.rangecode];
+               digits = units_exponents[mi.meas_mode][mi.rangecode] -
+                       decimal_digits[mi.meas_mode][mi.rangecode];
 
                *floatval = mi.reading;
                if (meterman_38xr_is_negative(&mi)) {
                        *floatval *= -1.0f;
                }
-               *floatval *= powf(10, -digits);
-               *floatval *= powf(10, exponent);
+               *floatval *= powf(10, digits);
        }
-       analog->encoding->digits = 4;
-       analog->spec->spec_digits = 4;
+       analog->encoding->digits = -digits;
+       analog->spec->spec_digits = -digits;
 
        return SR_OK;
 }
index 30d6308a5ba57348c4c2e3695f83e6066b293864..6dc2c55cbd18785813186a5e814d0a7d020d1583 100644 (file)
 #include "libsigrok-internal.h"
 
 /*
- * sr_driver_list is a special section contains pointers to all the hardware
- * drivers built into the library. The __start and __stop symbols are
- * created from driver_list_start.c and driver_list_stop.c, and point to the
- * start and end of the section. They are used to iterate over the list of
- * all drivers.
+ * The special __sr_driver_list section contains pointers to all hardware
+ * drivers which were built into the library according to its configuration
+ * (will depend on the availability of dependencies, as well as user provided
+ * specs). The __start and __stop symbols point to the start and end of the
+ * section. They are used to iterate over the list of all drivers which were
+ * included in the library.
  */
 SR_PRIV extern const struct sr_dev_driver *sr_driver_list__start[];
 SR_PRIV extern const struct sr_dev_driver *sr_driver_list__stop[];
@@ -51,6 +52,5 @@ SR_API void sr_drivers_init(struct sr_context *ctx)
             drivers < sr_driver_list__stop; drivers++)
                g_array_append_val(array, *drivers);
 #endif
-       ctx->driver_list = (struct sr_dev_driver **)array->data;
-       g_array_free(array, FALSE);
+       ctx->driver_list = (struct sr_dev_driver **)g_array_free(array, FALSE);
 }
index 82ef93cb6253e498b5b7f9b95187b56c64cecf2a..f004ec4b751531d525f22196b664d20b4632ebe3 100644 (file)
@@ -1011,11 +1011,12 @@ SR_PRIV const struct agdmm_recv agdmm_recvs_u124x[] = {
        { "^\"(\\d\\d.{18}\\d)\"$", recv_stat_u124x },
        { "^\\*([0-9])$", recv_switch },
        { "^([-+][0-9]\\.[0-9]{8}E[-+][0-9]{2})$", recv_fetc },
-       { "^\"(VOLT|CURR|RES|CAP|FREQ) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
-       { "^\"(VOLT:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
-       { "^\"(CURR:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
-       { "^\"(CPER:[40]-20mA) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+       { "^\"(VOLT|CURR|RES|CAP|FREQ) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+       { "^\"(VOLT:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+       { "^\"(CURR:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+       { "^\"(CPER:[40]-20mA) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
        { "^\"(T[0-9]:[A-Z]+) ([A-Z]+)\"$", recv_conf_u124x_5x },
+       { "^\"(TEMP:[A-Z]+) ([A-Z]+)\"$", recv_conf_u124x_5x },
        { "^\"(DIOD)\"$", recv_conf_u124x_5x },
        ALL_ZERO
 };
index fb6f82727dedd9eafe515a5b27310bed96c6075d..c14c20d400d05db58c3c55b32680f91893666b8e 100644 (file)
@@ -146,9 +146,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        sdi->inst_type = SR_INST_SERIAL;
        sdi->conn = serial;
 
-       cg = g_malloc0(sizeof(struct sr_channel_group));
-       cg->name = g_strdup("1");
-       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+       cg = sr_channel_group_new(sdi, "1", NULL);
 
        ch = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V");
        cg->channels = g_slist_append(cg->channels, ch);
index 71e922543f1a4c453233f6b9cc3d78cfc2342f45..60c3bfeac90954982e10a10b574613e69f89296e 100644 (file)
@@ -126,9 +126,10 @@ static const uint32_t devopts[] = {
 
 static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       const char *conn, *serno, *exe;
-       GSList *devices;
-       size_t argc, chidx;
+       const char *conn, *probe_names, *serno, *exe;
+       GSList *devices, *l;
+       struct sr_config *src;
+       size_t argc, chmax, chidx;
        gchar **argv, *output, *vers_text, *eol;
        GSpawnFlags flags;
        GError *error;
@@ -139,9 +140,18 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
        /* Extract optional serial number from conn= spec. */
        conn = NULL;
+       probe_names = NULL;
        (void)sr_serial_extract_options(options, &conn, NULL);
        if (!conn || !*conn)
                conn = NULL;
+       for (l = options; l; l = l->next) {
+               src = l->data;
+               switch (src->key) {
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(src->data, NULL);
+                       break;
+               }
+       }
        serno = NULL;
        if (conn) {
                if (!g_str_has_prefix(conn, "sn=")) {
@@ -242,13 +252,16 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                sdi->serial_num = g_strdup(serno);
        if (conn)
                sdi->connection_id = g_strdup(conn);
-       for (chidx = 0; chidx < ARRAY_SIZE(channel_names); chidx++) {
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+       devc->channel_names = sr_parse_probe_names(probe_names,
+               channel_names, ARRAY_SIZE(channel_names),
+               ARRAY_SIZE(channel_names), &chmax);
+       for (chidx = 0; chidx < chmax; chidx++) {
                sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC,
-                       TRUE, channel_names[chidx]);
+                       TRUE, devc->channel_names[chidx]);
        }
 
-       devc = g_malloc0(sizeof(*devc));
-       sdi->priv = devc;
        sr_sw_limits_init(&devc->limits);
        argc = 1;
        g_free(argv[argc]);
index e72036bfb32338e32a02955736613a089459f40b..d74349516999a7222880965f3169bda40de79214 100644 (file)
@@ -215,7 +215,7 @@ static int omega_rtm_cli_process_rawdata(const struct sr_dev_inst *sdi)
                        devc->samples.remain_count -= count;
                }
                if (count) {
-                       ret = feed_queue_logic_submit(devc->samples.queue,
+                       ret = feed_queue_logic_submit_one(devc->samples.queue,
                                devc->samples.last_sample, count);
                        if (ret != SR_OK)
                                break;
@@ -229,13 +229,13 @@ static int omega_rtm_cli_process_rawdata(const struct sr_dev_inst *sdi)
                 * hand because future chunks might repeat it.
                 */
                write_u16le(devc->samples.last_sample, sample1);
-               ret = feed_queue_logic_submit(devc->samples.queue,
+               ret = feed_queue_logic_submit_one(devc->samples.queue,
                        devc->samples.last_sample, 1);
                if (ret != SR_OK)
                        break;
 
                write_u16le(devc->samples.last_sample, sample2);
-               ret = feed_queue_logic_submit(devc->samples.queue,
+               ret = feed_queue_logic_submit_one(devc->samples.queue,
                        devc->samples.last_sample, 1);
                if (ret != SR_OK)
                        break;
index 2f4641c8a371871140bbb30d4fe575fb29bdf8a1..b55f5f186304876ff43eac64ba4df5419ff7531d 100644 (file)
@@ -32,6 +32,7 @@
 #define FEED_QUEUE_DEPTH (256 * 1024)
 
 struct dev_context {
+       char **channel_names;
        struct sr_sw_limits limits;
        struct {
                gchar **argv;
index 4fdafec955eb1cfb1b1ef4eb112a2192f63b1e22..f1b96bb79fdd16eb489792989e7dbc486323efdc 100644 (file)
@@ -35,6 +35,7 @@ static const char *channel_names[] = {
 
 static const uint32_t scanopts[] = {
        SR_CONF_CONN,
+       SR_CONF_PROBE_NAMES,
 };
 
 static const uint32_t drvopts[] = {
@@ -109,6 +110,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        struct drv_context *drvc;
        libusb_context *usbctx;
        const char *conn;
+       const char *probe_names;
        GSList *l, *conn_devices;
        struct sr_config *src;
        GSList *devices;
@@ -126,18 +128,23 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        struct sr_dev_inst *sdi;
        struct dev_context *devc;
        size_t devidx, chidx;
+       size_t count;
 
        drvc = di->context;
        usbctx = drvc->sr_ctx->libusb_ctx;
 
        /* Find all devices which match an (optional) conn= spec. */
        conn = NULL;
+       probe_names = NULL;
        for (l = options; l; l = l->next) {
                src = l->data;
                switch (src->key) {
                case SR_CONF_CONN:
                        conn = g_variant_get_string(src->data, NULL);
                        break;
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(src->data, NULL);
+                       break;
                }
        }
        conn_devices = NULL;
@@ -234,12 +241,14 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                sdi->model = g_strdup(dev_text);
                sdi->serial_num = g_strdup(serno_txt);
                sdi->connection_id = g_strdup(conn_id);
-               for (chidx = 0; chidx < ARRAY_SIZE(channel_names); chidx++)
-                       sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC,
-                               TRUE, channel_names[chidx]);
-
                devc = g_malloc0(sizeof(*devc));
                sdi->priv = devc;
+               devc->channel_names = sr_parse_probe_names(probe_names,
+                       channel_names, ARRAY_SIZE(channel_names),
+                       ARRAY_SIZE(channel_names), &count);
+               for (chidx = 0; chidx < count; chidx++)
+                       sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC,
+                               TRUE, devc->channel_names[chidx]);
                devc->id.vid = des.idVendor;
                devc->id.pid = des.idProduct;
                devc->id.serno = serno_num;
@@ -304,7 +313,7 @@ static int config_get(uint32_t key, GVariant **data,
                *data = g_variant_new_boolean(devc->clock.use_ext_clock);
                break;
        case SR_CONF_EXTERNAL_CLOCK_SOURCE:
-               clock_text = channel_names[devc->clock.clock_pin];
+               clock_text = devc->channel_names[devc->clock.clock_pin];
                *data = g_variant_new_string(clock_text);
                break;
        case SR_CONF_CLOCK_EDGE:
@@ -330,6 +339,8 @@ static int config_set(uint32_t key, GVariant *data,
        struct dev_context *devc;
        int ret;
        uint64_t want_rate, have_rate;
+       const char **names;
+       size_t count;
        int idx;
 
        (void)cg;
@@ -357,7 +368,9 @@ static int config_set(uint32_t key, GVariant *data,
                devc->clock.use_ext_clock = g_variant_get_boolean(data);
                break;
        case SR_CONF_EXTERNAL_CLOCK_SOURCE:
-               idx = std_str_idx(data, ARRAY_AND_SIZE(channel_names));
+               names = (const char **)devc->channel_names;
+               count = g_strv_length(devc->channel_names);
+               idx = std_str_idx(data, names, count);
                if (idx < 0)
                        return SR_ERR_ARG;
                devc->clock.clock_pin = idx;
@@ -384,6 +397,11 @@ 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)
 {
+       struct dev_context *devc;
+       const char **names;
+       size_t count;
+
+       devc = sdi ? sdi->priv : NULL;
        switch (key) {
        case SR_CONF_SCAN_OPTIONS:
        case SR_CONF_DEVICE_OPTIONS:
@@ -395,7 +413,11 @@ static int config_list(uint32_t key, GVariant **data,
                *data = sigma_get_samplerates_list();
                break;
        case SR_CONF_EXTERNAL_CLOCK_SOURCE:
-               *data = g_variant_new_strv(ARRAY_AND_SIZE(channel_names));
+               if (!devc)
+                       return SR_ERR_ARG;
+               names = (const char **)devc->channel_names;
+               count = g_strv_length(devc->channel_names);
+               *data = g_variant_new_strv(names, count);
                break;
        case SR_CONF_CLOCK_EDGE:
                *data = g_variant_new_strv(ARRAY_AND_SIZE(ext_clock_edges));
index 26370170481e008e97954ca2826ab5b4a0dd5869..a35bcb0ad08ba45f66744997f66e866efb427289 100644 (file)
@@ -330,6 +330,7 @@ struct dev_context {
                uint16_t prefix;
                enum asix_device_type type;
        } id;
+       char **channel_names;
        struct {
                struct ftdi_context ctx;
                gboolean is_open, must_close;
diff --git a/src/hardware/atorch/api.c b/src/hardware/atorch/api.c
new file mode 100644 (file)
index 0000000..69fe4e3
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Mathieu Pilato <pilato.mathieu@free.fr>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include "protocol.h"
+
+static struct sr_dev_driver atorch_driver_info;
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_ENERGYMETER,
+       SR_CONF_POWERMETER,
+       SR_CONF_ELECTRONIC_LOAD,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONTINUOUS,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET,
+};
+
+static int create_channels_feed_queues(struct sr_dev_inst *sdi,
+       struct dev_context *devc)
+{
+       size_t i;
+       struct sr_channel *sr_ch;
+       const struct atorch_channel_desc *at_ch;
+       struct feed_queue_analog *feed;
+       const struct atorch_device_profile *p;
+
+       p = devc->profile;
+       devc->feeds = g_malloc0(p->channel_count * sizeof(devc->feeds[0]));
+       for (i = 0; i < p->channel_count; i++) {
+               at_ch = &p->channels[i];
+               sr_ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, at_ch->name);
+               feed = feed_queue_analog_alloc(sdi, 1, at_ch->digits, sr_ch);
+               feed_queue_analog_mq_unit(feed, at_ch->mq, at_ch->flags, at_ch->unit);
+               feed_queue_analog_scale_offset(feed, &at_ch->scale, NULL);
+               devc->feeds[i] = feed;
+       }
+
+       return SR_OK;
+}
+
+static GSList *atorch_scan(struct sr_dev_driver *di,
+       const char *conn, const char *serialcomm)
+{
+       struct sr_serial_dev_inst *serial;
+       GSList *devices;
+       struct dev_context *devc;
+       struct sr_dev_inst *sdi;
+
+       serial = sr_serial_dev_inst_new(conn, serialcomm);
+       if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+               goto err_out;
+
+       devc = g_malloc0(sizeof(*devc));
+
+       if (atorch_probe(serial, devc) != SR_OK) {
+               sr_err("Failed to find a supported Atorch device.");
+               goto err_out_serial;
+       }
+
+       sr_sw_limits_init(&devc->limits);
+
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->priv = devc;
+       sdi->status = SR_ST_INACTIVE;
+       sdi->vendor = g_strdup("Atorch");
+       sdi->model = g_strdup(devc->profile->device_name);
+       sdi->version = NULL;
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->conn = serial;
+
+       create_channels_feed_queues(sdi, devc);
+
+       serial_close(serial);
+
+       devices = g_slist_append(NULL, sdi);
+       return std_scan_complete(di, devices);
+
+err_out_serial:
+       g_free(devc);
+       serial_close(serial);
+err_out:
+       sr_serial_dev_inst_free(serial);
+
+       return NULL;
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       const char *serial_device, *serial_options;
+
+       serial_device = NULL;
+       serial_options = "9600/8n1";
+
+       (void)sr_serial_extract_options(options, &serial_device, &serial_options);
+       if (!serial_device || !*serial_device)
+               return NULL;
+
+       return atorch_scan(di, serial_device, serial_options);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+
+       (void)cg;
+
+       if (!sdi || !data)
+               return SR_ERR_ARG;
+
+       devc = sdi->priv;
+
+       switch (key) {
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_FRAMES:
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_get(&devc->limits, key, data);
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_set(uint32_t key, GVariant *data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+
+       (void)data;
+       (void)cg;
+
+       devc = sdi->priv;
+
+       switch (key) {
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_FRAMES:
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_set(&devc->limits, key, data);
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_list(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+       struct sr_serial_dev_inst *serial;
+       struct dev_context *devc;
+
+       serial = sdi->conn;
+       devc = sdi->priv;
+
+       sr_sw_limits_acquisition_start(&devc->limits);
+       std_session_send_df_header(sdi);
+
+       serial_source_add(sdi->session, serial, G_IO_IN, 100,
+               atorch_receive_data_callback, (void *)sdi);
+
+       return SR_OK;
+}
+
+static void clear_helper(struct dev_context *devc)
+{
+       size_t idx;
+
+       if (!devc)
+               return;
+
+       if (devc->feeds && devc->profile) {
+               for (idx = 0; idx < devc->profile->channel_count; idx++)
+                       feed_queue_analog_free(devc->feeds[idx]);
+               g_free(devc->feeds);
+       }
+}
+
+static int dev_clear(const struct sr_dev_driver *driver)
+{
+       return std_dev_clear_with_callback(driver, (std_dev_clear_callback)clear_helper);
+}
+
+static struct sr_dev_driver atorch_driver_info = {
+       .name = "atorch",
+       .longname = "atorch meters and loads",
+       .api_version = 1,
+       .init = std_init,
+       .cleanup = std_cleanup,
+       .scan = scan,
+       .dev_list = std_dev_list,
+       .dev_clear = dev_clear,
+       .config_get = config_get,
+       .config_set = config_set,
+       .config_list = config_list,
+       .dev_open = std_serial_dev_open,
+       .dev_close = std_serial_dev_close,
+       .dev_acquisition_start = dev_acquisition_start,
+       .dev_acquisition_stop = std_serial_dev_acquisition_stop,
+       .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(atorch_driver_info);
diff --git a/src/hardware/atorch/protocol.c b/src/hardware/atorch/protocol.c
new file mode 100644 (file)
index 0000000..595e619
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Mathieu Pilato <pilato.mathieu@free.fr>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "protocol.h"
+
+/* Duration of scan. */
+#define ATORCH_PROBE_TIMEOUT_MS        10000
+
+/*
+ * Message layout:
+ * 2 magic header bytes
+ * 1 message type byte
+ * N payload bytes, determined by message type
+ */
+
+/* Position of message type byte in a message. */
+#define HEADER_MSGTYPE_IDX     2
+#define PAYLOAD_START_IDX      3
+
+/* Length of each message type. */
+#define MSGLEN_REPORT  (4 + 32)
+#define MSGLEN_REPLY   (4 + 4)
+#define MSGLEN_COMMAND (4 + 6)
+
+/* Minimal length of a valid message. */
+#define MSGLEN_MIN     4
+
+static const uint8_t header_magic[] = {
+       0xff, 0x55,
+};
+
+static const struct atorch_channel_desc atorch_dc_power_meter_channels[] = {
+       { "V", { 4, BVT_BE_UINT24, }, { 100, 1e3, }, 1, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, },
+       { "I", { 7, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, },
+       { "C", { 10, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, },
+       { "E", { 13, BVT_BE_UINT32, }, { 10, 1, }, -2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, },
+       { "T", { 24, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, },
+};
+
+static const struct atorch_channel_desc atorch_usb_power_meter_channels[] = {
+       { "V", { 4, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, },
+       { "I", { 7, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, },
+       { "C", { 10, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, },
+       { "E", { 13, BVT_BE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, },
+       { "D-", { 17, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, },
+       { "D+", { 19, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, },
+       { "T", { 21, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, },
+};
+
+static const struct atorch_device_profile atorch_profiles[] = {
+       { 0x02, "DC Meter", ARRAY_AND_SIZE(atorch_dc_power_meter_channels), },
+       { 0x03, "USB Meter", ARRAY_AND_SIZE(atorch_usb_power_meter_channels), },
+};
+
+static size_t get_length_for_msg_type(uint8_t msg_type)
+{
+       switch (msg_type) {
+       case MSG_REPORT:
+               return MSGLEN_REPORT;
+       case MSG_REPLY:
+               return MSGLEN_REPLY;
+       case MSG_COMMAND:
+               return MSGLEN_COMMAND;
+       default:
+               return 0;
+       }
+}
+
+static void log_atorch_msg(const uint8_t *buf, size_t len)
+{
+       GString *text;
+
+       if (sr_log_loglevel_get() < SR_LOG_DBG)
+               return;
+
+       text = sr_hexdump_new(buf, len);
+       sr_dbg("Atorch msg: %s", text->str);
+       sr_hexdump_free(text);
+}
+
+static const uint8_t *locate_next_valid_msg(struct dev_context *devc)
+{
+       uint8_t *valid_msg_ptr;
+       size_t valid_msg_len;
+       uint8_t *msg_ptr;
+
+       /* Enough byte to make a message? */
+       while (devc->rd_idx + MSGLEN_MIN <= devc->wr_idx) {
+               /* Look for header magic. */
+               msg_ptr = devc->buf + devc->rd_idx;
+               if (memcmp(msg_ptr, header_magic, sizeof(header_magic)) != 0) {
+                       devc->rd_idx += 1;
+                       continue;
+               }
+
+               /* Determine msg type and length. */
+               valid_msg_len = get_length_for_msg_type(msg_ptr[HEADER_MSGTYPE_IDX]);
+               if (!valid_msg_len) {
+                       devc->rd_idx += 2;
+                       continue;
+               }
+
+               /* Do we have the complete message? */
+               if (devc->rd_idx + valid_msg_len <= devc->wr_idx) {
+                       valid_msg_ptr = msg_ptr;
+                       devc->rd_idx += valid_msg_len;
+                       log_atorch_msg(valid_msg_ptr, valid_msg_len);
+                       return valid_msg_ptr;
+               }
+
+               return NULL;
+       }
+       return NULL;
+}
+
+static const uint8_t *receive_msg(struct sr_serial_dev_inst *serial,
+       struct dev_context *devc)
+{
+       size_t len;
+       const uint8_t *valid_msg_ptr;
+
+       while (1) {
+               /* Remove bytes already processed. */
+               if (devc->rd_idx > 0) {
+                       len = devc->wr_idx - devc->rd_idx;
+                       memmove(devc->buf, devc->buf + devc->rd_idx, len);
+                       devc->wr_idx -= devc->rd_idx;
+                       devc->rd_idx = 0;
+               }
+
+               /* Read more bytes to process. */
+               len = ATORCH_BUFSIZE - devc->wr_idx;
+               len = serial_read_nonblocking(serial, devc->buf + devc->wr_idx, len);
+               if (len <= 0)
+                       return NULL;
+               devc->wr_idx += len;
+
+               /* Locate next start of message. */
+               valid_msg_ptr = locate_next_valid_msg(devc);
+               if (valid_msg_ptr)
+                       return valid_msg_ptr;
+       }
+}
+
+static const struct atorch_device_profile *find_profile_for_device_type(uint8_t dev_type)
+{
+       size_t i;
+
+       for (i = 0; i < ARRAY_SIZE(atorch_profiles); i++) {
+               if (atorch_profiles[i].device_type == dev_type)
+                       return &atorch_profiles[i];
+       }
+       return NULL;
+}
+
+static void parse_report_msg(struct sr_dev_inst *sdi, const uint8_t *report_ptr)
+{
+       struct dev_context *devc;
+       float val;
+       size_t i;
+
+       devc = sdi->priv;
+
+       std_session_send_df_frame_begin(sdi);
+
+       for (i = 0; i < devc->profile->channel_count; i++) {
+               bv_get_value(&val, &devc->profile->channels[i].spec, report_ptr);
+               feed_queue_analog_submit_one(devc->feeds[i], val, 1);
+       }
+
+       std_session_send_df_frame_end(sdi);
+
+       sr_sw_limits_update_frames_read(&devc->limits, 1);
+       if (sr_sw_limits_check(&devc->limits))
+               sr_dev_acquisition_stop(sdi);
+}
+
+SR_PRIV int atorch_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc)
+{
+       int64_t deadline_us;
+       const struct atorch_device_profile *p;
+       const uint8_t *msg_ptr;
+
+       devc->wr_idx = 0;
+       devc->rd_idx = 0;
+
+       deadline_us = g_get_monotonic_time();
+       deadline_us += ATORCH_PROBE_TIMEOUT_MS * 1000;
+       while (g_get_monotonic_time() <= deadline_us) {
+               msg_ptr = receive_msg(serial, devc);
+               if (msg_ptr && msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT) {
+                       p = find_profile_for_device_type(msg_ptr[PAYLOAD_START_IDX]);
+                       if (p) {
+                               devc->profile = p;
+                               return SR_OK;
+                       }
+                       sr_err("Unrecognized device type (0x%.4" PRIx8 ").",
+                              devc->buf[PAYLOAD_START_IDX]);
+                       return SR_ERR;
+               }
+               g_usleep(100 * 1000);
+       }
+       return SR_ERR;
+}
+
+SR_PRIV int atorch_receive_data_callback(int fd, int revents, void *cb_data)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       const uint8_t *msg_ptr;
+
+       (void)fd;
+
+       sdi = cb_data;
+       devc = sdi->priv;
+
+       if (!sdi || !devc)
+               return TRUE;
+
+       if (revents & G_IO_IN) {
+               while ((msg_ptr = receive_msg(sdi->conn, devc))) {
+                       if (msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT)
+                               parse_report_msg(sdi, msg_ptr);
+               }
+       }
+
+       return TRUE;
+}
diff --git a/src/hardware/atorch/protocol.h b/src/hardware/atorch/protocol.h
new file mode 100644 (file)
index 0000000..05cbe83
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Mathieu Pilato <pilato.mathieu@free.fr>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_ATORCH_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_ATORCH_PROTOCOL_H
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "atorch"
+
+#define ATORCH_BUFSIZE 128
+
+struct atorch_device_profile {
+       uint8_t device_type;
+       const char *device_name;
+       const struct atorch_channel_desc *channels;
+       size_t channel_count;
+};
+
+struct atorch_channel_desc {
+       const char *name;
+       struct binary_value_spec spec;
+       struct sr_rational scale;
+       int digits;
+       enum sr_mq mq;
+       enum sr_unit unit;
+       enum sr_mqflag flags;
+};
+
+enum atorch_msg_type {
+       MSG_REPORT = 0x01,
+       MSG_REPLY = 0x02,
+       MSG_COMMAND = 0x11,
+};
+
+struct dev_context {
+       const struct atorch_device_profile *profile;
+       struct sr_sw_limits limits;
+       struct feed_queue_analog **feeds;
+       uint8_t buf[ATORCH_BUFSIZE];
+       size_t wr_idx;
+       size_t rd_idx;
+};
+
+SR_PRIV int atorch_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc);
+SR_PRIV int atorch_receive_data_callback(int fd, int revents, void *cb_data);
+
+#endif
index dc6e5201977d30769956521c1450ffebfd8eb8b4..28b22015d3fd13f449c0379cf7f84d92b5e8363b 100644 (file)
@@ -157,11 +157,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options, int modelid)
        for (i = 0; i < MAX_CHANNELS; i++) {
                snprintf(channel, 10, "CH%d", i + 1);
                ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel);
-               cg = g_malloc(sizeof(struct sr_channel_group));
-               cg->name = g_strdup(channel);
+               cg = sr_channel_group_new(sdi, channel, NULL);
                cg->channels = g_slist_append(NULL, ch);
-               cg->priv = NULL;
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 
        devc = g_malloc0(sizeof(struct dev_context));
index 89391d5482286e541873910e457be935b6e7818b..8d23b9e2f9875d36093115c52b1911c3e1ac1e2a 100644 (file)
@@ -332,9 +332,8 @@ SR_PRIV gboolean bl_acme_register_probe(struct sr_dev_inst *sdi, int type,
        if (hwmon < 0)
                return FALSE;
 
-       cg = g_malloc0(sizeof(struct sr_channel_group));
        cgp = g_malloc0(sizeof(struct channel_group_priv));
-       cg->priv = cgp;
+       cg = sr_channel_group_new(sdi, NULL, cgp);
 
        /*
         * See if we can read the EEPROM contents. If not, assume it's
@@ -380,8 +379,6 @@ SR_PRIV gboolean bl_acme_register_probe(struct sr_dev_inst *sdi, int type,
                sr_err("Invalid probe type: %d.", type);
        }
 
-       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
-
        return TRUE;
 }
 
index d35a4f5377d2b8d8bc943cfbf893b2a043340444..d5ca278b728d999557576d291f9720bbfe491b75 100644 (file)
@@ -20,6 +20,8 @@
 #include <config.h>
 #include "protocol.h"
 
+#define SCAN_EXPECTED_VENDOR 0x0403
+
 static const uint32_t scanopts[] = {
        SR_CONF_CONN,
 };
@@ -123,52 +125,62 @@ err_free_devc:
 
 static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       int i, ret, model;
        struct drv_context *drvc;
-       GSList *devices, *conn_devices, *l;
+       GSList *devices;
+       const char *conn;
+       int ret;
+       GSList *conn_devices, *l;
+       size_t i;
        struct sr_usb_dev_inst *usb;
-       struct sr_config *src;
+       uint8_t bus, addr;
        struct libusb_device_descriptor des;
        libusb_device **devlist;
        struct libusb_device_handle *hdl;
-       const char *conn;
        char product[64], serial_num[64], connection_id[64];
+       int model;
 
        drvc = di->context;
+       devices = NULL;
 
        conn = NULL;
-       for (l = options; l; l = l->next) {
-               src = l->data;
-               switch (src->key) {
-               case SR_CONF_CONN:
-                       conn = g_variant_get_string(src->data, NULL);
-                       break;
-               }
-       }
+       (void)sr_serial_extract_options(options, &conn, NULL);
+       conn_devices = NULL;
        if (conn)
                conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn);
-       else
-               conn_devices = NULL;
 
-       devices = NULL;
        libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist);
-
        for (i = 0; devlist[i]; i++) {
+               bus = libusb_get_bus_number(devlist[i]);
+               addr = libusb_get_device_address(devlist[i]);
                if (conn) {
+                       /* Check if the connection matches the user spec. */
                        for (l = conn_devices; l; l = l->next) {
                                usb = l->data;
-                               if (usb->bus == libusb_get_bus_number(devlist[i])
-                                       && usb->address == libusb_get_device_address(devlist[i]))
+                               if (usb->bus == bus && usb->address == addr)
                                        break;
                        }
                        if (!l)
-                               /* This device matched none of the ones that
-                                * matched the conn specification. */
                                continue;
                }
 
                libusb_get_device_descriptor(devlist[i], &des);
 
+               /*
+                * In theory we'd accept any USB device with a matching
+                * product string. In practice the enumeration takes a
+                * shortcut and only inspects devices when their USB VID
+                * matches the expectation. This avoids access to flaky
+                * devices which are unrelated to measurement purposes
+                * yet cause trouble when accessed including segfaults,
+                * while libusb won't transparently handle their flaws.
+                *
+                * See https://sigrok.org/bugzilla/show_bug.cgi?id=1115
+                * and https://github.com/sigrokproject/libsigrok/pull/166
+                * for a discussion.
+                */
+               if (des.idVendor != SCAN_EXPECTED_VENDOR)
+                       continue;
+
                if ((ret = libusb_open(devlist[i], &hdl)) < 0)
                        continue;
 
@@ -214,7 +226,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                        sr_dbg("Failed to add device: %d.", ret);
                }
        }
-
        libusb_free_device_list(devlist, 1);
        g_slist_free_full(conn_devices, (GDestroyNotify)sr_usb_dev_inst_free);
 
index 137277d0176b014758883bba82d988cd1c58a985..deeb234a95eb2e72e640e6a3d0bcc8e2b9e328a6 100644 (file)
@@ -63,6 +63,7 @@ static struct sr_dev_inst *probe_device_common(const char *path,
        struct channel_group_context *cgc;
        size_t idx, nr;
        struct sr_channel_group *cg;
+       char cg_name[24];
 
        /*
         * Get relay count from product string. Weak condition,
@@ -153,12 +154,11 @@ static struct sr_dev_inst *probe_device_common(const char *path,
        devc->relay_mask = (1U << relay_count) - 1;
        for (idx = 0; idx < devc->relay_count; idx++) {
                nr = idx + 1;
-               cg = g_malloc0(sizeof(*cg));
-               cg->name = g_strdup_printf("R%zu", nr);
+               snprintf(cg_name, sizeof(cg_name), "R%zu", nr);
                cgc = g_malloc0(sizeof(*cgc));
-               cg->priv = cgc;
                cgc->number = nr;
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               (void)cg;
        }
 
        return sdi;
index ba317fbe4a1b8fa389a8de39966c833956820659..bea35d068f63042a04fd7b27b6d691f3778ad0fb 100644 (file)
@@ -151,14 +151,12 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
        if (num_logic_channels > 0) {
                /* Logic channels, all in one channel group. */
-               cg = g_malloc0(sizeof(struct sr_channel_group));
-               cg->name = g_strdup("Logic");
+               cg = sr_channel_group_new(sdi, "Logic", NULL);
                for (i = 0; i < num_logic_channels; i++) {
                        sprintf(channel_name, "D%d", i);
                        ch = sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name);
                        cg->channels = g_slist_append(cg->channels, ch);
                }
-               sdi->channel_groups = g_slist_append(NULL, cg);
        }
 
        /* Analog channels, channel groups and pattern generators. */
@@ -173,9 +171,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
                pattern = 0;
                /* An "Analog" channel group with all analog channels in it. */
-               acg = g_malloc0(sizeof(struct sr_channel_group));
-               acg->name = g_strdup("Analog");
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, acg);
+               acg = sr_channel_group_new(sdi, "Analog", NULL);
 
                for (i = 0; i < num_analog_channels; i++) {
                        snprintf(channel_name, 16, "A%d", i);
@@ -184,10 +180,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                        acg->channels = g_slist_append(acg->channels, ch);
 
                        /* Every analog channel gets its own channel group as well. */
-                       cg = g_malloc0(sizeof(struct sr_channel_group));
-                       cg->name = g_strdup(channel_name);
+                       cg = sr_channel_group_new(sdi, channel_name, NULL);
                        cg->channels = g_slist_append(NULL, ch);
-                       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
 
                        /* Every channel gets a generator struct. */
                        ag = g_malloc(sizeof(struct analog_gen));
diff --git a/src/hardware/devantech-eth008/api.c b/src/hardware/devantech-eth008/api.c
new file mode 100644 (file)
index 0000000..9bace4e
--- /dev/null
@@ -0,0 +1,389 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "protocol.h"
+
+#define VENDOR_TEXT    "Devantech"
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_MULTIPLEXER,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */
+};
+
+static const uint32_t devopts_cg_do[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint32_t devopts_cg_di[] = {
+       SR_CONF_ENABLED | SR_CONF_GET,
+};
+
+static const uint32_t devopts_cg_ai[] = {
+       SR_CONF_VOLTAGE | SR_CONF_GET,
+};
+
+/* List of supported devices. Sorted by model ID. */
+static const struct devantech_eth008_model models[] = {
+       { 18, "ETH002",    2,  0,  0, 0, 1, 0, 0, },
+       { 19, "ETH008",    8,  0,  0, 0, 1, 0, 0, },
+       { 20, "ETH484",   16,  8,  4, 0, 2, 2, 0x00f0, },
+       { 21, "ETH8020",  20,  8,  8, 0, 3, 4, 0, },
+       { 22, "WIFI484",  16,  8,  4, 0, 2, 2, 0x00f0, },
+       { 24, "WIFI8020", 20,  8,  8, 0, 3, 4, 0, },
+       { 26, "WIFI002",   2,  0,  0, 0, 1, 0, 0, },
+       { 28, "WIFI008",   8,  0,  0, 0, 1, 0, 0, },
+       { 52, "ETH1610",  10, 16, 16, 0, 2, 2, 0, },
+};
+
+static const struct devantech_eth008_model *find_model(uint8_t code)
+{
+       size_t idx;
+       const struct devantech_eth008_model *check;
+
+       for (idx = 0; idx < ARRAY_SIZE(models); idx++) {
+               check = &models[idx];
+               if (check->code != code)
+                       continue;
+               return check;
+       }
+
+       return NULL;
+}
+
+static struct sr_dev_driver devantech_eth008_driver_info;
+
+static struct sr_dev_inst *probe_device_conn(const char *conn)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *ser;
+       uint8_t code, hwver, fwver;
+       const struct devantech_eth008_model *model;
+       gboolean has_serno_cmd;
+       char snr_txt[16];
+       struct channel_group_context *cgc;
+       size_t ch_idx, nr, do_idx, di_idx, ai_idx;
+       struct sr_channel_group *cg;
+       char cg_name[24];
+       int ret;
+
+       sdi = g_malloc0(sizeof(*sdi));
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+       ser = sr_serial_dev_inst_new(conn, NULL);
+       sdi->conn = ser;
+       if (!ser)
+               goto probe_fail;
+       ret = serial_open(ser, 0);
+       if (ret != SR_OK)
+               goto probe_fail;
+
+       ret = devantech_eth008_get_model(ser, &code, &hwver, &fwver);
+       if (ret != SR_OK)
+               goto probe_fail;
+       model = find_model(code);
+       if (!model) {
+               sr_err("Unknown model ID 0x%02x (HW %u, FW %u).",
+                       code, hwver, fwver);
+               goto probe_fail;
+       }
+       devc->model_code = code;
+       devc->hardware_version = hwver;
+       devc->firmware_version = fwver;
+       devc->model = model;
+       sdi->vendor = g_strdup(VENDOR_TEXT);
+       sdi->model = g_strdup(model->name);
+       sdi->version = g_strdup_printf("HW%u FW%u", hwver, fwver);
+       sdi->connection_id = g_strdup(conn);
+       sdi->driver = &devantech_eth008_driver_info;
+       sdi->inst_type = SR_INST_SERIAL;
+
+       has_serno_cmd = TRUE;
+       if (model->min_serno_fw && fwver < model->min_serno_fw)
+               has_serno_cmd = FALSE;
+       if (has_serno_cmd) {
+               snr_txt[0] = '\0';
+               ret = devantech_eth008_get_serno(ser,
+                       snr_txt, sizeof(snr_txt));
+               if (ret != SR_OK)
+                       goto probe_fail;
+               sdi->serial_num = g_strdup(snr_txt);
+       }
+
+       ch_idx = 0;
+       devc->mask_do = (1UL << devc->model->ch_count_do) - 1;
+       devc->mask_do &= ~devc->model->mask_do_missing;
+       for (do_idx = 0; do_idx < devc->model->ch_count_do; do_idx++) {
+               nr = do_idx + 1;
+               if (devc->model->mask_do_missing & (1UL << do_idx))
+                       continue;
+               snprintf(cg_name, sizeof(cg_name), "DO%zu", nr);
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = do_idx;
+               cgc->number = nr;
+               cgc->ch_type = DV_CH_DIGITAL_OUTPUT;
+               (void)cg;
+               ch_idx++;
+       }
+       for (di_idx = 0; di_idx < devc->model->ch_count_di; di_idx++) {
+               nr = di_idx + 1;
+               snprintf(cg_name, sizeof(cg_name), "DI%zu", nr);
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = di_idx;
+               cgc->number = nr;
+               cgc->ch_type = DV_CH_DIGITAL_INPUT;
+               (void)cg;
+               ch_idx++;
+       }
+       for (ai_idx = 0; ai_idx < devc->model->ch_count_ai; ai_idx++) {
+               nr = ai_idx + 1;
+               snprintf(cg_name, sizeof(cg_name), "AI%zu", nr);
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = ai_idx;
+               cgc->number = nr;
+               cgc->ch_type = DV_CH_ANALOG_INPUT;
+               (void)cg;
+               ch_idx++;
+       }
+       if (1) {
+               /* Create an analog channel for the supply voltage. */
+               snprintf(cg_name, sizeof(cg_name), "Vsupply");
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = 0;
+               cgc->number = 0;
+               cgc->ch_type = DV_CH_SUPPLY_VOLTAGE;
+               (void)cg;
+               ch_idx++;
+       }
+
+       return sdi;
+
+probe_fail:
+       if (ser) {
+               serial_close(ser);
+               sr_serial_dev_inst_free(ser);
+       }
+       if (devc) {
+               g_free(devc);
+       }
+       if (sdi) {
+               sdi->priv = NULL;
+               sr_dev_inst_free(sdi);
+               sdi = NULL;
+       }
+       return sdi;
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       struct drv_context *drvc;
+       const char *conn;
+       GSList *devices;
+       struct sr_dev_inst *sdi;
+
+       drvc = di->context;
+       drvc->instances = NULL;
+
+       /* A conn= spec is required for the TCP attached device. */
+       conn = NULL;
+       (void)sr_serial_extract_options(options, &conn, NULL);
+       if (!conn || !*conn)
+               return NULL;
+
+       devices = NULL;
+       sdi = probe_device_conn(conn);
+       if (sdi)
+               devices = g_slist_append(devices, sdi);
+
+       return std_scan_complete(di, devices);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct channel_group_context *cgc;
+       gboolean on;
+       uint16_t vin;
+       double vsupply;
+       int ret;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_CONN:
+                       if (!sdi->connection_id)
+                               return SR_ERR_NA;
+                       *data = g_variant_new_string(sdi->connection_id);
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_NA;
+       switch (key) {
+       case SR_CONF_ENABLED:
+               if (cgc->ch_type == DV_CH_DIGITAL_OUTPUT) {
+                       ret = devantech_eth008_query_do(sdi, cg, &on);
+                       if (ret != SR_OK)
+                               return ret;
+                       *data = g_variant_new_boolean(on);
+                       return SR_OK;
+               }
+               if (cgc->ch_type == DV_CH_DIGITAL_INPUT) {
+                       ret = devantech_eth008_query_di(sdi, cg, &on);
+                       if (ret != SR_OK)
+                               return ret;
+                       *data = g_variant_new_boolean(on);
+                       return SR_OK;
+               }
+               return SR_ERR_NA;
+       case SR_CONF_VOLTAGE:
+               if (cgc->ch_type == DV_CH_ANALOG_INPUT) {
+                       ret = devantech_eth008_query_ai(sdi, cg, &vin);
+                       if (ret != SR_OK)
+                               return ret;
+                       *data = g_variant_new_uint32(vin);
+                       return SR_OK;
+               }
+               if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) {
+                       ret = devantech_eth008_query_supply(sdi, cg, &vin);
+                       if (ret != SR_OK)
+                               return ret;
+                       vsupply = vin;
+                       vsupply /= 1000.;
+                       *data = g_variant_new_double(vsupply);
+                       return SR_OK;
+               }
+               return SR_ERR_NA;
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_set(uint32_t key, GVariant *data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct channel_group_context *cgc;
+       gboolean on;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       /* Enable/disable all channels at the same time. */
+                       on = g_variant_get_boolean(data);
+                       return devantech_eth008_setup_do(sdi, cg, on);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_NA;
+       switch (key) {
+       case SR_CONF_ENABLED:
+               if (cgc->ch_type != DV_CH_DIGITAL_OUTPUT)
+                       return SR_ERR_NA;
+               on = g_variant_get_boolean(data);
+               return devantech_eth008_setup_do(sdi, cg, on);
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_list(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct channel_group_context *cgc;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_SCAN_OPTIONS:
+               case SR_CONF_DEVICE_OPTIONS:
+                       return STD_CONFIG_LIST(key, data, sdi, cg,
+                               scanopts, drvopts, devopts);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_NA;
+       switch (key) {
+       case SR_CONF_DEVICE_OPTIONS:
+               if (cgc->ch_type == DV_CH_DIGITAL_OUTPUT) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_do));
+                       return SR_OK;
+               }
+               if (cgc->ch_type == DV_CH_DIGITAL_INPUT) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_di));
+                       return SR_OK;
+               }
+               if (cgc->ch_type == DV_CH_ANALOG_INPUT) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_ai));
+                       return SR_OK;
+               }
+               if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_ai));
+                       return SR_OK;
+               }
+               return SR_ERR_NA;
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static struct sr_dev_driver devantech_eth008_driver_info = {
+       .name = "devantech-eth008",
+       .longname = "Devantech ETH008",
+       .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 = std_serial_dev_open,
+       .dev_close = std_serial_dev_close,
+       .dev_acquisition_start = std_dummy_dev_acquisition_start,
+       .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
+       .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(devantech_eth008_driver_info);
diff --git a/src/hardware/devantech-eth008/protocol.c b/src/hardware/devantech-eth008/protocol.c
new file mode 100644 (file)
index 0000000..afbbea9
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Communicate to the Devantech ETH008 relay card via TCP and Ethernet.
+ * Also supports other cards when their protocol is similar enough.
+ * USB and Modbus attached cards are not covered by this driver.
+ *
+ * See http://www.robot-electronics.co.uk/files/eth008b.pdf for device
+ * capabilities and a protocol discussion. See other devices' documents
+ * for additional features (digital input, analog input, TCP requests
+ * which ETH008 does not implement).
+ * See https://github.com/devantech/devantech_eth_python for MIT licensed
+ * Python source code which is maintained by the vendor.
+ * This sigrok driver implementation was created based on information in
+ * version 0.1.2 of the Python code (corresponds to commit 0c0080b88e29),
+ * and PDF files that are referenced in the shop's product pages (which
+ * also happen to provide ZIP archives with examples that are written
+ * using other programming languages).
+ *
+ * The device provides several means of communication: HTTP requests
+ * (as well as an interactive web form). Raw TCP communication with
+ * binary requests and responses. Text requests and responses over
+ * TCP sockets. Some of these depend on the firmware version. Version
+ * checks before command transmission is essentially non-existent in
+ * this sigrok driver implementation. Binary transmission is preferred
+ * because it is assumed that this existed in all firmware versions.
+ * The firmware interestingly accepts concurrent network connections
+ * (up to five of them, all share the same password). Which means that
+ * the peripheral's state can change even while we are controlling it.
+ *
+ * TCP communication seems to rely on network fragmentation and assumes
+ * that software stacks provide all of a request in a single receive
+ * call on the firmware side. Which works for local communication, but
+ * could become an issue when long distances and tunnels are involved.
+ * This sigrok driver also assumes complete reception within a single
+ * receive call. The short length of binary transmission helps here
+ * (the largest payloads has a length of four bytes).
+ *
+ * The lack of length specs as well as termination in the protocol
+ * (both binary as well as text variants over TCP sockets) results in
+ * the inability to synchronize to the firmware when connecting and
+ * after hiccups in an established connection. The fixed length of
+ * requests and responses for binary payloads helps a little bit,
+ * assuming that TCP connect is used to recover. The overhead of
+ * HTTP requests and responses is considered undesirable for this
+ * sigrok driver implementation. [This also means that a transport
+ * which lacks the concept of network frames cannot send passwords.]
+ * The binary transport appears to lack HELLO or NOP requests that
+ * could be used to synchronize. Firmware just would not respond to
+ * unsupported commands. Maybe a repeated sequence of identity reads
+ * combined with a read timeout could help synchronize, but only if
+ * the response is known because the model was identified before.
+ *
+ * The sigrok driver source code was phrased with the addition of more
+ * models in mind. Only few code paths require adjustment when similar
+ * variants of requests or responses are involved in the communication
+ * to relay cards that support between two and twenty channels. Chances
+ * are good, existing firmware is compatible across firmware versions,
+ * and even across hardware revisions (model upgrades). Firmware just
+ * happens to not respond to unknown requests.
+ *
+ * Support for models with differing features also was kept somehwat
+ * simple and straight forward. The mapping of digital outputs to relay
+ * numbers from the user's perspective is incomplete for those cards
+ * where users decide whether relays are attached to digital outputs.
+ * When an individual physical channel can be operated in different
+ * modes, or when its value gets presented in different formats, then
+ * these values are not synchronized. This applies for digital inputs
+ * which are the result of applying a threshold to an analog value.
+ *
+ * TODO
+ * - Add support for other models.
+ *   - The Ethernet (and Wifi) devices should work as they are with
+ *     the current implementation.
+ *     https://www.robot-electronics.co.uk/files/eth484b.pdf.
+ *   - USB could get added here with reasonable effort. Serial over
+ *     CDC is transparently supported (lack of framing prevents the
+ *     use of variable length requests or responses, but should not
+ *     apply to these models anyway). The protocol radically differs
+ *     from Ethernet variants:
+ *     https://www.robot-electronics.co.uk/files/usb-rly16b.pdf
+ *     - 0x38 get serial number, yields 8 bytes
+ *     - 0x5a get software version, yields module ID 9, 1 byte version
+ *     - 0x5b get relay states, yields 1 byte current state
+ *     - 0x5c set relay state, takes 1 byte for all 8 relays
+ *     - 0x5d get supply voltage, yields 1 byte in 0.1V steps
+ *     - 0x5e set individual relay, takes 3 more bytes: relay number,
+ *       hi/lo pulse time in 10ms steps
+ *     - for interactive use? 'd' all relays on, 'e'..'l' relay 1..8 on,
+ *       'n' all relays off, 'o'..'v' relay 1..8 off
+ *   - Modbus may or may not be a good match for this driver, or may
+ *     better be kept in yet another driver? Requests and responses
+ *     again differ from Ethernet and USB models, refer to traditional
+ *     "coils" and have their individual and grouped access.
+ *     https://www.robot-electronics.co.uk/files/mbh88.pdf
+ * - Reconsider the relation of relay channels, and digital outputs
+ *   and their analog sampling and digital input interpretation. The
+ *   current implementation is naive, assumes the simple DO/DI/AI
+ *   groups and ignores their interaction within the firmware.
+ * - Add support for password protection?
+ *   - See command 0x79 to "login" (beware of the differing return value
+ *     compared to other commands), command 0x7a to check if passwords
+ *     are involved and whether the login needs refreshing, command 0x7b
+ *     for immediate "logout" in contrast to expiration.
+ *   - Alternatively consider switching to the "text protocol" in that
+ *     use case, which can send an optional password in every request
+ *     that controls relays (command 0x3a).
+ *   - How to specify the password in applications and how to pass them
+ *     to this driver is yet another issue that needs consideration.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "protocol.h"
+
+#define READ_TIMEOUT_MS        20
+
+enum cmd_code {
+       CMD_GET_MODULE_INFO = 0x10,
+       CMD_DIGITAL_ACTIVE = 0x20,
+       CMD_DIGITAL_INACTIVE = 0x21,
+       CMD_DIGITAL_SET_OUTPUTS = 0x23,
+       CMD_DIGITAL_GET_OUTPUTS = 0x24,
+       CMD_DIGITAL_GET_INPUTS = 0x25,
+       CMD_DIGITAL_ACTIVE_1MS = 0x26,
+       CMD_DIGITAL_INACTIVE_1MS = 0x27,
+       CMD_ANALOG_GET_INPUT = 0x32,
+       CMD_ANALOG_GET_INPUT_12BIT = 0x33,
+       CMD_ANALOG_GET_ALL_VOLTAGES = 0x34,
+       CMD_ASCII_TEXT_COMMAND = 0x3a,
+       CMD_GET_SERIAL_NUMBER = 0x77,
+       CMD_GET_SUPPLY_VOLTS = 0x78,
+       CMD_PASSWORD_ENTRY = 0x79,
+       CMD_GET_UNLOCK_TIME = 0x7a,
+       CMD_IMMEDIATE_LOGOUT = 0x7b,
+};
+
+/*
+ * Transmit a request to the relay card. Checks that all bytes get sent,
+ * short writes are considered fatal.
+ */
+static int send_request(struct sr_serial_dev_inst *ser,
+       const uint8_t *data, size_t dlen)
+{
+       int ret;
+       size_t written;
+
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *txt = sr_hexdump_new(data, dlen);
+               sr_spew("TX --> %s.", txt->str);
+               sr_hexdump_free(txt);
+       }
+       ret = serial_write_blocking(ser, data, dlen, 0);
+       if (ret < 0)
+               return ret;
+       written = (size_t)ret;
+       if (written != dlen)
+               return SR_ERR_DATA;
+       return SR_OK;
+}
+
+/*
+ * Receive a response from the relay card. Assumes fixed size payload,
+ * considers short reads fatal.
+ */
+static int recv_response(struct sr_serial_dev_inst *ser,
+       uint8_t *data, size_t dlen)
+{
+       int ret;
+       size_t got;
+
+       ret = serial_read_blocking(ser, data, dlen, READ_TIMEOUT_MS);
+       if (ret < 0)
+               return ret;
+       got = (size_t)ret;
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *txt = sr_hexdump_new(data, got);
+               sr_spew("<-- RX %s.", txt->str);
+               sr_hexdump_free(txt);
+       }
+       if (got != dlen)
+               return SR_ERR_DATA;
+       return SR_OK;
+}
+
+/* Send a request then receive a response. Convenience routine. */
+static int send_then_recv(struct sr_serial_dev_inst *serial,
+       const uint8_t *tx_data, size_t tx_length,
+       uint8_t *rx_data, size_t rx_length)
+{
+       int ret;
+
+       if (tx_data && tx_length) {
+               ret = send_request(serial, tx_data, tx_length);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       if (rx_data && rx_length) {
+               ret = recv_response(serial, rx_data, rx_length);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* Identify the relay card, gather version information details. */
+SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
+       uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version)
+{
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[3], v8;
+       const uint8_t *rdptr;
+       int ret;
+
+       if (model_code)
+               *model_code = 0;
+       if (hw_version)
+               *hw_version = 0;
+       if (fw_version)
+               *fw_version = 0;
+
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_GET_MODULE_INFO);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       v8 = read_u8_inc(&rdptr);
+       if (model_code)
+               *model_code = v8;
+       v8 = read_u8_inc(&rdptr);
+       if (hw_version)
+               *hw_version = v8;
+       v8 = read_u8_inc(&rdptr);
+       if (fw_version)
+               *fw_version = v8;
+
+       return SR_OK;
+}
+
+/* Get the relay card's serial number (its MAC address). */
+SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial,
+       char *text_buffer, size_t text_length)
+{
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[6], b;
+       const uint8_t *rdptr, *endptr;
+       size_t written;
+       int ret;
+
+       if (text_buffer && !text_length)
+               return SR_ERR_ARG;
+       if (text_buffer)
+               memset(text_buffer, 0, text_length);
+
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_GET_SERIAL_NUMBER);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       endptr = rsp + sizeof(rsp);
+       while (rdptr < endptr && text_buffer && text_length >= 3) {
+               b = read_u8_inc(&rdptr);
+               written = snprintf(text_buffer, text_length, "%02x", b);
+               text_buffer += written;
+               text_length -= written;
+       }
+
+       return SR_OK;
+}
+
+/* Update an internal cache from the relay card's current state. */
+SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
+{
+       struct sr_serial_dev_inst *serial;
+       struct dev_context *devc;
+       size_t rx_size;
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[4];
+       const uint8_t *rdptr;
+       uint32_t have;
+       int ret;
+
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Get the state of digital outputs when the model supports them. */
+       if (devc->model->ch_count_do) {
+               rx_size = devc->model->width_do;
+               if (rx_size > sizeof(rsp))
+                       return SR_ERR_NA;
+
+               wrptr = req;
+               write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS);
+               ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
+               if (ret != SR_OK)
+                       return ret;
+               rdptr = rsp;
+
+               switch (rx_size) {
+               case 1:
+                       have = read_u8_inc(&rdptr);
+                       break;
+               case 2:
+                       have = read_u16le_inc(&rdptr);
+                       break;
+               case 3:
+                       have = read_u24le_inc(&rdptr);
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+               have &= devc->mask_do;
+               devc->curr_do = have;
+       }
+
+       /*
+        * Get the state of digital inputs when the model supports them.
+        * (Sending unsupported requests to unaware firmware versions
+        * yields no response. That's why requests must be conditional.)
+        *
+        * Caching the state of analog inputs is condidered undesirable.
+        * Firmware does conversion at the very moment when the request
+        * is received to get a voltage reading.
+        */
+       if (devc->model->ch_count_di) {
+               rx_size = devc->model->width_di;
+               if (rx_size > sizeof(rsp))
+                       return SR_ERR_NA;
+
+               wrptr = req;
+               write_u8_inc(&wrptr, CMD_DIGITAL_GET_INPUTS);
+               ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
+               if (ret != SR_OK)
+                       return ret;
+               rdptr = rsp;
+
+               switch (rx_size) {
+               case 2:
+                       have = read_u16be_inc(&rdptr);
+                       break;
+               case 4:
+                       have = read_u32be_inc(&rdptr);
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+               have &= (1UL << devc->model->ch_count_di) - 1;
+               devc->curr_di = have;
+       }
+
+       return SR_OK;
+}
+
+/* Query the state of an individual relay channel. */
+SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean *on)
+{
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint32_t have;
+       int ret;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Unconditionally update the internal cache. */
+       ret = devantech_eth008_cache_state(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Only reject unexpected requests after the update. Get the
+        * individual channel's state from the cache of all channels.
+        */
+       if (!cg)
+               return SR_ERR_ARG;
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_BUG;
+       if (cgc->index >= devc->model->ch_count_do)
+               return SR_ERR_ARG;
+       have = devc->curr_do;
+       have >>= cgc->index;
+       have &= 1 << 0;
+       if (on)
+               *on = have ? TRUE : FALSE;
+
+       return SR_OK;
+}
+
+/*
+ * Manipulate the state of an individual relay channel (when cg is given).
+ * Or set/clear all channels at the same time (when cg is NULL).
+ */
+SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on)
+{
+       struct sr_serial_dev_inst *serial;
+       struct dev_context *devc;
+       size_t width_do;
+       struct channel_group_context *cgc;
+       size_t number;
+       uint32_t reg;
+       uint8_t req[4], *wrptr, cmd;
+       uint8_t rsp[1], v8;
+       const uint8_t *rdptr;
+       int ret;
+
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       cgc = cg ? cg->priv : NULL;
+       if (cgc && cgc->index >= devc->model->ch_count_do)
+               return SR_ERR_ARG;
+
+       width_do = devc->model->width_do;
+       if (1 + width_do > sizeof(req))
+               return SR_ERR_NA;
+
+       wrptr = req;
+       if (cgc) {
+               /* Manipulate an individual channel. */
+               cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE;
+               number = cgc->number;
+               write_u8_inc(&wrptr, cmd);
+               write_u8_inc(&wrptr, number & 0xff);
+               write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */
+       } else {
+               /* Manipulate all channels at the same time. */
+               reg = on ? devc->mask_do : 0;
+               write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS);
+               switch (width_do) {
+               case 1:
+                       write_u8_inc(&wrptr, reg & 0xff);
+                       break;
+               case 2:
+                       write_u16le_inc(&wrptr, reg & 0xffff);
+                       break;
+               case 3:
+                       write_u24le_inc(&wrptr, reg & 0xffffff);
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       v8 = read_u8_inc(&rdptr);
+       if (v8 != 0)
+               return SR_ERR_DATA;
+
+       return SR_OK;
+}
+
+SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean *on)
+{
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint32_t have;
+       int ret;
+
+       /* Unconditionally update the internal cache. */
+       ret = devantech_eth008_cache_state(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Only reject unexpected requests after the update. Get the
+        * individual channel's state from the cache of all channels.
+        */
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (!cg)
+               return SR_ERR_ARG;
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_BUG;
+       if (cgc->index >= devc->model->ch_count_di)
+               return SR_ERR_ARG;
+       have = devc->curr_di;
+       have >>= cgc->index;
+       have &= 1 << 0;
+       if (on)
+               *on = have ? TRUE : FALSE;
+
+       return SR_OK;
+}
+
+SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, uint16_t *adc_value)
+{
+       struct sr_serial_dev_inst *serial;
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint8_t req[2], *wrptr;
+       uint8_t rsp[2];
+       const uint8_t *rdptr;
+       uint32_t have;
+       int ret;
+
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (!cg)
+               return SR_ERR_ARG;
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_ARG;
+       if (cgc->index >= devc->model->ch_count_ai)
+               return SR_ERR_ARG;
+
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_ANALOG_GET_INPUT);
+       write_u8_inc(&wrptr, cgc->number & 0xff);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       /*
+        * The interpretation of analog readings differs across models.
+        * All firmware versions provide an ADC result in BE format in
+        * a 16bit response. Some models provide 10 significant digits,
+        * others provide 12 bits. Full scale can either be 3V3 or 5V0.
+        * Some devices are 5V tolerant but won't read more than 3V3
+        * values (and clip above that full scale value). Some firmware
+        * versions support request 0x33 in addition to 0x32.
+        *
+        * This is why this implementation provides the result to the
+        * caller as a unit-less value. It is also what the firmware's
+        * web interface does.
+        */
+       have = read_u16be_inc(&rdptr);
+       if (adc_value)
+               *adc_value = have;
+
+       return SR_OK;
+}
+
+SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, uint16_t *millivolts)
+{
+       struct sr_serial_dev_inst *serial;
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[1];
+       const uint8_t *rdptr;
+       uint16_t have;
+       int ret;
+
+       (void)cg;
+
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */
+       have = read_u8_inc(&rdptr);
+       have *= 100;
+       if (millivolts)
+               *millivolts = have;
+
+       return SR_OK;
+}
diff --git a/src/hardware/devantech-eth008/protocol.h b/src/hardware/devantech-eth008/protocol.h
new file mode 100644 (file)
index 0000000..7a687c7
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_DEVANTECH_ETH008_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_DEVANTECH_ETH008_PROTOCOL_H
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "devantech-eth008"
+
+/*
+ * See developer notes in the protocol.c source file for details on the
+ * feature set and communication protocol variants across the series.
+ * This 'model' description tells their discriminating properties apart.
+ */
+struct devantech_eth008_model {
+       uint8_t code;           /**!< model ID */
+       const char *name;       /**!< model name */
+       size_t ch_count_do;     /**!< digital output channel count */
+       size_t ch_count_di;     /**!< digital input channel count */
+       size_t ch_count_ai;     /**!< analog input channel count */
+       uint8_t min_serno_fw;   /**!< min FW version to get serial nr */
+       size_t width_do;        /**!< digital output image width */
+       size_t width_di;        /**!< digital input image width */
+       uint32_t mask_do_missing; /**!< missing digital output channels */
+};
+
+struct channel_group_context {
+       size_t index;
+       size_t number;
+       enum devantech_eth008_channel_type {
+               DV_CH_DIGITAL_OUTPUT,
+               DV_CH_DIGITAL_INPUT,
+               DV_CH_ANALOG_INPUT,
+               DV_CH_SUPPLY_VOLTAGE,
+       } ch_type;
+};
+
+struct dev_context {
+       uint8_t model_code, hardware_version, firmware_version;
+       const struct devantech_eth008_model *model;
+       uint32_t mask_do;
+       uint32_t curr_do;
+       uint32_t curr_di;
+};
+
+SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
+       uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version);
+SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial,
+       char *text_buffer, size_t text_length);
+SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi);
+SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean *on);
+SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on);
+SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean *on);
+SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, uint16_t *adc_value);
+SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, uint16_t *millivolts);
+
+#endif
index 98f4acdb159269efed773f517a18d96f9e119503..6d5b7fc7d6cd009d5bf90c8f043fa0cc28162993 100644 (file)
@@ -240,15 +240,13 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                sdi->connection_id = g_strdup(connection_id);
 
                /* Logic channels, all in one channel group. */
-               cg = g_malloc0(sizeof(struct sr_channel_group));
-               cg->name = g_strdup("Logic");
+               cg = sr_channel_group_new(sdi, "Logic", NULL);
                for (j = 0; j < NUM_CHANNELS; j++) {
                        sprintf(channel_name, "%d", j);
                        ch = sr_channel_new(sdi, j, SR_CHANNEL_LOGIC,
                                                TRUE, channel_name);
                        cg->channels = g_slist_append(cg->channels, ch);
                }
-               sdi->channel_groups = g_slist_append(NULL, cg);
 
                devc = dslogic_dev_new();
                devc->profile = prof;
index f533f1c5df7d7a2731f187b28779efea7c709944..b28e9be4ca8e4d4c81826fda93604e4d4dbdc9d5 100644 (file)
@@ -45,7 +45,7 @@ static const uint32_t devopts[] = {
 static const char *scan_conn[] = {
        /* 287/289 */
        "115200/8n1",
-       /* 187/189 */
+       /* 87/89/187/189 */
        "9600/8n1",
        /* Scopemeter 190 series */
        "1200/8n1",
@@ -53,10 +53,12 @@ static const char *scan_conn[] = {
 };
 
 static const struct flukedmm_profile supported_flukedmm[] = {
+       { FLUKE_87, "87", 100, 1000 },
+       { FLUKE_89, "89", 100, 1000 },
        { FLUKE_187, "187", 100, 1000 },
        { FLUKE_189, "189", 100, 1000 },
-       { FLUKE_287, "287", 100, 1000 },
        { FLUKE_190, "199B", 1000, 3500 },
+       { FLUKE_287, "287", 100, 1000 },
        { FLUKE_289, "289", 100, 1000 },
 };
 
@@ -226,7 +228,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 
 static struct sr_dev_driver flukedmm_driver_info = {
        .name = "fluke-dmm",
-       .longname = "Fluke 18x/28x series DMMs",
+       .longname = "Fluke 8x/18x/28x series DMMs",
        .api_version = 1,
        .init = std_init,
        .cleanup = std_cleanup,
index fc2f11dde0af368b9b3b48c4ccb135c4f1e0de52..d8837f661c1efc45049eaf529ba1ce52c470cfbb 100644 (file)
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
+static int count_digits(const char *str)
+{
+       int digits;
+
+       if (*str == '-' || *str == '+')
+               str++;
+
+       while (*str >= '0' && *str <= '9')
+               str++;
+
+       digits = 0;
+       if (*str == '.') {
+               str++;
+               while (*str >= '0' && *str <= '9') {
+                       str++;
+                       digits++;
+               }
+       }
+
+       return digits;
+}
+
 static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens)
 {
        struct dev_context *devc;
@@ -37,6 +59,11 @@ static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens)
        float fvalue;
        char *e, *u;
        gboolean is_oor;
+       int digits;
+       int exponent;
+       enum sr_mq mq;
+       enum sr_unit unit;
+       enum sr_mqflag mqflags;
 
        devc = sdi->priv;
 
@@ -46,6 +73,7 @@ static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens)
        if ((e = strstr(tokens[1], "Out of range"))) {
                is_oor = TRUE;
                fvalue = -1;
+               digits = 0;
                while (*e && *e != '.')
                        e++;
        } else {
@@ -56,101 +84,111 @@ static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens)
                while (*e && *e != ' ')
                        e++;
                *e++ = '\0';
-               if (sr_atof_ascii(tokens[1], &fvalue) != SR_OK || fvalue == 0.0) {
+               if (sr_atof_ascii(tokens[1], &fvalue) != SR_OK) {
                        /* Happens all the time, when switching modes. */
-                       sr_dbg("Invalid float.");
+                       sr_dbg("Invalid float: '%s'", tokens[1]);
                        return;
                }
+               digits = count_digits(tokens[1]);
        }
        while (*e && *e == ' ')
                e++;
 
-       /* TODO: Use proper 'digits' value for this device (and its modes). */
-       sr_analog_init(&analog, &encoding, &meaning, &spec, 2);
-       analog.data = &fvalue;
-       analog.meaning->channels = sdi->channels;
-       analog.num_samples = 1;
        if (is_oor)
                fvalue = NAN;
-       analog.meaning->mq = 0;
 
+       mq = 0;
+       unit = 0;
+       exponent = 0;
+       mqflags = 0;
        if ((u = strstr(e, "V DC")) || (u = strstr(e, "V AC"))) {
-               analog.meaning->mq = SR_MQ_VOLTAGE;
-               analog.meaning->unit = SR_UNIT_VOLT;
+               mq = SR_MQ_VOLTAGE;
+               unit = SR_UNIT_VOLT;
                if (!is_oor && e[0] == 'm')
-                       fvalue /= 1000;
+                       exponent = -3;
                /* This catches "V AC", "V DC" and "V AC+DC". */
                if (strstr(u, "AC"))
-                       analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+                       mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
                if (strstr(u, "DC"))
-                       analog.meaning->mqflags |= SR_MQFLAG_DC;
+                       mqflags |= SR_MQFLAG_DC;
        } else if ((u = strstr(e, "dBV")) || (u = strstr(e, "dBm"))) {
-               analog.meaning->mq = SR_MQ_VOLTAGE;
+               mq = SR_MQ_VOLTAGE;
                if (u[2] == 'm')
-                       analog.meaning->unit = SR_UNIT_DECIBEL_MW;
+                       unit = SR_UNIT_DECIBEL_MW;
                else
-                       analog.meaning->unit = SR_UNIT_DECIBEL_VOLT;
-               analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+                       unit = SR_UNIT_DECIBEL_VOLT;
+               mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
        } else if ((u = strstr(e, "Ohms"))) {
-               analog.meaning->mq = SR_MQ_RESISTANCE;
-               analog.meaning->unit = SR_UNIT_OHM;
+               mq = SR_MQ_RESISTANCE;
+               unit = SR_UNIT_OHM;
                if (is_oor)
                        fvalue = INFINITY;
                else if (e[0] == 'k')
-                       fvalue *= 1000;
+                       exponent = 3;
                else if (e[0] == 'M')
-                       fvalue *= 1000000;
+                       exponent = 6;
        } else if (!strcmp(e, "nS")) {
-               analog.meaning->mq = SR_MQ_CONDUCTANCE;
-               analog.meaning->unit = SR_UNIT_SIEMENS;
-               *((float *)analog.data) /= 1e+9;
+               mq = SR_MQ_CONDUCTANCE;
+               unit = SR_UNIT_SIEMENS;
+               exponent = -9;
        } else if ((u = strstr(e, "Farads"))) {
-               analog.meaning->mq = SR_MQ_CAPACITANCE;
-               analog.meaning->unit = SR_UNIT_FARAD;
+               mq = SR_MQ_CAPACITANCE;
+               unit = SR_UNIT_FARAD;
                if (!is_oor) {
                        if (e[0] == 'm')
-                               fvalue /= 1e+3;
+                               exponent = -3;
                        else if (e[0] == 'u')
-                               fvalue /= 1e+6;
+                               exponent = -6;
                        else if (e[0] == 'n')
-                               fvalue /= 1e+9;
+                               exponent = -9;
                }
        } else if ((u = strstr(e, "Deg C")) || (u = strstr(e, "Deg F"))) {
-               analog.meaning->mq = SR_MQ_TEMPERATURE;
+               mq = SR_MQ_TEMPERATURE;
                if (u[4] == 'C')
-                       analog.meaning->unit = SR_UNIT_CELSIUS;
+                       unit = SR_UNIT_CELSIUS;
                else
-                       analog.meaning->unit = SR_UNIT_FAHRENHEIT;
+                       unit = SR_UNIT_FAHRENHEIT;
        } else if ((u = strstr(e, "A AC")) || (u = strstr(e, "A DC"))) {
-               analog.meaning->mq = SR_MQ_CURRENT;
-               analog.meaning->unit = SR_UNIT_AMPERE;
+               mq = SR_MQ_CURRENT;
+               unit = SR_UNIT_AMPERE;
                /* This catches "A AC", "A DC" and "A AC+DC". */
                if (strstr(u, "AC"))
-                       analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+                       mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
                if (strstr(u, "DC"))
-                       analog.meaning->mqflags |= SR_MQFLAG_DC;
+                       mqflags |= SR_MQFLAG_DC;
                if (!is_oor) {
                        if (e[0] == 'm')
-                               fvalue /= 1e+3;
+                               exponent = -3;
                        else if (e[0] == 'u')
-                               fvalue /= 1e+6;
+                               exponent = -6;
                }
        } else if ((u = strstr(e, "Hz"))) {
-               analog.meaning->mq = SR_MQ_FREQUENCY;
-               analog.meaning->unit = SR_UNIT_HERTZ;
+               mq = SR_MQ_FREQUENCY;
+               unit = SR_UNIT_HERTZ;
                if (e[0] == 'k')
-                       fvalue *= 1e+3;
+                       exponent = 3;
        } else if (!strcmp(e, "%")) {
-               analog.meaning->mq = SR_MQ_DUTY_CYCLE;
-               analog.meaning->unit = SR_UNIT_PERCENTAGE;
+               mq = SR_MQ_DUTY_CYCLE;
+               unit = SR_UNIT_PERCENTAGE;
        } else if ((u = strstr(e, "ms"))) {
-               analog.meaning->mq = SR_MQ_PULSE_WIDTH;
-               analog.meaning->unit = SR_UNIT_SECOND;
-               fvalue /= 1e+3;
+               mq = SR_MQ_PULSE_WIDTH;
+               unit = SR_UNIT_SECOND;
+               exponent = -3;
        }
 
-       if (analog.meaning->mq != 0) {
+       if (mq != 0) {
                /* Got a measurement. */
+               digits -= exponent;
+               fvalue *= pow(10.0f, exponent);
+
+               sr_analog_init(&analog, &encoding, &meaning, &spec, digits);
+               analog.data = &fvalue;
+               analog.num_samples = 1;
+               analog.meaning->unit = unit;
+               analog.meaning->mq = mq;
+               analog.meaning->mqflags = mqflags;
+               analog.meaning->channels = sdi->channels;
+
                packet.type = SR_DF_ANALOG;
                packet.payload = &analog;
                sr_session_send(sdi, &packet);
@@ -167,19 +205,40 @@ static void handle_qm_28x(const struct sr_dev_inst *sdi, char **tokens)
        struct sr_analog_meaning meaning;
        struct sr_analog_spec spec;
        float fvalue;
+       int digits;
+       int exponent;
+       char *e;
 
        devc = sdi->priv;
 
        if (!tokens[1])
                return;
 
-       if (sr_atof_ascii(tokens[0], &fvalue) != SR_OK || fvalue == 0.0) {
-               sr_err("Invalid float '%s'.", tokens[0]);
+       /* Split measurement into mantissa / exponent */
+       e = tokens[0];
+       while (*e) {
+               if (*e == 'e' || *e == 'E') {
+                       *e = '\0';
+                       e++;
+                       break;
+               }
+               e++;
+       }
+
+       if (sr_atof_ascii(tokens[0], &fvalue) != SR_OK) {
+               sr_err("Invalid mantissa '%s'.", tokens[0]);
+               return;
+       }
+
+       if (sr_atoi(e, &exponent) != SR_OK) {
+               sr_err("Invalid exponent '%s'.", e);
                return;
        }
 
-       /* TODO: Use proper 'digits' value for this device (and its modes). */
-       sr_analog_init(&analog, &encoding, &meaning, &spec, 2);
+       digits = count_digits(tokens[0]) - exponent;
+       fvalue *= pow(10.0f, exponent);
+
+       sr_analog_init(&analog, &encoding, &meaning, &spec, digits);
        analog.data = &fvalue;
        analog.meaning->channels = sdi->channels;
        analog.num_samples = 1;
@@ -378,7 +437,9 @@ static void handle_qm_19x_data(const struct sr_dev_inst *sdi, char **tokens)
        struct sr_analog_meaning meaning;
        struct sr_analog_spec spec;
        float fvalue;
+       int digits;
 
+       digits = 2;
        if (!strcmp(tokens[0], "9.9E+37")) {
                /* An invalid measurement shows up on the display as "OL", but
                 * comes through like this. Since comparing 38-digit floats
@@ -389,6 +450,7 @@ static void handle_qm_19x_data(const struct sr_dev_inst *sdi, char **tokens)
                        sr_err("Invalid float '%s'.", tokens[0]);
                        return;
                }
+               digits = count_digits(tokens[0]);
        }
 
        devc = sdi->priv;
@@ -405,8 +467,7 @@ static void handle_qm_19x_data(const struct sr_dev_inst *sdi, char **tokens)
                        fvalue = 1.0;
        }
 
-       /* TODO: Use proper 'digits' value for this device (and its modes). */
-       sr_analog_init(&analog, &encoding, &meaning, &spec, 2);
+       sr_analog_init(&analog, &encoding, &meaning, &spec, digits);
        analog.meaning->channels = sdi->channels;
        analog.num_samples = 1;
        analog.data = &fvalue;
@@ -426,6 +487,7 @@ static void handle_line(const struct sr_dev_inst *sdi)
        struct sr_serial_dev_inst *serial;
        int num_tokens, n, i;
        char cmd[16], **tokens;
+       int ret;
 
        devc = sdi->priv;
        serial = sdi->conn;
@@ -443,34 +505,48 @@ static void handle_line(const struct sr_dev_inst *sdi)
 
        tokens = g_strsplit(devc->buf, ",", 0);
        if (tokens[0]) {
-               if (devc->profile->model == FLUKE_187 || devc->profile->model == FLUKE_189) {
+               switch (devc->profile->model) {
+               case FLUKE_87:
+               case FLUKE_89:
+               case FLUKE_187:
+               case FLUKE_189:
                        devc->expect_response = FALSE;
                        handle_qm_18x(sdi, tokens);
-               } else if (devc->profile->model == FLUKE_287 || devc->profile->model == FLUKE_289) {
+                       break;
+               case FLUKE_190:
                        devc->expect_response = FALSE;
-                       handle_qm_28x(sdi, tokens);
-               } else if (devc->profile->model == FLUKE_190) {
-                       devc->expect_response = FALSE;
-                       for (num_tokens = 0; tokens[num_tokens]; num_tokens++);
-                       if (num_tokens >= 7) {
-                               /* Response to QM: this is a comma-separated list of
-                                * fields with metadata about the measurement. This
-                                * format can return multiple sets of metadata,
-                                * split into sets of 7 tokens each. */
-                               devc->meas_type = 0;
-                               for (i = 0; i < num_tokens; i += 7)
-                                       handle_qm_19x_meta(sdi, tokens + i);
-                               if (devc->meas_type) {
-                                       /* Slip the request in now, before the main
-                                        * timer loop asks for metadata again. */
-                                       n = sprintf(cmd, "QM %d\r", devc->meas_type);
-                                       if (serial_write_blocking(serial, cmd, n, SERIAL_WRITE_TIMEOUT_MS) < 0)
-                                               sr_err("Unable to send QM (measurement).");
-                               }
-                       } else {
+                       num_tokens = g_strv_length(tokens);
+                       if (num_tokens < 7) {
                                /* Response to QM <n> measurement request. */
                                handle_qm_19x_data(sdi, tokens);
+                               break;
                        }
+                       /*
+                        * Response to QM: This is a comma-separated list of
+                        * fields with metadata about the measurement. This
+                        * format can return multiple sets of metadata,
+                        * split into sets of 7 tokens each.
+                        */
+                       devc->meas_type = 0;
+                       for (i = 0; i < num_tokens; i += 7)
+                               handle_qm_19x_meta(sdi, tokens + i);
+                       if (devc->meas_type) {
+                               /*
+                                * Slip the request in now, before the main
+                                * timer loop asks for metadata again.
+                                */
+                               n = sprintf(cmd, "QM %d\r", devc->meas_type);
+                               ret = serial_write_blocking(serial,
+                                       cmd, n, SERIAL_WRITE_TIMEOUT_MS);
+                               if (ret < 0)
+                                       sr_err("Cannot send QM (measurement).");
+                       }
+                       break;
+               case FLUKE_287:
+               case FLUKE_289:
+                       devc->expect_response = FALSE;
+                       handle_qm_28x(sdi, tokens);
+                       break;
                }
        }
        g_strfreev(tokens);
index 36ece3c9eda9c7d7aee5a8d0b46e57afe8eca182..4eab15acb7e4b959174c97fbbaa9014e23ccbbf9 100644 (file)
 
 /* Supported models */
 enum {
-       FLUKE_187 = 1,
+       FLUKE_87 = 1,
+       FLUKE_89,
+       FLUKE_187,
        FLUKE_189,
-       FLUKE_287,
        FLUKE_190,
+       FLUKE_287,
        FLUKE_289,
 };
 
index 5acb0016d0b8e6ab5ad204aea7e238f55e2f5fa8..255788bfdd0731233ca75e0a9454bb7021ce5de2 100644 (file)
@@ -117,6 +117,7 @@ static const struct fx2lafw_profile supported_fx2[] = {
 
 static const uint32_t scanopts[] = {
        SR_CONF_CONN,
+       SR_CONF_PROBE_NAMES,
 };
 
 static const uint32_t drvopts[] = {
@@ -161,6 +162,15 @@ static const uint64_t samplerates[] = {
        SR_MHZ(48),
 };
 
+static const char *channel_names_logic[] = {
+       "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+       "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
+};
+
+static const char *channel_names_analog[] = {
+       "A0", "A1", "A2", "A3",
+};
+
 static gboolean is_plausible(const struct libusb_device_descriptor *des)
 {
        int i;
@@ -190,21 +200,27 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        struct libusb_device_descriptor des;
        libusb_device **devlist;
        struct libusb_device_handle *hdl;
-       int ret, i, j;
-       int num_logic_channels = 0, num_analog_channels = 0;
+       int ret, i;
+       size_t j, num_logic_channels, num_analog_channels;
        const char *conn;
+       const char *probe_names;
        char manufacturer[64], product[64], serial_num[64], connection_id[64];
-       char channel_name[16];
+       size_t ch_max, ch_idx;
+       const char *channel_name;
 
        drvc = di->context;
 
        conn = NULL;
+       probe_names = NULL;
        for (l = options; l; l = l->next) {
                src = l->data;
                switch (src->key) {
                case SR_CONF_CONN:
                        conn = g_variant_get_string(src->data, NULL);
                        break;
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(src->data, NULL);
+                       break;
                }
        }
        if (conn)
@@ -301,38 +317,56 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                sdi->serial_num = g_strdup(serial_num);
                sdi->connection_id = g_strdup(connection_id);
 
+               devc = fx2lafw_dev_new();
+               devc->profile = prof;
+               sdi->priv = devc;
+               devices = g_slist_append(devices, sdi);
+
                /* Fill in channellist according to this device's profile. */
                num_logic_channels = prof->dev_caps & DEV_CAPS_16BIT ? 16 : 8;
+               if (num_logic_channels > ARRAY_SIZE(channel_names_logic))
+                       num_logic_channels = ARRAY_SIZE(channel_names_logic);
                num_analog_channels = prof->dev_caps & DEV_CAPS_AX_ANALOG ? 1 : 0;
+               if (num_analog_channels > ARRAY_SIZE(channel_names_analog))
+                       num_analog_channels = ARRAY_SIZE(channel_names_analog);
+
+               /*
+                * Allow user specs to override the builtin probe names.
+                *
+                * Implementor's note: Because the device's number of
+                * logic channels is not known at compile time, and thus
+                * the location of the analog channel names is not known
+                * at compile time, and the construction of a list with
+                * default names at runtime is not done here, and we
+                * don't want to keep several default lists around, this
+                * implementation only supports to override the names of
+                * logic probes. The use case which motivated the config
+                * key is protocol decoders, which are logic only.
+                */
+               ch_max = num_logic_channels;
+               devc->channel_names = sr_parse_probe_names(probe_names,
+                       channel_names_logic, ch_max, ch_max, &ch_max);
+               ch_idx = 0;
 
                /* Logic channels, all in one channel group. */
-               cg = g_malloc0(sizeof(struct sr_channel_group));
-               cg->name = g_strdup("Logic");
+               cg = sr_channel_group_new(sdi, "Logic", NULL);
                for (j = 0; j < num_logic_channels; j++) {
-                       sprintf(channel_name, "D%d", j);
-                       ch = sr_channel_new(sdi, j, SR_CHANNEL_LOGIC,
-                                               TRUE, channel_name);
+                       channel_name = devc->channel_names[j];
+                       ch = sr_channel_new(sdi, ch_idx++, SR_CHANNEL_LOGIC,
+                               TRUE, channel_name);
                        cg->channels = g_slist_append(cg->channels, ch);
                }
-               sdi->channel_groups = g_slist_append(NULL, cg);
 
                for (j = 0; j < num_analog_channels; j++) {
-                       snprintf(channel_name, 16, "A%d", j);
-                       ch = sr_channel_new(sdi, j + num_logic_channels,
-                                       SR_CHANNEL_ANALOG, TRUE, channel_name);
+                       channel_name = channel_names_analog[j];
+                       ch = sr_channel_new(sdi, ch_idx++, SR_CHANNEL_ANALOG,
+                               TRUE, channel_name);
 
                        /* Every analog channel gets its own channel group. */
-                       cg = g_malloc0(sizeof(struct sr_channel_group));
-                       cg->name = g_strdup(channel_name);
+                       cg = sr_channel_group_new(sdi, channel_name, NULL);
                        cg->channels = g_slist_append(NULL, ch);
-                       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
                }
 
-               devc = fx2lafw_dev_new();
-               devc->profile = prof;
-               sdi->priv = devc;
-               devices = g_slist_append(devices, sdi);
-
                devc->samplerates = samplerates;
                devc->num_samplerates = ARRAY_SIZE(samplerates);
                has_firmware = usb_match_manuf_prod(devlist[i],
index 963d0336e59c4000e24a0f51542776cbda9678b4..8854f5a69afd7221931b606b6d0c24a6001426c9 100644 (file)
@@ -255,6 +255,7 @@ SR_PRIV struct dev_context *fx2lafw_dev_new(void)
 
        devc = g_malloc0(sizeof(struct dev_context));
        devc->profile = NULL;
+       devc->channel_names = NULL;
        devc->fw_updated = 0;
        devc->cur_samplerate = 0;
        devc->limit_frames = 1;
index 2c56b02ca6962cae97d55d03f190182aa4b1438f..91f0e9fb64745154b4b0fddbbcab5c0157a213b5 100644 (file)
@@ -89,6 +89,7 @@ struct fx2lafw_profile {
 
 struct dev_context {
        const struct fx2lafw_profile *profile;
+       char **channel_names;
        GSList *enabled_analog_channels;
        /*
         * Since we can't keep track of an fx2lafw device after upgrading
diff --git a/src/hardware/greatfet/api.c b/src/hardware/greatfet/api.c
new file mode 100644 (file)
index 0000000..db05c3d
--- /dev/null
@@ -0,0 +1,568 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Katherine J. Temkin <k@ktemkin.com>
+ * Copyright (C) 2019 Mikaela Szekely <qyriad@gmail.com>
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "protocol.h"
+
+#define DEFAULT_CONN           "1d50.60e6"
+#define CONTROL_INTERFACE      0
+#define SAMPLES_INTERFACE      1
+
+#define VENDOR_TEXT            "Great Scott Gadgets"
+#define MODEL_TEXT             "GreatFET"
+
+#define BUFFER_SIZE            (4 * 1024 * 1024)
+
+#define DEFAULT_SAMPLERATE     SR_KHZ(34000)
+#define BANDWIDTH_THRESHOLD    (SR_MHZ(42) * 8)
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_PROBE_NAMES,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_LOGIC_ANALYZER,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONTINUOUS | SR_CONF_GET,
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint32_t devopts_cg[] = {
+       /* EMPTY */
+};
+
+static const char *channel_names[] = {
+       "SGPIO0", "SGPIO1", "SGPIO2", "SGPIO3",
+       "SGPIO4", "SGPIO5", "SGPIO6", "SGPIO7",
+       "SGPIO8", "SGPIO9", "SGPIO10", "SGPIO11",
+       "SGPIO12", "SGPIO13", "SGPIO14", "SGPIO15",
+};
+
+/*
+ * The seemingly odd samplerates result from the 204MHz base clock and
+ * a 12bit integer divider. Theoretical minimum could be 50kHz but we
+ * don't bother to provide so low a selection item here.
+ *
+ * When users specify different samplerates, device firmware will pick
+ * the minimum rate which satisfies the user's request.
+ */
+static const uint64_t samplerates[] = {
+       SR_KHZ(1000),   /*   1.0MHz */
+       SR_KHZ(2000),   /*   2.0MHz */
+       SR_KHZ(4000),   /*   4.0MHz */
+       SR_KHZ(8500),   /*   8.5MHz */
+       SR_KHZ(10200),  /*  10.2MHz */
+       SR_KHZ(12000),  /*  12.0MHz */
+       SR_KHZ(17000),  /*  17.0MHz */
+       SR_KHZ(20400),  /*  20.4MHz, the maximum for 16 channels */
+       SR_KHZ(25500),  /*  25.5MHz */
+       SR_KHZ(34000),  /*  34.0MHz */
+       SR_KHZ(40800),  /*  40.8MHz, the maximum for 8 channels */
+       SR_KHZ(51000),  /*  51.0MHz */
+       SR_KHZ(68000),  /*  68.0MHz, the maximum for 4 channels */
+       SR_KHZ(102000), /* 102.0MHz, the maximum for 2 channels */
+       SR_KHZ(204000), /* 204.0MHz, the maximum for 1 channel */
+};
+
+static void greatfet_free_devc(struct dev_context *devc)
+{
+
+       if (!devc)
+               return;
+
+       if (devc->sdi)
+               devc->sdi->priv = NULL;
+
+       g_string_free(devc->usb_comm_buffer, TRUE);
+       g_free(devc->firmware_version);
+       g_free(devc->serial_number);
+       sr_free_probe_names(devc->channel_names);
+       feed_queue_logic_free(devc->acquisition.feed_queue);
+       g_free(devc->transfers.transfers);
+       g_free(devc->transfers.transfer_buffer);
+       /*
+        * USB transfers should not have been allocated when we get here
+        * during device probe/scan, or during shutdown after acquisition
+        * has terminated.
+        */
+
+       g_free(devc);
+}
+
+static void greatfet_free_sdi(struct sr_dev_inst *sdi)
+{
+       struct sr_usb_dev_inst *usb;
+       struct dev_context *devc;
+
+       if (!sdi)
+               return;
+
+       usb = sdi->conn;
+       sdi->conn = NULL;
+       if (usb && usb->devhdl)
+               sr_usb_close(usb);
+       sr_usb_dev_inst_free(usb);
+
+       devc = sdi->priv;
+       sdi->priv = NULL;
+       greatfet_free_devc(devc);
+
+       sr_dev_inst_free(sdi);
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       struct drv_context *drvc;
+       struct sr_context *ctx;
+       GSList *devices;
+       const char *conn, *probe_names;
+       const char *want_snr;
+       struct sr_config *src;
+       GSList *conn_devices, *l;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_usb_dev_inst *usb;
+       gboolean skip_device;
+       struct libusb_device *dev;
+       struct libusb_device_descriptor des;
+       char *match;
+       char serno_txt[64], conn_id[64];
+       int ret;
+       size_t ch_off, ch_max, ch_idx;
+       gboolean enabled;
+       struct sr_channel *ch;
+       struct sr_channel_group *cg;
+
+       drvc = di->context;
+       ctx = drvc->sr_ctx;
+
+       devices = NULL;
+
+       /* Accept user specs for conn= and probe names. */
+       conn = DEFAULT_CONN;
+       probe_names = NULL;
+       for (l = options; l; l = l->next) {
+               src = l->data;
+               switch (src->key) {
+               case SR_CONF_CONN:
+                       conn = g_variant_get_string(src->data, NULL);
+                       break;
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(src->data, NULL);
+                       break;
+               }
+       }
+
+       /*
+        * By default search for all devices with the expected VID/PID.
+        * Accept external specs in either "bus.addr" or "vid.pid" form.
+        * As an alternative accept "sn=..." specs and keep using the
+        * default VID/PID in that case. This should result in maximum
+        * usability while still using a maximum amount of common code.
+        */
+       want_snr = NULL;
+       if (g_str_has_prefix(conn, "sn=")) {
+               want_snr = conn + strlen("sn=");
+               conn = DEFAULT_CONN;
+               sr_info("Searching default %s and serial number %s.",
+                       conn, want_snr);
+       }
+       conn_devices = sr_usb_find(ctx->libusb_ctx, conn);
+       if (!conn_devices)
+               return devices;
+
+       /*
+        * Iterate over all devices that have the matching VID/PID.
+        * Skip those which we cannot open. Skip those which don't
+        * match additional serial number conditions. Allocate the
+        * structs for found devices "early", to re-use common code
+        * for communication to the firmware. Release these structs
+        * when identification fails or the device does not match.
+        *
+        * Notice that the scan for devices uses the USB string for
+        * the serial number, and does a weak check (partial match).
+        * This allows users to either use lsusb(8) or gf(1) output
+        * as well as match lazily when only part of the serial nr is
+        * known and becomes unique. Matching against serial nr and
+        * finding multiple devices is as acceptable, just might be a
+        * rare use case. Failure in this stage is silent, there are
+        * legal reasons why we cannot access a device during scan.
+        *
+        * Once a device was found usable, we get its serial number
+        * and version details by means of firmware communication.
+        * To verify that the firmware is operational and that the
+        * protocol works to a minimum degree. And to present data
+        * in --scan output which matches the vendor's gf(1) utility.
+        * This version detail is _not_ checked against conn= specs
+        * because users may specify the longer text string with
+        * more leading digits from lsusb(8) output. That test would
+        * fail when executed against the shorter firmware output.
+        */
+       for (l = conn_devices; l; l = l->next) {
+               usb = l->data;
+
+               ret = sr_usb_open(ctx->libusb_ctx, usb);
+               if (ret != SR_OK)
+                       continue;
+
+               skip_device = FALSE;
+               if (want_snr) do {
+                       dev = libusb_get_device(usb->devhdl);
+                       ret = libusb_get_device_descriptor(dev, &des);
+                       if (ret != 0 || !des.iSerialNumber) {
+                               skip_device = TRUE;
+                               break;
+                       }
+                       ret = libusb_get_string_descriptor_ascii(usb->devhdl,
+                               des.iSerialNumber,
+                               (uint8_t *)serno_txt, sizeof(serno_txt));
+                       if (ret < 0) {
+                               skip_device = TRUE;
+                               break;
+                       }
+                       match = strstr(serno_txt, want_snr);
+                       skip_device = !match;
+                       sr_dbg("got serno %s, checking %s, match %d",
+                               serno_txt, want_snr, !!match);
+               } while (0);
+               if (skip_device) {
+                       sr_usb_close(usb);
+                       continue;
+               }
+
+               sdi = g_malloc0(sizeof(*sdi));
+               sdi->conn = usb;
+               sdi->inst_type = SR_INST_USB;
+               sdi->status = SR_ST_INACTIVE;
+               devc = g_malloc0(sizeof(*devc));
+               sdi->priv = devc;
+               devc->sdi = sdi;
+               devc->usb_comm_buffer = NULL;
+
+               /*
+                * Get the serial number by way of device communication.
+                * Get the firmware version. Failure is fatal.
+                */
+               ret = greatfet_get_serial_number(sdi);
+               if (ret != SR_OK || !devc->serial_number) {
+                       sr_err("Cannot get serial number.");
+                       greatfet_free_sdi(sdi);
+                       continue;
+               }
+               ret = greatfet_get_version_number(sdi);
+               if (ret != SR_OK || !devc->firmware_version) {
+                       sr_err("Cannot get firmware version.");
+                       greatfet_free_sdi(sdi);
+                       continue;
+               }
+
+               /* Continue filling in sdi and devc. */
+               snprintf(conn_id, sizeof(conn_id), "%u.%u",
+                       usb->bus, usb->address);
+               sdi->connection_id = g_strdup(conn_id);
+               sr_usb_close(usb);
+
+               sdi->vendor = g_strdup(VENDOR_TEXT);
+               sdi->model = g_strdup(MODEL_TEXT);
+               sdi->version = g_strdup(devc->firmware_version);
+               sdi->serial_num = g_strdup(devc->serial_number);
+
+               /* Create the "Logic" channel group. */
+               ch_off = 0;
+               ch_max = ARRAY_SIZE(channel_names);
+               devc->channel_names = sr_parse_probe_names(probe_names,
+                       channel_names, ch_max, ch_max, &ch_max);
+               devc->channel_count = ch_max;
+               cg = sr_channel_group_new(sdi, "Logic", NULL);
+               for (ch_idx = 0; ch_idx < ch_max; ch_idx++) {
+                       enabled = ch_idx < 8;
+                       ch = sr_channel_new(sdi, ch_off,
+                               SR_CHANNEL_LOGIC, enabled,
+                               devc->channel_names[ch_idx]);
+                       ch_off++;
+                       cg->channels = g_slist_append(cg->channels, ch);
+               }
+               devc->feed_unit_size = (ch_max + 8 - 1) / 8;
+
+               sr_sw_limits_init(&devc->sw_limits);
+               devc->samplerate = DEFAULT_SAMPLERATE;
+               devc->acquisition.bandwidth_threshold = BANDWIDTH_THRESHOLD;
+               devc->acquisition.control_interface = CONTROL_INTERFACE;
+               devc->acquisition.samples_interface = SAMPLES_INTERFACE;
+               devc->acquisition.acquisition_state = ACQ_IDLE;
+
+               devices = g_slist_append(devices, sdi);
+       }
+       g_slist_free(conn_devices);
+
+       return std_scan_complete(di, devices);
+}
+
+static int dev_open(struct sr_dev_inst *sdi)
+{
+       struct sr_dev_driver *di;
+       struct drv_context *drvc;
+       struct sr_context *ctx;
+       struct sr_usb_dev_inst *usb;
+
+       di = sdi->driver;
+       drvc = di->context;
+       ctx = drvc->sr_ctx;
+       usb = sdi->conn;
+
+       return sr_usb_open(ctx->libusb_ctx, usb);
+}
+
+static int dev_close(struct sr_dev_inst *sdi)
+{
+       struct sr_usb_dev_inst *usb;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       usb = sdi->conn;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       greatfet_release_resources(sdi);
+
+       if (!usb->devhdl)
+               return SR_ERR_BUG;
+
+       sr_info("Closing device on %s interface %d.",
+               sdi->connection_id, acq->control_interface);
+       if (acq->control_interface_claimed) {
+               libusb_release_interface(usb->devhdl, acq->control_interface);
+               acq->control_interface_claimed = FALSE;
+       }
+       sr_usb_close(usb);
+
+       return SR_OK;
+}
+
+static void clear_helper(struct dev_context *devc)
+{
+       greatfet_free_devc(devc);
+}
+
+static int dev_clear(const struct sr_dev_driver *driver)
+{
+       return std_dev_clear_with_callback(driver,
+               (std_dev_clear_callback)clear_helper);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+
+       /* Handle requests for the "Logic" channel group. */
+       if (cg) {
+               switch (key) {
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       /* Handle global options for the device. */
+       switch (key) {
+       case SR_CONF_CONN:
+               if (!sdi->connection_id)
+                       return SR_ERR_NA;
+               *data = g_variant_new_string(sdi->connection_id);
+               return SR_OK;
+       case SR_CONF_CONTINUOUS:
+               *data = g_variant_new_boolean(TRUE);
+               return SR_OK;
+       case SR_CONF_SAMPLERATE:
+               if (!devc)
+                       return SR_ERR_NA;
+               *data = g_variant_new_uint64(devc->samplerate);
+               return SR_OK;
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_MSEC:
+               if (!devc)
+                       return SR_ERR_NA;
+               return sr_sw_limits_config_get(&devc->sw_limits, key, data);
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_set(uint32_t key, GVariant *data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+
+       devc = sdi->priv;
+
+       /* Handle requests for the "Logic" channel group. */
+       if (cg) {
+               switch (key) {
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       /* Handle global options for the device. */
+       switch (key) {
+       case SR_CONF_SAMPLERATE:
+               if (!devc)
+                       return SR_ERR_NA;
+               devc->samplerate = g_variant_get_uint64(data);
+               return SR_OK;
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_MSEC:
+               if (!devc)
+                       return SR_ERR_NA;
+               return sr_sw_limits_config_set(&devc->sw_limits, key, data);
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_list(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+
+       /* Handle requests for the "Logic" channel group. */
+       if (cg) {
+               switch (key) {
+               case SR_CONF_DEVICE_OPTIONS:
+                       if (ARRAY_SIZE(devopts_cg) == 0)
+                               return SR_ERR_NA;
+                       *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                               ARRAY_AND_SIZE(devopts_cg),
+                               sizeof(devopts_cg[0]));
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       /* Handle global options for the device. */
+       switch (key) {
+       case SR_CONF_SCAN_OPTIONS:
+       case SR_CONF_DEVICE_OPTIONS:
+               return STD_CONFIG_LIST(key, data, sdi, cg,
+                       scanopts, drvopts, devopts);
+       case SR_CONF_SAMPLERATE:
+               *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates));
+               return SR_OK;
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+       struct sr_dev_driver *di;
+       struct drv_context *drvc;
+       struct sr_context *ctx;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       int ret;
+
+       if (!sdi || !sdi->driver || !sdi->priv)
+               return SR_ERR_ARG;
+       di = sdi->driver;
+       drvc = di->context;
+       ctx = drvc->sr_ctx;
+       devc = sdi->priv;
+       acq = &devc->acquisition;
+
+       acq->acquisition_state = ACQ_PREPARE;
+
+       ret = greatfet_setup_acquisition(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       if (!acq->feed_queue) {
+               acq->feed_queue = feed_queue_logic_alloc(sdi,
+                       BUFFER_SIZE, devc->feed_unit_size);
+               if (!acq->feed_queue) {
+                       sr_err("Cannot allocate session feed buffer.");
+                       return SR_ERR_MALLOC;
+               }
+       }
+
+       sr_sw_limits_acquisition_start(&devc->sw_limits);
+
+       ret = greatfet_start_acquisition(sdi);
+       acq->start_req_sent = ret == SR_OK;
+       if (ret != SR_OK) {
+               greatfet_abort_acquisition(sdi);
+               feed_queue_logic_free(acq->feed_queue);
+               acq->feed_queue = NULL;
+               return ret;
+       }
+       acq->acquisition_state = ACQ_RECEIVE;
+
+       usb_source_add(sdi->session, ctx, 50,
+               greatfet_receive_data, (void *)sdi);
+
+       ret = std_session_send_df_header(sdi);
+       acq->frame_begin_sent = ret == SR_OK;
+       (void)sr_session_send_meta(sdi, SR_CONF_SAMPLERATE,
+               g_variant_new_uint64(acq->capture_samplerate));
+
+       return SR_OK;
+}
+
+static int dev_acquisition_stop(struct sr_dev_inst *sdi)
+{
+       greatfet_abort_acquisition(sdi);
+       return SR_OK;
+}
+
+static struct sr_dev_driver greatfet_driver_info = {
+       .name = "greatfet",
+       .longname = "Great Scott Gadgets GreatFET One",
+       .api_version = 1,
+       .init = std_init,
+       .cleanup = std_cleanup,
+       .scan = scan,
+       .dev_list = std_dev_list,
+       .dev_clear = 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(greatfet_driver_info);
diff --git a/src/hardware/greatfet/protocol.c b/src/hardware/greatfet/protocol.c
new file mode 100644 (file)
index 0000000..4b1d1d9
--- /dev/null
@@ -0,0 +1,1424 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Katherine J. Temkin <k@ktemkin.com>
+ * Copyright (C) 2019 Mikaela Szekely <qyriad@gmail.com>
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "protocol.h"
+
+/*
+ * Communicate to GreatFET firmware, especially its Logic Analyzer mode.
+ *
+ * Firmware communication is done by two means: Control transfers to
+ * EP0 for command execution. Bulk transfer from EP1 for sample data.
+ * The sample data endpoint number is also provided by firmware in
+ * responses to LA configuration requests.
+ *
+ * Control transfers have a fixed layout: 2x u32 class and verb numbers,
+ * and u8[] payload data up to 512 bytes length. Payload layout depends
+ * on commands and the verb's parameters. Binary data is represented in
+ * LE format (firmware executes on Cortex-M). Strings are limited to a
+ * maximum of 128 bytes.
+ *
+ * The set of commands used by this sigrok driver is minimal:
+ * - Get the GreatFET's firmware version and serial number.
+ *   - String queries, a core verb, individual verb codes for the
+ *     version and for the serial number.
+ * - Configure Logic Analyzer mode, start and stop captures.
+ *   - Configure takes a u32 samplerate and u8 channel count. Yields
+ *     u32 samplerate, u32 buffer size, u8 endpoint number.
+ *   - Start takes a u32 samplerate (does it? depending on firmware
+ *     version?). Empty/no response.
+ *   - Stop has empty/no request and response payloads.
+ *
+ * Firmware implementation details, observed during sigrok driver
+ * creation.
+ * - Serial number "strings" in responses may carry binary data and
+ *   not a text presentation of the serial number. It's uncertain
+ *   whether that is by design or an oversight. This sigrok driver
+ *   copes when it happens. (Remainder from another request which
+ *   provided the part number as well?)
+ * - The GreatFET firmware is designed for exploration by host apps.
+ *   The embedded classes, their methods, their in/out parameters,
+ *   including builtin help texts, can get enumerated. This driver
+ *   does not use this discovery approach, assumes a given protocol.
+ * - The NXP LPC4330 chip has 16 SGPIO pins. It's assumed that the
+ *   GreatFET firmware currently does not support more than 8 logic
+ *   channels due to constraints on bitbang machinery synchronization
+ *   which is under construction (IIUC, it's about pin banks that
+ *   run independently). When firmware versions get identified which
+ *   transparently (from the host's perspective) support more than
+ *   8 channels, this host driver may need a little adjustment.
+ * - The device can sample and stream 8 channels to the host at a
+ *   continuous rate of 40.8MHz. Higher rates are possible assuming
+ *   that fewer pins get sampled. The firmware then provides sample
+ *   memory where data taken at several sample points reside in the
+ *   same byte of sample memory. It helps that power-of-two bitness
+ *   is applied, IOW that there are either 1, 2, 4, or 8 bits per
+ *   sample point. Even when say 3 or 5 channels are enabled. The
+ *   device firmware may assume that a "dense" list of channels gets
+ *   enabled, the sigrok driver supports when some disabled channels
+ *   preceed other enabled channels. The device is then asked to get
+ *   as many channels as are needed to cover all enabled channels,
+ *   including potentially disabled channels before them.
+ * - The LA configure request returns a samplerate that is supported
+ *   by the hardware/firmware combination and will be used during
+ *   acquisition. This returned rate is at least as high as the
+ *   requested samplerate. But might exceed the USB bandwidth which
+ *   the firmware is capable to sustain. Users may not expect that
+ *   since numbers add up differently from their perspective. In the
+ *   example of 3 enabled channels and a requested 72MHz samplerate,
+ *   the firmware will derive that it needs to sample 4 channels at
+ *   a 102MHz rate. Which exceeds its capabilities while users may
+ *   not be aware of these constraints. This sigrok driver attempts
+ *   to detect the condition, and not start an acquisition. And also
+ *   emits diagnostics (at info level which is silent by default).
+ *   It's assumed that users increase verbosity when diagnosing
+ *   issues they may experience.
+ */
+
+/*
+ * Assign a symbolic name to endpoint 0 which is used for USB control
+ * transfers. Although those "or 0" phrases don't take effect from the
+ * compiler's perspective, they hopefully increase readability of the
+ * USB related incantations.
+ *
+ * Endpoint 1 for sample data reception is not declared here. Its value
+ * is taken from logic analyzer configure response. Which remains more
+ * portable across firmware versions and supported device models.
+ */
+#define CONTROL_ENDPOINT       0
+
+/* Header fields for USB control requests. */
+#define LIBGREAT_REQUEST_NUMBER        0x65
+#define LIBGREAT_VALUE_EXECUTE 0
+#define LIBGREAT_FLAG_SKIP_RSP (1UL << 0)
+
+/* Classes and their verbs for core and logic analyzer. */
+#define GREATFET_CLASS_CORE    0x000
+#define CORE_VERB_READ_VERSION 0x1
+#define CORE_VERB_READ_SERIAL  0x3
+
+#define GREATFET_CLASS_LA      0x10d
+#define LA_VERB_CONFIGURE      0x0
+#define LA_VERB_FIRST_PIN      0x1
+#define LA_VERB_ALT_PIN_MAP    0x2
+#define LA_VERB_START_CAPTURE  0x3
+#define LA_VERB_STOP_CAPTURE   0x4
+
+/* Maximum text string and binary payload sizes for control requests. */
+#define CORE_MAX_STRING_LENGTH 128
+#define LOGIC_MAX_PAYLOAD_DATA 512
+
+/* USB communication parameters, pool dimensions. */
+#define LOGIC_DEFAULT_TIMEOUT  1000
+#define TRANSFER_POOL_SIZE     16
+#define TRANSFER_BUFFER_SIZE   (256 * 1024)
+
+static int greatfet_process_receive_data(const struct sr_dev_inst *sdi,
+       const uint8_t *data, size_t dlen);
+static int greatfet_cancel_transfers(const struct sr_dev_inst *sdi);
+
+/* Communicate a GreatFET request to EP0, and get its response. */
+static int greatfet_ctrl_out_in(const struct sr_dev_inst *sdi,
+       const uint8_t *tx_data, size_t tx_size,
+       uint8_t *rx_data, size_t rx_size, unsigned int timeout_ms)
+{
+       struct sr_usb_dev_inst *usb;
+       uint16_t flags;
+       int ret;
+       size_t sent, rcvd;
+
+       usb = sdi->conn;
+       if (!usb)
+               return SR_ERR_ARG;
+
+       /* Caller can request to skip transmission of a response. */
+       flags = 0;
+       if (!rx_size)
+               flags |= LIBGREAT_FLAG_SKIP_RSP;
+
+       /* Send USB Control OUT request. */
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *dump = sr_hexdump_new(tx_data, tx_size);
+               sr_spew("USB out data: %s", dump->str);
+               sr_hexdump_free(dump);
+       }
+       ret = libusb_control_transfer(usb->devhdl,
+               LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_ENDPOINT |
+               LIBUSB_ENDPOINT_OUT | CONTROL_ENDPOINT,
+               LIBGREAT_REQUEST_NUMBER, LIBGREAT_VALUE_EXECUTE,
+               flags, (void *)tx_data, tx_size, timeout_ms);
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               const char *msg;
+               msg = ret < 0 ? libusb_error_name(ret) : "-";
+               sr_spew("USB out, rc %d, %s", ret, msg);
+       }
+       if (ret < 0) {
+               /* Rate limit error messages. Skip "please retry" kinds. */
+               if (ret != LIBUSB_ERROR_BUSY) {
+                       sr_err("USB out transfer failed: %s (%d)",
+                               libusb_error_name(ret), ret);
+               }
+               return SR_ERR_IO;
+       }
+       sent = (size_t)ret;
+       if (sent != tx_size) {
+               sr_err("Short USB write: want %zu, got %zu: %s.",
+                       tx_size, sent, libusb_error_name(ret));
+               return SR_ERR_IO;
+       }
+
+       /* Get the USB Control IN response. */
+       if (!rx_size)
+               return SR_OK;
+       ret = libusb_control_transfer(usb->devhdl,
+               LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_ENDPOINT |
+               LIBUSB_ENDPOINT_IN | CONTROL_ENDPOINT,
+               LIBGREAT_REQUEST_NUMBER, LIBGREAT_VALUE_EXECUTE,
+               0, rx_data, rx_size, timeout_ms);
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               const char *msg;
+               msg = ret < 0 ? libusb_error_name(ret) : "-";
+               sr_spew("USB in, rc %d, %s", ret, msg);
+       }
+       if (ret < 0) {
+               sr_err("USB in transfer failed: %s (%d)",
+                       libusb_error_name(ret), ret);
+               return SR_ERR_IO;
+       }
+       rcvd = (size_t)ret;
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *dump = sr_hexdump_new(rx_data, rcvd);
+               sr_spew("USB in data: %s", dump->str);
+               sr_hexdump_free(dump);
+       }
+       /* Short read, including zero length, is not fatal. */
+
+       return rcvd;
+}
+
+/*
+ * Use a string buffer in devc for USB transfers. This simplifies
+ * resource management in error paths.
+ */
+static int greatfet_prep_usb_buffer(const struct sr_dev_inst *sdi,
+       uint8_t **tx_buff, size_t *tx_size, uint8_t **rx_buff, size_t *rx_size)
+{
+       struct dev_context *devc;
+       size_t want_len;
+       GString *s;
+
+       if (tx_buff)
+               *tx_buff = NULL;
+       if (tx_size)
+               *tx_size = 0;
+       if (rx_buff)
+               *rx_buff = NULL;
+       if (rx_size)
+               *rx_size = 0;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /*
+        * Allocate the string buffer unless previously done.
+        * Ensure sufficient allocated space for request/response use.
+        * Assume that glib GString is suitable to hold uint8_t[] data.
+        */
+       if (!devc->usb_comm_buffer) {
+               want_len = 2 * sizeof(uint32_t) + LOGIC_MAX_PAYLOAD_DATA;
+               devc->usb_comm_buffer = g_string_sized_new(want_len);
+               if (!devc->usb_comm_buffer)
+                       return SR_ERR_MALLOC;
+       }
+
+       /* Pass buffer start and size to the caller if requested. */
+       s = devc->usb_comm_buffer;
+       if (tx_buff)
+               *tx_buff = (uint8_t *)s->str;
+       if (tx_size)
+               *tx_size = s->allocated_len;
+       if (rx_buff)
+               *rx_buff = (uint8_t *)s->str;
+       if (rx_size)
+               *rx_size = s->allocated_len;
+
+       return SR_OK;
+}
+
+/* Retrieve a string by executing a core service. */
+static int greatfet_get_string(const struct sr_dev_inst *sdi,
+       uint32_t verb, char **value)
+{
+       uint8_t *req, *rsp;
+       size_t rsp_size;
+       uint8_t *wrptr;
+       size_t wrlen, rcvd;
+       const char *text;
+       int ret;
+
+       if (value)
+               *value = NULL;
+       if (!sdi)
+               return SR_ERR_ARG;
+       ret = greatfet_prep_usb_buffer(sdi, &req, NULL, &rsp, &rsp_size);
+       if (ret != SR_OK)
+               return ret;
+
+       wrptr = req;
+       write_u32le_inc(&wrptr, GREATFET_CLASS_CORE);
+       write_u32le_inc(&wrptr, verb);
+       wrlen = wrptr - req;
+       ret = greatfet_ctrl_out_in(sdi, req, wrlen,
+               rsp, rsp_size, LOGIC_DEFAULT_TIMEOUT);
+       if (ret < 0) {
+               sr_err("Cannot get core string.");
+               return ret;
+       }
+       rcvd = (size_t)ret;
+
+       rsp[rcvd] = '\0';
+       text = (const char *)rsp;
+       sr_dbg("got string, verb %u, text (%zu) %s", verb, rcvd, text);
+       if (value && *text) {
+               *value = g_strndup(text, rcvd);
+       } else if (value) {
+               /*
+                * g_strndup(3) does _not_ copy 'n' bytes. Instead it
+                * truncates the result at the first NUL character seen.
+                * That's why we need extra logic to pass binary data
+                * to callers, to not violate API layers and confuse
+                * USB readers with firmware implementation details
+                * (that may be version dependent).
+                * The very condition to determine whether text or some
+                * binary data was received is a simple check for NUL
+                * in the first position, implemented above. This is
+                * GoodEnough(TM) to handle the firmware version case.
+                */
+               *value = g_malloc0(rcvd + 1);
+               memcpy(*value, text, rcvd);
+       }
+
+       return rcvd;
+}
+
+SR_PRIV int greatfet_get_serial_number(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       char *text;
+       int ret;
+       const uint8_t *rdptr;
+       size_t rdlen;
+       GString *snr;
+       uint32_t chunk;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       ret = greatfet_get_string(sdi, CORE_VERB_READ_SERIAL, &text);
+       if (ret < 0)
+               return ret;
+       if (!text)
+               return SR_ERR_DATA;
+
+       /*
+        * The simple case, we got a text string. The 2019 K.Temkin
+        * implementation took the received string as is. So there
+        * are firmware versions which provide this presentation.
+        */
+       if (*text) {
+               devc->serial_number = text;
+               return SR_OK;
+       }
+
+       /*
+        * The complex case. The received "string" looks binary. Local
+        * setups with v2018.12.1 and v2021.2.1 firmware versions yield
+        * response data that does not look like a text string. Instead
+        * it looks like four u32 fields which carry a binary value and
+        * leading padding. Try that interpreation as well. Construct a
+        * twenty character text presentation from that binary content.
+        *
+        * Implementation detail: Is the "leader" the part number which
+        * a different firmware request may yield? Are there other verbs
+        * which reliably yield the serial number in text format?
+        */
+       rdptr = (const uint8_t *)text;
+       rdlen = (size_t)ret;
+       sr_dbg("trying to read serial nr \"text\" as binary");
+       if (rdlen != 4 * sizeof(uint32_t)) {
+               g_free(text);
+               return SR_ERR_DATA;
+       }
+       snr = g_string_sized_new(20 + 1);
+       chunk = read_u32le_inc(&rdptr);
+       if (chunk) {
+               g_free(text);
+               return SR_ERR_DATA;
+       }
+       chunk = read_u32le_inc(&rdptr);
+       if (chunk) {
+               g_free(text);
+               return SR_ERR_DATA;
+       }
+       g_string_append_printf(snr, "%04" PRIx32, chunk);
+       chunk = read_u32le_inc(&rdptr);
+       g_string_append_printf(snr, "%08" PRIx32, chunk);
+       chunk = read_u32le_inc(&rdptr);
+       g_string_append_printf(snr, "%08" PRIx32, chunk);
+       sr_dbg("got serial number text %s", snr->str);
+       g_free(text);
+       text = g_string_free(snr, FALSE);
+       devc->serial_number = text;
+       return SR_OK;
+}
+
+SR_PRIV int greatfet_get_version_number(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       char *text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       ret = greatfet_get_string(sdi, CORE_VERB_READ_VERSION, &text);
+       if (ret < SR_OK)
+               return ret;
+
+       devc->firmware_version = text;
+       return SR_OK;
+}
+
+/*
+ * Transmit a parameter-less request that wants no response. Or a
+ * request with just a few bytes worth of parameter values, still
+ * not expecting a response.
+ */
+static int greatfet_trivial_request(const struct sr_dev_inst *sdi,
+       uint32_t cls, uint32_t verb, const uint8_t *tx_data, size_t tx_dlen)
+{
+       struct dev_context *devc;
+       uint8_t *req;
+       uint8_t *wrptr;
+       size_t wrlen;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       ret = greatfet_prep_usb_buffer(sdi, &req, NULL, NULL, NULL);
+       if (ret != SR_OK)
+               return ret;
+
+       wrptr = req;
+       write_u32le_inc(&wrptr, cls);
+       write_u32le_inc(&wrptr, verb);
+       while (tx_dlen--)
+               write_u8_inc(&wrptr, *tx_data++);
+       wrlen = wrptr - req;
+       return greatfet_ctrl_out_in(sdi, req, wrlen,
+               NULL, 0, LOGIC_DEFAULT_TIMEOUT);
+}
+
+/*
+ * Transmit a "configure logic analyzer" request. Gets the resulting
+ * samplerate (which can differ from requested values) and endpoint
+ * (which is very useful for compatibility across devices/versions).
+ * Also gets the device firmware's buffer size, which is only used
+ * for information, while the host assumes a fixed larger buffer size
+ * for its own purposes.
+ */
+static int greatfet_logic_config(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct sr_usb_dev_inst *usb;
+       uint8_t *req, *rsp;
+       size_t rsp_size;
+       uint8_t *wrptr;
+       size_t wrlen, rcvd, want_len;
+       const uint8_t *rdptr;
+       uint64_t rate, bw;
+       size_t bufsize;
+       uint8_t ep;
+       char *print_bw;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       usb = sdi->conn;
+       if (!devc || !usb)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       ret = greatfet_prep_usb_buffer(sdi, &req, NULL, &rsp, &rsp_size);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Optionally request to capture the upper pin bank. The device
+        * can sample from pins starting at number 8. We use the feature
+        * transparently when the first 8 channels are disabled.
+        *
+        * Values different from 0 or 8 are not used here. The details
+        * of the SGPIO hardware implementation degrade performance in
+        * this case. Its use is not desirable for users.
+        */
+       sr_dbg("about to config first pin, upper %d", acq->use_upper_pins);
+       wrptr = req;
+       write_u32le_inc(&wrptr, GREATFET_CLASS_LA);
+       write_u32le_inc(&wrptr, LA_VERB_FIRST_PIN);
+       write_u8_inc(&wrptr, acq->use_upper_pins ? 8 : 0);
+       wrlen = wrptr - req;
+       ret = greatfet_ctrl_out_in(sdi, req, wrlen,
+               NULL, 0, LOGIC_DEFAULT_TIMEOUT);
+       if (ret < 0) {
+               sr_err("Cannot configure first capture pin.");
+               return ret;
+       }
+
+       /* Disable alt pin mapping, just for good measure. */
+       sr_dbg("about to config alt pin mapping");
+       wrptr = req;
+       write_u32le_inc(&wrptr, GREATFET_CLASS_LA);
+       write_u32le_inc(&wrptr, LA_VERB_ALT_PIN_MAP);
+       write_u8_inc(&wrptr, 0);
+       wrlen = wrptr - req;
+       ret = greatfet_ctrl_out_in(sdi, req, wrlen,
+               NULL, 0, LOGIC_DEFAULT_TIMEOUT);
+       if (ret < 0) {
+               sr_err("Cannot configure alt pin mapping.");
+               return ret;
+       }
+
+       /*
+        * Prepare to get a specific amount of receive data. The logic
+        * analyzer configure response is strictly binary, in contrast
+        * to variable length string responses elsewhere.
+        */
+       want_len = 2 * sizeof(uint32_t) + sizeof(uint8_t);
+       if (rsp_size < want_len)
+               return SR_ERR_BUG;
+       rsp_size = want_len;
+
+       sr_dbg("about to config LA, rate %" PRIu64 ", chans %zu",
+               devc->samplerate, acq->capture_channels);
+       wrptr = req;
+       write_u32le_inc(&wrptr, GREATFET_CLASS_LA);
+       write_u32le_inc(&wrptr, LA_VERB_CONFIGURE);
+       write_u32le_inc(&wrptr, devc->samplerate);
+       write_u8_inc(&wrptr, acq->capture_channels);
+       wrlen = wrptr - req;
+       ret = greatfet_ctrl_out_in(sdi, req, wrlen,
+               rsp, rsp_size, LOGIC_DEFAULT_TIMEOUT);
+       if (ret < 0) {
+               sr_err("Cannot configure logic analyzer mode.");
+               return ret;
+       }
+       rcvd = (size_t)ret;
+       if (rcvd != want_len) {
+               sr_warn("Unexpected LA configuration response length.");
+               return SR_ERR_DATA;
+       }
+
+       rdptr = rsp;
+       rate = read_u32le_inc(&rdptr);
+       bufsize = read_u32le_inc(&rdptr);
+       ep = read_u8_inc(&rdptr);
+       sr_dbg("LA configured, rate %" PRIu64 ", buf %zu, ep %" PRIu8,
+               rate, bufsize, ep);
+       if (rate != devc->samplerate) {
+               sr_info("Configuration feedback, want rate %" PRIu64 ", got rate %." PRIu64,
+                       devc->samplerate, rate);
+               devc->samplerate = rate;
+       }
+       acq->capture_samplerate = rate;
+       acq->firmware_bufsize = bufsize;
+       acq->samples_endpoint = ep;
+
+       /*
+        * The firmware does not reject requests that would exceed
+        * its capabilities. Yet the device becomes unaccessible when
+        * START is sent in that situation. (Observed with v2021.2.1
+        * firmware.)
+        *
+        * Assume a maximum USB bandwidth that we don't want to exceed.
+        * It's protecting the GreatFET's firmware. It's not a statement
+        * on the host's capability of keeping up with the GreatFET's
+        * firmware capabilities. :)
+        */
+       print_bw = sr_samplerate_string(acq->capture_samplerate);
+       sr_info("Capture configuration: %zu channels, samplerate %s.",
+               acq->capture_channels, print_bw);
+       g_free(print_bw);
+       bw = acq->capture_samplerate * 8 / acq->points_per_byte;
+       if (!acq->use_upper_pins)
+               bw *= acq->wire_unit_size;
+       print_bw = sr_si_string_u64(bw, "bps");
+       sr_info("Resulting USB bandwidth: %s.", print_bw);
+       g_free(print_bw);
+       if (acq->bandwidth_threshold && bw > acq->bandwidth_threshold) {
+               sr_err("Configuration exceeds bandwidth limit. Aborting.");
+               return SR_ERR_SAMPLERATE;
+       }
+
+       return SR_OK;
+}
+
+/* Transmit "start logic capture" request. */
+static int greatfet_logic_start(const struct sr_dev_inst *sdi)
+{
+       int ret;
+
+       ret = greatfet_trivial_request(sdi,
+               GREATFET_CLASS_LA, LA_VERB_START_CAPTURE, NULL, 0);
+       sr_dbg("LA start, USB out, rc %d", ret);
+       if (ret != SR_OK)
+               sr_err("Cannot start logic analyzer capture.");
+
+       return ret;
+}
+
+/* Transmit "stop logic capture" request. */
+static int greatfet_logic_stop(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       int ret;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       /* Only send STOP when START was sent before. */
+       if (!acq->start_req_sent)
+               return SR_OK;
+
+       ret = greatfet_trivial_request(sdi,
+               GREATFET_CLASS_LA, LA_VERB_STOP_CAPTURE, NULL, 0);
+       sr_dbg("LA stop, USB out, rc %d", ret);
+       if (ret == SR_OK)
+               acq->start_req_sent = FALSE;
+       else
+               sr_warn("Cannot stop logic analyzer capture in the device.");
+
+       return ret;
+}
+
+/*
+ * Determine how many channels the device firmware needs to sample.
+ * So that resulting capture data will cover all those logic channels
+ * which currently are enabled on the sigrok side. We (have to) accept
+ * when the sequence of enabled channels "has gaps" in them. Disabling
+ * channels in the middle of the pin groups is a user's choice that we
+ * need to obey. The count of enabled channels is not good enough for
+ * the purpose of acquisition, it must be "a maximum index" or a total
+ * to-get-sampled count.
+ */
+static int greatfet_calc_capture_chans(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       GSList *l;
+       struct sr_channel *ch;
+       int last_used_idx;
+       uint16_t pin_map;
+       size_t logic_ch_count, en_ch_count, fw_ch_count;
+       gboolean have_upper, have_lower, use_upper_pins;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       last_used_idx = -1;
+       logic_ch_count = 0;
+       pin_map = 0;
+       for (l = sdi->channels; l; l = l->next) {
+               ch = l->data;
+               if (ch->type != SR_CHANNEL_LOGIC)
+                       continue;
+               logic_ch_count++;
+               if (!ch->enabled)
+                       continue;
+               if (last_used_idx < ch->index)
+                       last_used_idx = ch->index;
+               pin_map |= 1UL << ch->index;
+       }
+       en_ch_count = last_used_idx + 1;
+       sr_dbg("channel count, logic %zu, highest enabled idx %d -> count %zu",
+               logic_ch_count, last_used_idx, en_ch_count);
+       if (!en_ch_count)
+               return SR_ERR_ARG;
+       have_upper = pin_map & 0xff00;
+       have_lower = pin_map & 0x00ff;
+       use_upper_pins = have_upper && !have_lower;
+       if (use_upper_pins) {
+               sr_dbg("ch mask 0x%04x -> using upper pins", pin_map);
+               last_used_idx -= 8;
+               en_ch_count -= 8;
+       }
+       if (have_upper && !use_upper_pins)
+               sr_warn("Multi-bank capture, check firmware support!");
+
+       acq->capture_channels = en_ch_count;
+       acq->use_upper_pins = use_upper_pins;
+       ret = sr_next_power_of_two(last_used_idx, NULL, &fw_ch_count);
+       if (ret != SR_OK)
+               return ret;
+       if (!fw_ch_count)
+               return SR_ERR_ARG;
+       if (fw_ch_count > 8) {
+               acq->wire_unit_size = sizeof(uint16_t);
+               acq->points_per_byte = 1;
+       } else {
+               acq->wire_unit_size = sizeof(uint8_t);
+               acq->points_per_byte = 8 / fw_ch_count;
+       }
+       acq->channel_shift = fw_ch_count % 8;
+       sr_dbg("unit %zu, dense %d -> shift %zu, points %zu",
+               acq->wire_unit_size, !!acq->channel_shift,
+               acq->channel_shift, acq->points_per_byte);
+
+       return SR_OK;
+}
+
+/*
+ * This is an opportunity to adapt the host's USB transfer size to
+ * the value which the device firmware has provided in the LA config
+ * response.
+ *
+ * We let the opportunity pass. Always use a fixed value for the host
+ * configuration. BULK transfers will adopt, which reduces the number
+ * of transfer completion events for the host.
+ *
+ * Notice that transfer size adjustment is _not_ a means to get user
+ * feedback earlier at low samplerates. This may be done in other
+ * drivers but does not take effect here. Because a buffer is used to
+ * submit sample values to the session. When in doubt, the feed queue
+ * needs flushing.
+ *
+ * TODO Consider whether sample data needs flushing when sample rates
+ * are low and buffers are deep. Ideally use common feed queue support
+ * if that becomes available in the future. Translate low samplerates
+ * (and channel counts) to the amount of samples after which the queue
+ * should get flushed.
+ *
+ * This implementation assumes that samplerates start at 1MHz, and
+ * flushing is not necessary.
+ */
+static int greatfet_calc_submit_size(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       dxfer = &devc->transfers;
+
+       dxfer->capture_bufsize = dxfer->transfer_bufsize;
+       return SR_OK;
+}
+
+/*
+ * This routine is local to protocol.c and does mere data manipulation
+ * and a single attempt at sending "logic analyzer stop" to the device.
+ * This routine gets invoked from USB transfer completion callbacks as
+ * well as periodic timer or data availability callbacks. It is essential
+ * to not spend extended periods of time here.
+ */
+static void greatfet_abort_acquisition_quick(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+
+       if (!sdi)
+               return;
+       devc = sdi->priv;
+       if (!devc)
+               return;
+       acq = &devc->acquisition;
+
+       if (acq->acquisition_state == ACQ_RECEIVE)
+               acq->acquisition_state = ACQ_SHUTDOWN;
+
+       (void)greatfet_logic_stop(sdi);
+       greatfet_cancel_transfers(sdi);
+
+       if (acq->feed_queue)
+               feed_queue_logic_flush(acq->feed_queue);
+}
+
+/* Allocate USB transfers and associated receive buffers. */
+static int greatfet_allocate_transfers(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+       size_t alloc_size, idx;
+       struct libusb_transfer *xfer;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       dxfer = &devc->transfers;
+
+       dxfer->transfer_bufsize = TRANSFER_BUFFER_SIZE;
+       dxfer->transfers_count = TRANSFER_POOL_SIZE;
+
+       alloc_size = dxfer->transfers_count * dxfer->transfer_bufsize;
+       dxfer->transfer_buffer = g_malloc0(alloc_size);
+       if (!dxfer->transfer_buffer)
+               return SR_ERR_MALLOC;
+
+       alloc_size = dxfer->transfers_count;
+       alloc_size *= sizeof(dxfer->transfers[0]);
+       dxfer->transfers = g_malloc0(alloc_size);
+       if (!dxfer->transfers)
+               return SR_ERR_MALLOC;
+
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               xfer = libusb_alloc_transfer(0);
+               if (!xfer)
+                       return SR_ERR_MALLOC;
+               dxfer->transfers[idx] = xfer;
+       }
+
+       return SR_OK;
+}
+
+/* Submit USB transfers for reception, registers the data callback. */
+static int greatfet_prepare_transfers(const struct sr_dev_inst *sdi,
+       libusb_transfer_cb_fn callback)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct dev_transfers_t *dxfer;
+       struct sr_usb_dev_inst *conn;
+       uint8_t ep;
+       size_t submit_length;
+       size_t off, idx;
+       struct libusb_transfer *xfer;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       conn = sdi->conn;
+       if (!devc || !conn)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+       dxfer = &devc->transfers;
+
+       ep = acq->samples_endpoint;
+       ret = greatfet_calc_submit_size(sdi);
+       if (ret != SR_OK)
+               return ret;
+       submit_length = dxfer->capture_bufsize;
+       if (submit_length > dxfer->transfer_bufsize)
+               submit_length = dxfer->transfer_bufsize;
+       sr_dbg("prep xfer, ep %u (%u), len %zu",
+               ep, ep & ~LIBUSB_ENDPOINT_IN, submit_length);
+
+       dxfer->active_transfers = 0;
+       off = 0;
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               xfer = dxfer->transfers[idx];
+               libusb_fill_bulk_transfer(xfer, conn->devhdl, ep,
+                       &dxfer->transfer_buffer[off], submit_length,
+                       callback, (void *)sdi, 0);
+               if (!xfer->buffer)
+                       return SR_ERR_MALLOC;
+               ret = libusb_submit_transfer(xfer);
+               if (ret != 0) {
+                       sr_spew("submit bulk xfer failed, idx %zu, %d: %s",
+                               idx, ret, libusb_error_name(ret));
+                       return SR_ERR_IO;
+               }
+               dxfer->active_transfers++;
+               off += submit_length;
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Initiate the termination of an acquisition. Cancel all USB transfers.
+ * Their completion will drive further progress including resource
+ * release.
+ */
+static int greatfet_cancel_transfers(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+       size_t idx;
+       struct libusb_transfer *xfer;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       dxfer = &devc->transfers;
+       if (!dxfer->transfers)
+               return SR_OK;
+
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               xfer = dxfer->transfers[idx];
+               if (!xfer)
+                       continue;
+               (void)libusb_cancel_transfer(xfer);
+               /*
+                * Cancelled transfers will cause acquisitions to abort
+                * in their callback. Keep the "active" count as is.
+                */
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Free an individual transfer during its callback's execution.
+ * Releasing the last USB transfer also happens to drive more of
+ * the shutdown path.
+ */
+static void greatfet_free_transfer(const struct sr_dev_inst *sdi,
+       struct libusb_transfer *xfer)
+{
+       struct drv_context *drvc;
+       struct sr_usb_dev_inst *usb;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct dev_transfers_t *dxfer;
+       size_t idx;
+
+       if (!sdi || !sdi->driver)
+               return;
+       drvc = sdi->driver->context;
+       usb = sdi->conn;
+       devc = sdi->priv;
+       if (!drvc || !usb || !devc)
+               return;
+       acq = &devc->acquisition;
+       dxfer = &devc->transfers;
+
+       /* Void the transfer in the driver's list of transfers. */
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               if (xfer != dxfer->transfers[idx])
+                       continue;
+               dxfer->transfers[idx] = NULL;
+               dxfer->active_transfers--;
+               break;
+       }
+
+       /* Release the transfer from libusb use. */
+       libusb_free_transfer(xfer);
+
+       /* Done here when more transfers are still pending. */
+       if (!dxfer->active_transfers)
+               return;
+
+       /*
+        * The last USB transfer has been freed after completion.
+        * Post process the previous acquisition's execution.
+        */
+       (void)greatfet_stop_acquisition(sdi);
+       if (acq->frame_begin_sent) {
+               std_session_send_df_end(sdi);
+               acq->frame_begin_sent = FALSE;
+       }
+       usb_source_remove(sdi->session, drvc->sr_ctx);
+       if (acq->samples_interface_claimed) {
+               libusb_release_interface(usb->devhdl, acq->samples_interface);
+               acq->samples_interface_claimed = FALSE;
+       }
+       feed_queue_logic_free(acq->feed_queue);
+       acq->feed_queue = NULL;
+       acq->acquisition_state = ACQ_IDLE;
+}
+
+/*
+ * Callback for the completion of previously submitted USB transfers.
+ * Processes received sample memory content. Initiates termination of
+ * the current acquisition in case of failed processing or failed
+ * communication to the acquisition device. Also initiates termination
+ * when previously configured acquisition limits were reached.
+ */
+static void LIBUSB_CALL xfer_complete_cb(struct libusb_transfer *xfer)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       const uint8_t *data;
+       size_t dlen;
+       gboolean was_completed, was_cancelled;
+       gboolean has_timedout, device_gone, is_stalled;
+       int level;
+       gboolean shall_abort;
+       int ret;
+
+       sdi = xfer ? xfer->user_data : NULL;
+       devc = sdi ? sdi->priv : NULL;
+       if (!sdi || !devc) {
+               /* ShouldNotHappen(TM) */
+               sr_warn("Completion of unregistered USB transfer.");
+               libusb_free_transfer(xfer);
+               return;
+       }
+       acq = &devc->acquisition;
+
+       /*
+        * Outside of an acquisition? Or in its shutdown path?
+        * Just release the USB transfer, don't process its data.
+        */
+       if (acq->acquisition_state != ACQ_RECEIVE) {
+               greatfet_free_transfer(sdi, xfer);
+               return;
+       }
+
+       /*
+        * Avoid the unfortunate libusb identifiers and data types.
+        * Simplify USB transfer status checks for later code paths.
+        * Optionally log the USB transfers' completion.
+        */
+       data = xfer->buffer;
+       dlen = xfer->actual_length;
+       was_completed = xfer->status == LIBUSB_TRANSFER_COMPLETED;
+       has_timedout = xfer->status == LIBUSB_TRANSFER_TIMED_OUT;
+       was_cancelled = xfer->status == LIBUSB_TRANSFER_CANCELLED;
+       device_gone = xfer->status == LIBUSB_TRANSFER_NO_DEVICE;
+       is_stalled = xfer->status == LIBUSB_TRANSFER_STALL;
+       level = sr_log_loglevel_get();
+       if (level >= SR_LOG_SPEW) {
+               sr_spew("USB transfer, status %s, byte count %zu.",
+                       libusb_error_name(xfer->status), dlen);
+       } else if (level >= SR_LOG_DBG && !was_completed) {
+               sr_dbg("USB transfer, status %s, byte count %zu.",
+                       libusb_error_name(xfer->status), dlen);
+       }
+
+       /*
+        * Timed out transfers may contain a little data. Warn but accept.
+        * Typical case will be completed transfers. Cancelled transfers
+        * are seen in shutdown paths, their data need not get processed.
+        * Terminate acquisition in case of communication or processing
+        * failure, or when limits were reached.
+        */
+       shall_abort = FALSE;
+       if (has_timedout)
+               sr_warn("USB transfer timed out. Using available data.");
+       if (was_completed || has_timedout) {
+               ret = greatfet_process_receive_data(sdi, data, dlen);
+               if (ret != SR_OK) {
+                       sr_err("Error processing sample data. Aborting.");
+                       shall_abort = TRUE;
+               }
+               if (acq->acquisition_state != ACQ_RECEIVE) {
+                       sr_dbg("Sample data processing ends acquisition.");
+                       feed_queue_logic_flush(acq->feed_queue);
+                       shall_abort = TRUE;
+               }
+       } else if (device_gone) {
+               sr_err("Device gone during USB transfer. Aborting.");
+               shall_abort = TRUE;
+       } else if (was_cancelled) {
+               sr_dbg("Cancelled USB transfer. Terminating acquisition.");
+               shall_abort = TRUE;
+       } else if (is_stalled) {
+               sr_err("Device firmware is stalled on USB transfer. Aborting.");
+               shall_abort = TRUE;
+       } else {
+               sr_err("USB transfer failed (%s). Aborting.",
+                       libusb_error_name(xfer->status));
+               shall_abort = TRUE;
+       }
+
+       /*
+        * Resubmit the USB transfer for continued reception of sample
+        * data. Or release the transfer when acquisition terminates
+        * after errors were seen, or limits were reached, or the end
+        * was requested in other regular ways.
+        *
+        * In the case of error or other terminating conditions cancel
+        * the currently executing acquisition, end all USB transfers.
+        */
+       if (!shall_abort) {
+               ret = libusb_submit_transfer(xfer);
+               if (ret < 0) {
+                       sr_err("Cannot resubmit USB transfer. Aborting.");
+                       shall_abort = TRUE;
+               }
+       }
+       if (shall_abort) {
+               greatfet_free_transfer(sdi, xfer);
+               greatfet_abort_acquisition_quick(sdi);
+       }
+}
+
+/* The public protocol.c API to start/stop acquisitions. */
+
+SR_PRIV int greatfet_setup_acquisition(const struct sr_dev_inst *sdi)
+{
+       int ret;
+
+       ret = greatfet_allocate_transfers(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = greatfet_calc_capture_chans(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int greatfet_start_acquisition(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct sr_usb_dev_inst *usb;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       usb = sdi->conn;
+       if (!devc || !usb)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       /*
+        * Configure the logic analyzer. Claim the USB interface. This
+        * part of the sequence is not time critical.
+        */
+       ret = greatfet_logic_config(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = libusb_claim_interface(usb->devhdl, acq->samples_interface);
+       acq->samples_interface_claimed = ret == 0;
+
+       /*
+        * Ideally we could submit USB transfers before sending the
+        * logic analyzer start request. Experience suggests that this
+        * results in libusb IO errors. That's why we need to accept the
+        * window of blindness between sending the LA start request and
+        * initiating USB data reception.
+        */
+       ret = greatfet_logic_start(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = greatfet_prepare_transfers(sdi, xfer_complete_cb);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * The public acquisition abort routine, invoked by api.c logic. Could
+ * optionally spend more time than the _quick() routine.
+ */
+SR_PRIV void greatfet_abort_acquisition(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+
+       if (!sdi)
+               return;
+       devc = sdi->priv;
+       if (!devc)
+               return;
+
+       (void)greatfet_logic_stop(sdi);
+       greatfet_abort_acquisition_quick(sdi);
+}
+
+SR_PRIV int greatfet_stop_acquisition(const struct sr_dev_inst *sdi)
+{
+       struct sr_usb_dev_inst *usb;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       usb = sdi->conn;
+       if (!usb)
+               return SR_ERR_ARG;
+
+       ret = greatfet_logic_stop(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV void greatfet_release_resources(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+
+       if (!sdi)
+               return;
+       devc = sdi->priv;
+       if (!devc)
+               return;
+       dxfer = &devc->transfers;
+
+       /*
+        * Is there something that needs to be done here? Transfers'
+        * cancellation gets initiated and then happens as they keep
+        * completing. The completion handler releases their libusb
+        * resources. The last release also unregisters the periodic
+        * glib main loop callback.
+        *
+        * Can something be done here? The receive buffer still is
+        * allocated. As is the feed queue. Can we synchronize to the
+        * last release of the USB resources? Need we keep invoking
+        * the receive callback until the USB transfers pool has been
+        * released? Need we wait for the active transfers counter to
+        * drop to zero, is more checking involved?
+        */
+       if (dxfer->active_transfers)
+               sr_warn("Got active USB transfers in release code path.");
+}
+
+/*
+ * Process received sample date. There are two essential modes:
+ * - The straight forward case. The device provides 16 bits per sample
+ *   point. Forward raw received data as is to the sigrok session. The
+ *   device's endianess matches the session's LE expectation. And the
+ *   data matches the device's announced total channel count.
+ * - The compact presentation where a smaller number of channels is
+ *   active, and their data spans only part of a byte per sample point.
+ *   Multiple samples' data is sharing bytes, and bytes will carry data
+ *   that was taken at different times. This requires some untangling
+ *   before forwarding sample data to the sigrok session which is of
+ *   the expected width (unit size) and carries one sample per item.
+ * - The cases where one sample point's data occupies full bytes, but
+ *   the firmware only communicates one byte per sample point, are seen
+ *   as a special case of the above bit packing. The "complex case"
+ *   logic covers the "bytes extension" as well.
+ *
+ * Implementation details:
+ * - Samples taken first are found in the least significant bits of a
+ *   byte. Samples taken next are found in upper bits of the byte. For
+ *   example a byte containing 4x 2bit sample data is seen as 33221100.
+ * - Depending on the number of enabled channels there could be up to
+ *   eight samples in one byte of sample memory. This implementation
+ *   tries to accumulate one input byte's content, but not more. To
+ *   simplify the implementation. Performance can get tuned later as
+ *   the need gets identified. Sampling at 204MHz results in some 3%
+ *   CPU load with Pulseview on the local workstation.
+ * - Samples for 16 channels transparently are handled by the simple
+ *   8 channel case above. All logic data of an individual samplepoint
+ *   occupies full bytes, endianess of sample data as provided by the
+ *   device firmware and the sigrok session are the same. No conversion
+ *   is required.
+ */
+static int greatfet_process_receive_data(const struct sr_dev_inst *sdi,
+       const uint8_t *data, size_t dlen)
+{
+       static int diag_shown;
+
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct feed_queue_logic *q;
+       uint64_t samples_remain;
+       gboolean exceeded;
+       size_t samples_rcvd;
+       uint8_t raw_mask, raw_data;
+       size_t points_per_byte, points_count;
+       uint16_t wr_data;
+       uint8_t accum[8 * sizeof(wr_data)];
+       const uint8_t *rdptr;
+       uint8_t *wrptr;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+       q = acq->feed_queue;
+
+       /*
+        * Check whether acquisition limits apply, and whether they
+        * were reached or exceeded before. Constrain the submission
+        * of more sample values to what's still within the limits of
+        * the current acquisition.
+        */
+       ret = sr_sw_limits_get_remain(&devc->sw_limits,
+               &samples_remain, NULL, NULL, &exceeded);
+       if (ret != SR_OK)
+               return ret;
+       if (exceeded)
+               return SR_OK;
+
+       /*
+        * Check for the simple case first. Where the firmware provides
+        * sample data for all logic channels supported by the device.
+        * Pass sample memory as received from the device in verbatim
+        * form to the session feed.
+        *
+        * This happens to work because sample data received from the
+        * device and logic data in sigrok sessions both are in little
+        * endian format.
+        */
+       if (acq->wire_unit_size == devc->feed_unit_size) {
+               samples_rcvd = dlen / acq->wire_unit_size;
+               if (samples_remain && samples_rcvd > samples_remain)
+                       samples_rcvd = samples_remain;
+               ret = feed_queue_logic_submit_many(q, data, samples_rcvd);
+               if (ret != SR_OK)
+                       return ret;
+               sr_sw_limits_update_samples_read(&devc->sw_limits, samples_rcvd);
+               return SR_OK;
+       }
+       if (sizeof(wr_data) != devc->feed_unit_size) {
+               sr_err("Unhandled unit size mismatch. Flawed implementation?");
+               return SR_ERR_BUG;
+       }
+
+       /*
+        * Handle the complex cases where one byte carries values that
+        * were taken at multiple sample points, or where the firmware
+        * does not communicate all pin banks to the host (upper pins
+        * or lower pins only on the wire).
+        *
+        * This involves manipulation between reception and forwarding.
+        * It helps that the firmware provides sample data in units of
+        * power-of-two bit counts per sample point. This eliminates
+        * fragments which could span several transfers.
+        *
+        * Notice that "upper pins" and "multiple samples per byte" can
+        * happen in combination. The implementation transparently deals
+        * with upper pin use where bytes carry exactly one value.
+        */
+       if (acq->channel_shift) {
+               raw_mask = (1UL << acq->channel_shift) - 1;
+               points_per_byte = 8 / acq->channel_shift;
+       } else {
+               raw_mask = (1UL << 8) - 1;
+               points_per_byte = 1;
+       }
+       if (!diag_shown++) {
+               sr_dbg("sample mem: ch count %zu, ch shift %zu, mask 0x%x, points %zu, upper %d",
+                       acq->capture_channels, acq->channel_shift,
+                       raw_mask, points_per_byte, acq->use_upper_pins);
+       }
+       samples_rcvd = dlen * points_per_byte;
+       if (samples_remain && samples_rcvd > samples_remain) {
+               samples_rcvd = samples_remain;
+               dlen = samples_rcvd;
+               dlen += points_per_byte - 1;
+               dlen /= points_per_byte;
+       }
+       rdptr = data;
+       while (dlen--) {
+               raw_data = read_u8_inc(&rdptr);
+               wrptr = accum;
+               points_count = points_per_byte;
+               while (points_count--) {
+                       wr_data = raw_data & raw_mask;
+                       if (acq->use_upper_pins)
+                               wr_data <<= 8;
+                       write_u16le_inc(&wrptr, wr_data);
+                       raw_data >>= acq->channel_shift;
+               }
+               points_count = points_per_byte;
+               ret = feed_queue_logic_submit_many(q, accum, points_count);
+               if (ret != SR_OK)
+                       return ret;
+               sr_sw_limits_update_samples_read(&devc->sw_limits, points_count);
+       }
+       return SR_OK;
+}
+
+/* Receive callback, invoked when data is available, or periodically. */
+SR_PRIV int greatfet_receive_data(int fd, int revents, void *cb_data)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct drv_context *drvc;
+       libusb_context *ctx;
+       struct timeval tv;
+
+       (void)fd;
+       (void)revents;
+
+       sdi = cb_data;
+       if (!sdi || !sdi->priv || !sdi->driver)
+               return TRUE;
+       devc = sdi->priv;
+       if (!devc)
+               return TRUE;
+       drvc = sdi->driver->context;
+       if (!drvc || !drvc->sr_ctx)
+               return TRUE;
+       ctx = drvc->sr_ctx->libusb_ctx;
+
+       /*
+        * Handle those USB transfers which have completed so far
+        * in a regular fashion. These carry desired sample values.
+        */
+       tv.tv_sec = tv.tv_usec = 0;
+       libusb_handle_events_timeout(ctx, &tv);
+
+       /*
+        * End the current acquisition when limites were reached.
+        * Process USB transfers again here before returning, because
+        * acquisition termination will unregister the receive callback,
+        * and cancel previously submitted transfers. Reap those here.
+        */
+       if (sr_sw_limits_check(&devc->sw_limits)) {
+               greatfet_abort_acquisition_quick(sdi);
+               tv.tv_sec = tv.tv_usec = 0;
+               libusb_handle_events_timeout(ctx, &tv);
+       }
+
+       return TRUE;
+}
diff --git a/src/hardware/greatfet/protocol.h b/src/hardware/greatfet/protocol.h
new file mode 100644 (file)
index 0000000..fe344a7
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Katherine J. Temkin <k@ktemkin.com>
+ * Copyright (C) 2019 Mikaela Szekely <qyriad@gmail.com>
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_GREATFET_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_GREATFET_PROTOCOL_H
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "greatfet"
+
+struct dev_context {
+       struct sr_dev_inst *sdi;
+       GString *usb_comm_buffer;
+       char *firmware_version;
+       char *serial_number;
+       size_t channel_count;
+       char **channel_names;
+       size_t feed_unit_size;
+       struct sr_sw_limits sw_limits;
+       uint64_t samplerate;
+       struct dev_acquisition_t {
+               uint64_t bandwidth_threshold;
+               size_t wire_unit_size;
+               struct feed_queue_logic *feed_queue;
+               size_t capture_channels;
+               gboolean use_upper_pins;
+               size_t channel_shift;
+               size_t points_per_byte;
+               uint64_t capture_samplerate;
+               size_t firmware_bufsize;
+               uint8_t samples_endpoint;
+               uint8_t control_interface;
+               uint8_t samples_interface;
+               enum {
+                       ACQ_IDLE,
+                       ACQ_PREPARE,
+                       ACQ_RECEIVE,
+                       ACQ_SHUTDOWN,
+               } acquisition_state;
+               gboolean frame_begin_sent;
+               gboolean control_interface_claimed;
+               gboolean samples_interface_claimed;
+               gboolean start_req_sent;
+       } acquisition;
+       struct dev_transfers_t {
+               size_t transfer_bufsize;
+               size_t transfers_count;
+               uint8_t *transfer_buffer;
+               struct libusb_transfer **transfers;
+               size_t active_transfers;
+               size_t capture_bufsize;
+       } transfers;
+};
+
+SR_PRIV int greatfet_get_serial_number(const struct sr_dev_inst *sdi);
+SR_PRIV int greatfet_get_version_number(const struct sr_dev_inst *sdi);
+
+SR_PRIV int greatfet_setup_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV int greatfet_start_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV void greatfet_abort_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV int greatfet_stop_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV void greatfet_release_resources(const struct sr_dev_inst *sdi);
+
+SR_PRIV int greatfet_receive_data(int fd, int revents, void *cb_data);
+
+#endif
index 22ac40e6c77ef0192e64601f24d53abf8aecd00c..6e9b85bbbb6d89374cf71e43cd3e67cedf03fc21 100644 (file)
@@ -76,12 +76,9 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
        sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "CH1");
        sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "CH2");
 
-       cg = g_malloc0(sizeof(struct sr_channel_group));
-       cg->name = g_strdup("");
+       cg = sr_channel_group_new(sdi, "", NULL);
        cg->channels = g_slist_append(cg->channels, g_slist_nth_data(sdi->channels, 0));
        cg->channels = g_slist_append(cg->channels, g_slist_nth_data(sdi->channels, 1));
-       cg->priv = NULL;
-       sdi->channel_groups = g_slist_append(NULL, cg);
 
        return sdi;
 }
index 2b685202733612b8e6db6262f47841c8080b69bf..a85be2ed3d433746338580b0544e0aecac5ccbe9 100644 (file)
@@ -194,11 +194,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                for (i = 0; i < model->num_channels; i++) {
                        snprintf(channel, sizeof(channel), "CH%d", i + 1);
                        ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel);
-                       cg = g_malloc(sizeof(struct sr_channel_group));
-                       cg->name = g_strdup(channel);
+                       cg = sr_channel_group_new(sdi, channel, NULL);
                        cg->channels = g_slist_append(NULL, ch);
-                       cg->priv = NULL;
-                       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
                }
 
                devc = g_malloc0(sizeof(struct dev_context));
index 26dac0cb037f34de3a8e370bc2d74a99285a799d..f355e2c272dc882293857a651aaaf73b0361eb84 100644 (file)
@@ -161,6 +161,11 @@ static const char *coupling_options_rtm300x[] = {
        "GND",
 };
 
+static const char *coupling_options_rth100x[] = {
+       "ACL", // AC with 1 MOhm termination
+       "DCL", // DC with 1 MOhm termination
+};
+
 static const char *scope_trigger_slopes[] = {
        "POS",
        "NEG",
@@ -209,6 +214,18 @@ static const char *an2_dig16_sbus_trigger_sources[] = {
        "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
 };
 
+/* RTH1002 */
+static const char *an2_dig8_isol_trigger_sources[] = {
+       "CH1", "CH2",
+       "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+};
+
+/* RTH1004 */
+static const char *an4_dig8_isol_trigger_sources[] = {
+       "CH1", "CH2", "CH3", "CH4",
+       "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+};
+
 /* HMO Compact4 */
 static const char *an4_dig8_trigger_sources[] = {
        "CH1", "CH2", "CH3", "CH4",
@@ -330,7 +347,17 @@ static const uint64_t vdivs[][2] = {
        { 2, 1 },
        { 5, 1 },
        { 10, 1 },
+       { 20, 1 },
+       { 50, 1 },
 };
+/*
+ * It feels a little hacky to use a single table yet use different item
+ * count values here. But it simplifies maintenance, reduces redundancy
+ * by avoiding several vdivs[] table versions of mostly identical content,
+ * still references which declare models' capabilities remain readable.
+ */
+#define VDIVS_COUNT_UPTO_10V   (ARRAY_SIZE(vdivs) - 2)
+#define VDIVS_COUNT_UPTO_50V   (ARRAY_SIZE(vdivs))
 
 static const char *scope_analog_channel_names[] = {
        "CH1", "CH2", "CH3", "CH4",
@@ -377,7 +404,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases_hmo_compact),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
 
                .num_ydivs = 8,
 
@@ -418,7 +445,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
 
                .num_ydivs = 8,
 
@@ -459,7 +486,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
 
                .num_ydivs = 8,
 
@@ -500,7 +527,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases_hmo_compact),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
 
                .num_ydivs = 8,
 
@@ -540,7 +567,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
 
                .num_ydivs = 8,
 
@@ -580,7 +607,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_50V,
 
                .num_ydivs = 8,
 
@@ -620,7 +647,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_50V,
 
                .num_ydivs = 8,
 
@@ -660,7 +687,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
 
                .num_ydivs = 8,
 
@@ -700,7 +727,7 @@ static struct scope_config scope_models[] = {
                .num_timebases = ARRAY_SIZE(timebases),
 
                .vdivs = &vdivs,
-               .num_vdivs = ARRAY_SIZE(vdivs),
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
 
                .num_ydivs = 8,
 
@@ -739,6 +766,86 @@ static struct scope_config scope_models[] = {
                .timebases = &timebases,
                .num_timebases = ARRAY_SIZE(timebases),
 
+               .vdivs = &vdivs,
+               .num_vdivs = VDIVS_COUNT_UPTO_10V,
+
+               .num_ydivs = 8,
+
+               .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
+       },
+       {
+               .name = {"RTH1002", NULL},
+               .analog_channels = 2,
+               .digital_channels = 8,
+
+               .analog_names = &scope_analog_channel_names,
+               .digital_names = &scope_digital_channel_names,
+
+               .devopts = &devopts,
+               .num_devopts = ARRAY_SIZE(devopts),
+
+               .devopts_cg_analog = &devopts_cg_analog,
+               .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+               .devopts_cg_digital = &devopts_cg_digital,
+               .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+               .coupling_options = &coupling_options_rth100x,
+               .num_coupling_options = ARRAY_SIZE(coupling_options_rth100x),
+
+               .logic_threshold = &logic_threshold,
+               .num_logic_threshold = ARRAY_SIZE(logic_threshold),
+               .logic_threshold_for_pod = TRUE,
+
+               .trigger_sources = &an2_dig8_isol_trigger_sources,
+               .num_trigger_sources = ARRAY_SIZE(an2_dig8_isol_trigger_sources),
+
+               .trigger_slopes = &scope_trigger_slopes,
+               .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+               .timebases = &timebases,
+               .num_timebases = ARRAY_SIZE(timebases),
+
+               .vdivs = &vdivs,
+               .num_vdivs = ARRAY_SIZE(vdivs),
+
+               .num_ydivs = 8,
+
+               .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
+       },
+       {
+               .name = {"RTH1004", NULL},
+               .analog_channels = 4,
+               .digital_channels = 8,
+
+               .analog_names = &scope_analog_channel_names,
+               .digital_names = &scope_digital_channel_names,
+
+               .devopts = &devopts,
+               .num_devopts = ARRAY_SIZE(devopts),
+
+               .devopts_cg_analog = &devopts_cg_analog,
+               .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+               .devopts_cg_digital = &devopts_cg_digital,
+               .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+               .coupling_options = &coupling_options_rth100x,
+               .num_coupling_options = ARRAY_SIZE(coupling_options_rth100x),
+
+               .logic_threshold = &logic_threshold,
+               .num_logic_threshold = ARRAY_SIZE(logic_threshold),
+               .logic_threshold_for_pod = TRUE,
+
+               .trigger_sources = &an4_dig8_isol_trigger_sources,
+               .num_trigger_sources = ARRAY_SIZE(an4_dig8_isol_trigger_sources),
+
+               .trigger_slopes = &scope_trigger_slopes,
+               .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+               .timebases = &timebases,
+               .num_timebases = ARRAY_SIZE(timebases),
+
                .vdivs = &vdivs,
                .num_vdivs = ARRAY_SIZE(vdivs),
 
@@ -896,7 +1003,7 @@ static int analog_channel_state_get(struct sr_dev_inst *sdi,
                if (sr_scpi_get_string(scpi, command, &tmp_str) != SR_OK)
                        return SR_ERR;
 
-               if (array_float_get(tmp_str, ARRAY_AND_SIZE(vdivs), &j) != SR_OK) {
+               if (array_float_get(tmp_str, *(config->vdivs), config->num_vdivs, &j) != SR_OK) {
                        g_free(tmp_str);
                        sr_err("Could not determine array index for vertical div scale.");
                        return SR_ERR;
@@ -1192,6 +1299,7 @@ SR_PRIV int hmo_init_device(struct sr_dev_inst *sdi)
        unsigned int i, j, group;
        struct sr_channel *ch;
        struct dev_context *devc;
+       const char *cg_name;
        int ret;
 
        devc = sdi->priv;
@@ -1232,27 +1340,20 @@ SR_PRIV int hmo_init_device(struct sr_dev_inst *sdi)
                ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE,
                           (*scope_models[model_index].analog_names)[i]);
 
-               devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group));
-
-               devc->analog_groups[i]->name = g_strdup(
-                       (char *)(*scope_models[model_index].analog_names)[i]);
+               cg_name = (*scope_models[model_index].analog_names)[i];
+               devc->analog_groups[i] = sr_channel_group_new(sdi, cg_name, NULL);
                devc->analog_groups[i]->channels = g_slist_append(NULL, ch);
-
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                                                  devc->analog_groups[i]);
        }
 
        /* Add digital channel groups. */
        ret = SR_OK;
        for (i = 0; i < scope_models[model_index].digital_pods; i++) {
-               devc->digital_groups[i] = g_malloc0(sizeof(struct sr_channel_group));
+               devc->digital_groups[i] = sr_channel_group_new(sdi, NULL, NULL);
                if (!devc->digital_groups[i]) {
                        ret = SR_ERR_MALLOC;
                        break;
                }
                devc->digital_groups[i]->name = g_strdup_printf("POD%d", i + 1);
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                                  devc->digital_groups[i]);
        }
        if (ret != SR_OK)
                return ret;
index 26a9315e5035024c9559f679a950576a72246b95..7e823f8c7935cb9eb1422fcc6e1fdb5b389f991c 100644 (file)
@@ -233,10 +233,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
                struct sr_channel_group *channel_groups[2];
                for (int j = 0; j < 2; j++) {
-                       cg = g_malloc0(sizeof(struct sr_channel_group));
+                       cg = sr_channel_group_new(sdi, NULL, NULL);
                        cg->name = g_strdup_printf("%c", 'A' + j);
                        channel_groups[j] = cg;
-                       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
                }
 
                /* Assemble channel list and add channel to channel groups. */
index 408d1fcdcc03ec026edd2a57ba9c140855d7c9d2..f9194b668944580cb1e85d7a93ea6ba963db2eda 100644 (file)
@@ -86,6 +86,12 @@ static const struct hantek_6xxx_profile dev_profiles[] = {
                ARRAY_AND_SIZE(dc_coupling), FALSE,
                ARRAY_AND_SIZE(vdivs),
        },
+       {
+               0x04b4, 0x2020, 0x1d50, 0x608e, 0x0001,
+               "Voltcraft", "DSO2020",  "fx2lafw-hantek-6022be.fw",
+               ARRAY_AND_SIZE(dc_coupling), FALSE,
+               ARRAY_AND_SIZE(vdivs),
+       },
        {
                0x8102, 0x8102, 0x1d50, 0x608e, 0x0002,
                "Sainsmart", "DDS120", "fx2lafw-sainsmart-dds120.fw",
@@ -140,10 +146,8 @@ static struct sr_dev_inst *hantek_6xxx_dev_new(const struct hantek_6xxx_profile
 
        for (i = 0; i < ARRAY_SIZE(channel_names); i++) {
                ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_names[i]);
-               cg = g_malloc0(sizeof(struct sr_channel_group));
-               cg->name = g_strdup(channel_names[i]);
+               cg = sr_channel_group_new(sdi, channel_names[i], NULL);
                cg->channels = g_slist_append(cg->channels, ch);
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 
        devc = g_malloc0(sizeof(struct dev_context));
index a3cc8a283304ae233ebb1c857ad13eb08be2e094..bfad5f7135f07750901e04a9fda5eb4fe1a87fbd 100644 (file)
@@ -169,7 +169,6 @@ static const uint64_t vdivs[][2] = {
 
 static const char *trigger_sources[] = {
        "CH1", "CH2", "EXT",
-       /* TODO: forced */
 };
 
 static const char *trigger_slopes[] = {
@@ -199,10 +198,8 @@ static struct sr_dev_inst *dso_dev_new(const struct dso_profile *prof)
         */
        for (i = 0; i < ARRAY_SIZE(channel_names); i++) {
                ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_names[i]);
-               cg = g_malloc0(sizeof(struct sr_channel_group));
-               cg->name = g_strdup(channel_names[i]);
+               cg = sr_channel_group_new(sdi, channel_names[i], NULL);
                cg->channels = g_slist_append(cg->channels, ch);
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 
        devc = g_malloc0(sizeof(struct dev_context));
@@ -221,7 +218,7 @@ static struct sr_dev_inst *dso_dev_new(const struct dso_profile *prof)
        devc->voffset_trigger = DEFAULT_VERT_TRIGGERPOS;
        devc->framesize = DEFAULT_FRAMESIZE;
        devc->triggerslope = SLOPE_POSITIVE;
-       devc->triggersource = g_strdup(DEFAULT_TRIGGER_SOURCE);
+       devc->triggersource = NULL;
        devc->capture_ratio = DEFAULT_CAPTURE_RATIO;
        sdi->priv = devc;
 
@@ -468,6 +465,8 @@ static int config_get(uint32_t key, GVariant **data,
                        *data = g_variant_new_uint64(devc->framesize);
                        break;
                case SR_CONF_TRIGGER_SOURCE:
+                       if (!devc->triggersource)
+                               return SR_ERR_NA;
                        *data = g_variant_new_string(devc->triggersource);
                        break;
                case SR_CONF_TRIGGER_SLOPE:
@@ -558,6 +557,7 @@ static int config_set(uint32_t key, GVariant *data,
                case SR_CONF_TRIGGER_SOURCE:
                        if ((idx = std_str_idx(data, ARRAY_AND_SIZE(trigger_sources))) < 0)
                                return SR_ERR_ARG;
+                       g_free(devc->triggersource);
                        devc->triggersource = g_strdup(trigger_sources[idx]);
                        break;
                default:
@@ -839,8 +839,10 @@ static int handle_event(int fd, int revents, void *cb_data)
                        return TRUE;
                if (dso_enable_trigger(sdi) != SR_OK)
                        return TRUE;
-//             if (dso_force_trigger(sdi) != SR_OK)
-//                     return TRUE;
+               if (!devc->triggersource) {
+                       if (dso_force_trigger(sdi) != SR_OK)
+                               return TRUE;
+               }
                sr_dbg("Successfully requested next chunk.");
                devc->dev_state = CAPTURE;
                return TRUE;
@@ -861,8 +863,10 @@ static int handle_event(int fd, int revents, void *cb_data)
                                break;
                        if (dso_enable_trigger(sdi) != SR_OK)
                                break;
-//                     if (dso_force_trigger(sdi) != SR_OK)
-//                             break;
+                       if (!devc->triggersource) {
+                               if (dso_force_trigger(sdi) != SR_OK)
+                                       break;
+                       }
                        sr_dbg("Successfully requested next chunk.");
                }
                break;
index d2101f2a8170c892af1311c7a0e12f482aa3d84e..34a3935b9fd4fa6e36f3ceab8197652f2629e625 100644 (file)
@@ -275,8 +275,10 @@ static int dso2250_set_trigger_samplerate(const struct sr_dev_inst *sdi)
        memset(cmdstring, 0, sizeof(cmdstring));
        /* Command */
        cmdstring[0] = CMD_2250_SET_TRIGGERSOURCE;
-       sr_dbg("Trigger source %s.", devc->triggersource);
-       if (!strcmp("CH2", devc->triggersource))
+       sr_dbg("Trigger source %s.", devc->triggersource ? : "<none>");
+       if (!devc->triggersource)
+               tmp = 0;
+       else if (!strcmp("CH2", devc->triggersource))
                tmp = 3;
        else if (!strcmp("CH1", devc->triggersource))
                tmp = 2;
@@ -421,8 +423,10 @@ SR_PRIV int dso_set_trigger_samplerate(const struct sr_dev_inst *sdi)
        cmdstring[0] = CMD_SET_TRIGGER_SAMPLERATE;
 
        /* Trigger source */
-       sr_dbg("Trigger source %s.", devc->triggersource);
-       if (!strcmp("CH2", devc->triggersource))
+       sr_dbg("Trigger source %s.", devc->triggersource ? : "<none>");
+       if (!devc->triggersource)
+               tmp = 2;
+       else if (!strcmp("CH2", devc->triggersource))
                tmp = 0;
        else if (!strcmp("CH1", devc->triggersource))
                tmp = 1;
@@ -668,7 +672,7 @@ static int dso_set_relays(const struct sr_dev_inst *sdi)
        if (devc->coupling[1] != COUPLING_AC)
                relays[6] = ~relays[6];
 
-       if (!strcmp(devc->triggersource, "EXT"))
+       if (devc->triggersource && strcmp(devc->triggersource, "EXT") == 0)
                relays[7] = ~relays[7];
 
        if (sr_log_loglevel_get() >= SR_LOG_DBG) {
index 4cfc2d758b54e79625609d408ef0f506d028a5b7..9f006df5600f43889b8a58bfde775c079c49c14d 100644 (file)
@@ -52,12 +52,9 @@ static int create_front_channel(struct sr_dev_inst *sdi, int chan_idx)
                                 TRUE, "Front");
        channel->priv = chanc;
 
-       front = g_malloc0(sizeof(*front));
-       front->name = g_strdup("Front");
+       front = sr_channel_group_new(sdi, "Front", NULL);
        front->channels = g_slist_append(front->channels, channel);
 
-       sdi->channel_groups = g_slist_append(sdi->channel_groups, front);
-
        return chan_idx;
 }
 
@@ -74,10 +71,7 @@ static int create_rear_channels(struct sr_dev_inst *sdi, int chan_idx,
        if (!card)
                return chan_idx;
 
-       group = g_malloc0(sizeof(*group));
-       group->priv = NULL;
-       group->name = g_strdup(card->cg_name);
-       sdi->channel_groups = g_slist_append(sdi->channel_groups, group);
+       group = sr_channel_group_new(sdi, card->cg_name, NULL);
 
        for (i = 0; i < card->num_channels; i++) {
 
index 0c461bb1740bebd4c4c4f1e41b892cfac1248057..c21546f2562267cfd788560f782fb5d66da97c94 100644 (file)
@@ -71,21 +71,21 @@ static const struct {
        {SR_MQ_VOLTAGE, SR_MQFLAG_AC,               2,   "300V"},
        /* -99 is a dummy exponent for auto ranging. */
        {SR_MQ_CURRENT, SR_MQFLAG_DC,             -99,   "Auto"},
-       {SR_MQ_CURRENT, SR_MQFLAG_DC,              -1,   "300mV"},
-       {SR_MQ_CURRENT, SR_MQFLAG_DC,               0,   "3V"},
+       {SR_MQ_CURRENT, SR_MQFLAG_DC,              -1,   "300mA"},
+       {SR_MQ_CURRENT, SR_MQFLAG_DC,               0,   "3A"},
        /* -99 is a dummy exponent for auto ranging. */
        {SR_MQ_CURRENT, SR_MQFLAG_AC,             -99,   "Auto"},
-       {SR_MQ_CURRENT, SR_MQFLAG_AC,              -1,   "300mV"},
-       {SR_MQ_CURRENT, SR_MQFLAG_AC,               0,   "3V"},
+       {SR_MQ_CURRENT, SR_MQFLAG_AC,              -1,   "300mA"},
+       {SR_MQ_CURRENT, SR_MQFLAG_AC,               0,   "3A"},
        /* -99 is a dummy exponent for auto ranging. */
        {SR_MQ_RESISTANCE, 0,                     -99,   "Auto"},
-       {SR_MQ_RESISTANCE, 0,                       1,   "30"},
-       {SR_MQ_RESISTANCE, 0,                       2,   "300"},
-       {SR_MQ_RESISTANCE, 0,                       3,   "3k"},
-       {SR_MQ_RESISTANCE, 0,                       4,   "30k"},
-       {SR_MQ_RESISTANCE, 0,                       5,   "300k"},
-       {SR_MQ_RESISTANCE, 0,                       6,   "3M"},
-       {SR_MQ_RESISTANCE, 0,                       7,   "30M"},
+       {SR_MQ_RESISTANCE, 0,                       1,   "30R"},
+       {SR_MQ_RESISTANCE, 0,                       2,   "300R"},
+       {SR_MQ_RESISTANCE, 0,                       3,   "3kR"},
+       {SR_MQ_RESISTANCE, 0,                       4,   "30kR"},
+       {SR_MQ_RESISTANCE, 0,                       5,   "300kR"},
+       {SR_MQ_RESISTANCE, 0,                       6,   "3MR"},
+       {SR_MQ_RESISTANCE, 0,                       7,   "30MR"},
        /* -99 is a dummy exponent for auto ranging. */
        {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE,   -99,   "Auto"},
        {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE,     1,   "30R"},
@@ -102,7 +102,7 @@ static const char *digits[] = {
        "3.5", "4.5", "5.5",
 };
 
-/** Mapping between devc->spec_digits and digits string. */
+/** Mapping between devc->digits and digits string. */
 static const char *digits_map[] = {
        "", "", "", "", "3.5", "4.5", "5.5",
 };
@@ -192,7 +192,9 @@ static int config_get(uint32_t key, GVariant **data,
 
        (void)cg;
 
-       devc = sdi ? sdi->priv : NULL;
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
 
        switch (key) {
        case SR_CONF_LIMIT_SAMPLES:
@@ -225,7 +227,7 @@ static int config_get(uint32_t key, GVariant **data,
                ret = hp_3478a_get_status_bytes(sdi);
                if (ret != SR_OK)
                        return ret;
-               *data = g_variant_new_string(digits_map[devc->spec_digits]);
+               *data = g_variant_new_string(digits_map[devc->digits]);
                break;
        default:
                return SR_ERR_NA;
@@ -247,7 +249,9 @@ static int config_set(uint32_t key, GVariant *data,
 
        (void)cg;
 
-       devc = sdi ? sdi->priv : NULL;
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
 
        switch (key) {
        case SR_CONF_LIMIT_SAMPLES:
@@ -294,7 +298,11 @@ static int config_list(uint32_t key, GVariant **data,
        GVariant *gvar, *arr[2];
        GVariantBuilder gvb;
 
-       devc = sdi ? sdi->priv : NULL;
+       /* Only handle standard keys when no device instance is given. */
+       if (!sdi)
+               return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+
+       devc = sdi->priv;
 
        switch (key) {
        case SR_CONF_SCAN_OPTIONS:
index 2e9130b022a9ab01832701ebdae41eb0c1533c47..6e3b2bc1aff8c887ef77ea9c03d86ee6f8ca6a51 100644 (file)
@@ -115,11 +115,11 @@ SR_PRIV int hp_3478a_set_digits(const struct sr_dev_inst *sdi, uint8_t digits)
        struct sr_scpi_dev_inst *scpi = sdi->conn;
        struct dev_context *devc = sdi->priv;
 
-       /* No need to send command if we're not changing the range. */
-       if (devc->spec_digits == digits)
+       /* No need to send command if we're not changing the resolution. */
+       if (devc->digits == digits)
                return SR_OK;
 
-       /* digits are based on devc->spec_digits, so we have to substract 1 */
+       /* digits are the total number of digits, so we have to substract 1 */
        ret = sr_scpi_send(scpi, "N%i", digits-1);
        if (ret != SR_OK)
                return ret;
@@ -131,19 +131,19 @@ static int parse_range_vdc(struct dev_context *devc, uint8_t range_byte)
 {
        if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30MV) {
                devc->range_exp = -2;
-               devc->enc_digits = devc->spec_digits - 2;
+               devc->sr_digits = devc->digits + 1;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300MV) {
                devc->range_exp = -1;
-               devc->enc_digits = devc->spec_digits - 3;
+               devc->sr_digits = devc->digits;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_3V) {
                devc->range_exp = 0;
-               devc->enc_digits = devc->spec_digits - 1;
+               devc->sr_digits = devc->digits - 1;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30V) {
                devc->range_exp = 1;
-               devc->enc_digits = devc->spec_digits - 2;
+               devc->sr_digits = devc->digits - 2;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300V) {
                devc->range_exp = 2;
-               devc->enc_digits = devc->spec_digits - 3;
+               devc->sr_digits = devc->digits - 3;
        } else
                return SR_ERR_DATA;
 
@@ -154,16 +154,16 @@ static int parse_range_vac(struct dev_context *devc, uint8_t range_byte)
 {
        if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300MV) {
                devc->range_exp = -1;
-               devc->enc_digits = devc->spec_digits - 3;
+               devc->sr_digits = devc->digits;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_3V) {
                devc->range_exp = 0;
-               devc->enc_digits = devc->spec_digits - 1;
+               devc->sr_digits = devc->digits - 1;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_30V) {
                devc->range_exp = 1;
-               devc->enc_digits = devc->spec_digits - 2;
+               devc->sr_digits = devc->digits - 2;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300V) {
                devc->range_exp = 2;
-               devc->enc_digits = devc->spec_digits - 3;
+               devc->sr_digits = devc->digits - 3;
        } else
                return SR_ERR_DATA;
 
@@ -174,10 +174,10 @@ static int parse_range_a(struct dev_context *devc, uint8_t range_byte)
 {
        if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_300MA) {
                devc->range_exp = -1;
-               devc->enc_digits = devc->spec_digits - 3;
+               devc->sr_digits = devc->digits;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_3A) {
                devc->range_exp = 0;
-               devc->enc_digits = devc->spec_digits - 1;
+               devc->sr_digits = devc->digits - 1;
        } else
                return SR_ERR_DATA;
 
@@ -188,25 +188,25 @@ static int parse_range_ohm(struct dev_context *devc, uint8_t range_byte)
 {
        if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30R) {
                devc->range_exp = 1;
-               devc->enc_digits = devc->spec_digits - 2;
+               devc->sr_digits = devc->digits - 2;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300R) {
                devc->range_exp = 2;
-               devc->enc_digits = devc->spec_digits - 3;
+               devc->sr_digits = devc->digits - 3;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3KR) {
                devc->range_exp = 3;
-               devc->enc_digits = devc->spec_digits - 1;
+               devc->sr_digits = devc->digits - 4;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30KR) {
                devc->range_exp = 4;
-               devc->enc_digits = devc->spec_digits - 2;
+               devc->sr_digits = devc->digits - 5;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300KR) {
                devc->range_exp = 5;
-               devc->enc_digits = devc->spec_digits - 3;
+               devc->sr_digits = devc->digits - 6;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3MR) {
                devc->range_exp = 6;
-               devc->enc_digits = devc->spec_digits - 1;
+               devc->sr_digits = devc->digits - 7;
        } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30MR) {
                devc->range_exp = 7;
-               devc->enc_digits = devc->spec_digits - 2;
+               devc->sr_digits = devc->digits - 8;
        } else
                return SR_ERR_DATA;
 
@@ -215,13 +215,13 @@ static int parse_range_ohm(struct dev_context *devc, uint8_t range_byte)
 
 static int parse_function_byte(struct dev_context *devc, uint8_t function_byte)
 {
-       /* Digits / Resolution (spec_digits must be set before range parsing) */
+       /* Digits / Resolution (digits must be set before range parsing) */
        if ((function_byte & SB1_DIGITS_BLOCK) == DIGITS_5_5)
-               devc->spec_digits = 6;
+               devc->digits = 6;
        else if ((function_byte & SB1_DIGITS_BLOCK) == DIGITS_4_5)
-               devc->spec_digits = 5;
+               devc->digits = 5;
        else if ((function_byte & SB1_DIGITS_BLOCK) == DIGITS_3_5)
-               devc->spec_digits = 4;
+               devc->digits = 4;
        else
                return SR_ERR_DATA;
 
@@ -443,7 +443,7 @@ static void acq_send_measurement(struct sr_dev_inst *sdi)
        packet.type = SR_DF_ANALOG;
        packet.payload = &analog;
 
-       sr_analog_init(&analog, &encoding, &meaning, &spec, devc->enc_digits);
+       sr_analog_init(&analog, &encoding, &meaning, &spec, devc->sr_digits);
 
        /* TODO: Implement NAN, depending on counts, range and value. */
        f = devc->measurement;
@@ -452,14 +452,14 @@ static void acq_send_measurement(struct sr_dev_inst *sdi)
 
        encoding.unitsize = sizeof(float);
        encoding.is_float = TRUE;
-       encoding.digits = devc->enc_digits;
+       encoding.digits = devc->sr_digits;
 
        meaning.mq = devc->measurement_mq;
        meaning.mqflags = devc->acquisition_mq_flags;
        meaning.unit = devc->measurement_unit;
        meaning.channels = sdi->channels;
 
-       spec.spec_digits = devc->spec_digits;
+       spec.spec_digits = devc->sr_digits;
 
        sr_session_send(sdi, &packet);
 }
@@ -489,7 +489,7 @@ SR_PRIV int hp_3478a_receive_data(int fd, int revents, void *cb_data)
         */
        if (sr_scpi_gpib_spoll(scpi, &status_register) != SR_OK)
                return FALSE;
-       if (!(((uint8_t)status_register) & 0x01))
+       if (!(((uint8_t)status_register) & SRQ_BUS_AVAIL))
                return TRUE;
 
        /* Get a reading from the DMM. */
index a6318f92c67f39f91f29ca8f296b84e8832ba8d1..2b394aa5961a00c552dd53acfbdb9ec0d72b5804 100644 (file)
@@ -150,8 +150,16 @@ struct dev_context {
        enum sr_mqflag acquisition_mq_flags;
        enum sr_unit measurement_unit;
        int range_exp;
-       uint8_t enc_digits;
-       uint8_t spec_digits;
+       /**
+        * The total number of digits. Rounded up from the resoultion of
+        * the device, so a 5.5 resolution would be 6 digits.
+        */
+       uint8_t digits;
+       /**
+        * The digits used for encoding.digits and spec.spec_digits in
+        * the analog payload.
+        */
+       uint8_t sr_digits;
 
        enum terminal_connector terminal;
        enum trigger_state trigger;
index c7a0afca5b2ad34d11309313754c0aa0a5fbb45e..d2b840041d36dc227979f5b6dfde0a8f353372a4 100644 (file)
@@ -47,6 +47,7 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
        struct channel_group_context *cgc;
        size_t idx, nr;
        struct sr_channel_group *cg;
+       char cg_name[24];
 
        /*
         * The device cannot get identified by means of SCPI queries.
@@ -73,15 +74,11 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
        devc->channel_count = 6;
        for (idx = 0; idx < devc->channel_count; idx++) {
                nr = idx + 1;
-
-               cg = g_malloc0(sizeof(*cg));
-               cg->name = g_strdup_printf("R%zu", nr);
-
+               snprintf(cg_name, sizeof(cg_name), "R%zu", nr);
                cgc = g_malloc0(sizeof(*cgc));
                cgc->number = nr;
-               cg->priv = cgc;
-
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               (void)cg;
        }
 
        return sdi;
index f3d94d2003d386de64760e37d31d4499f20b2bb0..88423357592dd6eeefa230abf980ddab09015fbf 100644 (file)
@@ -138,11 +138,9 @@ static GSList *scan_port(GSList *devices, struct parport *port)
        ieee1284_ref(port);
 
        for (i = 0; i < NUM_CHANNELS; i++) {
-               cg = g_malloc0(sizeof(struct sr_channel_group));
-               cg->name = g_strdup(trigger_sources[i]);
+               cg = sr_channel_group_new(sdi, trigger_sources[i], NULL);
                ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, FALSE, trigger_sources[i]);
                cg->channels = g_slist_append(cg->channels, ch);
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 
        devc = g_malloc0(sizeof(struct dev_context));
diff --git a/src/hardware/icstation-usbrelay/api.c b/src/hardware/icstation-usbrelay/api.c
new file mode 100644 (file)
index 0000000..db57673
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2021-2023 Frank Stettner <frank-stettner@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "protocol.h"
+
+#define SERIALCOMM "9600/8n1"
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_MULTIPLEXER,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */
+};
+
+static const uint32_t devopts_cg[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const struct ics_usbrelay_profile supported_ics_usbrelay[] = {
+       { ICSE012A, 0xAB, "ICSE012A", 4 },
+       { ICSE013A, 0xAD, "ICSE013A", 2 },
+       { ICSE014A, 0xAC, "ICSE014A", 8 },
+};
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       GSList *devices;
+       size_t i, ch_idx;
+       const char *conn, *serialcomm;
+       int ret;
+       uint8_t device_id;
+       const struct ics_usbrelay_profile *profile;
+       struct sr_channel_group *cg;
+       struct channel_group_context *cgc;
+
+       devices = NULL;
+
+       /* Only scan for a device when conn= was specified. */
+       conn = NULL;
+       serialcomm = SERIALCOMM;
+       if (sr_serial_extract_options(options, &conn, &serialcomm) != SR_OK)
+               return NULL;
+
+       serial = sr_serial_dev_inst_new(conn, serialcomm);
+       if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+               return NULL;
+
+       /* Get device model. */
+       ret = icstation_usbrelay_identify(serial, &device_id);
+       if (ret != SR_OK) {
+               sr_err("Cannot retrieve identification details.");
+               serial_close(serial);
+               return NULL;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(supported_ics_usbrelay); i++) {
+               profile = &supported_ics_usbrelay[i];
+               if (device_id != profile->id)
+                       continue;
+               sdi = g_malloc0(sizeof(*sdi));
+               sdi->status = SR_ST_INACTIVE;
+               sdi->vendor = g_strdup("ICStation");
+               sdi->model = g_strdup(profile->modelname);
+               sdi->inst_type = SR_INST_SERIAL;
+               sdi->conn = serial;
+               sdi->connection_id = g_strdup(conn);
+
+               devc = g_malloc0(sizeof(*devc));
+               sdi->priv = devc;
+               devc->relay_count = profile->nb_channels;
+               devc->relay_mask = (1U << devc->relay_count) - 1;
+               /* Assume that all relays are off at the start. */
+               devc->relay_state = 0;
+               for (ch_idx = 0; ch_idx < devc->relay_count; ch_idx++) {
+                       cg = g_malloc0(sizeof(*cg));
+                       cg->name = g_strdup_printf("R%zu", ch_idx + 1);
+                       cgc = g_malloc0(sizeof(*cgc));
+                       cg->priv = cgc;
+                       cgc->index = ch_idx;
+                       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+               }
+
+               devices = g_slist_append(devices, sdi);
+               break;
+       }
+
+       serial_close(serial);
+       if (!devices) {
+               sr_serial_dev_inst_free(serial);
+               sr_warn("Unknown device identification 0x%02hhx.", device_id);
+       }
+
+       return std_scan_complete(di, devices);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint8_t mask;
+       gboolean on;
+
+       if (!sdi || !data)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_CONN:
+                       *data = g_variant_new_string(sdi->connection_id);
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       switch (key) {
+       case SR_CONF_ENABLED:
+               cgc = cg->priv;
+               mask = 1U << cgc->index;
+               on = devc->relay_state & mask;
+               *data = g_variant_new_boolean(on);
+               return SR_OK;
+       default:
+               return SR_ERR_NA;
+       }
+
+       return SR_OK;
+}
+
+static int config_set(uint32_t key, GVariant *data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       gboolean on;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       /* Enable/disable all channels at the same time. */
+                       on = g_variant_get_boolean(data);
+                       return icstation_usbrelay_switch_cg(sdi, cg, on);
+               default:
+                       return SR_ERR_NA;
+               }
+       } else {
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       on = g_variant_get_boolean(data);
+                       return icstation_usbrelay_switch_cg(sdi, cg, on);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       return SR_OK;
+}
+
+static int config_list(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_SCAN_OPTIONS:
+               case SR_CONF_DEVICE_OPTIONS:
+                       return STD_CONFIG_LIST(key, data, sdi, cg,
+                               scanopts, drvopts, devopts);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       switch (key) {
+       case SR_CONF_DEVICE_OPTIONS:
+               *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
+               return SR_OK;
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int dev_open(struct sr_dev_inst *sdi)
+{
+       struct sr_serial_dev_inst *serial;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+
+       ret = std_serial_dev_open(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Start command mode. */
+       ret = icstation_usbrelay_start(sdi);
+       if (ret != SR_OK) {
+               sr_err("Cannot initiate command mode.");
+               serial_close(serial);
+               return SR_ERR_IO;
+       }
+
+       return SR_OK;
+}
+
+static struct sr_dev_driver icstation_usbrelay_driver_info = {
+       .name = "icstation-usbrelay",
+       .longname = "ICStation USBRelay",
+       .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 = std_serial_dev_close,
+       .dev_acquisition_start = std_dummy_dev_acquisition_start,
+       .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
+       .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(icstation_usbrelay_driver_info);
diff --git a/src/hardware/icstation-usbrelay/protocol.c b/src/hardware/icstation-usbrelay/protocol.c
new file mode 100644 (file)
index 0000000..78a9996
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2021-2023 Frank Stettner <frank-stettner@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "protocol.h"
+
+#define SERIAL_TIMEOUT_MS              1000
+
+#define ICSTATION_USBRELAY_CMD_ID      0x50
+#define ICSTATION_USBRELAY_CMD_START   0x51
+
+static int icstation_usbrelay_send_byte(struct sr_serial_dev_inst *serial,
+       uint8_t b)
+{
+       int ret;
+
+       ret = serial_write_blocking(serial, &b, sizeof(b), SERIAL_TIMEOUT_MS);
+       if (ret < SR_OK)
+               return SR_ERR_IO;
+       if ((size_t)ret != sizeof(b))
+               return SR_ERR_IO;
+
+       return SR_OK;
+}
+
+static int icstation_usbrelay_recv_byte(struct sr_serial_dev_inst *serial,
+       uint8_t *b)
+{
+       int ret;
+
+       ret = serial_read_blocking(serial, b, sizeof(*b), SERIAL_TIMEOUT_MS);
+       if (ret < SR_OK)
+               return SR_ERR_IO;
+       if ((size_t)ret != sizeof(*b))
+               return SR_ERR_IO;
+
+       return SR_OK;
+}
+
+SR_PRIV int icstation_usbrelay_identify(struct sr_serial_dev_inst *serial,
+       uint8_t *id)
+{
+       int ret;
+
+       if (!id)
+               return SR_ERR_ARG;
+
+       /*
+        * Send the identification request. Receive the device firmware's
+        * identification response.
+        *
+        * BEWARE!
+        * A vendor firmware implementation detail prevents the host from
+        * identifying the device again once command mode was entered.
+        * The UART protocol provides no means to leave command mode.
+        * The subsequent identification request is mistaken instead as
+        * another relay control request! Identifying the device will fail.
+        * The device must be power cycled before it identifies again.
+        */
+       ret = icstation_usbrelay_send_byte(serial, ICSTATION_USBRELAY_CMD_ID);
+       if (ret != SR_OK) {
+               sr_dbg("Could not send identification request.");
+               return SR_ERR_IO;
+       }
+       ret = icstation_usbrelay_recv_byte(serial, id);
+       if (ret != SR_OK) {
+               sr_dbg("Could not receive identification response.");
+               return SR_ERR_IO;
+       }
+       sr_dbg("Identification response 0x%02hhx.", *id);
+
+       return SR_OK;
+}
+
+SR_PRIV int icstation_usbrelay_start(const struct sr_dev_inst *sdi)
+{
+       struct sr_serial_dev_inst *serial;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+
+       return icstation_usbrelay_send_byte(serial,
+               ICSTATION_USBRELAY_CMD_START);
+}
+
+SR_PRIV int icstation_usbrelay_switch_cg(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on)
+{
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint8_t state, mask;
+       uint8_t tx_state;
+
+       devc = sdi->priv;
+
+       /*
+        * The device requires the communication of all relay states
+        * at the same time. Calling applications control individual
+        * relays. The device wants active-low state in the physical
+        * transport. Application uses positive logic (active-high).
+        *
+        * Update the locally cached state from the most recent request.
+        * Invert the result and send it to the device. Only update
+        * the internal cache after successful transmission.
+        */
+
+       state = devc->relay_state;
+       if (!cg) {
+               /* Set all relays. */
+               if (on)
+                       state |= devc->relay_mask;
+               else
+                       state &= ~devc->relay_mask;
+       } else {
+               cgc = cg->priv;
+               mask = 1UL << cgc->index;
+               if (on)
+                       state |= mask;
+               else
+                       state &= ~mask;
+       }
+
+       tx_state = ~state & devc->relay_mask;
+       sr_spew("Sending status byte: %x", tx_state);
+       if (icstation_usbrelay_send_byte(sdi->conn, tx_state) != SR_OK) {
+               sr_err("Unable to send status byte.");
+               return SR_ERR_IO;
+       }
+
+       devc->relay_state = state;
+
+       return SR_OK;
+}
diff --git a/src/hardware/icstation-usbrelay/protocol.h b/src/hardware/icstation-usbrelay/protocol.h
new file mode 100644 (file)
index 0000000..bcf9dd0
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2021-2023 Frank Stettner <frank-stettner@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "icstation-usbrelay"
+
+/* Known models. */
+enum icstation_model {
+       ICSE012A = 1,
+       ICSE013A,
+       ICSE014A,
+};
+
+/* Supported device profiles */
+struct ics_usbrelay_profile {
+       enum icstation_model model;
+       uint8_t id;
+       const char *modelname;
+       size_t nb_channels;
+};
+
+struct dev_context {
+       size_t relay_count;
+       uint8_t relay_mask;
+       uint8_t relay_state;
+};
+
+struct channel_group_context {
+       size_t index;
+};
+
+SR_PRIV int icstation_usbrelay_identify(struct sr_serial_dev_inst *serial,
+       uint8_t *id);
+SR_PRIV int icstation_usbrelay_start(const struct sr_dev_inst *sdi);
+SR_PRIV int icstation_usbrelay_switch_cg(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on);
+
+#endif
index a720af5207aea98f414025aa983c4ed91f092e18..f6156f761de6d72bd0dc03c1099fc6161275dec8 100644 (file)
@@ -270,9 +270,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        sdi->version = g_strdup_printf("%x.%02x", fw_major, fw_minor);
        sdi->serial_num = unit_serial;
 
-       cg = g_malloc0(sizeof(*cg));
-       cg->name = g_strdup("1");
-       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+       cg = sr_channel_group_new(sdi, "1", NULL);
        ch = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V1");
        cg->channels = g_slist_append(cg->channels, ch);
        ch = sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I1");
diff --git a/src/hardware/juntek-jds6600/api.c b/src/hardware/juntek-jds6600/api.c
new file mode 100644 (file)
index 0000000..3aa07f8
--- /dev/null
@@ -0,0 +1,419 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "protocol.h"
+
+#define DFLT_SERIALCOMM        "115200/8n1"
+
+#define VENDOR_TEXT    "Juntek"
+#define MODEL_TEXT     "JDS6600"
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_SIGNAL_GENERATOR,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_SET,
+       SR_CONF_PHASE | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint32_t devopts_cg[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_PATTERN_MODE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_AMPLITUDE | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_OFFSET | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_DUTY_CYCLE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       GSList *devices;
+       const char *conn, *serialcomm;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *ser;
+       int ret;
+       size_t ch_idx, idx, ch_nr;
+       char cg_name[8];
+       struct sr_channel_group *cg;
+       struct sr_channel *ch;
+
+       devices = NULL;
+
+       conn = NULL;
+       serialcomm = DFLT_SERIALCOMM;
+       (void)sr_serial_extract_options(options, &conn, &serialcomm);
+       if (!conn)
+               return devices;
+
+       ser = sr_serial_dev_inst_new(conn, serialcomm);
+       if (!ser)
+               return devices;
+       ret = serial_open(ser, SERIAL_RDWR);
+       if (ret != SR_OK) {
+               sr_serial_dev_inst_free(ser);
+               return devices;
+       }
+
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->status = SR_ST_INACTIVE;
+       sdi->inst_type = SR_INST_USB;
+       sdi->conn = ser;
+       sdi->connection_id = g_strdup(conn);
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+
+       ret = jds6600_identify(sdi);
+       if (ret != SR_OK)
+               goto fail;
+       ret = jds6600_setup_devc(sdi);
+       if (ret != SR_OK)
+               goto fail;
+       (void)serial_close(ser);
+
+       sdi->vendor = g_strdup(VENDOR_TEXT);
+       sdi->model = g_strdup(MODEL_TEXT);
+       if (devc->device.serial_number)
+               sdi->serial_num = g_strdup(devc->device.serial_number);
+
+       ch_idx = 0;
+       for (idx = 0; idx < MAX_GEN_CHANNELS; idx++) {
+               ch_nr = idx + 1;
+               snprintf(cg_name, sizeof(cg_name), "CH%zu", ch_nr);
+               cg = sr_channel_group_new(sdi, cg_name, NULL);
+               (void)cg;
+               ch = sr_channel_new(sdi, ch_idx,
+                       SR_CHANNEL_ANALOG, FALSE, cg_name);
+               cg->channels = g_slist_append(cg->channels, ch);
+               ch_idx++;
+       }
+
+       devices = g_slist_append(devices, sdi);
+       return std_scan_complete(di, devices);
+
+fail:
+       (void)serial_close(ser);
+       sr_serial_dev_inst_free(ser);
+       if (devc) {
+               g_free(devc->device.serial_number);
+               g_free(devc->waveforms.fw_codes);
+               g_free(devc->waveforms.names);
+       }
+       g_free(devc);
+       sr_dev_inst_free(sdi);
+
+       return devices;
+}
+
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       int ret;
+       size_t cg_idx;
+       struct devc_wave *waves;
+       struct devc_chan *chan;
+       double dvalue;
+       const char *s;
+
+       devc = sdi ? sdi->priv : NULL;
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_CONN:
+                       if (!sdi->connection_id)
+                               return SR_ERR_NA;
+                       *data = g_variant_new_string(sdi->connection_id);
+                       return SR_OK;
+               case SR_CONF_PHASE:
+                       if (!devc)
+                               return SR_ERR_NA;
+                       ret = jds6600_get_phase_chans(sdi);
+                       if (ret != SR_OK)
+                               return SR_ERR_NA;
+                       dvalue = devc->channels_phase;
+                       *data = g_variant_new_double(dvalue);
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       if (!devc)
+               return SR_ERR_NA;
+       ret = g_slist_index(sdi->channel_groups, cg);
+       if (ret < 0)
+               return SR_ERR_NA;
+       cg_idx = (size_t)ret;
+       if (cg_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_NA;
+       chan = &devc->channel_config[cg_idx];
+
+       switch (key) {
+       case SR_CONF_ENABLED:
+               ret = jds6600_get_chans_enable(sdi);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               *data = g_variant_new_boolean(chan->enabled);
+               return SR_OK;
+       case SR_CONF_PATTERN_MODE:
+               ret = jds6600_get_waveform(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               waves = &devc->waveforms;
+               s = waves->names[chan->waveform_index];
+               *data = g_variant_new_string(s);
+               return SR_OK;
+       case SR_CONF_OUTPUT_FREQUENCY:
+               ret = jds6600_get_frequency(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->output_frequency;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
+       case SR_CONF_AMPLITUDE:
+               ret = jds6600_get_amplitude(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->amplitude;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
+       case SR_CONF_OFFSET:
+               ret = jds6600_get_offset(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->offset;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
+       case SR_CONF_DUTY_CYCLE:
+               ret = jds6600_get_dutycycle(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->dutycycle;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_set(uint32_t key, GVariant *data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       struct devc_chan *chan;
+       size_t cg_idx;
+       double dvalue;
+       gboolean on;
+       int ret, idx;
+
+       devc = sdi ? sdi->priv : NULL;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       /* Enable/disable all channels at the same time. */
+                       on = g_variant_get_boolean(data);
+                       if (!devc)
+                               return SR_ERR_ARG;
+                       cg_idx = devc->device.channel_count_gen;
+                       while (cg_idx) {
+                               chan = &devc->channel_config[--cg_idx];
+                               chan->enabled = on;
+                       }
+                       ret = jds6600_set_chans_enable(sdi);
+                       if (ret != SR_OK)
+                               return SR_ERR_NA;
+                       return SR_OK;
+               case SR_CONF_PHASE:
+                       if (!devc)
+                               return SR_ERR_ARG;
+                       dvalue = g_variant_get_double(data);
+                       devc->channels_phase = dvalue;
+                       ret = jds6600_set_phase_chans(sdi);
+                       if (ret != SR_OK)
+                               return SR_ERR_NA;
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       ret = g_slist_index(sdi->channel_groups, cg);
+       if (ret < 0)
+               return SR_ERR_NA;
+       cg_idx = (size_t)ret;
+       if (cg_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_NA;
+       chan = &devc->channel_config[cg_idx];
+
+       switch (key) {
+       case SR_CONF_ENABLED:
+               on = g_variant_get_boolean(data);
+               chan->enabled = on;
+               ret = jds6600_set_chans_enable(sdi);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_PATTERN_MODE:
+               waves = &devc->waveforms;
+               idx = std_str_idx(data, waves->names, waves->names_count);
+               if (idx < 0)
+                       return SR_ERR_NA;
+               if ((size_t)idx >= waves->names_count)
+                       return SR_ERR_NA;
+               chan->waveform_index = idx;
+               chan->waveform_code = waves->fw_codes[chan->waveform_index];
+               ret = jds6600_set_waveform(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_OUTPUT_FREQUENCY:
+               dvalue = g_variant_get_double(data);
+               chan->output_frequency = dvalue;
+               ret = jds6600_set_frequency(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_AMPLITUDE:
+               dvalue = g_variant_get_double(data);
+               chan->amplitude = dvalue;
+               ret = jds6600_set_amplitude(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_OFFSET:
+               dvalue = g_variant_get_double(data);
+               chan->offset = dvalue;
+               ret = jds6600_set_offset(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_DUTY_CYCLE:
+               dvalue = g_variant_get_double(data);
+               chan->dutycycle = dvalue;
+               ret = jds6600_set_dutycycle(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static int config_list(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       double fspec[3];
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_SCAN_OPTIONS:
+               case SR_CONF_DEVICE_OPTIONS:
+                       return STD_CONFIG_LIST(key, data, sdi, cg,
+                               scanopts, drvopts, devopts);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       if (!sdi)
+               return SR_ERR_NA;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_NA;
+       switch (key) {
+       case SR_CONF_DEVICE_OPTIONS:
+               *data =std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
+               return SR_OK;
+       case SR_CONF_PATTERN_MODE:
+               waves = &devc->waveforms;
+               *data = std_gvar_array_str(waves->names, waves->names_count);
+               return SR_OK;
+       case SR_CONF_OUTPUT_FREQUENCY:
+               /* Announce range as tuple of min, max, step. */
+               fspec[0] = 0.01;
+               fspec[1] = devc->device.max_output_frequency;
+               fspec[2] = 0.01;
+               *data = std_gvar_min_max_step_array(fspec);
+               return SR_OK;
+       case SR_CONF_DUTY_CYCLE:
+               /* Announce range as tuple of min, max, step. */
+               fspec[0] = 0.0;
+               fspec[1] = 1.0;
+               fspec[2] = 0.001;
+               *data = std_gvar_min_max_step_array(fspec);
+               return SR_OK;
+       default:
+               return SR_ERR_NA;
+       }
+}
+
+static void clear_helper(struct dev_context *devc)
+{
+       struct devc_wave *waves;
+
+       if (!devc)
+               return;
+
+       g_free(devc->device.serial_number);
+       waves = &devc->waveforms;
+       while (waves->names_count)
+               g_free((char *)waves->names[--waves->names_count]);
+       g_free(waves->names);
+       g_free(waves->fw_codes);
+       if (devc->quick_req)
+               g_string_free(devc->quick_req, TRUE);
+}
+
+static int dev_clear(const struct sr_dev_driver *driver)
+{
+       return std_dev_clear_with_callback(driver,
+               (std_dev_clear_callback)clear_helper);
+}
+
+static struct sr_dev_driver juntek_jds6600_driver_info = {
+       .name = "juntek-jds6600",
+       .longname = "JUNTEK JDS6600",
+       .api_version = 1,
+       .init = std_init,
+       .cleanup = std_cleanup,
+       .scan = scan,
+       .dev_list = std_dev_list,
+       .dev_clear = dev_clear,
+       .config_get = config_get,
+       .config_set = config_set,
+       .config_list = config_list,
+       .dev_open = std_serial_dev_open,
+       .dev_close = std_serial_dev_close,
+       .dev_acquisition_start = std_dummy_dev_acquisition_start,
+       .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
+       .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(juntek_jds6600_driver_info);
diff --git a/src/hardware/juntek-jds6600/protocol.c b/src/hardware/juntek-jds6600/protocol.c
new file mode 100644 (file)
index 0000000..5759c6a
--- /dev/null
@@ -0,0 +1,1651 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Juntek JDS6600 is a DDS signal generator.
+ * Often rebranded, goes by different names, among them Joy-IT JDS6600.
+ *
+ * This driver was built using Kristoff Bonne's knowledge as seen in his
+ * MIT licensed Python code for JDS6600 control. For details see the
+ * https://github.com/on1arf/jds6600_python repository.
+ *
+ * Supported features:
+ * - Model detection, which determines the upper output frequency limit
+ *   (15..60MHz models exist).
+ * - Assumes exactly two channels. Other models were not seen out there.
+ * - Per channel configuration of: Waveform, output frequency, amplitude,
+ *   offset, duty cycle.
+ * - Phase between channels is a global property and affects multiple
+ *   channels at the same time (their relation to each other).
+ *
+ * TODO
+ * - Add support for the frequency measurement and/or the counter. This
+ *   feature's availability may depend on or interact with the state of
+ *   other generator channels. Needs consideration of constraints.
+ * - Add support for "modes" (sweep, pulse, burst; modulation if the
+ *   device supports it).
+ * - Add support for download/upload of arbitrary waveforms. This needs
+ *   infrastructure in common libsigrok code as well as in applications.
+ *   At the moment "blob transfer" (waveform upload/download) appears to
+ *   not be supported.
+ * - Re-consider parameter value ranges. Frequency depends on the model.
+ *   Amplitude depends on the model and frequencies. Can be -20..+20,
+ *   or -10..+10, or -5..+5. Could be affected by offsets and further
+ *   get clipped. This implementation caps application's input to the
+ *   -20..+20 range, and sends the set request to the device. If any
+ *   further transformation happens in the device then applications
+ *   need to read back, this library driver doesn't.
+ *
+ * Implementation details:
+ * - Communicates via USB CDC at 115200/8n1 (virtual COM port). The user
+ *   perceives a USB attached device (full speed, CDC/ACM class). The
+ *   implementation needs to remember that a WCH CH340G forwards data
+ *   to a microcontroller. Maximum throughput is in the 10KiB/s range.
+ * - Requests are in text format. Start with a ':' colon, followed by a
+ *   single letter instruction opcode, followed by a number which either
+ *   addresses a parameter (think hardware register) or storage slot for
+ *   an arbitrary waveform. Can be followed by an '=' equals sign and a
+ *   value. Multiple values are comma separated. The line may end in a
+ *   '.' period. Several end-of-line conventions are supported by the
+ *   devices' firmware versions, LF and CR/LF are reported to work.
+ * - Responses also are in text format. Start with a ':' colon, followed
+ *   by an instruction letter, followed by a number (a parameter index,
+ *   or a waveform index), followed by '=' equal sign and one or more
+ *   values. Optionally ending in a '.' period. And ending in the
+ *   firmware's end-of-line. Read responses will have this format.
+ *   Responses to write requests might just have the ":ok." literal.
+ * - There are four instructions: 'r' to read and 'w' to write parameters
+ *   (think "hardware registers", optionaly multi-valued), 'a' to write
+ *   and 'b' to read arbitrary waveform data (sequence of sample values).
+ * - Am not aware of a vendor's documentation for the protocol. Joy-IT
+ *   provides the JT-JDS6600-Communication-protocol.pdf document which
+ *   leaves a lot of questions. This sigrok driver implementation used
+ *   a lot of https://github.com/on1arf/jds6600_python knowledge for
+ *   the initial version (MIT licenced Python code by Kristoff Bonne).
+ * - The requests take effect when sent from application code. While
+ *   the requests remain uneffective when typed in interactive terminal
+ *   sessions. Though there are ":ok" responses, the action would not
+ *   happen in the device. It's assumed to be a firmware implementation
+ *   constraint that is essential to keep in mind.
+ * - The right hand side of write requests or read responses can carry
+ *   any number of values, both numbers and text, integers and floats.
+ *   Still some of the parameters (voltages, times, frequencies) come in
+ *   interesting formats. A floating point "mantissa" and an integer code
+ *   for scaling the value. Not an exponent, but some kind of index. In
+ *   addition to an open coded "fixed point" style multiplier that is
+ *   implied and essential, but doesn't show on the wire. Interpretation
+ *   of responses and phrasing of values in requests is arbitrary, this
+ *   "black magic" was found by local experimentation (reading back the
+ *   values which were configured by local UI interaction).
+ * - Communication is more reliable when the host unconditionally sends
+ *   "function codes" (register and waveform indices) in two-digit form.
+ *   Device firmware might implement rather specific assumptions.
+ * - Semantics of the right hand side in :rNN= and :bNN= read requests
+ *   is uncertain. Just passing 0 in all situations worked in a local
+ *   setup. As did omitting the value during interactive exploration.
+ *
+ * Example requests and responses.
+ * - Get model identification (max output frequency)
+ *    TX text: --> :r00=0.
+ *    TX bytes: --> 3a 72 30 30 3d 30 2e 0d  0a
+ *    RX bytes: <-- 3a 72 30 30 3d 36 30 2e  0d 0a
+ *    RX text: <-- :r00=60.
+ * - Get all channels' enabled state
+ *    TX text: --> :r20=0.
+ *    TX bytes: --> 3a 72 32 30 3d 30 2e 0d  0a
+ *    RX bytes: <-- 3a 72 32 30 3d 31 2c 31  2e 0d 0a
+ *    RX text: <-- :r20=1,1.
+ * - Get first channel's waveform selection
+ *    TX text: --> :r21=0.
+ *    TX bytes: --> 3a 72 32 31 3d 30 2e 0d  0a
+ *    RX bytes: <-- 3a 72 32 31 3d 31 30 33  2e 0d 0a
+ *    RX text: <-- :r21=103.
+ * - Set second channel's output frequency
+ *    TX text: --> :w24=1234500,0.
+ *    TX bytes: --> 3a 77 32 34 3d 31 32 33  34 35 30 30 2c 30 2e 0d   0a
+ *    RX bytes: <-- 3a 6f 6b 0d 0a
+ *    RX text: <-- :ok
+ * - Read arbitrary waveform number 13
+ *    TX text: --> :b13=0.
+ *    TX bytes: --> 3a 62 31 33 3d 30 2e 0d  0a
+ *    RX bytes: <-- 3a 62 31 33 3d 34 30 39  35 2c 34 30 39 35 2c ... 2c 34 30 39 35 2c   34 30 39 35 2c 0d 0a
+ *    RX text: <-- :b13=4095,4095,...,4095,4095,
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <math.h>
+#include <string.h>
+
+#include "protocol.h"
+
+#define WITH_SERIAL_RAW_DUMP   0 /* Includes EOL and non-printables. */
+#define WITH_ARBWAVE_DOWNLOAD  0 /* Development HACK */
+
+/*
+ * The firmware's maximum response length. Seen when an arbitrary
+ * waveform gets retrieved. Carries 2048 samples in the 0..4095 range.
+ * Plus some decoration around that data.
+ *   :b01=4095,4095,...,4095,<CRLF>
+ */
+#define MAX_RSP_LENGTH (8 + 2048 * 5)
+
+/*
+ * Times are in milliseconds.
+ * - Delay after transmission was an option during initial development.
+ *   Has become obsolete. Support remains because it doesn't harm.
+ * - Delay after flash is essential when writing multiple waveforms to
+ *   the device. Not letting more idle time pass after successful write
+ *   and reception of the "ok" response, and before the next write, will
+ *   result in corrupted waveform storage in the device. The next wave
+ *   that is written waveform will start with several hundred samples
+ *   of all-one bits.
+ * - Timeout per receive attempt at the physical layer can be short.
+ *   Experience suggests that 2ms are a good value. Reception ends when
+ *   the response termination was seen. Or when no receive data became
+ *   available within that per-attemt timeout, and no higher level total
+ *   timeout was specified. Allow some slack for USB FS frame intervals.
+ * - Timeout for identify attempts at the logical level can be short.
+ *   Captures of the microcontroller communication suggest that firmware
+ *   responds immediately (within 2ms). So 10ms per identify attempt
+ *   are plenty for successful communication, yet quick enough to not
+ *   stall on missing peripherals.
+ * - Timeout for waveform upload/download needs to be huge. Textual
+ *   presentation of 2k samples with 12 significant bits (0..4095 range)
+ *   combined with 115200bps UART communication result in a 1s maximum
+ *   transfer time per waveform. So 1.2s is a good value.
+ */
+#define DELAY_AFTER_SEND       0
+#define DELAY_AFTER_FLASH      100
+#define TIMEOUT_READ_CHUNK     2
+#define TIMEOUT_IDENTIFY       10
+#define TIMEOUT_WAVEFORM       1200
+
+/* Instruction codes. Read/write parameters/waveforms. */
+#define INSN_WRITE_PARA        'w'
+#define INSN_READ_PARA 'r'
+#define INSN_WRITE_WAVE        'a'
+#define INSN_READ_WAVE 'b'
+
+/* Indices for "register access". */
+enum param_index {
+       IDX_DEVICE_TYPE = 0,
+       IDX_SERIAL_NUMBER = 1,
+       IDX_CHANNELS_ENABLE = 20,
+       IDX_WAVEFORM_CH1 = 21,
+       IDX_WAVEFORM_CH2 = 22,
+       IDX_FREQUENCY_CH1 = 23,
+       IDX_FREQUENCY_CH2 = 24,
+       IDX_AMPLITUDE_CH1 = 25,
+       IDX_AMPLITUDE_CH2 = 26,
+       IDX_OFFSET_CH1 = 27,
+       IDX_OFFSET_CH2 = 28,
+       IDX_DUTYCYCLE_CH1 = 29,
+       IDX_DUTYCYCLE_CH2 = 30,
+       IDX_PHASE_CHANNELS = 31,
+       IDX_ACTION = 32,
+       IDX_MODE = 33,
+       IDX_INPUT_COUPLING = 36,
+       IDX_MEASURE_GATE = 37,
+       IDX_MEASURE_MODE = 38,
+       IDX_COUNTER_RESET = 39,
+       IDX_SWEEP_STARTFREQ = 40,
+       IDX_SWEEP_ENDFREQ = 41,
+       IDX_SWEEP_TIME = 42,
+       IDX_SWEEP_DIRECTION = 43,
+       IDX_SWEEP_MODE = 44,
+       IDX_PULSE_WIDTH = 45,
+       IDX_PULSE_PERIOD = 46,
+       IDX_PULSE_OFFSET = 47,
+       IDX_PULSE_AMPLITUDE = 48,
+       IDX_BURST_COUNT = 49,
+       IDX_BURST_MODE = 50,
+       IDX_SYSTEM_SOUND = 51,
+       IDX_SYSTEM_BRIGHTNESS = 52,
+       IDX_SYSTEM_LANGUAGE = 53,
+       IDX_SYSTEM_SYNC = 54, /* "Tracking" channels? */
+       IDX_SYSTEM_ARBMAX = 55,
+       IDX_PROFILE_SAVE = 70,
+       IDX_PROFILE_LOAD = 71,
+       IDX_PROFILE_CLEAR = 72,
+       IDX_COUNTER_VALUE = 80,
+       IDX_MEAS_VALUE_FREQLOW = 81,
+       IDX_MEAS_VALUE_FREQHI = 82,
+       IDX_MEAS_VALUE_WIDTHHI = 83,
+       IDX_MEAS_VALUE_WIDTHLOW = 84,
+       IDX_MEAS_VALUE_PERIOD = 85,
+       IDX_MEAS_VALUE_DUTYCYCLE = 86,
+       IDX_MEAS_VALUE_U1 = 87,
+       IDX_MEAS_VALUE_U2 = 88,
+       IDX_MEAS_VALUE_U3 = 89,
+};
+
+/* Firmware's codes for waveform selection. */
+enum waveform_index_t {
+       /* 17 pre-defined waveforms. */
+       WAVE_SINE = 0,
+       WAVE_SQUARE = 1,
+       WAVE_PULSE = 2,
+       WAVE_TRIANGLE = 3,
+       WAVE_PARTIAL_SINE = 4,
+       WAVE_CMOS = 5,
+       WAVE_DC = 6,
+       WAVE_HALF_WAVE = 7,
+       WAVE_FULL_WAVE = 8,
+       WAVE_POS_LADDER = 9,
+       WAVE_NEG_LADDER = 10,
+       WAVE_NOISE = 11,
+       WAVE_EXP_RISE = 12,
+       WAVE_EXP_DECAY = 13,
+       WAVE_MULTI_TONE = 14,
+       WAVE_SINC = 15,
+       WAVE_LORENZ = 16,
+       WAVES_COUNT_BUILTIN,
+       /* Up to 60 arbitrary waveforms. */
+       WAVES_ARB_BASE = 100,
+       WAVE_ARB01 = WAVES_ARB_BASE +  1,
+       /* ... */
+       WAVE_ARB60 = WAVES_ARB_BASE + 60,
+       WAVES_PAST_LAST_ARB,
+};
+#define WAVES_COUNT_ARBITRARY  (WAVES_PAST_LAST_ARB - WAVE_ARB01)
+
+static const char *waveform_names[] = {
+       [WAVE_SINE] = "sine",
+       [WAVE_SQUARE] = "square",
+       [WAVE_PULSE] = "pulse",
+       [WAVE_TRIANGLE] = "triangle",
+       [WAVE_PARTIAL_SINE] = "partial-sine",
+       [WAVE_CMOS] = "cmos",
+       [WAVE_DC] = "dc",
+       [WAVE_HALF_WAVE] = "half-wave",
+       [WAVE_FULL_WAVE] = "full-wave",
+       [WAVE_POS_LADDER] = "pos-ladder",
+       [WAVE_NEG_LADDER] = "neg-ladder",
+       [WAVE_NOISE] = "noise",
+       [WAVE_EXP_RISE] = "exp-rise",
+       [WAVE_EXP_DECAY] = "exp-decay",
+       [WAVE_MULTI_TONE] = "multi-tone",
+       [WAVE_SINC] = "sinc",
+       [WAVE_LORENZ] = "lorenz",
+};
+#define WAVEFORM_ARB_NAME_FMT  "arb-%02zu"
+
+static void log_raw_bytes(const char *caption, GString *buff)
+{
+       GString *text;
+
+       if (!WITH_SERIAL_RAW_DUMP)
+               return;
+       if (sr_log_loglevel_get() < SR_LOG_SPEW)
+               return;
+
+       if (!caption)
+               caption = "";
+       text = sr_hexdump_new((const uint8_t *)buff->str, buff->len);
+       sr_spew("%s%s", caption, text->str);
+       sr_hexdump_free(text);
+}
+
+/*
+ * Writes a text line to the serial port. Normalizes end-of-line
+ * including trailing period.
+ *
+ * Accepts:
+ *   ":r01=0.<CR><LF>"
+ *   ":r01=0."
+ *   ":r01=0<LF>"
+ *   ":r01=0"
+ * Normalizes to:
+ *   ":r01=0.<CR><LF>"
+ */
+static int serial_send_textline(const struct sr_dev_inst *sdi,
+       GString *s, unsigned int delay_ms)
+{
+       struct sr_serial_dev_inst *conn;
+       const char *rdptr;
+       size_t padlen, rdlen, wrlen;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       conn = sdi->conn;
+       if (!conn)
+               return SR_ERR_ARG;
+       if (!s)
+               return SR_ERR_ARG;
+
+       /*
+        * Trim surrounding whitespace. Normalize to canonical format.
+        * Make sure there is enough room for the period and CR/LF
+        * (and NUL termination). Use a glib API that's easy to adjust
+        * the padded length of. Performance is not a priority here.
+        */
+       padlen = 4;
+       while (padlen--)
+               g_string_append_c(s, '\0');
+       rdptr = sr_text_trim_spaces(s->str);
+       rdlen = strlen(rdptr);
+       if (rdlen && rdptr[rdlen - 1] == '.')
+               rdlen--;
+       g_string_set_size(s, rdlen);
+       g_string_append_c(s, '.');
+       sr_spew("serial TX text: --> %s", rdptr);
+       g_string_append_c(s, '\r');
+       g_string_append_c(s, '\n');
+       rdlen = strlen(rdptr);
+       log_raw_bytes("serial TX bytes: --> ", s);
+
+       /* Handle chunked writes, check for transmission errors. */
+       while (rdlen) {
+               ret = serial_write_blocking(conn, rdptr, rdlen, 0);
+               if (ret < 0)
+                       return SR_ERR_IO;
+               wrlen = (size_t)ret;
+               if (wrlen > rdlen)
+                       wrlen = rdlen;
+               rdptr += wrlen;
+               rdlen -= wrlen;
+       }
+
+       if (delay_ms)
+               g_usleep(delay_ms * 1000);
+
+       return SR_OK;
+}
+
+/*
+ * Reads a text line from the serial port. Assumes that only a single
+ * response text line is in flight (does not handle the case of more
+ * receive data following after the first EOL). Transparently deals
+ * with trailing period and end-of-line, so callers need not bother.
+ *
+ * Checks plausibility when the caller specifies conditions to check.
+ * Optionally returns references (and lengths) to the response's RHS.
+ * That's fine because data resides in a caller provided buffer.
+ */
+static int serial_recv_textline(const struct sr_dev_inst *sdi,
+       GString *s, unsigned int delay_ms, unsigned int timeout_ms,
+       gboolean *is_ok, char wants_insn, size_t wants_index,
+       char **rhs_start, size_t *rhs_length)
+{
+       struct sr_serial_dev_inst *ser;
+       char *rdptr;
+       size_t rdlen, got;
+       int ret;
+       guint64 now_us, deadline_us;
+       gboolean has_timedout;
+       char *eol_pos, *endptr;
+       char got_insn;
+       unsigned long got_index;
+
+       if (is_ok)
+               *is_ok = FALSE;
+       if (rhs_start)
+               *rhs_start = NULL;
+       if (rhs_length)
+               *rhs_length = 0;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       ser = sdi->conn;
+       if (!ser)
+               return SR_ERR_ARG;
+       if (!s)
+               return SR_ERR_ARG;
+
+       g_string_set_size(s, MAX_RSP_LENGTH);
+       g_string_truncate(s, 0);
+
+       /* Arrange for overall receive timeout when caller specified. */
+       now_us = deadline_us = 0;
+       if (timeout_ms) {
+               now_us = g_get_monotonic_time();
+               deadline_us = now_us;
+               deadline_us += timeout_ms * 1000;
+       }
+
+       rdptr = s->str;
+       rdlen = s->allocated_len - 1 - s->len;
+       while (rdlen) {
+               /* Get another chunk of receive data. Check for EOL. */
+               ret = serial_read_blocking(ser, rdptr, rdlen, delay_ms);
+               if (ret < 0)
+                       return SR_ERR_IO;
+               got = (size_t)ret;
+               if (got > rdlen)
+                       got = rdlen;
+               rdptr[got] = '\0';
+               eol_pos = strchr(rdptr, '\n');
+               rdptr += got;
+               rdlen -= got;
+               g_string_set_size(s, s->len + got);
+               /* Check timeout expiration upon empty reception. */
+               has_timedout = FALSE;
+               if (timeout_ms && !got) {
+                       now_us = g_get_monotonic_time();
+                       if (now_us >= deadline_us)
+                               has_timedout = TRUE;
+               }
+               if (!eol_pos) {
+                       if (has_timedout)
+                               break;
+                       continue;
+               }
+               log_raw_bytes("serial RX bytes: <-- ", s);
+
+               /* Normalize the received text line. */
+               *eol_pos++ = '\0';
+               rdptr = s->str;
+               (void)sr_text_trim_spaces(rdptr);
+               rdlen = strlen(rdptr);
+               sr_spew("serial RX text: <-- %s", rdptr);
+               if (rdlen && rdptr[rdlen - 1] == '.')
+                       rdptr[--rdlen] = '\0';
+
+               /* Check conditions as requested by the caller. */
+               if (is_ok || wants_insn || rhs_start) {
+                       if (*rdptr != ':') {
+                               sr_dbg("serial read, colon missing");
+                               return SR_ERR_DATA;
+                       }
+                       rdptr++;
+                       rdlen--;
+               }
+               /*
+                * The check for 'ok' is terminal. Does not combine with
+                * responses which carry payload data on their RHS.
+                */
+               if (is_ok) {
+                       *is_ok = strcmp(rdptr, "ok") == 0;
+                       sr_dbg("serial read, 'ok' check %d", *is_ok);
+                       return *is_ok ? SR_OK : SR_ERR_DATA;
+               }
+               /*
+                * Conditional strict checks for caller's expected fields.
+                * Unconditional weaker checks for general structure.
+                */
+               if (wants_insn && *rdptr != wants_insn) {
+                       sr_dbg("serial read, unexpected insn");
+                       return SR_ERR_DATA;
+               }
+               got_insn = *rdptr++;
+               switch (got_insn) {
+               case INSN_WRITE_PARA:
+               case INSN_READ_PARA:
+               case INSN_WRITE_WAVE:
+               case INSN_READ_WAVE:
+                       /* EMPTY */
+                       break;
+               default:
+                       sr_dbg("serial read, unknown insn %c", got_insn);
+                       return SR_ERR_DATA;
+               }
+               endptr = NULL;
+               ret = sr_atoul_base(rdptr, &got_index, &endptr, 10);
+               if (ret != SR_OK || !endptr)
+                       return SR_ERR_DATA;
+               if (wants_index && got_index != wants_index) {
+                       sr_dbg("serial read, unexpected index %lu", got_index);
+                       return SR_ERR_DATA;
+               }
+               rdptr = endptr;
+               if (rhs_start || rhs_length) {
+                       if (*rdptr != '=') {
+                               sr_dbg("serial read, equals sign missing");
+                               return SR_ERR_DATA;
+                       }
+               }
+               if (*rdptr)
+                       rdptr++;
+
+               /* Response is considered plausible here. */
+               if (rhs_start)
+                       *rhs_start = rdptr;
+               if (rhs_length)
+                       *rhs_length = strlen(rdptr);
+               return SR_OK;
+       }
+       log_raw_bytes("serial RX bytes: <-- ", s);
+       sr_dbg("serial read, unterminated response, discarded");
+
+       return SR_ERR_DATA;
+}
+
+/* Formatting helpers for request construction. */
+
+static void append_insn_read_para(GString *s, char insn, size_t idx)
+{
+       g_string_append_printf(s, ":%c%02zu=0", insn, idx & 0xff);
+}
+
+static void append_insn_write_para_va(GString *s, char insn, size_t idx,
+       const char *fmt, va_list args) ATTR_FMT_PRINTF(4, 0);
+static void append_insn_write_para_va(GString *s, char insn, size_t idx,
+       const char *fmt, va_list args)
+{
+       g_string_append_printf(s, ":%c%02zu=", insn, idx & 0xff);
+       g_string_append_vprintf(s, fmt, args);
+}
+
+static void append_insn_write_para_dots(GString *s, char insn, size_t idx,
+       const char *fmt, ...) ATTR_FMT_PRINTF(4, 5);
+static void append_insn_write_para_dots(GString *s, char insn, size_t idx,
+       const char *fmt, ...)
+{
+       va_list args;
+
+       va_start(args, fmt);
+       append_insn_write_para_va(s, insn, idx, fmt, args);
+       va_end(args);
+}
+
+/*
+ * Turn comma separators into whitespace. Simplifies the interpretation
+ * of multi-value response payloads. Also replaces any trailing period
+ * in case callers kept one in the receive buffer.
+ */
+static void replace_separators(char *s)
+{
+
+       while (s && *s) {
+               if (s[0] == ',') {
+                       *s++ = ' ';
+                       continue;
+               }
+               if (s[0] == '.' && s[1] == '\0') {
+                       *s++ = ' ';
+                       continue;
+               }
+               s++;
+       }
+}
+
+/*
+ * Convenience to interpret responses' values. Also concentrates the
+ * involved magic and simplifies diagnostics. It's essential to apply
+ * implicit multipliers, and to properly combine multiple fields into
+ * the resulting parameter's value (think scaling and offsetting).
+ */
+
+static const double scales_freq[] = {
+       1, 1, 1, 1e-3, 1e-6,
+};
+
+static int parse_freq_text(char *s, double *value)
+{
+       char *word;
+       int ret;
+       double dvalue;
+       unsigned long scale;
+
+       replace_separators(s);
+
+       /* First word is a mantissa, in centi-Hertz. :-O */
+       word = sr_text_next_word(s, &s);
+       ret = sr_atod(word, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Next word is an encoded scaling factor. */
+       word = sr_text_next_word(s, &s);
+       ret = sr_atoul_base(word, &scale, NULL, 10);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse freq, mant %f, scale %lu", dvalue, scale);
+       if (scale >= ARRAY_SIZE(scales_freq))
+               return SR_ERR_DATA;
+
+       /* Do scale the mantissa's value. */
+       dvalue /= 100.0;
+       dvalue /= scales_freq[scale];
+       sr_spew("parse freq, value %f", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_volt_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /* Single value, in units of mV. */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse volt, mant %f", dvalue);
+       dvalue /= 1000.0;
+       sr_spew("parse volt, value %f", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_bias_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /*
+        * Single value, in units of 10mV with a 10V offset. Capped to
+        * the +9.99V..-9.99V range. The Joy-IT PDF is a little weird
+        * suggesting that ":w27=9999." translates to 9.99 volts.
+        */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse bias, mant %f", dvalue);
+       dvalue /= 100.0;
+       dvalue -= 10.0;
+       if (dvalue >= 9.99)
+               dvalue = 9.99;
+       if (dvalue <= -9.99)
+               dvalue = -9.99;
+       sr_spew("parse bias, value %f", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_duty_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /*
+        * Single value, in units of 0.1% (permille).
+        * Scale to the 0.0..1.0 range.
+        */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse duty, mant %f", dvalue);
+       dvalue /= 1000.0;
+       sr_spew("parse duty, value %f", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_phase_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /* Single value, in units of deci-degrees. */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse phase, mant %f", dvalue);
+       dvalue /= 10.0;
+       sr_spew("parse phase, value %f", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+/*
+ * Convenience to generate request presentations. Also concentrates the
+ * involved magic and simplifies diagnostics. It's essential to apply
+ * implicit multipliers, and to properly create all request fields that
+ * communicate a value to the device's firmware (think scale and offset).
+ */
+
+static void write_freq_text(GString *s, double freq)
+{
+       unsigned long scale_idx;
+       const char *text_pos;
+
+       sr_spew("write freq, value %f", freq);
+       text_pos = &s->str[s->len];
+
+       /*
+        * First word is mantissa in centi-Hertz. Second word is a
+        * scaling factor code. Keep scaling simple, always scale
+        * by a factor of 1.0.
+        */
+       scale_idx = 0;
+       freq *= scales_freq[scale_idx];
+       freq *= 100.0;
+
+       g_string_append_printf(s, "%.0f,%lu", freq, scale_idx);
+       sr_spew("write freq, text %s", text_pos);
+}
+
+static void write_volt_text(GString *s, double volt)
+{
+       const char *text_pos;
+
+       sr_spew("write volt, value %f", volt);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of 1mV.
+        * Limit input values to the 0..+20 range. This writer is only
+        * used by the amplitude setter.
+        */
+       if (volt > 20.0)
+               volt = 20.0;
+       if (volt < 0.0)
+               volt = 0.0;
+       volt *= 1000.0;
+       g_string_append_printf(s, "%.0f", volt);
+       sr_spew("write volt, text %s", text_pos);
+}
+
+static void write_bias_text(GString *s, double volt)
+{
+       const char *text_pos;
+
+       sr_spew("write bias, value %f", volt);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of 10mV with a 10V offset. Capped to
+        * the +9.99..-9.99 range.
+        */
+       if (volt > 9.99)
+               volt = 9.99;
+       if (volt < -9.99)
+               volt = -9.99;
+       volt += 10.0;
+       volt *= 100.0;
+
+       g_string_append_printf(s, "%.0f", volt);
+       sr_spew("write bias, text %s", text_pos);
+}
+
+static void write_duty_text(GString *s, double duty)
+{
+       const char *text_pos;
+
+       sr_spew("write duty, value %f", duty);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of 0.1% (permille). Capped to the
+        * 0.0..1.0 range.
+        */
+       if (duty < 0.0)
+               duty = 0.0;
+       if (duty > 1.0)
+               duty = 1.0;
+       duty *= 1000.0;
+
+       g_string_append_printf(s, "%.0f", duty);
+       sr_spew("write duty, text %s", text_pos);
+}
+
+static void write_phase_text(GString *s, double phase)
+{
+       const char *text_pos;
+
+       sr_spew("write phase, value %f", phase);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of deci-degrees.
+        * Kept to the 0..360 range by means of a modulo operation.
+        */
+       phase = fmod(phase, 360.0);
+       phase *= 10.0;
+
+       g_string_append_printf(s, "%.0f", phase);
+       sr_spew("write phase, text %s", text_pos);
+}
+
+/*
+ * Convenience communication wrapper. Re-uses a buffer in devc, which
+ * simplifies resource handling in error paths. Sends a parameter-less
+ * read-request. Then receives a response which can carry values.
+ */
+static int quick_send_read_then_recv(const struct sr_dev_inst *sdi,
+       char insn, size_t idx,
+       unsigned int read_timeout_ms,
+       char **rhs_start, size_t *rhs_length)
+{
+       struct dev_context *devc;
+       GString *s;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (!devc->quick_req)
+               devc->quick_req = g_string_sized_new(MAX_RSP_LENGTH);
+       s = devc->quick_req;
+
+       g_string_truncate(s, 0);
+       append_insn_read_para(s, insn, idx);
+       ret = serial_send_textline(sdi, s, DELAY_AFTER_SEND);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = serial_recv_textline(sdi, s,
+               TIMEOUT_READ_CHUNK, read_timeout_ms,
+               NULL, insn, idx, rhs_start, rhs_length);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * Convenience communication wrapper, re-uses a buffer in devc. Sends a
+ * write-request with parameters. Then receives an "ok" style response.
+ * Had to put the request details after the response related parameters
+ * because of the va_list API.
+ */
+static int quick_send_write_then_recv_ok(const struct sr_dev_inst *sdi,
+       unsigned int read_timeout_ms, gboolean *is_ok,
+       char insn, size_t idx, const char *fmt, ...) ATTR_FMT_PRINTF(6, 7);
+static int quick_send_write_then_recv_ok(const struct sr_dev_inst *sdi,
+       unsigned int read_timeout_ms, gboolean *is_ok,
+       char insn, size_t idx, const char *fmt, ...)
+{
+       struct dev_context *devc;
+       GString *s;
+       va_list args;
+       int ret;
+       gboolean ok;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (!devc->quick_req)
+               devc->quick_req = g_string_sized_new(MAX_RSP_LENGTH);
+       s = devc->quick_req;
+
+       g_string_truncate(s, 0);
+       va_start(args, fmt);
+       append_insn_write_para_va(s, insn, idx, fmt, args);
+       va_end(args);
+       ret = serial_send_textline(sdi, s, DELAY_AFTER_SEND);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = serial_recv_textline(sdi, s,
+               TIMEOUT_READ_CHUNK, read_timeout_ms,
+               &ok, '\0', 0, NULL, NULL);
+       if (is_ok)
+               *is_ok = ok;
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * High level getters/setters for device properties.
+ * To be used by the api.c config get/set infrastructure.
+ */
+
+SR_PRIV int jds6600_get_chans_enable(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr, *word, *endptr;
+       struct devc_dev *device;
+       struct devc_chan *chans;
+       size_t idx;
+       unsigned long on;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_CHANNELS_ENABLE,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get enabled, response text: %s", rdptr);
+
+       /* Interpret the response (multiple values, boolean). */
+       replace_separators(rdptr);
+       device = &devc->device;
+       chans = devc->channel_config;
+       for (idx = 0; idx < device->channel_count_gen; idx++) {
+               word = sr_text_next_word(rdptr, &rdptr);
+               if (!word || !*word)
+                       return SR_ERR_DATA;
+               endptr = NULL;
+               ret = sr_atoul_base(word, &on, &endptr, 10);
+               if (ret != SR_OK || !endptr || *endptr)
+                       return SR_ERR_DATA;
+               chans[idx].enabled = on;
+       }
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_waveform(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr, *endptr;
+       struct devc_wave *waves;
+       struct devc_chan *chan;
+       unsigned long code;
+       size_t idx;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       waves = &devc->waveforms;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_WAVEFORM_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get waveform, response text: %s", rdptr);
+
+       /*
+        * Interpret the response (integer value, waveform code).
+        * Lookup the firmware's code for that waveform in the
+        * list of user perceivable names for waveforms.
+        */
+       endptr = NULL;
+       ret = sr_atoul_base(rdptr, &code, &endptr, 10);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       for (idx = 0; idx < waves->names_count; idx++) {
+               if (code != waves->fw_codes[idx])
+                       continue;
+               chan->waveform_code = code;
+               chan->waveform_index = idx;
+               sr_dbg("get waveform, code %lu, idx %zu, name %s",
+                       code, idx, waves->names[idx]);
+               return SR_OK;
+       }
+
+       return SR_ERR_DATA;
+}
+
+#if WITH_ARBWAVE_DOWNLOAD
+/*
+ * Development HACK. Get a waveform from the device. Uncertain where to
+ * dump it though. Have yet to identify a sigrok API for waveforms.
+ */
+static int jds6600_get_arb_waveform(const struct sr_dev_inst *sdi, size_t idx)
+{
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       int ret;
+       char *rdptr, *word, *endptr;
+       size_t sample_count;
+       unsigned long value;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       waves = &devc->waveforms;
+
+       if (idx >= waves->arbitrary_count)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_WAVE, idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get arb wave, response text: %s", rdptr);
+
+       /* Extract the sequence of samples for the waveform. */
+       replace_separators(rdptr);
+       sample_count = 0;
+       while (rdptr && *rdptr) {
+               word = sr_text_next_word(rdptr, &rdptr);
+               if (!word)
+                       break;
+               endptr = NULL;
+               ret = sr_atoul_base(word, &value, &endptr, 10);
+               if (ret != SR_OK || !endptr || *endptr) {
+                       sr_dbg("get arb wave, conv error: %s", word);
+                       return SR_ERR_DATA;
+               }
+               sample_count++;
+       }
+       sr_dbg("get arb wave, samples count: %zu", sample_count);
+
+       return SR_OK;
+}
+#endif
+
+SR_PRIV int jds6600_get_frequency(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double freq;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_FREQUENCY_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get frequency, response text: %s", rdptr);
+
+       /* Interpret the response (value and scale, frequency). */
+       ret = parse_freq_text(rdptr, &freq);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get frequency, value %f", freq);
+       chan->output_frequency = freq;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double amp;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_AMPLITUDE_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get amplitude, response text: %s", rdptr);
+
+       /* Interpret the response (single value, a voltage). */
+       ret = parse_volt_text(rdptr, &amp);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get amplitude, value %f", amp);
+       chan->amplitude = amp;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_offset(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double off;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_OFFSET_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get offset, response text: %s", rdptr);
+
+       /* Interpret the response (single value, an offset). */
+       ret = parse_bias_text(rdptr, &off);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get offset, value %f", off);
+       chan->offset = off;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double duty;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_DUTYCYCLE_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get duty cycle, response text: %s", rdptr);
+
+       /* Interpret the response (single value, a percentage). */
+       ret = parse_duty_text(rdptr, &duty);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get duty cycle, value %f", duty);
+       chan->dutycycle = duty;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_phase_chans(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr;
+       double phase;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_PHASE_CHANNELS,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get phase, response text: %s", rdptr);
+
+       /* Interpret the response (single value, an angle). */
+       ret = parse_phase_text(rdptr, &phase);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get phase, value %f", phase);
+       devc->channels_phase = phase;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_chans_enable(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct devc_chan *chans;
+       GString *en_text;
+       size_t idx;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive an "ok" style response. */
+       chans = devc->channel_config;
+       en_text = g_string_sized_new(20);
+       for (idx = 0; idx < devc->device.channel_count_gen; idx++) {
+               if (en_text->len)
+                       g_string_append_c(en_text, ',');
+               g_string_append_c(en_text, chans[idx].enabled ? '1' : '0');
+       }
+       sr_dbg("set enabled, request text: %s", en_text->str);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_CHANNELS_ENABLE, "%s", en_text->str);
+       g_string_free(en_text, 20);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_waveform(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_WAVEFORM_CH1 + ch_idx,
+               "%" PRIu32, chan->waveform_code);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+#if WITH_ARBWAVE_DOWNLOAD
+/*
+ * Development HACK. Send a waveform to the device. Uncertain where
+ * to get it from though. Just generate some stupid pattern that's
+ * seen on the LCD later.
+ *
+ * Local experiments suggest that writing another waveform after having
+ * written one earlier results in the next waveform to become mangled.
+ * It appears to start with an all-bits-set pattern for a remarkable
+ * number of samples, before the actually written pattern is seen. Some
+ * delay after reception of the ":ok" response may be required to avoid
+ * this corruption.
+ */
+
+/* Stupid creation of one sample value. Gets waveform index and sample count. */
+static uint16_t make_sample(size_t wave, size_t curr, size_t total)
+{
+       uint16_t max_value, high_value, low_value;
+       size_t ival, high_width;
+       gboolean is_high;
+
+       /* Get the waveform's amplitudes. */
+       max_value = 4096;
+       high_value = max_value / (wave + 3);
+       high_value = max_value - high_value;
+       low_value = max_value - high_value;
+
+       /* Get pulses' total interval, high and low half-periods. */
+       ival = (total - 10) / wave;
+       high_width = ival / 2;
+
+       /* Check location in the current period. */
+       curr %= ival;
+       is_high = curr <= high_width;
+       return is_high ? high_value : low_value;
+}
+
+/* Creation and download of the sequence of samples. */
+static int jds6600_set_arb_waveform(const struct sr_dev_inst *sdi, size_t idx)
+{
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       GString *wave_text;
+       size_t samples_total, samples_curr;
+       uint16_t value;
+       gboolean ok;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       waves = &devc->waveforms;
+
+       if (idx >= waves->arbitrary_count)
+               return SR_ERR_ARG;
+
+       /* Construct a pattern that depends on the waveform index. */
+       wave_text = g_string_sized_new(MAX_RSP_LENGTH);
+       samples_total = 2048;
+       samples_curr = 0;
+       for (samples_curr = 0; samples_curr < samples_total; samples_curr++) {
+               value = make_sample(idx, samples_curr, samples_total);
+               if (samples_curr)
+                       g_string_append_c(wave_text, ',');
+               g_string_append_printf(wave_text, "%" PRIu16, value);
+       }
+       sr_dbg("set arb wave, request text: %s", wave_text->str);
+
+       /* Transmit the request, receive an "ok" style response. */
+       ret = quick_send_write_then_recv_ok(sdi, 0, &ok,
+               INSN_WRITE_WAVE, idx, "%s", wave_text->str);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("set arb wave, response ok: %d", ok);
+
+       if (DELAY_AFTER_FLASH)
+               g_usleep(DELAY_AFTER_FLASH * 1000);
+
+       return SR_OK;
+}
+#endif
+
+SR_PRIV int jds6600_set_frequency(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       double freq;
+       GString *freq_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Limit input values to the range supported by the model. */
+       freq = chan->output_frequency;
+       if (freq < 0.01)
+               freq = 0.01;
+       if (freq > devc->device.max_output_frequency)
+               freq = devc->device.max_output_frequency;
+
+       /* Transmit the request, receive an "ok" style response. */
+       freq_text = g_string_sized_new(32);
+       write_freq_text(freq_text, freq);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_FREQUENCY_CH1 + ch_idx,
+               "%s", freq_text->str);
+       g_string_free(freq_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       GString *volt_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       volt_text = g_string_sized_new(32);
+       write_volt_text(volt_text, chan->amplitude);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_AMPLITUDE_CH1 + ch_idx,
+               "%s", volt_text->str);
+       g_string_free(volt_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_offset(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       GString *volt_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       volt_text = g_string_sized_new(32);
+       write_bias_text(volt_text, chan->offset);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_OFFSET_CH1 + ch_idx,
+               "%s", volt_text->str);
+       g_string_free(volt_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       GString *duty_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       duty_text = g_string_sized_new(32);
+       write_duty_text(duty_text, chan->dutycycle);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_DUTYCYCLE_CH1 + ch_idx,
+               "%s", duty_text->str);
+       g_string_free(duty_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_phase_chans(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       GString *phase_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive an "ok" style response. */
+       phase_text = g_string_sized_new(32);
+       write_phase_text(phase_text, devc->channels_phase);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_PHASE_CHANNELS,
+               "%s", phase_text->str);
+       g_string_free(phase_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * High level helpers for the scan/probe phase. Identify the attached
+ * device and synchronize to its current state and its capabilities.
+ */
+
+SR_PRIV int jds6600_identify(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr, *endptr;
+       unsigned long devtype;
+
+       (void)append_insn_write_para_dots;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit "read device type" request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_DEVICE_TYPE,
+               TIMEOUT_IDENTIFY, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("identify, device type '%s'", rdptr);
+
+       /* Interpret the response (integer value, max freq). */
+       endptr = NULL;
+       ret = sr_atoul_base(rdptr, &devtype, &endptr, 10);
+       if (ret != SR_OK || !endptr)
+               return SR_ERR_DATA;
+       devc->device.device_type = devtype;
+
+       /* Transmit "read serial number" request. receive response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_SERIAL_NUMBER,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("identify, serial number '%s'", rdptr);
+
+       /* Keep the response (in string format, some serial number). */
+       devc->device.serial_number = g_strdup(rdptr);
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_setup_devc(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       size_t alloc_count, assign_idx, idx;
+       struct devc_dev *device;
+       struct devc_wave *waves;
+       enum waveform_index_t code;
+       char *name;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /*
+        * Derive maximum output frequency from detected device type.
+        * Open coded generator channel count.
+        */
+       device = &devc->device;
+       if (!device->device_type)
+               return SR_ERR_DATA;
+       device->max_output_frequency = device->device_type;
+       device->max_output_frequency *= SR_MHZ(1);
+       device->channel_count_gen = MAX_GEN_CHANNELS;
+
+       /* Construct the list of waveform names and their codes. */
+       waves = &devc->waveforms;
+       waves->builtin_count = WAVES_COUNT_BUILTIN;
+       waves->arbitrary_count = WAVES_COUNT_ARBITRARY;
+       alloc_count = waves->builtin_count;
+       alloc_count += waves->arbitrary_count;
+       waves->names_count = alloc_count;
+       waves->fw_codes = g_malloc0(alloc_count * sizeof(waves->fw_codes[0]));
+       alloc_count++;
+       waves->names = g_malloc0(alloc_count * sizeof(waves->names[0]));
+       if (!waves->names || !waves->fw_codes) {
+               g_free(waves->names);
+               g_free(waves->fw_codes);
+               return SR_ERR_MALLOC;
+       }
+       assign_idx = 0;
+       for (idx = 0; idx < waves->builtin_count; idx++) {
+               code = idx;
+               name = g_strdup(waveform_names[idx]);
+               waves->fw_codes[assign_idx] = code;
+               waves->names[assign_idx] = name;
+               assign_idx++;
+       }
+       for (idx = 0; idx < waves->arbitrary_count; idx++) {
+               code = WAVE_ARB01 + idx;
+               name = g_strdup_printf(WAVEFORM_ARB_NAME_FMT, idx + 1);
+               waves->fw_codes[assign_idx] = code;
+               waves->names[assign_idx] = name;
+               assign_idx++;
+       }
+       waves->names[assign_idx] = NULL;
+
+       /*
+        * Populate internal channel configuration details from the
+        * device's current state. Emit a series of queries which
+        * update internal knowledge.
+        *
+        * Implementation detail: Channel count is low, all parameters
+        * are simple scalars. Communication cycles are few, while we
+        * still are in the scan/probe phase and successfully verified
+        * the device to respond. Disconnects and other exceptional
+        * conditions are extremely unlikely. Not checking every getter
+        * call's return value is acceptable here.
+        */
+       ret = SR_OK;
+       ret |= jds6600_get_chans_enable(sdi);
+       for (idx = 0; idx < device->channel_count_gen; idx++) {
+               ret |= jds6600_get_waveform(sdi, idx);
+               ret |= jds6600_get_frequency(sdi, idx);
+               ret |= jds6600_get_amplitude(sdi, idx);
+               ret |= jds6600_get_offset(sdi, idx);
+               ret |= jds6600_get_dutycycle(sdi, idx);
+               if (ret != SR_OK)
+                       break;
+       }
+       ret |= jds6600_get_phase_chans(sdi);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+
+#if WITH_ARBWAVE_DOWNLOAD
+       /*
+        * Development HACK, to see how waveform upload works.
+        * How to forward the data to the application? Or the
+        * sigrok session actually? Provide these as acquisition
+        * results?
+        */
+       ret |= jds6600_get_arb_waveform(sdi, 13);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       ret |= jds6600_set_arb_waveform(sdi, 12);
+       ret |= jds6600_set_arb_waveform(sdi, 13);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+#endif
+
+       return SR_OK;
+}
diff --git a/src/hardware/juntek-jds6600/protocol.h b/src/hardware/juntek-jds6600/protocol.h
new file mode 100644 (file)
index 0000000..9818ecc
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "juntek-jds6600"
+
+#define MAX_GEN_CHANNELS       2
+
+struct dev_context {
+       struct devc_dev {
+               unsigned int device_type;
+               char *serial_number;
+               uint64_t max_output_frequency;
+               size_t channel_count_gen;
+       } device;
+       struct devc_wave {
+               size_t builtin_count;
+               size_t arbitrary_count;
+               size_t names_count;
+               const char **names;
+               uint32_t *fw_codes;
+       } waveforms;
+       struct devc_chan {
+               gboolean enabled;
+               uint32_t waveform_code;
+               size_t waveform_index;
+               double output_frequency;
+               double amplitude;
+               double offset;
+               double dutycycle;
+       } channel_config[MAX_GEN_CHANNELS];
+       double channels_phase;
+       GString *quick_req;
+};
+
+SR_PRIV int jds6600_identify(struct sr_dev_inst *sdi);
+SR_PRIV int jds6600_setup_devc(struct sr_dev_inst *sdi);
+
+SR_PRIV int jds6600_get_chans_enable(const struct sr_dev_inst *sdi);
+SR_PRIV int jds6600_get_waveform(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_frequency(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_offset(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_phase_chans(const struct sr_dev_inst *sdi);
+
+SR_PRIV int jds6600_set_chans_enable(const struct sr_dev_inst *sdi);
+SR_PRIV int jds6600_set_waveform(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_frequency(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_offset(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_phase_chans(const struct sr_dev_inst *sdi);
+
+#endif
index 216869ed8f222b29bb7599712a24e26ee72b01c4..5a4ee6fa2ad48add223df0057ac6b1aefc4ebf9d 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * This file is part of the libsigrok project.
  *
+ * Copyright (C) 2022 Gerhard Sittig <gerhard.sittig@gmx.net>
  * Copyright (C) 2020 Florian Schmidt <schmidt_florian@gmx.de>
  * Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se>
  * Copyright (C) 2013 Bert Vermeulen <bert@biot.com>
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* mostly stolen from src/hardware/saleae-logic16/ */
+/*
+ * This driver implementation initially was derived from the
+ * src/hardware/saleae-logic16/ source code.
+ */
 
 #include <config.h>
-#include <glib.h>
-#include <libusb.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
+
 #include <libsigrok/libsigrok.h>
+#include <string.h>
+
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
 static const uint32_t scanopts[] = {
        SR_CONF_CONN,
+       SR_CONF_PROBE_NAMES,
 };
 
 static const uint32_t drvopts[] = {
        SR_CONF_LOGIC_ANALYZER,
+       SR_CONF_SIGNAL_GENERATOR,
 };
 
 static const uint32_t devopts[] = {
-       /* TODO: SR_CONF_CONTINUOUS, */
        SR_CONF_CONN | SR_CONF_GET,
        SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
-       SR_CONF_LIMIT_SAMPLES | SR_CONF_SET | SR_CONF_GET | SR_CONF_LIST,
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+#if WITH_THRESHOLD_DEVCFG
        SR_CONF_VOLTAGE_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
-       SR_CONF_LOGIC_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
-       SR_CONF_LOGIC_THRESHOLD_CUSTOM | SR_CONF_GET | SR_CONF_SET,
+#endif
        SR_CONF_TRIGGER_MATCH | SR_CONF_LIST,
        SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_CONTINUOUS | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint32_t devopts_cg_logic[] = {
+#if !WITH_THRESHOLD_DEVCFG
+       SR_CONF_VOLTAGE_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+#endif
+};
+
+static const uint32_t devopts_cg_pwm[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_DUTY_CYCLE | SR_CONF_GET | SR_CONF_SET,
 };
 
 static const int32_t trigger_matches[] = {
@@ -59,12 +76,35 @@ static const int32_t trigger_matches[] = {
        SR_TRIGGER_FALLING,
 };
 
-static const char *channel_names[] = {
-       "0", "1", "2", "3", "4", "5", "6", "7",
-       "8", "9", "10", "11", "12", "13", "14", "15",
+static const char *channel_names_logic[] = {
+       "CH0", "CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7",
+       "CH8", "CH9", "CH10", "CH11", "CH12", "CH13", "CH14", "CH15",
+       "CH16", "CH17", "CH18", "CH19", "CH20", "CH21", "CH22", "CH23",
+       "CH24", "CH25", "CH26", "CH27", "CH28", "CH29", "CH30", "CH31",
+};
+
+static const char *channel_names_pwm[] = {
+       "PWM1", "PWM2",
 };
 
-static const uint64_t samplerates_la2016[] = {
+/*
+ * The devices have an upper samplerate limit of 100/200/500 MHz each.
+ * But their hardware uses different base clocks (100/200/800MHz, this
+ * is _not_ a typo) and a 16bit divider. Which results in per-model ranges
+ * of supported rates which not only differ in the upper boundary, but
+ * also at the lower boundary. It's assumed that the 10kHz rate is not
+ * useful enough to provide by all means. Starting at 20kHz for all models
+ * simplfies the implementation of the config API routines, and eliminates
+ * redundancy in these samplerates tables.
+ *
+ * Streaming mode is constrained by the channel count and samplerate
+ * product (the bits per second which need to travel the USB connection
+ * while the acquisition is executing). Because streaming mode does not
+ * compress the capture data, a later implementation may desire a finer
+ * resolution. For now let's just stick with the 1/2/5 steps.
+ */
+
+static const uint64_t rates_500mhz[] = {
        SR_KHZ(20),
        SR_KHZ(50),
        SR_KHZ(100),
@@ -72,17 +112,16 @@ static const uint64_t samplerates_la2016[] = {
        SR_KHZ(500),
        SR_MHZ(1),
        SR_MHZ(2),
-       SR_MHZ(4),
        SR_MHZ(5),
-       SR_MHZ(8),
        SR_MHZ(10),
        SR_MHZ(20),
        SR_MHZ(50),
        SR_MHZ(100),
        SR_MHZ(200),
+       SR_MHZ(500),
 };
 
-static const uint64_t samplerates_la1016[] = {
+static const uint64_t rates_200mhz[] = {
        SR_KHZ(20),
        SR_KHZ(50),
        SR_KHZ(100),
@@ -90,291 +129,560 @@ static const uint64_t samplerates_la1016[] = {
        SR_KHZ(500),
        SR_MHZ(1),
        SR_MHZ(2),
-       SR_MHZ(4),
        SR_MHZ(5),
-       SR_MHZ(8),
        SR_MHZ(10),
        SR_MHZ(20),
        SR_MHZ(50),
        SR_MHZ(100),
+       SR_MHZ(200),
 };
 
-static const float logic_threshold_value[] = {
-       1.58,
-       2.5,
-       1.165,
-       1.5,
-       1.25,
-       0.9,
-       0.75,
-       0.60,
-       0.45,
+static const uint64_t rates_100mhz[] = {
+       SR_KHZ(20),
+       SR_KHZ(50),
+       SR_KHZ(100),
+       SR_KHZ(200),
+       SR_KHZ(500),
+       SR_MHZ(1),
+       SR_MHZ(2),
+       SR_MHZ(5),
+       SR_MHZ(10),
+       SR_MHZ(20),
+       SR_MHZ(50),
+       SR_MHZ(100),
 };
 
-static const char *logic_threshold[] = {
-       "TTL 5V",
-       "CMOS 5V",
-       "CMOS 3.3V",
-       "CMOS 3.0V",
-       "CMOS 2.5V",
-       "CMOS 1.8V",
-       "CMOS 1.5V",
-       "CMOS 1.2V",
-       "CMOS 0.9V",
-       "USER",
+/*
+ * Only list a few discrete voltages, to form a useful set which covers
+ * most logic families. Too many choices can make some applications use
+ * a slider again. Which may lack a scale for the current value, and
+ * leave users without feedback what the currently used value might be.
+ */
+static const double threshold_ranges[][2] = {
+       { 0.4, 0.4, },
+       { 0.6, 0.6, },
+       { 0.9, 0.9, },
+       { 1.2, 1.2, },
+       { 1.4, 1.4, }, /* Default, 1.4V, index 4. */
+       { 2.0, 2.0, },
+       { 2.5, 2.5, },
+       { 4.0, 4.0, },
 };
+#define LOGIC_THRESHOLD_IDX_DFLT       4
+
+static double threshold_voltage(const struct sr_dev_inst *sdi, double *high)
+{
+       struct dev_context *devc;
+       size_t idx;
+       double voltage;
+
+       devc = sdi->priv;
+       idx = devc->threshold_voltage_idx;
+       voltage = threshold_ranges[idx][0];
+       if (high)
+               *high = threshold_ranges[idx][1];
 
-#define MAX_NUM_LOGIC_THRESHOLD_ENTRIES ARRAY_SIZE(logic_threshold)
+       return voltage;
+}
+
+/* Convenience. Release an allocated devc from error paths. */
+static void kingst_la2016_free_devc(struct dev_context *devc)
+{
+       if (!devc)
+               return;
+       g_free(devc->mcu_firmware);
+       g_free(devc->fpga_bitstream);
+       g_free(devc);
+}
+
+/* Convenience. Release an allocated sdi from error paths. */
+static void kingst_la2016_free_sdi(struct sr_dev_inst *sdi)
+{
+       struct sr_usb_dev_inst *usb;
+       struct dev_context *devc;
+
+       if (!sdi)
+               return;
+
+       usb = sdi->conn;
+       if (usb && usb->devhdl)
+               sr_usb_close(usb);
+       sr_usb_dev_inst_free(usb);
+
+       devc = sdi->priv;
+       kingst_la2016_free_devc(devc);
+
+       sr_dev_inst_free(sdi);
+}
+
+/* Convenience. Open a USB device (including claiming an interface). */
+static int la2016_open_usb(struct sr_usb_dev_inst *usb,
+       libusb_device *dev, gboolean show_message)
+{
+       int ret;
+
+       ret = libusb_open(dev, &usb->devhdl);
+       if (ret != 0) {
+               if (show_message) {
+                       sr_err("Cannot open device: %s.",
+                               libusb_error_name(ret));
+               }
+               return SR_ERR_IO;
+       }
+
+       if (usb->address == 0xff) {
+               /*
+                * First encounter after firmware upload.
+                * Grab current address after enumeration.
+                */
+               usb->address = libusb_get_device_address(dev);
+       }
+
+       ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE);
+       if (ret == LIBUSB_ERROR_BUSY) {
+               sr_err("Cannot claim USB interface. Another program or driver using it?");
+               return SR_ERR_IO;
+       } else if (ret == LIBUSB_ERROR_NO_DEVICE) {
+               sr_err("Device has been disconnected.");
+               return SR_ERR_IO;
+       } else if (ret != 0) {
+               sr_err("Cannot claim USB interface: %s.",
+                       libusb_error_name(ret));
+               return SR_ERR_IO;
+       }
+
+       return SR_OK;
+}
+
+/* Convenience. Close an opened USB device (and release the interface). */
+static void la2016_close_usb(struct sr_usb_dev_inst *usb)
+{
+
+       if (!usb)
+               return;
+
+       if (usb->devhdl) {
+               libusb_release_interface(usb->devhdl, USB_INTERFACE);
+               libusb_close(usb->devhdl);
+               usb->devhdl = NULL;
+       }
+}
+
+/* Communicate to an USB device to identify the Kingst LA model. */
+static int la2016_identify_read(struct sr_dev_inst *sdi,
+       struct sr_usb_dev_inst *usb, libusb_device *dev,
+       gboolean show_message)
+{
+       int ret;
+
+       ret = la2016_open_usb(usb, dev, show_message);
+       if (ret != SR_OK) {
+               if (show_message)
+                       sr_err("Cannot communicate to MCU firmware.");
+               return ret;
+       }
+
+       /*
+        * Also complete the hardware configuration (FPGA bitstream)
+        * when MCU firmware communication became operational. Either
+        * failure is considered fatal when probing for the device.
+        */
+       ret = la2016_identify_device(sdi, show_message);
+       if (ret == SR_OK) {
+               ret = la2016_init_hardware(sdi);
+       }
+
+       la2016_close_usb(usb);
+
+       return ret;
+}
+
+/* Find given conn_id in another USB enum. Identify Kingst LA model. */
+static int la2016_identify_enum(struct sr_dev_inst *sdi)
+{
+       struct sr_dev_driver *di;
+       struct drv_context *drvc;
+       struct sr_context *ctx;
+       libusb_device **devlist, *dev;
+       struct libusb_device_descriptor des;
+       int ret, id_ret;
+       size_t device_count, dev_idx;
+       char conn_id[64];
+
+       di = sdi->driver;
+       drvc = di->context;
+       ctx = drvc->sr_ctx;;
+
+       ret = libusb_get_device_list(ctx->libusb_ctx, &devlist);
+       if (ret < 0)
+               return SR_ERR_IO;
+       device_count = ret;
+       if (!device_count)
+               return SR_ERR_IO;
+       id_ret = SR_ERR_IO;
+       for (dev_idx = 0; dev_idx < device_count; dev_idx++) {
+               dev = devlist[dev_idx];
+               libusb_get_device_descriptor(dev, &des);
+               if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID)
+                       continue;
+               if (des.iProduct != LA2016_IPRODUCT_INDEX)
+                       continue;
+               ret = usb_get_port_path(dev, conn_id, sizeof(conn_id));
+               if (ret < 0)
+                       continue;
+               if (strcmp(sdi->connection_id, conn_id) != 0)
+                       continue;
+               id_ret = la2016_identify_read(sdi, sdi->conn, dev, FALSE);
+               break;
+       }
+       libusb_free_device_list(devlist, 1);
+
+       return id_ret;
+}
+
+/* Wait for a device to re-appear after firmware upload. */
+static int la2016_identify_wait(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       uint64_t reset_done, now, elapsed_ms;
+       int ret;
+
+       devc = sdi->priv;
+
+       sr_info("Waiting for device to reset after firmware upload.");
+       now = g_get_monotonic_time();
+       reset_done = devc->fw_uploaded + RENUM_GONE_DELAY_MS * 1000;
+       if (now < reset_done)
+               g_usleep(reset_done - now);
+       do {
+               now = g_get_monotonic_time();
+               elapsed_ms = (now - devc->fw_uploaded) / 1000;
+               sr_spew("Waited %" PRIu64 "ms.", elapsed_ms);
+               ret = la2016_identify_enum(sdi);
+               if (ret == SR_OK) {
+                       devc->fw_uploaded = 0;
+                       break;
+               }
+               g_usleep(RENUM_POLL_INTERVAL_MS * 1000);
+       } while (elapsed_ms < RENUM_CHECK_PERIOD_MS);
+       if (ret != SR_OK) {
+               sr_err("Device failed to re-enumerate.");
+               return ret;
+       }
+       sr_info("Device came back after %" PRIi64 "ms.", elapsed_ms);
+
+       return SR_OK;
+}
+
+/*
+ * Open given conn_id from another USB enum. Used by dev_open(). Similar
+ * to, and should be kept in sync with la2016_identify_enum().
+ */
+static int la2016_open_enum(struct sr_dev_inst *sdi)
+{
+       struct sr_dev_driver *di;
+       struct drv_context *drvc;
+       struct sr_context *ctx;
+       libusb_device **devlist, *dev;
+       struct libusb_device_descriptor des;
+       int ret, open_ret;
+       size_t device_count, dev_idx;
+       char conn_id[64];
+
+       di = sdi->driver;
+       drvc = di->context;
+       ctx = drvc->sr_ctx;;
+
+       ret = libusb_get_device_list(ctx->libusb_ctx, &devlist);
+       if (ret < 0)
+               return SR_ERR_IO;
+       device_count = ret;
+       if (!device_count)
+               return SR_ERR_IO;
+       open_ret = SR_ERR_IO;
+       for (dev_idx = 0; dev_idx < device_count; dev_idx++) {
+               dev = devlist[dev_idx];
+               libusb_get_device_descriptor(dev, &des);
+               if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID)
+                       continue;
+               if (des.iProduct != LA2016_IPRODUCT_INDEX)
+                       continue;
+               ret = usb_get_port_path(dev, conn_id, sizeof(conn_id));
+               if (ret < 0)
+                       continue;
+               if (strcmp(sdi->connection_id, conn_id) != 0)
+                       continue;
+               open_ret = la2016_open_usb(sdi->conn, dev, TRUE);
+               break;
+       }
+       libusb_free_device_list(devlist, 1);
+
+       return open_ret;
+}
 
 static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
        struct drv_context *drvc;
+       struct sr_context *ctx;
        struct dev_context *devc;
        struct sr_dev_inst *sdi;
        struct sr_usb_dev_inst *usb;
        struct sr_config *src;
        GSList *l;
-       GSList *devices;
+       GSList *devices, *found_devices, *renum_devices;
        GSList *conn_devices;
        struct libusb_device_descriptor des;
-       libusb_device **devlist;
-       unsigned int i, j;
+       libusb_device **devlist, *dev;
+       size_t dev_count, dev_idx, ch_idx;
+       uint8_t bus, addr;
+       uint16_t pid;
        const char *conn;
-       char connection_id[64];
-       int64_t fw_updated;
-       unsigned int dev_addr;
+       const char *probe_names;
+       char conn_id[64];
+       int ret;
+       size_t ch_off, ch_max;
+       struct sr_channel *ch;
+       struct sr_channel_group *cg;
 
        drvc = di->context;
+       ctx = drvc->sr_ctx;;
 
        conn = NULL;
+       conn_devices = NULL;
+       probe_names = NULL;
        for (l = options; l; l = l->next) {
                src = l->data;
                switch (src->key) {
                case SR_CONF_CONN:
                        conn = g_variant_get_string(src->data, NULL);
                        break;
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(src->data, NULL);
+                       break;
                }
        }
        if (conn)
-               conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn);
-       else
-               conn_devices = NULL;
+               conn_devices = sr_usb_find(ctx->libusb_ctx, conn);
+       if (conn && !conn_devices) {
+               sr_err("Cannot find the specified connection '%s'.", conn);
+               return NULL;
+       }
 
-       /* Find all LA2016 devices and upload firmware to them. */
+       /*
+        * Find all LA2016 devices, optionally upload firmware to them.
+        * Defer completion of sdi/devc creation until all (selected)
+        * devices were found in a usable state, and their models got
+        * identified which affect their feature set. It appears that
+        * we cannot communicate to the device within the same USB enum
+        * cycle, needs another USB enumeration after firmware upload.
+        */
        devices = NULL;
-       libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist);
-       for (i = 0; devlist[i]; i++) {
-               if (conn) {
-                       usb = NULL;
-                       for (l = conn_devices; l; l = l->next) {
-                               usb = l->data;
-                               if (usb->bus == libusb_get_bus_number(devlist[i]) &&
-                                   usb->address == libusb_get_device_address(devlist[i]))
-                                       break;
-                       }
-                       if (!l) {
-                               /* This device matched none of the ones that
-                                * matched the conn specification. */
-                               continue;
-                       }
+       found_devices = NULL;
+       renum_devices = NULL;
+       ret = libusb_get_device_list(ctx->libusb_ctx, &devlist);
+       if (ret < 0) {
+               sr_err("Cannot get device list: %s.", libusb_error_name(ret));
+               return devices;
+       }
+       dev_count = ret;
+       for (dev_idx = 0; dev_idx < dev_count; dev_idx++) {
+               dev = devlist[dev_idx];
+               bus = libusb_get_bus_number(dev);
+               addr = libusb_get_device_address(dev);
+
+               /* Filter by connection when externally specified. */
+               for (l = conn_devices; l; l = l->next) {
+                       usb = l->data;
+                       if (usb->bus == bus && usb->address == addr)
+                               break;
                }
-
-               libusb_get_device_descriptor(devlist[i], &des);
-
-               if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0)
+               if (conn_devices && !l) {
+                       sr_spew("Bus %hhu, addr %hhu do not match specified filter.",
+                               bus, addr);
                        continue;
+               }
 
+               /* Check USB VID:PID. Get the connection string. */
+               libusb_get_device_descriptor(dev, &des);
                if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID)
                        continue;
+               pid = des.idProduct;
+               ret = usb_get_port_path(dev, conn_id, sizeof(conn_id));
+               if (ret < 0)
+                       continue;
+               sr_dbg("USB enum found %04x:%04x at path %s, %d.%d.",
+                       des.idVendor, des.idProduct, conn_id, bus, addr);
+               usb = sr_usb_dev_inst_new(bus, addr, NULL);
 
-               /* Already has the firmware */
-               sr_dbg("Found a LA2016 device.");
-               sdi = g_malloc0(sizeof(struct sr_dev_inst));
+               sdi = g_malloc0(sizeof(*sdi));
+               sdi->driver = di;
                sdi->status = SR_ST_INITIALIZING;
-               sdi->connection_id = g_strdup(connection_id);
+               sdi->inst_type = SR_INST_USB;
+               sdi->connection_id = g_strdup(conn_id);
+               sdi->conn = usb;
 
-               fw_updated = 0;
-               dev_addr = libusb_get_device_address(devlist[i]);
-               if (des.iProduct != 2) {
-                       sr_info("device at '%s' has no firmware loaded!", connection_id);
+               devc = g_malloc0(sizeof(*devc));
+               sdi->priv = devc;
 
-                       if (la2016_upload_firmware(drvc->sr_ctx, devlist[i], des.idProduct) != SR_OK) {
-                               sr_err("uC firmware upload failed!");
-                               g_free(sdi->connection_id);
-                               g_free(sdi);
+               /*
+                * Load MCU firmware if it is currently missing. Which
+                * makes the device disappear and renumerate in USB.
+                * We need to come back another time to communicate to
+                * this device.
+                */
+               devc->fw_uploaded = 0;
+               devc->usb_pid = pid;
+               if (des.iProduct != LA2016_IPRODUCT_INDEX) {
+                       sr_info("Uploading MCU firmware to '%s'.", conn_id);
+                       ret = la2016_upload_firmware(sdi, ctx, dev, FALSE);
+                       if (ret != SR_OK) {
+                               sr_err("MCU firmware upload failed.");
+                               kingst_la2016_free_sdi(sdi);
+                               continue;
+                       }
+                       devc->fw_uploaded = g_get_monotonic_time();
+                       usb->address = 0xff;
+                       renum_devices = g_slist_append(renum_devices, sdi);
+                       continue;
+               } else {
+                       ret = la2016_upload_firmware(sdi, NULL, NULL, TRUE);
+                       if (ret != SR_OK) {
+                               sr_err("MCU firmware filename check failed.");
+                               kingst_la2016_free_sdi(sdi);
                                continue;
                        }
-                       fw_updated = g_get_monotonic_time();
-                       dev_addr = 0xff; /* to mark that we don't know address yet... ugly */
                }
 
-               sdi->vendor = g_strdup("Kingst");
-               sdi->model = g_strdup("LA2016");
-
-               for (j = 0; j < ARRAY_SIZE(channel_names); j++)
-                       sr_channel_new(sdi, j, SR_CHANNEL_LOGIC, TRUE, channel_names[j]);
-
-               devices = g_slist_append(devices, sdi);
-
-               devc = g_malloc0(sizeof(struct dev_context));
-               sdi->priv = devc;
-               devc->fw_updated = fw_updated;
-               devc->threshold_voltage_idx = 0;
-               devc->threshold_voltage = logic_threshold_value[devc->threshold_voltage_idx];
-
-               sdi->status = SR_ST_INACTIVE;
-               sdi->inst_type = SR_INST_USB;
-
-               sdi->conn = sr_usb_dev_inst_new(
-                       libusb_get_bus_number(devlist[i]),
-                       dev_addr, NULL);
+               /*
+                * Communicate to the MCU firmware to access EEPROM data
+                * which lets us identify the device type. Then stop, to
+                * share remaining sdi/devc creation with those devices
+                * which had their MCU firmware uploaded above and which
+                * get revisited later.
+                */
+               ret = la2016_identify_read(sdi, usb, dev, TRUE);
+               if (ret != SR_OK || !devc->model) {
+                       sr_err("Unknown or unsupported device type.");
+                       kingst_la2016_free_sdi(sdi);
+                       continue;
+               }
+               found_devices = g_slist_append(found_devices, sdi);
        }
        libusb_free_device_list(devlist, 1);
-       g_slist_free_full(conn_devices, (GDestroyNotify)sr_usb_dev_inst_free);
-
-       return std_scan_complete(di, devices);
-}
-
-static int la2016_dev_open(struct sr_dev_inst *sdi)
-{
-       struct sr_dev_driver *di;
-       libusb_device **devlist;
-       struct sr_usb_dev_inst *usb;
-       struct libusb_device_descriptor des;
-       struct drv_context *drvc;
-       int ret, i, device_count;
-       char connection_id[64];
-
-       di = sdi->driver;
-       drvc = di->context;
-       usb = sdi->conn;
-       ret = SR_ERR;
-
-       device_count = libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist);
-       if (device_count < 0) {
-               sr_err("Failed to get device list: %s.", libusb_error_name(device_count));
-               return SR_ERR;
-       }
-
-       for (i = 0; i < device_count; i++) {
-               libusb_get_device_descriptor(devlist[i], &des);
+       g_slist_free_full(conn_devices, sr_usb_dev_inst_free_cb);
 
-               if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID || des.iProduct != 2)
+       /*
+        * Wait for devices to re-appear after firmware upload. Append
+        * the yet unidentified device to the list of found devices, or
+        * release the previously allocated sdi/devc.
+        */
+       for (l = renum_devices; l; l = l->next) {
+               sdi = l->data;
+               devc = sdi->priv;
+               ret = la2016_identify_wait(sdi);
+               if (ret != SR_OK || !devc->model) {
+                       sr_dbg("Skipping unusable '%s'.", sdi->connection_id);
+                       kingst_la2016_free_sdi(sdi);
                        continue;
+               }
+               found_devices = g_slist_append(found_devices, sdi);
+       }
+       g_slist_free(renum_devices);
 
-               if ((sdi->status == SR_ST_INITIALIZING) || (sdi->status == SR_ST_INACTIVE)) {
-                       /*
-                        * Check device by its physical USB bus/port address.
-                        */
-                       if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0)
-                               continue;
+       /*
+        * All found devices got identified, their type is known here.
+        * Complete the sdi/devc creation. Assign default settings
+        * because the vendor firmware would not let us read back the
+        * previously written configuration.
+        */
+       for (l = found_devices; l; l = l->next) {
+               sdi = l->data;
+               devc = sdi->priv;
 
-                       if (strcmp(sdi->connection_id, connection_id))
-                               /* This is not the one. */
-                               continue;
+               sdi->vendor = g_strdup("Kingst");
+               sdi->model = g_strdup(devc->model->name);
+               ch_off = 0;
+
+               /* Create the "Logic" channel group. */
+               ch_max = ARRAY_SIZE(channel_names_logic);
+               if (ch_max > devc->model->channel_count)
+                       ch_max = devc->model->channel_count;
+               devc->channel_names_logic = sr_parse_probe_names(probe_names,
+                       channel_names_logic, ch_max, ch_max, &ch_max);
+               cg = sr_channel_group_new(sdi, "Logic", NULL);
+               devc->cg_logic = cg;
+               for (ch_idx = 0; ch_idx < ch_max; ch_idx++) {
+                       ch = sr_channel_new(sdi, ch_off,
+                               SR_CHANNEL_LOGIC, TRUE,
+                               devc->channel_names_logic[ch_idx]);
+                       ch_off++;
+                       cg->channels = g_slist_append(cg->channels, ch);
                }
 
-               if (!(ret = libusb_open(devlist[i], &usb->devhdl))) {
-                       if (usb->address == 0xff)
-                               /*
-                                * First time we touch this device after FW
-                                * upload, so we don't know the address yet.
-                                */
-                               usb->address = libusb_get_device_address(devlist[i]);
-               } else {
-                       sr_err("Failed to open device: %s.", libusb_error_name(ret));
-                       ret = SR_ERR;
-                       break;
+               /* Create the "PWMx" channel groups. */
+               ch_max = ARRAY_SIZE(channel_names_pwm);
+               for (ch_idx = 0; ch_idx < ch_max; ch_idx++) {
+                       const char *name;
+                       name = channel_names_pwm[ch_idx];
+                       cg = sr_channel_group_new(sdi, name, NULL);
+                       if (!devc->cg_pwm)
+                               devc->cg_pwm = cg;
+                       ch = sr_channel_new(sdi, ch_off,
+                               SR_CHANNEL_ANALOG, FALSE, name);
+                       ch_off++;
+                       cg->channels = g_slist_append(cg->channels, ch);
                }
 
-               ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE);
-               if (ret == LIBUSB_ERROR_BUSY) {
-                       sr_err("Unable to claim USB interface. Another "
-                              "program or driver has already claimed it.");
-                       ret = SR_ERR;
-                       break;
-               } else if (ret == LIBUSB_ERROR_NO_DEVICE) {
-                       sr_err("Device has been disconnected.");
-                       ret = SR_ERR;
-                       break;
-               } else if (ret != 0) {
-                       sr_err("Unable to claim interface: %s.", libusb_error_name(ret));
-                       ret = SR_ERR;
-                       break;
+               /*
+                * Ideally we'd get the previous configuration from the
+                * hardware, but this device is write-only. So we have
+                * to assign a fixed set of initial configuration values.
+                */
+               sr_sw_limits_init(&devc->sw_limits);
+               devc->sw_limits.limit_samples = 0;
+               devc->capture_ratio = 50;
+               devc->samplerate = devc->model->samplerate;
+               if (!devc->model->memory_bits)
+                       devc->continuous = TRUE;
+               devc->threshold_voltage_idx = LOGIC_THRESHOLD_IDX_DFLT;
+               if  (ARRAY_SIZE(devc->pwm_setting) >= 1) {
+                       devc->pwm_setting[0].enabled = FALSE;
+                       devc->pwm_setting[0].freq = SR_KHZ(1);
+                       devc->pwm_setting[0].duty = 50;
                }
-
-               if ((ret = la2016_init_device(sdi)) != SR_OK) {
-                       sr_err("Failed to init device.");
-                       break;
+               if  (ARRAY_SIZE(devc->pwm_setting) >= 2) {
+                       devc->pwm_setting[1].enabled = FALSE;
+                       devc->pwm_setting[1].freq = SR_KHZ(100);
+                       devc->pwm_setting[1].duty = 50;
                }
 
-               sr_info("Opened device on %d.%d (logical) / %s (physical), interface %d.",
-                       usb->bus, usb->address, sdi->connection_id, USB_INTERFACE);
-
-               ret = SR_OK;
-
-               break;
-       }
-
-       libusb_free_device_list(devlist, 1);
-
-       if (ret != SR_OK) {
-               if (usb->devhdl) {
-                       libusb_release_interface(usb->devhdl, USB_INTERFACE);
-                       libusb_close(usb->devhdl);
-                       usb->devhdl = NULL;
-               }
-               return SR_ERR;
+               sdi->status = SR_ST_INACTIVE;
+               devices = g_slist_append(devices, sdi);
        }
+       g_slist_free(found_devices);
 
-       return SR_OK;
+       return std_scan_complete(di, devices);
 }
 
 static int dev_open(struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
-       int64_t timediff_us, timediff_ms;
-       uint64_t reset_done;
-       uint64_t now;
        int ret;
+       size_t ch;
 
        devc = sdi->priv;
 
-       /*
-        * If the firmware was recently uploaded, wait up to MAX_RENUM_DELAY_MS
-        * milliseconds for the FX2 to renumerate.
-        */
-       ret = SR_ERR;
-       if (devc->fw_updated > 0) {
-               sr_info("Waiting for device to reset after firmware upload.");
-               /* Takes >= 2000ms for the uC to be gone from the USB bus. */
-               reset_done = devc->fw_updated + 18 * (uint64_t)1e5; /* 1.8 seconds */
-               now = g_get_monotonic_time();
-               if (reset_done > now)
-                       g_usleep(reset_done - now);
-               timediff_ms = 0;
-               while (timediff_ms < MAX_RENUM_DELAY_MS) {
-                       g_usleep(200 * 1000);
-
-                       timediff_us = g_get_monotonic_time() - devc->fw_updated;
-                       timediff_ms = timediff_us / 1000;
-
-                       if ((ret = la2016_dev_open(sdi)) == SR_OK)
-                               break;
-                       sr_spew("Waited %" PRIi64 "ms.", timediff_ms);
-               }
-               if (ret != SR_OK) {
-                       sr_err("Device failed to re-enumerate.");
-                       return SR_ERR;
-               }
-               sr_info("Device came back after %" PRIi64 "ms.", timediff_ms);
-       } else {
-               ret = la2016_dev_open(sdi);
+       ret = la2016_open_enum(sdi);
+       if (ret != SR_OK) {
+               sr_err("Cannot open device.");
+               return ret;
        }
 
-       if (ret != SR_OK) {
-               sr_err("Unable to open device.");
-               return SR_ERR;
+       /* Send most recent PWM configuration to the device. */
+       for (ch = 0; ch < ARRAY_SIZE(devc->pwm_setting); ch++) {
+               ret = la2016_write_pwm_config(sdi, ch);
+               if (ret != SR_OK)
+                       return ret;
        }
 
        return SR_OK;
@@ -389,62 +697,151 @@ static int dev_close(struct sr_dev_inst *sdi)
        if (!usb->devhdl)
                return SR_ERR_BUG;
 
-       la2016_deinit_device(sdi);
+       la2016_release_resources(sdi);
+
+       if (WITH_DEINIT_IN_CLOSE)
+               la2016_deinit_hardware(sdi);
 
        sr_info("Closing device on %d.%d (logical) / %s (physical) interface %d.",
                usb->bus, usb->address, sdi->connection_id, USB_INTERFACE);
-       libusb_release_interface(usb->devhdl, USB_INTERFACE);
-       libusb_close(usb->devhdl);
-       usb->devhdl = NULL;
+       la2016_close_usb(sdi->conn);
 
        return SR_OK;
 }
 
+/* Config API helper. Get type and index of a channel group. */
+static int get_cg_index(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg,
+       int *type, size_t *logic, size_t *analog)
+{
+       struct dev_context *devc;
+       GSList *l;
+       size_t idx;
+
+       /* Preset return values. */
+       if (type)
+               *type = 0;
+       if (logic)
+               *logic = 0;
+       if (analog)
+               *analog = 0;
+
+       /* Start categorizing the received cg. */
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!cg)
+               return SR_OK;
+       l = sdi->channel_groups;
+
+       /* First sdi->channelgroups item is "Logic". */
+       if (!l)
+               return SR_ERR_BUG;
+       if (cg == l->data) {
+               if (type)
+                       *type = SR_CHANNEL_LOGIC;
+               if (logic)
+                       *logic = 0;
+               return SR_OK;
+       }
+       l = l->next;
+
+       /* Next sdi->channelgroups items are "PWMx". */
+       idx = 0;
+       while (l && l->data != cg) {
+               idx++;
+               l = l->next;
+       }
+       if (l && idx < ARRAY_SIZE(devc->pwm_setting)) {
+               if (type)
+                       *type = SR_CHANNEL_ANALOG;
+               if (analog)
+                       *analog = idx;
+               return SR_OK;
+       }
+
+       return SR_ERR_ARG;
+}
+
 static int config_get(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        struct dev_context *devc;
+       int ret, cg_type;
+       size_t logic_idx, analog_idx;
+       struct pwm_setting *pwm;
        struct sr_usb_dev_inst *usb;
-       double rounded;
+       double voltage, rounded;
 
-       (void)cg;
+       (void)rounded;
+       (void)voltage;
 
        if (!sdi)
                return SR_ERR_ARG;
        devc = sdi->priv;
 
+       /* Check for types (and index) of channel groups. */
+       ret = get_cg_index(sdi, cg, &cg_type, &logic_idx, &analog_idx);
+       if (cg && ret != SR_OK)
+               return SR_ERR_ARG;
+
+       /* Handle requests for the "Logic" channel group. */
+       if (cg && cg_type == SR_CHANNEL_LOGIC) {
+               switch (key) {
+#if !WITH_THRESHOLD_DEVCFG
+               case SR_CONF_VOLTAGE_THRESHOLD:
+                       voltage = threshold_voltage(sdi, NULL);
+                       *data = std_gvar_tuple_double(voltage, voltage);
+                       break;
+#endif /* WITH_THRESHOLD_DEVCFG */
+               default:
+                       return SR_ERR_NA;
+               }
+               return SR_OK;
+       }
+
+       /* Handle requests for the "PWMx" channel groups. */
+       if (cg && cg_type == SR_CHANNEL_ANALOG) {
+               pwm = &devc->pwm_setting[analog_idx];
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       *data = g_variant_new_boolean(pwm->enabled);
+                       break;
+               case SR_CONF_OUTPUT_FREQUENCY:
+                       *data = g_variant_new_double(pwm->freq);
+                       break;
+               case SR_CONF_DUTY_CYCLE:
+                       *data = g_variant_new_double(pwm->duty);
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+               return SR_OK;
+       }
+
        switch (key) {
        case SR_CONF_CONN:
-               if (!sdi->conn)
-                       return SR_ERR_ARG;
                usb = sdi->conn;
-               if (usb->address == 255) {
-                       /* Device still needs to re-enumerate after firmware
-                        * upload, so we don't know its (future) address. */
-                       return SR_ERR;
-               }
                *data = g_variant_new_printf("%d.%d", usb->bus, usb->address);
                break;
        case SR_CONF_SAMPLERATE:
-               *data = g_variant_new_uint64(devc->cur_samplerate);
+               *data = g_variant_new_uint64(devc->samplerate);
                break;
        case SR_CONF_LIMIT_SAMPLES:
-               *data = g_variant_new_uint64(devc->limit_samples);
-               break;
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_get(&devc->sw_limits, key, data);
        case SR_CONF_CAPTURE_RATIO:
                *data = g_variant_new_uint64(devc->capture_ratio);
                break;
+#if WITH_THRESHOLD_DEVCFG
        case SR_CONF_VOLTAGE_THRESHOLD:
-               rounded = (int)(devc->threshold_voltage / 0.1) * 0.1;
-               *data = std_gvar_tuple_double(rounded, rounded + 0.1);
-               return SR_OK;
-       case SR_CONF_LOGIC_THRESHOLD:
-               *data = g_variant_new_string(logic_threshold[devc->threshold_voltage_idx]);
+               voltage = threshold_voltage(sdi, NULL);
+               *data = std_gvar_tuple_double(voltage, voltage);
                break;
-       case SR_CONF_LOGIC_THRESHOLD_CUSTOM:
-               *data = g_variant_new_double(devc->threshold_voltage);
+#endif /* WITH_THRESHOLD_DEVCFG */
+       case SR_CONF_CONTINUOUS:
+               *data = g_variant_new_boolean(devc->continuous);
                break;
-
        default:
                return SR_ERR_NA;
        }
@@ -456,41 +853,96 @@ static int config_set(uint32_t key, GVariant *data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        struct dev_context *devc;
-       double low, high;
+       int ret, cg_type;
+       size_t logic_idx, analog_idx;
+       struct pwm_setting *pwm;
+       double value_f;
        int idx;
-
-       (void)cg;
+       gboolean on;
 
        devc = sdi->priv;
 
+       /* Check for types (and index) of channel groups. */
+       ret = get_cg_index(sdi, cg, &cg_type, &logic_idx, &analog_idx);
+       if (cg && ret != SR_OK)
+               return SR_ERR_ARG;
+
+       /* Handle requests for the "Logic" channel group. */
+       if (cg && cg_type == SR_CHANNEL_LOGIC) {
+               switch (key) {
+#if !WITH_THRESHOLD_DEVCFG
+               case SR_CONF_LOGIC_THRESHOLD:
+                       idx = std_double_tuple_idx(data,
+                               ARRAY_AND_SIZE(threshold_ranges));
+                       if (idx < 0)
+                               return SR_ERR_ARG;
+                       devc->threshold_voltage_idx = idx;
+                       break;
+#endif /* WITH_THRESHOLD_DEVCFG */
+               default:
+                       return SR_ERR_NA;
+               }
+               return SR_OK;
+       }
+
+       /* Handle requests for the "PWMx" channel groups. */
+       if (cg && cg_type == SR_CHANNEL_ANALOG) {
+               pwm = &devc->pwm_setting[analog_idx];
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       pwm->enabled = g_variant_get_boolean(data);
+                       ret = la2016_write_pwm_config(sdi, analog_idx);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case SR_CONF_OUTPUT_FREQUENCY:
+                       value_f = g_variant_get_double(data);
+                       if (value_f <= 0.0 || value_f > MAX_PWM_FREQ)
+                               return SR_ERR_ARG;
+                       pwm->freq = value_f;
+                       ret = la2016_write_pwm_config(sdi, analog_idx);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case SR_CONF_DUTY_CYCLE:
+                       value_f = g_variant_get_double(data);
+                       if (value_f <= 0.0 || value_f > 100.0)
+                               return SR_ERR_ARG;
+                       pwm->duty = value_f;
+                       ret = la2016_write_pwm_config(sdi, analog_idx);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+               return SR_OK;
+       }
+
        switch (key) {
        case SR_CONF_SAMPLERATE:
-               devc->cur_samplerate = g_variant_get_uint64(data);
+               devc->samplerate = g_variant_get_uint64(data);
                break;
        case SR_CONF_LIMIT_SAMPLES:
-               devc->limit_samples = g_variant_get_uint64(data);
-               break;
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_set(&devc->sw_limits, key, data);
        case SR_CONF_CAPTURE_RATIO:
                devc->capture_ratio = g_variant_get_uint64(data);
                break;
+#if WITH_THRESHOLD_DEVCFG
        case SR_CONF_VOLTAGE_THRESHOLD:
-               g_variant_get(data, "(dd)", &low, &high);
-               devc->threshold_voltage = (low + high) / 2.0;
-               devc->threshold_voltage_idx = MAX_NUM_LOGIC_THRESHOLD_ENTRIES - 1; /* USER */
-               break;
-       case SR_CONF_LOGIC_THRESHOLD: {
-               if ((idx = std_str_idx(data, logic_threshold, MAX_NUM_LOGIC_THRESHOLD_ENTRIES)) < 0)
+               idx = std_double_tuple_idx(data,
+                       ARRAY_AND_SIZE(threshold_ranges));
+               if (idx < 0)
                        return SR_ERR_ARG;
-               if (idx == MAX_NUM_LOGIC_THRESHOLD_ENTRIES - 1) {
-                       /* user threshold */
-               } else {
-                       devc->threshold_voltage = logic_threshold_value[idx];
-               }
                devc->threshold_voltage_idx = idx;
                break;
-       }
-       case SR_CONF_LOGIC_THRESHOLD_CUSTOM:
-               devc->threshold_voltage = g_variant_get_double(data);
+#endif /* WITH_THRESHOLD_DEVCFG */
+       case SR_CONF_CONTINUOUS:
+               on = g_variant_get_boolean(data);
+               if (!devc->model->memory_bits && !on)
+                       return SR_ERR_ARG;
+               devc->continuous = on;
                break;
        default:
                return SR_ERR_NA;
@@ -503,36 +955,79 @@ static int config_list(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        struct dev_context *devc;
+       int ret, cg_type;
+       size_t logic_idx, analog_idx;
+
+       devc = sdi ? sdi->priv : NULL;
+
+       /* Check for types (and index) of channel groups. */
+       ret = get_cg_index(sdi, cg, &cg_type, &logic_idx, &analog_idx);
+       if (cg && ret != SR_OK)
+               return SR_ERR_ARG;
+
+       /* Handle requests for the "Logic" channel group. */
+       if (cg && cg_type == SR_CHANNEL_LOGIC) {
+               switch (key) {
+               case SR_CONF_DEVICE_OPTIONS:
+                       if (ARRAY_SIZE(devopts_cg_logic) == 0)
+                               return SR_ERR_NA;
+                       *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                               devopts_cg_logic, ARRAY_SIZE(devopts_cg_logic),
+                               sizeof(devopts_cg_logic[0]));
+                       break;
+#if !WITH_THRESHOLD_DEVCFG
+               case SR_CONF_VOLTAGE_THRESHOLD:
+                       *data = std_gvar_thresholds(ARRAY_AND_SIZE(threshold_ranges));
+                       break;
+#endif /* WITH_THRESHOLD_DEVCFG */
+               default:
+                       return SR_ERR_NA;
+               }
+               return SR_OK;
+       }
+
+       /* Handle requests for the "PWMx" channel groups. */
+       if (cg && cg_type == SR_CHANNEL_ANALOG) {
+               switch (key) {
+               case SR_CONF_DEVICE_OPTIONS:
+                       *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                               devopts_cg_pwm, ARRAY_SIZE(devopts_cg_pwm),
+                               sizeof(devopts_cg_pwm[0]));
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+               return SR_OK;
+       }
 
        switch (key) {
        case SR_CONF_SCAN_OPTIONS:
        case SR_CONF_DEVICE_OPTIONS:
-               return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+               return STD_CONFIG_LIST(key, data, sdi, cg,
+                       scanopts, drvopts, devopts);
        case SR_CONF_SAMPLERATE:
                if (!sdi)
                        return SR_ERR_ARG;
-               devc = sdi->priv;
-               if (devc->max_samplerate == SR_MHZ(200)) {
-                       *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates_la2016));
-               }
-               else {
-                       *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates_la1016));
-               }
+               if (devc->model->samplerate == SR_MHZ(500))
+                       *data = std_gvar_samplerates(ARRAY_AND_SIZE(rates_500mhz));
+               else if (devc->model->samplerate == SR_MHZ(200))
+                       *data = std_gvar_samplerates(ARRAY_AND_SIZE(rates_200mhz));
+               else if (devc->model->samplerate == SR_MHZ(100))
+                       *data = std_gvar_samplerates(ARRAY_AND_SIZE(rates_100mhz));
+               else
+                       return SR_ERR_BUG;
                break;
        case SR_CONF_LIMIT_SAMPLES:
-               *data = std_gvar_tuple_u64(LA2016_NUM_SAMPLES_MIN, LA2016_NUM_SAMPLES_MAX);
+               *data = std_gvar_tuple_u64(0, LA2016_NUM_SAMPLES_MAX);
                break;
+#if WITH_THRESHOLD_DEVCFG
        case SR_CONF_VOLTAGE_THRESHOLD:
-               *data = std_gvar_min_max_step_thresholds(
-                       LA2016_THR_VOLTAGE_MIN,
-                       LA2016_THR_VOLTAGE_MAX, 0.1);
+               *data = std_gvar_thresholds(ARRAY_AND_SIZE(threshold_ranges));
                break;
+#endif /* WITH_THRESHOLD_DEVCFG */
        case SR_CONF_TRIGGER_MATCH:
                *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches));
                break;
-       case SR_CONF_LOGIC_THRESHOLD:
-               *data = g_variant_new_strv(logic_threshold, MAX_NUM_LOGIC_THRESHOLD_ENTRIES);
-               break;
        default:
                return SR_ERR_NA;
        }
@@ -540,247 +1035,74 @@ static int config_list(uint32_t key, GVariant **data,
        return SR_OK;
 }
 
-static void send_chunk(struct sr_dev_inst *sdi,
-       const uint8_t *packets, unsigned int num_tfers)
-{
-       struct dev_context *devc;
-       struct sr_datafeed_logic logic;
-       struct sr_datafeed_packet sr_packet;
-       unsigned int max_samples, n_samples, total_samples, free_n_samples;
-       unsigned int i, j, k;
-       int do_signal_trigger;
-       uint16_t *wp;
-       const uint8_t *rp;
-       uint16_t state;
-       uint8_t repetitions;
-
-       devc = sdi->priv;
-
-       logic.unitsize = 2;
-       logic.data = devc->convbuffer;
-
-       sr_packet.type = SR_DF_LOGIC;
-       sr_packet.payload = &logic;
-
-       max_samples = devc->convbuffer_size / 2;
-       n_samples = 0;
-       wp = (uint16_t *)devc->convbuffer;
-       total_samples = 0;
-       do_signal_trigger = 0;
-
-       if (devc->had_triggers_configured && devc->reading_behind_trigger == 0 && devc->info.n_rep_packets_before_trigger == 0) {
-               std_session_send_df_trigger(sdi);
-               devc->reading_behind_trigger = 1;
-       }
-
-       rp = packets;
-       for (i = 0; i < num_tfers; i++) {
-               for (k = 0; k < NUM_PACKETS_IN_CHUNK; k++) {
-                       free_n_samples = max_samples - n_samples;
-                       if (free_n_samples < 256 || do_signal_trigger) {
-                               logic.length = n_samples * 2;
-                               sr_session_send(sdi, &sr_packet);
-                               n_samples = 0;
-                               wp = (uint16_t *)devc->convbuffer;
-                               if (do_signal_trigger) {
-                                       std_session_send_df_trigger(sdi);
-                                       do_signal_trigger = 0;
-                               }
-                       }
-
-                       state = read_u16le_inc(&rp);
-                       repetitions = read_u8_inc(&rp);
-                       for (j = 0; j < repetitions; j++)
-                               *wp++ = state;
-
-                       n_samples += repetitions;
-                       total_samples += repetitions;
-                       devc->total_samples += repetitions;
-                       if (!devc->reading_behind_trigger) {
-                               devc->n_reps_until_trigger--;
-                               if (devc->n_reps_until_trigger == 0) {
-                                       devc->reading_behind_trigger = 1;
-                                       do_signal_trigger = 1;
-                                       sr_dbg("  here is trigger position after %" PRIu64 " samples, %.6fms",
-                                              devc->total_samples,
-                                              (double)devc->total_samples / devc->cur_samplerate * 1e3);
-                               }
-                       }
-               }
-               (void)read_u8_inc(&rp); /* Skip sequence number. */
-       }
-       if (n_samples) {
-               logic.length = n_samples * 2;
-               sr_session_send(sdi, &sr_packet);
-               if (do_signal_trigger) {
-                       std_session_send_df_trigger(sdi);
-               }
-       }
-       sr_dbg("send_chunk done after %d samples", total_samples);
-}
-
-static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer)
-{
-       struct sr_dev_inst *sdi;
-       struct dev_context *devc;
-       struct sr_usb_dev_inst *usb;
-       int ret;
-
-       sdi = transfer->user_data;
-       devc = sdi->priv;
-       usb = sdi->conn;
-
-       sr_dbg("receive_transfer(): status %s received %d bytes.",
-              libusb_error_name(transfer->status), transfer->actual_length);
-
-       if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) {
-               sr_err("bulk transfer timeout!");
-               devc->transfer_finished = 1;
-       }
-       send_chunk(sdi, transfer->buffer, transfer->actual_length / TRANSFER_PACKET_LENGTH);
-
-       devc->n_bytes_to_read -= transfer->actual_length;
-       if (devc->n_bytes_to_read) {
-               uint32_t to_read = devc->n_bytes_to_read;
-               /* determine read size for the next usb transfer */
-               if (to_read >= LA2016_USB_BUFSZ)
-                       to_read = LA2016_USB_BUFSZ;
-               else /* last transfer, make read size some multiple of LA2016_EP6_PKTSZ */
-                       to_read = (to_read + (LA2016_EP6_PKTSZ-1)) & ~(LA2016_EP6_PKTSZ-1);
-               libusb_fill_bulk_transfer(
-                       transfer, usb->devhdl,
-                       0x86, transfer->buffer, to_read,
-                       receive_transfer, (void *)sdi, DEFAULT_TIMEOUT_MS);
-
-               if ((ret = libusb_submit_transfer(transfer)) == 0)
-                       return;
-               sr_err("Failed to submit further transfer: %s.", libusb_error_name(ret));
-       }
-
-       g_free(transfer->buffer);
-       libusb_free_transfer(transfer);
-       devc->transfer_finished = 1;
-}
-
-static int handle_event(int fd, int revents, void *cb_data)
-{
-       const struct sr_dev_inst *sdi;
-       struct dev_context *devc;
-       struct drv_context *drvc;
-       struct timeval tv;
-
-       (void)fd;
-       (void)revents;
-
-       sdi = cb_data;
-       devc = sdi->priv;
-       drvc = sdi->driver->context;
-
-       if (devc->have_trigger == 0) {
-               if (la2016_has_triggered(sdi) == 0) {
-                       /* not yet ready for download */
-                       return TRUE;
-               }
-               devc->have_trigger = 1;
-               devc->transfer_finished = 0;
-               devc->reading_behind_trigger = 0;
-               devc->total_samples = 0;
-               /* we can start retrieving data! */
-               if (la2016_start_retrieval(sdi, receive_transfer) != SR_OK) {
-                       sr_err("failed to start retrieval!");
-                       return FALSE;
-               }
-               sr_dbg("retrieval is started...");
-               std_session_send_df_frame_begin(sdi);
-
-               return TRUE;
-       }
-
-       tv.tv_sec = tv.tv_usec = 0;
-       libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv);
-
-       if (devc->transfer_finished) {
-               sr_dbg("transfer is finished!");
-               std_session_send_df_frame_end(sdi);
-
-               usb_source_remove(sdi->session, drvc->sr_ctx);
-               std_session_send_df_end(sdi);
-
-               la2016_stop_acquisition(sdi);
-
-               g_free(devc->convbuffer);
-               devc->convbuffer = NULL;
-
-               devc->transfer = NULL;
-
-               sr_dbg("transfer is now finished");
-       }
-
-       return TRUE;
-}
-
-static void abort_acquisition(struct dev_context *devc)
-{
-       if (devc->transfer)
-               libusb_cancel_transfer(devc->transfer);
-}
-
-static int configure_channels(const struct sr_dev_inst *sdi)
-{
-       struct dev_context *devc;
-
-       devc = sdi->priv;
-       devc->cur_channels = 0;
-       devc->num_channels = 0;
-
-       for (GSList *l = sdi->channels; l; l = l->next) {
-               struct sr_channel *ch = (struct sr_channel*)l->data;
-               if (ch->enabled == FALSE)
-                       continue;
-               devc->cur_channels |= 1 << ch->index;
-               devc->num_channels++;
-       }
-
-       return SR_OK;
-}
-
 static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 {
        struct sr_dev_driver *di;
        struct drv_context *drvc;
+       struct sr_context *ctx;
        struct dev_context *devc;
+       size_t unitsize, xfersize, repsize, seqsize;
+       double voltage;
        int ret;
 
        di = sdi->driver;
        drvc = di->context;
+       ctx = drvc->sr_ctx;;
        devc = sdi->priv;
 
-       if (configure_channels(sdi) != SR_OK) {
-               sr_err("Failed to configure channels.");
-               return SR_ERR;
+       if (!devc->feed_queue) {
+               /*
+                * TODO
+                * Move this into protocol.c which concentrates the
+                * wire format. The api.c source should not bother.
+                */
+               if (devc->model->channel_count == 32) {
+                       unitsize = sizeof(uint32_t);
+                       repsize = sizeof(uint8_t);
+                       seqsize = 2 * sizeof(uint8_t);
+                       xfersize = 32;
+               } else if (devc->model->channel_count == 16) {
+                       unitsize = sizeof(uint16_t);
+                       repsize = sizeof(uint8_t);
+                       seqsize = 1 * sizeof(uint8_t);
+                       xfersize = 16;
+               } else {
+                       return SR_ERR_ARG;
+               }
+               devc->feed_queue = feed_queue_logic_alloc(sdi,
+                       LA2016_CONVBUFFER_SIZE, unitsize);
+               if (!devc->feed_queue) {
+                       sr_err("Cannot allocate buffer for session feed.");
+                       return SR_ERR_MALLOC;
+               }
+               devc->transfer_size = xfersize;
+               devc->sequence_size = seqsize;
+               devc->packets_per_chunk = xfersize;
+               devc->packets_per_chunk -= seqsize;
+               devc->packets_per_chunk /= unitsize + repsize;
        }
 
-       devc->convbuffer_size = 4 * 1024 * 1024;
-       if (!(devc->convbuffer = g_try_malloc(devc->convbuffer_size))) {
-               sr_err("Conversion buffer malloc failed.");
-               return SR_ERR_MALLOC;
-       }
+       sr_sw_limits_acquisition_start(&devc->sw_limits);
 
-       if ((ret = la2016_setup_acquisition(sdi)) != SR_OK) {
-               g_free(devc->convbuffer);
-               devc->convbuffer = NULL;
+       voltage = threshold_voltage(sdi, NULL);
+       ret = la2016_setup_acquisition(sdi, voltage);
+       if (ret != SR_OK) {
+               feed_queue_logic_free(devc->feed_queue);
+               devc->feed_queue = NULL;
                return ret;
        }
 
-       devc->ctx = drvc->sr_ctx;
-
-       if ((ret = la2016_start_acquisition(sdi)) != SR_OK) {
-               abort_acquisition(devc);
+       ret = la2016_start_acquisition(sdi);
+       if (ret != SR_OK) {
+               la2016_abort_acquisition(sdi);
+               feed_queue_logic_free(devc->feed_queue);
+               devc->feed_queue = NULL;
                return ret;
        }
 
-       devc->have_trigger = 0;
-       usb_source_add(sdi->session, drvc->sr_ctx, 50, handle_event, (void *)sdi);
+       devc->completion_seen = FALSE;
+       usb_source_add(sdi->session, ctx, 50,
+               la2016_receive_data, (void *)sdi);
 
        std_session_send_df_header(sdi);
 
@@ -792,7 +1114,6 @@ static int dev_acquisition_stop(struct sr_dev_inst *sdi)
        int ret;
 
        ret = la2016_abort_acquisition(sdi);
-       abort_acquisition(sdi->priv);
 
        return ret;
 }
index ecb21e3faa7af4c34eabccc3a6f85edf576f6a62..dd806eaa5e065acd0f9623c450045f58bebefdf7 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * This file is part of the libsigrok project.
  *
+ * Copyright (C) 2022 Gerhard Sittig <gerhard.sittig@gmx.net>
  * Copyright (C) 2020 Florian Schmidt <schmidt_florian@gmx.de>
  * Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se>
  * Copyright (C) 2013 Bert Vermeulen <bert@biot.com>
  */
 
 #include <config.h>
-#include <stdint.h>
-#include <string.h>
-#include <glib.h>
-#include <glib/gstdio.h>
-#include <stdio.h>
-#include <errno.h>
-#include <math.h>
-#include <inttypes.h>
+
 #include <libsigrok/libsigrok.h>
+#include <string.h>
+
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
-#define UC_FIRMWARE    "kingst-la-%04x.fw"
-#define FPGA_FW_LA2016 "kingst-la2016-fpga.bitstream"
-#define FPGA_FW_LA2016A        "kingst-la2016a1-fpga.bitstream"
-#define FPGA_FW_LA1016 "kingst-la1016-fpga.bitstream"
-#define FPGA_FW_LA1016A        "kingst-la1016a1-fpga.bitstream"
+/* USB PID dependent MCU firmware. Model dependent FPGA bitstream. */
+#define MCU_FWFILE_FMT "kingst-la-%04x.fw"
+#define FPGA_FWFILE_FMT        "kingst-%s-fpga.bitstream"
 
-#define MAX_SAMPLE_RATE_LA2016 SR_MHZ(200)
-#define MAX_SAMPLE_RATE_LA1016 SR_MHZ(100)
-#define MAX_SAMPLE_DEPTH 10e9
-#define MAX_PWM_FREQ     SR_MHZ(20)
-#define PWM_CLOCK        SR_MHZ(200)   /* this is 200MHz for both the LA2016 and LA1016 */
-
-/* usb vendor class control requests to the cypress FX2 microcontroller */
+/*
+ * List of known devices and their features. See @ref kingst_model
+ * for the fields' type and meaning. Table is sorted by EEPROM magic.
+ * More specific items need to go first (additional byte[2/6]). Not
+ * all devices are covered by this driver implementation, but telling
+ * users what was detected is considered useful.
+ *
+ * TODO Verify the identification of models that were not tested before.
+ */
+static const struct kingst_model models[] = {
+       { 0x02, 0x01, "LA2016", "la2016a1", SR_MHZ(200), 16, 1, 0, },
+       { 0x02, 0x00, "LA2016", "la2016",   SR_MHZ(200), 16, 1, 0, },
+       { 0x03, 0x01, "LA1016", "la1016a1", SR_MHZ(100), 16, 1, 0, },
+       { 0x03, 0x00, "LA1016", "la1016",   SR_MHZ(100), 16, 1, 0, },
+       { 0x04, 0x00, "LA1010", "la1010a0", SR_MHZ(100), 16, 0, SR_MHZ(800), },
+       { 0x05, 0x00, "LA5016", "la5016a1", SR_MHZ(500), 16, 2, SR_MHZ(800), },
+       { 0x06, 0x00, "LA5032", "la5032a0", SR_MHZ(500), 32, 4, SR_MHZ(800), },
+       { 0x07, 0x00, "LA1010", "la1010a1", SR_MHZ(100), 16, 0, SR_MHZ(800), },
+       { 0x08, 0x00, "LA2016", "la2016a1", SR_MHZ(200), 16, 1, 0, },
+       { 0x09, 0x00, "LA1016", "la1016a1", SR_MHZ(100), 16, 1, 0, },
+       { 0x0a, 0x00, "LA1010", "la1010a2", SR_MHZ(100), 16, 0, SR_MHZ(800), },
+       { 0x0b, 0x10, "LA2016", "la2016a2", SR_MHZ(200), 16, 1, 0, },
+       { 0x0c, 0x10, "LA5016", "la5016a2", SR_MHZ(500), 16, 2, SR_MHZ(800), },
+       { 0x0c, 0x00, "LA5016", "la5016a2", SR_MHZ(500), 16, 2, SR_MHZ(800), },
+       { 0x41, 0x00, "LA5016", "la5016a1", SR_MHZ(500), 16, 2, SR_MHZ(800), },
+};
+
+/* USB vendor class control requests, executed by the Cypress FX2 MCU. */
 #define CMD_FPGA_ENABLE        0x10
-#define CMD_FPGA_SPI   0x20    /* access registers in the FPGA over SPI bus, ctrl_in reads, ctrl_out writes */
-#define CMD_BULK_START 0x30    /* begin transfer of capture data via usb endpoint 6 IN */
-#define CMD_BULK_RESET 0x38    /* flush FX2 usb endpoint 6 IN fifos */
-#define CMD_FPGA_INIT  0x50    /* used before and after FPGA bitstream loading */
-#define CMD_KAUTH      0x60    /* communicate with authentication ic U10, not used */
-#define CMD_EEPROM     0xa2    /* ctrl_in reads, ctrl_out writes */
+#define CMD_FPGA_SPI   0x20    /* R/W access to FPGA registers via SPI. */
+#define CMD_BULK_START 0x30    /* Start sample data download via USB EP6 IN. */
+#define CMD_BULK_RESET 0x38    /* Flush FIFO of FX2 USB EP6 IN. */
+#define CMD_FPGA_INIT  0x50    /* Used before and after FPGA bitstream upload. */
+#define CMD_KAUTH      0x60    /* Communicate to auth IC (U10). Not used. */
+#define CMD_EEPROM     0xa2    /* R/W access to EEPROM content. */
 
 /*
- * fpga spi register addresses for control request CMD_FPGA_SPI:
- * There are around 60 byte-wide registers within the fpga and
- * these are the base addresses used for accessing them.
- * On the spi bus, the msb of the address byte is set for read
- * and cleared for write, but that is handled by the fx2 mcu
- * as appropriate. In this driver code just use IN transactions
- * to read, OUT to write.
+ * FPGA register addresses (base addresses when registers span multiple
+ * bytes, in that case data is kept in little endian format). Passed to
+ * CMD_FPGA_SPI requests. The FX2 MCU transparently handles the detail
+ * of SPI transfers encoding the read (1) or write (0) direction in the
+ * MSB of the address field. There are some 60 byte-wide FPGA registers.
+ *
+ * Unfortunately the FPGA registers change their meaning between the
+ * read and write directions of access, or exclusively provide one of
+ * these directions and not the other. This is an arbitrary vendor's
+ * choice, there is nothing which the sigrok driver could do about it.
+ * Values written to registers typically cannot get read back, neither
+ * verified after writing a configuration, nor queried upon startup for
+ * automatic detection of the current configuration. Neither appear to
+ * be there echo registers for presence and communication checks, nor
+ * version identifying registers, as far as we know.
  */
-#define REG_RUN                0x00    /* read capture status, write capture start */
-#define REG_PWM_EN     0x02    /* user pwm channels on/off */
-#define REG_CAPT_MODE  0x03    /* set to 0x00 for capture to sdram, 0x01 bypass sdram for streaming */
-#define REG_BULK       0x08    /* write start address and number of bytes for capture data bulk upload */
-#define REG_SAMPLING   0x10    /* write capture config, read capture data location in sdram */
-#define REG_TRIGGER    0x20    /* write level and edge trigger config */
-#define REG_THRESHOLD  0x68    /* write two pwm configs to control input threshold dac */
-#define REG_PWM1       0x70    /* write config for user pwm1 */
-#define REG_PWM2       0x78    /* write config for user pwm2 */
+#define REG_RUN                0x00    /* Read capture status, write start capture. */
+#define REG_PWM_EN     0x02    /* User PWM channels on/off. */
+#define REG_CAPT_MODE  0x03    /* Write 0x00 capture to SDRAM, 0x01 streaming. */
+#define REG_PIN_STATE  0x04    /* Read current pin state (real time display). */
+#define REG_BULK       0x08    /* Write start addr, byte count to download samples. */
+#define REG_SAMPLING   0x10    /* Write capture config, read capture SDRAM location. */
+#define REG_TRIGGER    0x20    /* Write level and edge trigger config. */
+#define REG_UNKNOWN_30 0x30
+#define REG_THRESHOLD  0x68    /* Write PWM config to setup input threshold DAC. */
+#define REG_PWM1       0x70    /* Write config for user PWM1. */
+#define REG_PWM2       0x78    /* Write config for user PWM2. */
+
+/* Bit patterns to write to REG_CAPT_MODE. */
+#define CAPTMODE_TO_RAM        0x00
+#define CAPTMODE_STREAM        0x01
+
+/* Bit patterns to write to REG_RUN, setup run mode. */
+#define RUNMODE_HALT   0x00
+#define RUNMODE_RUN    0x03
+
+/* Bit patterns when reading from REG_RUN, get run state. */
+#define RUNSTATE_IDLE_BIT      (1UL << 0)
+#define RUNSTATE_DRAM_BIT      (1UL << 1)
+#define RUNSTATE_TRGD_BIT      (1UL << 2)
+#define RUNSTATE_POST_BIT      (1UL << 3)
 
 static int ctrl_in(const struct sr_dev_inst *sdi,
-                  uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
-                  void *data, uint16_t wLength)
+       uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
+       void *data, uint16_t wLength)
 {
        struct sr_usb_dev_inst *usb;
        int ret;
 
        usb = sdi->conn;
 
-       if ((ret = libusb_control_transfer(
-                    usb->devhdl, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN,
-                    bRequest, wValue, wIndex, (unsigned char *)data, wLength,
-                    DEFAULT_TIMEOUT_MS)) != wLength) {
-               sr_err("failed to read %d bytes via ctrl-in %d %#x, %d: %s.",
-                      wLength, bRequest, wValue, wIndex,
-                      libusb_error_name(ret));
-               return SR_ERR;
+       ret = libusb_control_transfer(usb->devhdl,
+               LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN,
+               bRequest, wValue, wIndex, data, wLength,
+               DEFAULT_TIMEOUT_MS);
+       if (ret != wLength) {
+               sr_dbg("USB ctrl in: %d bytes, req %d val %#x idx %d: %s.",
+                       wLength, bRequest, wValue, wIndex,
+                       libusb_error_name(ret));
+               sr_err("Cannot read %d bytes from USB: %s.",
+                       wLength, libusb_error_name(ret));
+               return SR_ERR_IO;
        }
 
        return SR_OK;
 }
 
 static int ctrl_out(const struct sr_dev_inst *sdi,
-                   uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
-                   void *data, uint16_t wLength)
+       uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
+       void *data, uint16_t wLength)
 {
        struct sr_usb_dev_inst *usb;
        int ret;
 
        usb = sdi->conn;
 
-       if ((ret = libusb_control_transfer(
-                    usb->devhdl, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT,
-                    bRequest, wValue, wIndex, (unsigned char*)data, wLength,
-                    DEFAULT_TIMEOUT_MS)) != wLength) {
-               sr_err("failed to write %d bytes via ctrl-out %d %#x, %d: %s.",
-                      wLength, bRequest, wValue, wIndex,
-                      libusb_error_name(ret));
-               return SR_ERR;
+       ret = libusb_control_transfer(usb->devhdl,
+               LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT,
+               bRequest, wValue, wIndex, data, wLength,
+               DEFAULT_TIMEOUT_MS);
+       if (ret != wLength) {
+               sr_dbg("USB ctrl out: %d bytes, req %d val %#x idx %d: %s.",
+                       wLength, bRequest, wValue, wIndex,
+                       libusb_error_name(ret));
+               sr_err("Cannot write %d bytes to USB: %s.",
+                       wLength, libusb_error_name(ret));
+               return SR_ERR_IO;
        }
 
        return SR_OK;
 }
 
-static int upload_fpga_bitstream(const struct sr_dev_inst *sdi, const char *bitstream_fname)
+/* HACK Experiment to spot FPGA registers of interest. */
+static void la2016_dump_fpga_registers(const struct sr_dev_inst *sdi,
+       const char *caption, size_t reg_lower, size_t reg_upper)
+{
+       static const size_t dump_chunk_len = 16;
+
+       size_t rdlen;
+       uint8_t rdbuf[0x80 - 0x00];     /* Span all FPGA registers. */
+       const uint8_t *rdptr;
+       int ret;
+       size_t dump_addr, indent, dump_len;
+       GString *txt;
+
+       if (sr_log_loglevel_get() < SR_LOG_SPEW)
+               return;
+
+       if (!reg_lower && !reg_upper) {
+               reg_lower = 0;
+               reg_upper = sizeof(rdbuf);
+       }
+       if (reg_upper - reg_lower > sizeof(rdbuf))
+               reg_upper = sizeof(rdbuf) - reg_lower;
+
+       rdlen = reg_upper - reg_lower;
+       ret = ctrl_in(sdi, CMD_FPGA_SPI, reg_lower, 0, rdbuf, rdlen);
+       if (ret != SR_OK) {
+               sr_err("Cannot get registers space.");
+               return;
+       }
+       rdptr = rdbuf;
+
+       sr_spew("FPGA registers dump: %s", caption ? : "for fun");
+       dump_addr = reg_lower;
+       while (rdlen) {
+               dump_len = rdlen;
+               indent = dump_addr % dump_chunk_len;
+               if (dump_len > dump_chunk_len)
+                       dump_len = dump_chunk_len;
+               if (dump_len + indent > dump_chunk_len)
+                       dump_len = dump_chunk_len - indent;
+               txt = sr_hexdump_new(rdptr, dump_len);
+               sr_spew("  %04zx  %*s%s",
+                       dump_addr, (int)(3 * indent), "", txt->str);
+               sr_hexdump_free(txt);
+               dump_addr += dump_len;
+               rdptr += dump_len;
+               rdlen -= dump_len;
+       }
+}
+
+/*
+ * Check the necessity for FPGA bitstream upload, because another upload
+ * would take some 600ms which is undesirable after program startup. Try
+ * to access some FPGA registers and check the values' plausibility. The
+ * check should fail on the safe side, request another upload when in
+ * doubt. A positive response (the request to continue operation with the
+ * currently active bitstream) should be conservative. Accessing multiple
+ * registers is considered cheap compared to the cost of bitstream upload.
+ *
+ * It helps though that both the vendor software and the sigrok driver
+ * use the same bundle of MCU firmware and FPGA bitstream for any of the
+ * supported models. We don't expect to successfully communicate to the
+ * device yet disagree on its protocol. Ideally we would access version
+ * identifying registers for improved robustness, but are not aware of
+ * any. A bitstream reload can always be forced by a power cycle.
+ */
+static int check_fpga_bitstream(const struct sr_dev_inst *sdi)
+{
+       uint8_t init_rsp;
+       uint8_t buff[REG_PWM_EN - REG_RUN]; /* Larger of REG_RUN, REG_PWM_EN. */
+       int ret;
+       uint16_t run_state;
+       uint8_t pwm_en;
+       size_t read_len;
+       const uint8_t *rdptr;
+
+       sr_dbg("Checking operation of the FPGA bitstream.");
+       la2016_dump_fpga_registers(sdi, "bitstream check", 0, 0);
+
+       init_rsp = ~0;
+       ret = ctrl_in(sdi, CMD_FPGA_INIT, 0x00, 0, &init_rsp, sizeof(init_rsp));
+       if (ret != SR_OK || init_rsp != 0) {
+               sr_dbg("FPGA init query failed, or unexpected response.");
+               return SR_ERR_IO;
+       }
+
+       read_len = sizeof(run_state);
+       ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_RUN, 0, buff, read_len);
+       if (ret != SR_OK) {
+               sr_dbg("FPGA register access failed (run state).");
+               return SR_ERR_IO;
+       }
+       rdptr = buff;
+       run_state = read_u16le_inc(&rdptr);
+       sr_spew("FPGA register: run state 0x%04x.", run_state);
+       if (run_state && (run_state & 0x3) != 0x1) {
+               sr_dbg("Unexpected FPGA register content (run state).");
+               return SR_ERR_DATA;
+       }
+       if (run_state && (run_state & ~0xf) != 0x85e0) {
+               sr_dbg("Unexpected FPGA register content (run state).");
+               return SR_ERR_DATA;
+       }
+
+       read_len = sizeof(pwm_en);
+       ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0, buff, read_len);
+       if (ret != SR_OK) {
+               sr_dbg("FPGA register access failed (PWM enable).");
+               return SR_ERR_IO;
+       }
+       rdptr = buff;
+       pwm_en = read_u8_inc(&rdptr);
+       sr_spew("FPGA register: PWM enable 0x%02x.", pwm_en);
+       if ((pwm_en & 0x3) != 0x0) {
+               sr_dbg("Unexpected FPGA register content (PWM enable).");
+               return SR_ERR_DATA;
+       }
+
+       sr_info("Could re-use current FPGA bitstream. No upload required.");
+       return SR_OK;
+}
+
+static int upload_fpga_bitstream(const struct sr_dev_inst *sdi,
+       const char *bitstream_fname)
 {
-       struct dev_context *devc;
        struct drv_context *drvc;
        struct sr_usb_dev_inst *usb;
        struct sr_resource bitstream;
+       uint32_t bitstream_size;
        uint8_t buffer[sizeof(uint32_t)];
        uint8_t *wrptr;
-       uint8_t cmd_resp;
        uint8_t block[4096];
        int len, act_len;
        unsigned int pos;
        int ret;
-       unsigned int zero_pad_to = 0x2c000;
+       unsigned int zero_pad_to;
 
-       devc = sdi->priv;
        drvc = sdi->driver->context;
        usb = sdi->conn;
 
        sr_info("Uploading FPGA bitstream '%s'.", bitstream_fname);
 
-       ret = sr_resource_open(drvc->sr_ctx, &bitstream, SR_RESOURCE_FIRMWARE, bitstream_fname);
+       ret = sr_resource_open(drvc->sr_ctx, &bitstream,
+               SR_RESOURCE_FIRMWARE, bitstream_fname);
        if (ret != SR_OK) {
-               sr_err("could not find fpga firmware %s!", bitstream_fname);
+               sr_err("Cannot find FPGA bitstream %s.", bitstream_fname);
                return ret;
        }
 
-       devc->bitstream_size = (uint32_t)bitstream.size;
+       bitstream_size = (uint32_t)bitstream.size;
        wrptr = buffer;
-       write_u32le_inc(&wrptr, devc->bitstream_size);
-       if ((ret = ctrl_out(sdi, CMD_FPGA_INIT, 0x00, 0, buffer, wrptr - buffer)) != SR_OK) {
-               sr_err("failed to give upload init command");
+       write_u32le_inc(&wrptr, bitstream_size);
+       ret = ctrl_out(sdi, CMD_FPGA_INIT, 0x00, 0, buffer, wrptr - buffer);
+       if (ret != SR_OK) {
+               sr_err("Cannot initiate FPGA bitstream upload.");
                sr_resource_close(drvc->sr_ctx, &bitstream);
                return ret;
        }
+       zero_pad_to = bitstream_size;
+       zero_pad_to += LA2016_EP2_PADDING - 1;
+       zero_pad_to /= LA2016_EP2_PADDING;
+       zero_pad_to *= LA2016_EP2_PADDING;
 
        pos = 0;
        while (1) {
                if (pos < bitstream.size) {
-                       len = (int)sr_resource_read(drvc->sr_ctx, &bitstream, &block, sizeof(block));
+                       len = (int)sr_resource_read(drvc->sr_ctx, &bitstream,
+                               block, sizeof(block));
                        if (len < 0) {
-                               sr_err("failed to read from fpga bitstream!");
+                               sr_err("Cannot read FPGA bitstream.");
                                sr_resource_close(drvc->sr_ctx, &bitstream);
-                               return SR_ERR;
+                               return SR_ERR_IO;
                        }
                } else {
-                       // fill with zero's until zero_pad_to
+                       /*  Zero-pad until 'zero_pad_to'. */
                        len = zero_pad_to - pos;
                        if ((unsigned)len > sizeof(block))
                                len = sizeof(block);
@@ -172,101 +345,103 @@ static int upload_fpga_bitstream(const struct sr_dev_inst *sdi, const char *bits
                if (len == 0)
                        break;
 
-               ret = libusb_bulk_transfer(usb->devhdl, 2, (unsigned char*)&block[0], len, &act_len, DEFAULT_TIMEOUT_MS);
+               ret = libusb_bulk_transfer(usb->devhdl, USB_EP_FPGA_BITSTREAM,
+                       &block[0], len, &act_len, DEFAULT_TIMEOUT_MS);
                if (ret != 0) {
-                       sr_dbg("failed to write fpga bitstream block at %#x len %d: %s.", pos, (int)len, libusb_error_name(ret));
-                       ret = SR_ERR;
+                       sr_dbg("Cannot write FPGA bitstream, block %#x len %d: %s.",
+                               pos, (int)len, libusb_error_name(ret));
+                       ret = SR_ERR_IO;
                        break;
                }
                if (act_len != len) {
-                       sr_dbg("failed to write fpga bitstream block at %#x len %d: act_len is %d.", pos, (int)len, act_len);
-                       ret = SR_ERR;
+                       sr_dbg("Short write for FPGA bitstream, block %#x len %d: got %d.",
+                               pos, (int)len, act_len);
+                       ret = SR_ERR_IO;
                        break;
                }
                pos += len;
        }
        sr_resource_close(drvc->sr_ctx, &bitstream);
-       if (ret != 0)
+       if (ret != SR_OK)
                return ret;
-       sr_info("FPGA bitstream upload (%" PRIu64 " bytes) done.", bitstream.size);
+       sr_info("FPGA bitstream upload (%" PRIu64 " bytes) done.",
+               bitstream.size);
+
+       return SR_OK;
+}
 
-       if ((ret = ctrl_in(sdi, CMD_FPGA_INIT, 0x00, 0, &cmd_resp, sizeof(cmd_resp))) != SR_OK) {
-               sr_err("failed to read response after FPGA bitstream upload");
+static int enable_fpga_bitstream(const struct sr_dev_inst *sdi)
+{
+       int ret;
+       uint8_t resp;
+
+       ret = ctrl_in(sdi, CMD_FPGA_INIT, 0x00, 0, &resp, sizeof(resp));
+       if (ret != SR_OK) {
+               sr_err("Cannot read response after FPGA bitstream upload.");
                return ret;
        }
-       if (cmd_resp != 0) {
-               sr_err("after fpga bitstream upload command response is 0x%02x, expect 0!", cmd_resp);
-               return SR_ERR;
+       if (resp != 0) {
+               sr_err("Unexpected FPGA bitstream upload response, got 0x%02x, want 0.",
+                       resp);
+               return SR_ERR_DATA;
        }
+       g_usleep(30 * 1000);
 
-       g_usleep(30000);
-
-       if ((ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x01, 0, NULL, 0)) != SR_OK) {
-               sr_err("failed enable fpga");
+       ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x01, 0, NULL, 0);
+       if (ret != SR_OK) {
+               sr_err("Cannot enable FPGA after bitstream upload.");
                return ret;
        }
+       g_usleep(40 * 1000);
 
-       g_usleep(40000);
        return SR_OK;
 }
 
 static int set_threshold_voltage(const struct sr_dev_inst *sdi, float voltage)
 {
-       struct dev_context *devc;
        int ret;
-
-       devc = sdi->priv;
-
-       uint16_t duty_R79,duty_R56;
-       uint8_t buf[2 * sizeof(uint16_t)];
+       uint16_t duty_R79, duty_R56;
+       uint8_t buf[REG_PWM1 - REG_THRESHOLD]; /* Width of REG_THRESHOLD. */
        uint8_t *wrptr;
 
-       /* clamp threshold setting within valid range for LA2016 */
-       if (voltage > 4.0) {
-               voltage = 4.0;
-       }
-       else if (voltage < -4.0) {
-               voltage = -4.0;
+       /* Clamp threshold setting to valid range for LA2016. */
+       if (voltage > LA2016_THR_VOLTAGE_MAX) {
+               voltage = LA2016_THR_VOLTAGE_MAX;
+       } else if (voltage < -LA2016_THR_VOLTAGE_MAX) {
+               voltage = -LA2016_THR_VOLTAGE_MAX;
        }
 
        /*
-        * The fpga has two programmable pwm outputs which feed a dac that
-        * is used to adjust input offset. The dac changes the input
-        * swing around the fixed fpga input threshold.
-        * The two pwm outputs can be seen on R79 and R56 respectvely.
-        * Frequency is fixed at 100kHz and duty is varied.
-        * The R79 pwm uses just three settings.
-        * The R56 pwm varies with required threshold and its behaviour
-        * also changes depending on the setting of R79 PWM.
-        */
-
-       /*
-        * calculate required pwm duty register values from requested threshold voltage
-        * see last page of schematic (on wiki) for an explanation of these numbers
+        * Two PWM output channels feed one DAC which generates a bias
+        * voltage, which offsets the input probe's voltage level, and
+        * in combination with the FPGA pins' fixed threshold result in
+        * a programmable input threshold from the user's perspective.
+        * The PWM outputs can be seen on R79 and R56 respectively, the
+        * frequency is 100kHz and the duty cycle varies. The R79 PWM
+        * uses three discrete settings. The R56 PWM varies with desired
+        * thresholds and depends on the R79 PWM configuration. See the
+        * schematics comments which discuss the formulae.
         */
        if (voltage >= 2.9) {
-               duty_R79 = 0;           /* this pwm is off (0V)*/
+               duty_R79 = 0;           /* PWM off (0V). */
                duty_R56 = (uint16_t)(302 * voltage - 363);
-       }
-       else if (voltage <= -0.4) {
-               duty_R79 = 0x02D7;      /* 72% duty */
-               duty_R56 = (uint16_t)(302 * voltage + 1090);
-       }
-       else {
-               duty_R79 = 0x00f2;      /* 25% duty */
+       } else if (voltage > -0.4) {
+               duty_R79 = 0x00f2;      /* 25% duty cycle. */
                duty_R56 = (uint16_t)(302 * voltage + 121);
+       } else {
+               duty_R79 = 0x02d7;      /* 72% duty cycle. */
+               duty_R56 = (uint16_t)(302 * voltage + 1090);
        }
 
-       /* clamp duty register values at sensible limits */
+       /* Clamp duty register values to sensible limits. */
        if (duty_R56 < 10) {
                duty_R56 = 10;
-       }
-       else if (duty_R56 > 1100) {
+       } else if (duty_R56 > 1100) {
                duty_R56 = 1100;
        }
 
-       sr_dbg("set threshold voltage %.2fV", voltage);
-       sr_dbg("duty_R56=0x%04x, duty_R79=0x%04x", duty_R56, duty_R79);
+       sr_dbg("Set threshold voltage %.2fV.", voltage);
+       sr_dbg("Duty cycle values: R56 0x%04x, R79 0x%04x.", duty_R56, duty_R79);
 
        wrptr = buf;
        write_u16le_inc(&wrptr, duty_R56);
@@ -274,149 +449,190 @@ static int set_threshold_voltage(const struct sr_dev_inst *sdi, float voltage)
 
        ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_THRESHOLD, 0, buf, wrptr - buf);
        if (ret != SR_OK) {
-               sr_err("error setting new threshold voltage of %.2fV", voltage);
+               sr_err("Cannot set threshold voltage %.2fV.", voltage);
                return ret;
        }
-       devc->threshold_voltage = voltage;
 
        return SR_OK;
 }
 
-static int enable_pwm(const struct sr_dev_inst *sdi, uint8_t p1, uint8_t p2)
+/*
+ * Communicates a channel's configuration to the device after the
+ * parameters may have changed. Configuration of one channel may
+ * interfere with other channels since they share FPGA registers.
+ */
+static int set_pwm_config(const struct sr_dev_inst *sdi, size_t idx)
 {
+       static uint8_t reg_bases[] = { REG_PWM1, REG_PWM2, };
+
        struct dev_context *devc;
-       uint8_t cfg;
+       struct pwm_setting *params;
+       uint8_t reg_base;
+       double val_f;
+       uint32_t val_u;
+       uint32_t period, duty;
+       size_t ch;
        int ret;
+       uint8_t enable_all, enable_cfg, reg_val;
+       uint8_t buf[REG_PWM2 - REG_PWM1]; /* Width of one REG_PWMx. */
+       uint8_t *wrptr;
 
        devc = sdi->priv;
-       cfg = 0;
+       if (idx >= ARRAY_SIZE(devc->pwm_setting))
+               return SR_ERR_ARG;
+       params = &devc->pwm_setting[idx];
+       if (idx >= ARRAY_SIZE(reg_bases))
+               return SR_ERR_ARG;
+       reg_base = reg_bases[idx];
 
-       if (p1) cfg |= 1 << 0;
-       if (p2) cfg |= 1 << 1;
+       /*
+        * Map application's specs to hardware register values. Do math
+        * in floating point initially, but convert to u32 eventually.
+        */
+       sr_dbg("PWM config, app spec, ch %zu, en %d, freq %.1f, duty %.1f.",
+               idx, params->enabled ? 1 : 0, params->freq, params->duty);
+       val_f = PWM_CLOCK;
+       val_f /= params->freq;
+       val_u = val_f;
+       period = val_u;
+       val_f = period;
+       val_f *= params->duty;
+       val_f /= 100.0;
+       val_f += 0.5;
+       val_u = val_f;
+       duty = val_u;
+       sr_dbg("PWM config, reg 0x%04x, freq %u, duty %u.",
+               (unsigned)reg_base, (unsigned)period, (unsigned)duty);
+
+       /* Get the "enabled" state of all supported PWM channels. */
+       enable_all = 0;
+       for (ch = 0; ch < ARRAY_SIZE(devc->pwm_setting); ch++) {
+               if (!devc->pwm_setting[ch].enabled)
+                       continue;
+               enable_all |= 1U << ch;
+       }
+       enable_cfg = 1U << idx;
+       sr_spew("PWM config, enable all 0x%02hhx, cfg 0x%02hhx.",
+               enable_all, enable_cfg);
 
-       sr_dbg("set pwm enable %d %d", p1, p2);
-       ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0, &cfg, sizeof(cfg));
+       /*
+        * Disable the to-get-configured channel before its parameters
+        * will change. Or disable and exit when the channel is supposed
+        * to get turned off.
+        */
+       sr_spew("PWM config, disabling before param change.");
+       reg_val = enable_all & ~enable_cfg;
+       ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0,
+               &reg_val, sizeof(reg_val));
        if (ret != SR_OK) {
-               sr_err("error setting new pwm enable 0x%02x", cfg);
+               sr_err("Cannot adjust PWM enabled state.");
                return ret;
        }
-       devc->pwm_setting[0].enabled = (p1) ? 1 : 0;
-       devc->pwm_setting[1].enabled = (p2) ? 1 : 0;
+       if (!params->enabled)
+               return SR_OK;
 
-       return SR_OK;
-}
-
-static int set_pwm(const struct sr_dev_inst *sdi, uint8_t which, float freq, float duty)
-{
-       int CTRL_PWM[] = { REG_PWM1, REG_PWM2 };
-       struct dev_context *devc;
-       pwm_setting_dev_t cfg;
-       pwm_setting_t *setting;
-       int ret;
-       uint8_t buf[2 * sizeof(uint32_t)];
-       uint8_t *wrptr;
-
-       devc = sdi->priv;
-
-       if (which < 1 || which > 2) {
-               sr_err("invalid pwm channel: %d", which);
-               return SR_ERR;
-       }
-       if (freq > MAX_PWM_FREQ) {
-               sr_err("pwm frequency too high: %.1f", freq);
-               return SR_ERR;
-       }
-       if (duty > 100 || duty < 0) {
-               sr_err("invalid pwm percentage: %f", duty);
-               return SR_ERR;
+       /* Write register values to device. */
+       sr_spew("PWM config, sending new parameters.");
+       wrptr = buf;
+       write_u32le_inc(&wrptr, period);
+       write_u32le_inc(&wrptr, duty);
+       ret = ctrl_out(sdi, CMD_FPGA_SPI, reg_base, 0, buf, wrptr - buf);
+       if (ret != SR_OK) {
+               sr_err("Cannot change PWM parameters.");
+               return ret;
        }
 
-       cfg.period = (uint32_t)(PWM_CLOCK / freq);
-       cfg.duty = (uint32_t)(0.5f + (cfg.period * duty / 100.));
-       sr_dbg("set pwm%d period %d, duty %d", which, cfg.period, cfg.duty);
-
-       wrptr = buf;
-       write_u32le_inc(&wrptr, cfg.period);
-       write_u32le_inc(&wrptr, cfg.duty);
-       ret = ctrl_out(sdi, CMD_FPGA_SPI, CTRL_PWM[which - 1], 0, buf, wrptr - buf);
+       /* Enable configured channel after write completion. */
+       sr_spew("PWM config, enabling after param change.");
+       reg_val = enable_all | enable_cfg;
+       ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0,
+               &reg_val, sizeof(reg_val));
        if (ret != SR_OK) {
-               sr_err("error setting new pwm%d config %d %d", which, cfg.period, cfg.duty);
+               sr_err("Cannot adjust PWM enabled state.");
                return ret;
        }
-       setting = &devc->pwm_setting[which - 1];
-       setting->freq = freq;
-       setting->duty = duty;
 
        return SR_OK;
 }
 
-static int set_defaults(const struct sr_dev_inst *sdi)
+/*
+ * Determine the number of enabled channels as well as their bitmask
+ * representation. Derive data here which later simplifies processing
+ * of raw capture data memory content in streaming mode.
+ */
+static void la2016_prepare_stream(const struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
-       int ret;
+       struct stream_state_t *stream;
+       size_t channel_mask;
+       GSList *l;
+       struct sr_channel *ch;
 
        devc = sdi->priv;
-
-       devc->capture_ratio = 5; /* percent */
-       devc->cur_channels = 0xffff;
-       devc->limit_samples = 5000000;
-       devc->cur_samplerate = SR_MHZ(100);
-
-       ret = set_threshold_voltage(sdi, devc->threshold_voltage);
-       if (ret)
-               return ret;
-
-       ret = enable_pwm(sdi, 0, 0);
-       if (ret)
-               return ret;
-
-       ret = set_pwm(sdi, 1, 1e3, 50);
-       if (ret)
-               return ret;
-
-       ret = set_pwm(sdi, 2, 100e3, 50);
-       if (ret)
-               return ret;
-
-       ret = enable_pwm(sdi, 1, 1);
-       if (ret)
-               return ret;
-
-       return SR_OK;
+       stream = &devc->stream;
+       memset(stream, 0, sizeof(*stream));
+
+       stream->enabled_count = 0;
+       for (l = sdi->channels; l; l = l->next) {
+               ch = l->data;
+               if (ch->type != SR_CHANNEL_LOGIC)
+                       continue;
+               if (!ch->enabled)
+                       continue;
+               channel_mask = 1UL << ch->index;
+               stream->enabled_mask |= channel_mask;
+               stream->channel_masks[stream->enabled_count++] = channel_mask;
+       }
+       stream->channel_index = 0;
 }
 
+/*
+ * This routine configures the set of enabled channels, as well as the
+ * trigger condition (if one was specified). Also prepares the capture
+ * data processing in stream mode, where the memory layout dramatically
+ * differs from normal mode.
+ */
 static int set_trigger_config(const struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
        struct sr_trigger *trigger;
-       trigger_cfg_t cfg;
+       struct trigger_cfg {
+               uint32_t channels;      /* Actually: Enabled channels? */
+               uint32_t enabled;       /* Actually: Triggering channels? */
+               uint32_t level;
+               uint32_t high_or_falling;
+       } cfg;
        GSList *stages;
        GSList *channel;
        struct sr_trigger_stage *stage1;
        struct sr_trigger_match *match;
-       uint16_t ch_mask;
+       uint32_t ch_mask;
        int ret;
-       uint8_t buf[4 * sizeof(uint32_t)];
+       uint8_t buf[REG_UNKNOWN_30 - REG_TRIGGER]; /* Width of REG_TRIGGER. */
        uint8_t *wrptr;
 
        devc = sdi->priv;
-       trigger = sr_session_trigger_get(sdi->session);
-
-       memset(&cfg, 0, sizeof(cfg));
 
-       cfg.channels = devc->cur_channels;
+       la2016_prepare_stream(sdi);
 
+       memset(&cfg, 0, sizeof(cfg));
+       cfg.channels = devc->stream.enabled_mask;
+       if (!cfg.channels) {
+               sr_err("Need at least one enabled logic channel.");
+               return SR_ERR_ARG;
+       }
+       trigger = sr_session_trigger_get(sdi->session);
        if (trigger && trigger->stages) {
                stages = trigger->stages;
                stage1 = stages->data;
                if (stages->next) {
                        sr_err("Only one trigger stage supported for now.");
-                       return SR_ERR;
+                       return SR_ERR_ARG;
                }
                channel = stage1->matches;
                while (channel) {
                        match = channel->data;
-                       ch_mask = 1 << match->channel->index;
+                       ch_mask = 1UL << match->channel->index;
 
                        switch (match->match) {
                        case SR_TRIGGER_ZERO:
@@ -429,162 +645,318 @@ static int set_trigger_config(const struct sr_dev_inst *sdi)
                                break;
                        case SR_TRIGGER_RISING:
                                if ((cfg.enabled & ~cfg.level)) {
-                                       sr_err("Only one trigger signal with falling-/rising-edge allowed.");
-                                       return SR_ERR;
+                                       sr_err("Device only supports one edge trigger.");
+                                       return SR_ERR_ARG;
                                }
                                cfg.level &= ~ch_mask;
                                cfg.high_or_falling &= ~ch_mask;
                                break;
                        case SR_TRIGGER_FALLING:
                                if ((cfg.enabled & ~cfg.level)) {
-                                       sr_err("Only one trigger signal with falling-/rising-edge allowed.");
-                                       return SR_ERR;
+                                       sr_err("Device only supports one edge trigger.");
+                                       return SR_ERR_ARG;
                                }
                                cfg.level &= ~ch_mask;
                                cfg.high_or_falling |= ch_mask;
                                break;
                        default:
-                               sr_err("Unknown trigger value.");
-                               return SR_ERR;
+                               sr_err("Unknown trigger condition.");
+                               return SR_ERR_ARG;
                        }
                        cfg.enabled |= ch_mask;
                        channel = channel->next;
                }
        }
-       sr_dbg("set trigger configuration channels: 0x%04x, "
-              "trigger-enabled 0x%04x, level-triggered 0x%04x, "
-              "high/falling 0x%04x", cfg.channels, cfg.enabled, cfg.level,
-              cfg.high_or_falling);
+       sr_dbg("Set trigger config: "
+               "enabled-channels 0x%04x, triggering-channels 0x%04x, "
+               "level-triggered 0x%04x, high/falling 0x%04x.",
+               cfg.channels, cfg.enabled, cfg.level, cfg.high_or_falling);
 
-       devc->had_triggers_configured = cfg.enabled != 0;
+       /*
+        * Don't configure hardware trigger parameters in streaming mode
+        * or when the device lacks local memory. Yet the above dump of
+        * derived parameters from user specs is considered valueable.
+        *
+        * TODO Add support for soft triggers when hardware triggers in
+        * the device are not used or are not available at all.
+        */
+       if (!devc->model->memory_bits || devc->continuous) {
+               if (!devc->model->memory_bits)
+                       sr_dbg("Device without memory. No hardware triggers.");
+               else if (devc->continuous)
+                       sr_dbg("Streaming mode. No hardware triggers.");
+               cfg.enabled = 0;
+               cfg.level = 0;
+               cfg.high_or_falling = 0;
+       }
+
+       devc->trigger_involved = cfg.enabled != 0;
 
        wrptr = buf;
        write_u32le_inc(&wrptr, cfg.channels);
        write_u32le_inc(&wrptr, cfg.enabled);
        write_u32le_inc(&wrptr, cfg.level);
        write_u32le_inc(&wrptr, cfg.high_or_falling);
+       /* TODO
+        * Comment on this literal 16. Origin, meaning? Cannot be the
+        * register offset, nor the transfer length. Is it a channels
+        * count that is relevant for 16 and 32 channel models? Is it
+        * an obsolete experiment?
+        */
        ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_TRIGGER, 16, buf, wrptr - buf);
        if (ret != SR_OK) {
-               sr_err("error setting trigger config!");
+               sr_err("Cannot setup trigger configuration.");
                return ret;
        }
 
        return SR_OK;
 }
 
+/*
+ * This routine communicates the sample configuration to the device:
+ * Total samples count and samplerate, pre-trigger configuration.
+ */
 static int set_sample_config(const struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
-       double clock_divisor;
-       uint64_t total;
-       int ret;
-       uint16_t divisor;
-       uint8_t buf[2 * sizeof(uint32_t) + 48 / 8 + sizeof(uint16_t)];
+       uint64_t baseclock;
+       uint64_t min_samplerate, eff_samplerate;
+       uint64_t stream_bandwidth;
+       uint16_t divider_u16;
+       uint64_t limit_samples;
+       uint64_t pre_trigger_samples;
+       uint64_t pre_trigger_memory;
+       uint8_t buf[REG_TRIGGER - REG_SAMPLING]; /* Width of REG_SAMPLING. */
        uint8_t *wrptr;
+       int ret;
 
        devc = sdi->priv;
-       total = 128 * 1024 * 1024;
 
-       if (devc->cur_samplerate > devc->max_samplerate) {
-               sr_err("too high sample rate: %" PRIu64, devc->cur_samplerate);
-               return SR_ERR;
+       /*
+        * The base clock need not be identical to the maximum samplerate,
+        * and differs between models. The 500MHz devices even use a base
+        * clock of 800MHz, and communicate divider 1 to the hardware to
+        * configure the 500MHz samplerate. This allows them to operate at
+        * a 200MHz samplerate which uses divider 4.
+        */
+       if (devc->samplerate > devc->model->samplerate) {
+               sr_err("Too high a sample rate: %" PRIu64 ".",
+                       devc->samplerate);
+               return SR_ERR_ARG;
        }
-
-       clock_divisor = devc->max_samplerate / (double)devc->cur_samplerate;
-       if (clock_divisor > 0xffff)
-               clock_divisor = 0xffff;
-       divisor = (uint16_t)(clock_divisor + 0.5);
-       devc->cur_samplerate = devc->max_samplerate / divisor;
-
-       if (devc->limit_samples > MAX_SAMPLE_DEPTH) {
-               sr_err("too high sample depth: %" PRIu64, devc->limit_samples);
-               return SR_ERR;
+       baseclock = devc->model->baseclock;
+       if (!baseclock)
+               baseclock = devc->model->samplerate;
+       min_samplerate = baseclock;
+       min_samplerate /= 65536;
+       if (devc->samplerate < min_samplerate) {
+               sr_err("Too low a sample rate: %" PRIu64 ".",
+                       devc->samplerate);
+               return SR_ERR_ARG;
        }
+       divider_u16 = baseclock / devc->samplerate;
+       eff_samplerate = baseclock / divider_u16;
+       if (eff_samplerate > devc->model->samplerate)
+               eff_samplerate = devc->model->samplerate;
 
-       devc->pre_trigger_size = (devc->capture_ratio * devc->limit_samples) / 100;
+       ret = sr_sw_limits_get_remain(&devc->sw_limits,
+               &limit_samples, NULL, NULL, NULL);
+       if (ret != SR_OK) {
+               sr_err("Cannot get acquisition limits.");
+               return ret;
+       }
+       if (limit_samples > LA2016_NUM_SAMPLES_MAX) {
+               sr_warn("Too high a sample depth: %" PRIu64 ", capping.",
+                       limit_samples);
+               limit_samples = LA2016_NUM_SAMPLES_MAX;
+       }
+       if (limit_samples == 0) {
+               limit_samples = LA2016_NUM_SAMPLES_MAX;
+               sr_dbg("Passing %" PRIu64 " to HW for unlimited samples.",
+                       limit_samples);
+       }
 
-       sr_dbg("set sampling configuration %.0fkHz, %d samples, trigger-pos %d%%",
-              devc->cur_samplerate / 1e3, (unsigned int)devc->limit_samples, (unsigned int)devc->capture_ratio);
+       /*
+        * The acquisition configuration communicates "pre-trigger"
+        * specs in several formats. sigrok users provide a percentage
+        * (0-100%), which translates to a pre-trigger samples count
+        * (assuming that a total samples count limit was specified).
+        * The device supports hardware compression, which depends on
+        * slowly changing input data to be effective. Fast changing
+        * input data may occupy more space in sample memory than its
+        * uncompressed form would. This is why a third parameter can
+        * limit the amount of sample memory to use for pre-trigger
+        * data. Only the upper 24 bits of that memory size spec get
+        * communicated to the device (written to its FPGA register).
+        */
+       if (!devc->model->memory_bits) {
+               sr_dbg("Memory-less device, skipping pre-trigger config.");
+               pre_trigger_samples = 0;
+               pre_trigger_memory = 0;
+       } else if (devc->trigger_involved) {
+               pre_trigger_samples = limit_samples;
+               pre_trigger_samples *= devc->capture_ratio;
+               pre_trigger_samples /= 100;
+               pre_trigger_memory = devc->model->memory_bits;
+               pre_trigger_memory *= UINT64_C(1024 * 1024 * 1024);
+               pre_trigger_memory /= 8; /* devc->model->channel_count ? */
+               pre_trigger_memory *= devc->capture_ratio;
+               pre_trigger_memory /= 100;
+       } else {
+               sr_dbg("No trigger setup, skipping pre-trigger config.");
+               pre_trigger_samples = 0;
+               pre_trigger_memory = 0;
+       }
+       /* Ensure non-zero value after LSB shift out in HW reg. */
+       if (pre_trigger_memory < 0x100)
+               pre_trigger_memory = 0x100;
+
+       sr_dbg("Set sample config: %" PRIu64 "kHz (div %" PRIu16 "), %" PRIu64 " samples.",
+               eff_samplerate / SR_KHZ(1), divider_u16, limit_samples);
+       sr_dbg("Capture ratio %" PRIu64 "%%, count %" PRIu64 ", mem %" PRIu64 ".",
+               devc->capture_ratio, pre_trigger_samples, pre_trigger_memory);
+
+       if (devc->continuous) {
+               stream_bandwidth = eff_samplerate;
+               stream_bandwidth *= devc->stream.enabled_count;
+               sr_dbg("Streaming: channel count %zu, product %" PRIu64 ".",
+                       devc->stream.enabled_count, stream_bandwidth);
+               stream_bandwidth /= 1000 * 1000;
+               if (stream_bandwidth >= LA2016_STREAM_MBPS_MAX) {
+                       sr_warn("High USB stream bandwidth: %" PRIu64 "Mbps.",
+                               stream_bandwidth);
+               }
+               if (stream_bandwidth < LA2016_STREAM_PUSH_THR) {
+                       sr_dbg("Streaming: low Mbps, suggest periodic flush.");
+                       devc->stream.flush_period_ms = LA2016_STREAM_PUSH_IVAL;
+               }
+       }
 
+       /*
+        * The acquisition configuration occupies a total of 16 bytes:
+        * - A 34bit total samples count limit (up to 10 billions) that
+        *   is kept in a 40bit register.
+        * - A 34bit pre-trigger samples count limit (up to 10 billions)
+        *   in another 40bit register.
+        * - A 32bit pre-trigger memory space limit (in bytes) of which
+        *   the upper 24bits are kept in an FPGA register.
+        * - A 16bit clock divider which gets applied to the maximum
+        *   samplerate of the device.
+        * - An 8bit register of unknown meaning. Currently always 0.
+        */
        wrptr = buf;
-       write_u32le_inc(&wrptr, devc->limit_samples);
+       write_u40le_inc(&wrptr, limit_samples);
+       write_u40le_inc(&wrptr, pre_trigger_samples);
+       write_u24le_inc(&wrptr, pre_trigger_memory >> 8);
+       write_u16le_inc(&wrptr, divider_u16);
        write_u8_inc(&wrptr, 0);
-       write_u32le_inc(&wrptr, devc->pre_trigger_size);
-       write_u32le_inc(&wrptr, ((total * devc->capture_ratio) / 100) & 0xFFFFFF00);
-       write_u16le_inc(&wrptr, divisor);
-       write_u8_inc(&wrptr, 0);
-
        ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_SAMPLING, 0, buf, wrptr - buf);
        if (ret != SR_OK) {
-               sr_err("error setting sample config!");
+               sr_err("Cannot setup acquisition configuration.");
                return ret;
        }
 
        return SR_OK;
 }
 
-/* The run state is read from FPGA registers 1[hi-byte] and 0[lo-byte]
- * and the bits are interpreted as follows:
- *
- * register 0:
- *     bit0 1= idle
- *     bit1 1= writing to sdram
- *     bit2 0= waiting_for_trigger 1=been_triggered
- *     bit3 0= pretrigger_sampling 1=posttrigger_sampling
- *     ...unknown...
- * register 1:
- *     meaning of bits unknown (but vendor software reads this, so just do the same)
+/*
+ * FPGA register REG_RUN holds the run state (u16le format). Bit fields
+ * of interest:
+ *   bit 0: value 1 = idle
+ *   bit 1: value 1 = writing to SDRAM
+ *   bit 2: value 0 = waiting for trigger, 1 = trigger seen
+ *   bit 3: value 0 = pretrigger sampling, 1 = posttrigger sampling
+ * The meaning of other bit fields is unknown.
  *
- * The run state values occur in this order:
- * 0x85E2: pre-sampling (for samples before trigger position, capture ratio > 0%)
- * 0x85EA: pre-sampling complete, now waiting for trigger (whilst sampling continuously)
- * 0x85EE: running
- * 0x85ED: idle
+ * Typical values in order of appearance during execution:
+ *   0x85e1: idle, no acquisition pending
+ *     IDLE set, TRGD don't care, POST don't care; DRAM don't care
+ *     "In idle state." Takes precedence over all others.
+ *   0x85e2: pre-sampling, samples before the trigger position,
+ *     when capture ratio > 0%
+ *     IDLE clear, TRGD clear, POST clear; DRAM don't care
+ *     "Not idle any more, no post yet, not triggered yet."
+ *   0x85ea: pre-sampling complete, now waiting for the trigger
+ *     (whilst sampling continuously)
+ *     IDLE clear, TRGD clear, POST set; DRAM don't care
+ *     "Post set thus after pre, not triggered yet"
+ *   0x85ee: trigger seen, capturing post-trigger samples, running
+ *     IDLE clear, TRGD set, POST set; DRAM don't care
+ *     "Triggered and in post, not idle yet."
+ *   0x85ed: idle
+ *     IDLE set, TRGD don't care, POST don't care; DRAM don't care
+ *     "In idle state." TRGD/POST don't care, same meaning as above.
  */
+static const uint16_t runstate_mask_idle = RUNSTATE_IDLE_BIT;
+static const uint16_t runstate_patt_idle = RUNSTATE_IDLE_BIT;
+static const uint16_t runstate_mask_step =
+       RUNSTATE_IDLE_BIT | RUNSTATE_TRGD_BIT | RUNSTATE_POST_BIT;
+static const uint16_t runstate_patt_pre_trig = 0;
+static const uint16_t runstate_patt_wait_trig = RUNSTATE_POST_BIT;
+static const uint16_t runstate_patt_post_trig =
+       RUNSTATE_TRGD_BIT | RUNSTATE_POST_BIT;
+
 static uint16_t run_state(const struct sr_dev_inst *sdi)
 {
-       uint16_t state;
-       static uint16_t previous_state = 0;
+       static uint16_t previous_state;
+
        int ret;
+       uint16_t state;
+       uint8_t buff[REG_PWM_EN - REG_RUN]; /* Width of REG_RUN. */
+       const uint8_t *rdptr;
+       const char *label;
 
-       if ((ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_RUN, 0, &state, sizeof(state))) != SR_OK) {
-               sr_err("failed to read run state!");
+       ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_RUN, 0, buff, sizeof(state));
+       if (ret != SR_OK) {
+               sr_err("Cannot read run state.");
                return ret;
        }
+       rdptr = buff;
+       state = read_u16le_inc(&rdptr);
 
-       /* This function is called about every 50ms.
-        * To avoid filling the log file with redundant information during long captures,
-        * just print a log message if status has changed.
+       /*
+        * Avoid flooding the log, only dump values as they change.
+        * The routine is called about every 50ms.
         */
-
-       if (state != previous_state) {
-               previous_state = state;
-               if ((state & 0x0003) == 0x01) {
-                       sr_dbg("run_state: 0x%04x (%s)", state, "idle");
-               }
-               else if ((state & 0x000f) == 0x02) {
-                       sr_dbg("run_state: 0x%04x (%s)", state, "pre-trigger sampling");
-               }
-               else if ((state & 0x000f) == 0x0a) {
-                       sr_dbg("run_state: 0x%04x (%s)", state, "sampling, waiting for trigger");
-               }
-               else if ((state & 0x000f) == 0x0e) {
-                       sr_dbg("run_state: 0x%04x (%s)", state, "post-trigger sampling");
-               }
-               else {
-                       sr_dbg("run_state: 0x%04x", state);
-               }
-       }
+       if (state == previous_state)
+               return state;
+
+       previous_state = state;
+       label = NULL;
+       if ((state & runstate_mask_idle) == runstate_patt_idle)
+               label = "idle";
+       if ((state & runstate_mask_step) == runstate_patt_pre_trig)
+               label = "pre-trigger sampling";
+       if ((state & runstate_mask_step) == runstate_patt_wait_trig)
+               label = "sampling, waiting for trigger";
+       if ((state & runstate_mask_step) == runstate_patt_post_trig)
+               label = "post-trigger sampling";
+       if (label && *label)
+               sr_dbg("Run state: 0x%04x (%s).", state, label);
+       else
+               sr_dbg("Run state: 0x%04x.", state);
 
        return state;
 }
 
-static int set_run_mode(const struct sr_dev_inst *sdi, uint8_t fast_blinking)
+static gboolean la2016_is_idle(const struct sr_dev_inst *sdi)
+{
+       uint16_t state;
+
+       state = run_state(sdi);
+       if ((state & runstate_mask_idle) == runstate_patt_idle)
+               return TRUE;
+
+       return FALSE;
+}
+
+static int set_run_mode(const struct sr_dev_inst *sdi, uint8_t mode)
 {
        int ret;
 
-       if ((ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_RUN, 0, &fast_blinking, sizeof(fast_blinking))) != SR_OK) {
-               sr_err("failed to send set-run-mode command %d", fast_blinking);
+       ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_RUN, 0, &mode, sizeof(mode));
+       if (ret != SR_OK) {
+               sr_err("Cannot configure run mode %d.", mode);
                return ret;
        }
 
@@ -595,13 +967,14 @@ static int get_capture_info(const struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
        int ret;
-       uint8_t buf[3 * sizeof(uint32_t)];
+       uint8_t buf[REG_TRIGGER - REG_SAMPLING]; /* Width of REG_SAMPLING. */
        const uint8_t *rdptr;
 
        devc = sdi->priv;
 
-       if ((ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_SAMPLING, 0, buf, sizeof(buf))) != SR_OK) {
-               sr_err("failed to read capture info!");
+       ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_SAMPLING, 0, buf, sizeof(buf));
+       if (ret != SR_OK) {
+               sr_err("Cannot read capture info.");
                return ret;
        }
 
@@ -610,25 +983,199 @@ static int get_capture_info(const struct sr_dev_inst *sdi)
        devc->info.n_rep_packets_before_trigger = read_u32le_inc(&rdptr);
        devc->info.write_pos = read_u32le_inc(&rdptr);
 
-       sr_dbg("capture info: n_rep_packets: 0x%08x/%d, before_trigger: 0x%08x/%d, write_pos: 0x%08x%d",
-              devc->info.n_rep_packets, devc->info.n_rep_packets,
-              devc->info.n_rep_packets_before_trigger, devc->info.n_rep_packets_before_trigger,
-              devc->info.write_pos, devc->info.write_pos);
+       sr_dbg("Capture info: n_rep_packets: 0x%08x/%d, before_trigger: 0x%08x/%d, write_pos: 0x%08x/%d.",
+               devc->info.n_rep_packets, devc->info.n_rep_packets,
+               devc->info.n_rep_packets_before_trigger,
+               devc->info.n_rep_packets_before_trigger,
+               devc->info.write_pos, devc->info.write_pos);
+
+       if (devc->info.n_rep_packets % devc->packets_per_chunk) {
+               sr_warn("Unexpected packets count %lu, not a multiple of %lu.",
+                       (unsigned long)devc->info.n_rep_packets,
+                       (unsigned long)devc->packets_per_chunk);
+       }
+
+       return SR_OK;
+}
+
+SR_PRIV int la2016_upload_firmware(const struct sr_dev_inst *sdi,
+       struct sr_context *sr_ctx, libusb_device *dev, gboolean skip_upload)
+{
+       struct dev_context *devc;
+       uint16_t pid;
+       char *fw;
+       int ret;
+
+       devc = sdi ? sdi->priv : NULL;
+       if (!devc || !devc->usb_pid)
+               return SR_ERR_ARG;
+       pid = devc->usb_pid;
+
+       fw = g_strdup_printf(MCU_FWFILE_FMT, pid);
+       sr_info("USB PID %04hx, MCU firmware '%s'.", pid, fw);
+       devc->mcu_firmware = g_strdup(fw);
+
+       if (skip_upload)
+               ret = SR_OK;
+       else
+               ret = ezusb_upload_firmware(sr_ctx, dev, USB_CONFIGURATION, fw);
+       g_free(fw);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+static void LIBUSB_CALL receive_transfer(struct libusb_transfer *xfer);
+
+static void la2016_usbxfer_release_cb(gpointer p)
+{
+       struct libusb_transfer *xfer;
+
+       xfer = p;
+       g_free(xfer->buffer);
+       libusb_free_transfer(xfer);
+}
+
+static int la2016_usbxfer_release(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+
+       devc = sdi ? sdi->priv : NULL;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Release all USB transfers. */
+       g_slist_free_full(devc->transfers, la2016_usbxfer_release_cb);
+       devc->transfers = NULL;
+
+       return SR_OK;
+}
+
+static int la2016_usbxfer_allocate(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       size_t bufsize, xfercount;
+       uint8_t *buffer;
+       struct libusb_transfer *xfer;
+
+       devc = sdi ? sdi->priv : NULL;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transfers were already allocated before? */
+       if (devc->transfers)
+               return SR_OK;
 
-       if (devc->info.n_rep_packets % 5)
-               sr_warn("number of packets is not as expected multiples of 5: %d", devc->info.n_rep_packets);
+       /*
+        * Allocate all USB transfers and their buffers. Arrange for a
+        * buffer size which is within the device's capabilities, and
+        * is a multiple of the USB endpoint's size, to make use of the
+        * RAW_IO performance feature.
+        *
+        * Implementation detail: The LA2016_USB_BUFSZ value happens
+        * to match all those constraints. No additional arithmetics is
+        * required in this location.
+        */
+       bufsize = LA2016_USB_BUFSZ;
+       xfercount = LA2016_USB_XFER_COUNT;
+       while (xfercount--) {
+               buffer = g_try_malloc(bufsize);
+               if (!buffer) {
+                       sr_err("Cannot allocate USB transfer buffer.");
+                       return SR_ERR_MALLOC;
+               }
+               xfer = libusb_alloc_transfer(0);
+               if (!xfer) {
+                       sr_err("Cannot allocate USB transfer.");
+                       g_free(buffer);
+                       return SR_ERR_MALLOC;
+               }
+               xfer->buffer = buffer;
+               devc->transfers = g_slist_append(devc->transfers, xfer);
+       }
+       devc->transfer_bufsize = bufsize;
 
        return SR_OK;
 }
 
-SR_PRIV int la2016_upload_firmware(struct sr_context *sr_ctx, libusb_device *dev, uint16_t product_id)
+static int la2016_usbxfer_cancel_all(const struct sr_dev_inst *sdi)
 {
-       char fw_file[1024];
-       snprintf(fw_file, sizeof(fw_file) - 1, UC_FIRMWARE, product_id);
-       return ezusb_upload_firmware(sr_ctx, dev, USB_CONFIGURATION, fw_file);
+       struct dev_context *devc;
+       GSList *l;
+       struct libusb_transfer *xfer;
+
+       devc = sdi ? sdi->priv : NULL;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Unconditionally cancel the transfer. Ignore errors. */
+       for (l = devc->transfers; l; l = l->next) {
+               xfer = l->data;
+               if (!xfer)
+                       continue;
+               libusb_cancel_transfer(xfer);
+       }
+
+       return SR_OK;
 }
 
-SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi)
+static int la2016_usbxfer_resubmit(const struct sr_dev_inst *sdi,
+       struct libusb_transfer *xfer)
+{
+       struct dev_context *devc;
+       struct sr_usb_dev_inst *usb;
+       libusb_transfer_cb_fn cb;
+       int ret;
+
+       devc = sdi ? sdi->priv : NULL;
+       usb = sdi ? sdi->conn : NULL;
+       if (!devc || !usb)
+               return SR_ERR_ARG;
+
+       if (!xfer)
+               return SR_ERR_ARG;
+
+       cb = receive_transfer;
+       libusb_fill_bulk_transfer(xfer, usb->devhdl,
+               USB_EP_CAPTURE_DATA | LIBUSB_ENDPOINT_IN,
+               xfer->buffer, devc->transfer_bufsize,
+               cb, (void *)sdi, CAPTURE_TIMEOUT_MS);
+       ret = libusb_submit_transfer(xfer);
+       if (ret != 0) {
+               sr_err("Cannot submit USB transfer: %s.",
+                       libusb_error_name(ret));
+               return SR_ERR_IO;
+       }
+
+       return SR_OK;
+}
+
+static int la2016_usbxfer_submit_all(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       GSList *l;
+       struct libusb_transfer *xfer;
+       int ret;
+
+       devc = sdi ? sdi->priv : NULL;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       for (l = devc->transfers; l; l = l->next) {
+               xfer = l->data;
+               if (!xfer)
+                       return SR_ERR_ARG;
+               ret = la2016_usbxfer_resubmit(sdi, xfer);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi,
+       double voltage)
 {
        struct dev_context *devc;
        int ret;
@@ -636,13 +1183,14 @@ SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi)
 
        devc = sdi->priv;
 
-       ret = set_threshold_voltage(sdi, devc->threshold_voltage);
+       ret = set_threshold_voltage(sdi, voltage);
        if (ret != SR_OK)
                return ret;
 
-       cmd = 0;
-       if ((ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_CAPT_MODE, 0, &cmd, sizeof(cmd))) != SR_OK) {
-               sr_err("failed to send stop sampling command");
+       cmd = devc->continuous ? CAPTMODE_STREAM : CAPTMODE_TO_RAM;
+       ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_CAPT_MODE, 0, &cmd, sizeof(cmd));
+       if (ret != SR_OK) {
+               sr_err("Cannot send command to stop sampling.");
                return ret;
        }
 
@@ -659,225 +1207,723 @@ SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi)
 
 SR_PRIV int la2016_start_acquisition(const struct sr_dev_inst *sdi)
 {
-       return set_run_mode(sdi, 3);
+       struct dev_context *devc;
+       int ret;
+
+       devc = sdi->priv;
+
+       ret = la2016_usbxfer_allocate(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       if (devc->continuous) {
+               ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0);
+               if (ret != SR_OK)
+                       return ret;
+
+               ret = la2016_usbxfer_submit_all(sdi);
+               if (ret != SR_OK)
+                       return ret;
+
+               /*
+                * Periodic receive callback will set runmode. This
+                * activity MUST be close to data reception, a pause
+                * between these steps breaks the stream's operation.
+                */
+       } else {
+               ret = set_run_mode(sdi, RUNMODE_RUN);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
 }
 
-SR_PRIV int la2016_stop_acquisition(const struct sr_dev_inst *sdi)
+static int la2016_stop_acquisition(const struct sr_dev_inst *sdi)
 {
-       return set_run_mode(sdi, 0);
+       struct dev_context *devc;
+       int ret;
+
+       ret = set_run_mode(sdi, RUNMODE_HALT);
+       if (ret != SR_OK)
+               return ret;
+
+       devc = sdi->priv;
+       if (devc->continuous)
+               devc->download_finished = TRUE;
+
+       return SR_OK;
 }
 
 SR_PRIV int la2016_abort_acquisition(const struct sr_dev_inst *sdi)
 {
-       return la2016_stop_acquisition(sdi);
-}
+       int ret;
 
-SR_PRIV int la2016_has_triggered(const struct sr_dev_inst *sdi)
-{
-       uint16_t state;
+       ret = la2016_stop_acquisition(sdi);
+       if (ret != SR_OK)
+               return ret;
 
-       state = run_state(sdi);
+       (void)la2016_usbxfer_cancel_all(sdi);
 
-       return (state & 0x3) == 1;
+       return SR_OK;
 }
 
-SR_PRIV int la2016_start_retrieval(const struct sr_dev_inst *sdi, libusb_transfer_cb_fn cb)
+static int la2016_start_download(const struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
-       struct sr_usb_dev_inst *usb;
        int ret;
-       uint8_t wrbuf[2 * sizeof(uint32_t)];
+       uint8_t wrbuf[REG_SAMPLING - REG_BULK]; /* Width of REG_BULK. */
        uint8_t *wrptr;
-       uint32_t to_read;
-       uint8_t *buffer;
 
        devc = sdi->priv;
-       usb = sdi->conn;
 
-       if ((ret = get_capture_info(sdi)) != SR_OK)
+       ret = get_capture_info(sdi);
+       if (ret != SR_OK)
                return ret;
 
-       devc->n_transfer_packets_to_read = devc->info.n_rep_packets / NUM_PACKETS_IN_CHUNK;
-       devc->n_bytes_to_read = devc->n_transfer_packets_to_read * TRANSFER_PACKET_LENGTH;
+       devc->n_transfer_packets_to_read = devc->info.n_rep_packets;
+       devc->n_transfer_packets_to_read /= devc->packets_per_chunk;
+       devc->n_bytes_to_read = devc->n_transfer_packets_to_read;
+       devc->n_bytes_to_read *= devc->transfer_size;
        devc->read_pos = devc->info.write_pos - devc->n_bytes_to_read;
        devc->n_reps_until_trigger = devc->info.n_rep_packets_before_trigger;
 
-       sr_dbg("want to read %d tfer-packets starting from pos %d",
-              devc->n_transfer_packets_to_read, devc->read_pos);
+       sr_dbg("Want to read %u xfer-packets starting from pos %" PRIu32 ".",
+               devc->n_transfer_packets_to_read, devc->read_pos);
 
-       if ((ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0)) != SR_OK) {
-               sr_err("failed to reset bulk state");
+       ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0);
+       if (ret != SR_OK) {
+               sr_err("Cannot reset USB bulk state.");
                return ret;
        }
-       sr_dbg("will read from 0x%08x, 0x%08x bytes", devc->read_pos, devc->n_bytes_to_read);
+       sr_dbg("Will read from 0x%08lx, 0x%08x bytes.",
+               (unsigned long)devc->read_pos, devc->n_bytes_to_read);
        wrptr = wrbuf;
        write_u32le_inc(&wrptr, devc->read_pos);
        write_u32le_inc(&wrptr, devc->n_bytes_to_read);
-       if ((ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_BULK, 0, wrbuf, wrptr - wrbuf)) != SR_OK) {
-               sr_err("failed to send bulk config");
+       ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_BULK, 0, wrbuf, wrptr - wrbuf);
+       if (ret != SR_OK) {
+               sr_err("Cannot send USB bulk config.");
                return ret;
        }
-       if ((ret = ctrl_out(sdi, CMD_BULK_START, 0x00, 0, NULL, 0)) != SR_OK) {
-               sr_err("failed to unblock bulk transfers");
+
+       ret = la2016_usbxfer_submit_all(sdi);
+       if (ret != SR_OK) {
+               sr_err("Cannot submit USB bulk transfers.");
                return ret;
        }
 
-       to_read = devc->n_bytes_to_read;
-       /* choose a buffer size for all of the usb transfers */
-       if (to_read >= LA2016_USB_BUFSZ)
-               to_read = LA2016_USB_BUFSZ; /* multiple transfers */
-       else /* one transfer, make buffer size some multiple of LA2016_EP6_PKTSZ */
-               to_read = (to_read + (LA2016_EP6_PKTSZ-1)) & ~(LA2016_EP6_PKTSZ-1);
-       buffer = g_try_malloc(to_read);
-       if (!buffer) {
-               sr_err("Failed to allocate %d bytes for bulk transfer", to_read);
-               return SR_ERR_MALLOC;
+       ret = ctrl_out(sdi, CMD_BULK_START, 0x00, 0, NULL, 0);
+       if (ret != SR_OK) {
+               sr_err("Cannot start USB bulk transfers.");
+               return ret;
        }
 
-       devc->transfer = libusb_alloc_transfer(0);
-       libusb_fill_bulk_transfer(
-               devc->transfer, usb->devhdl,
-               0x86, buffer, to_read,
-               cb, (void *)sdi, DEFAULT_TIMEOUT_MS);
+       return SR_OK;
+}
+
+/*
+ * A chunk of sample memory was received via USB. These chunks contain
+ * transfers of 16 or 32 bytes each (model dependent size and layout).
+ * Transfers contain a number of packets (5 or 6 per transfer), which
+ * contain a number of samples (16 or 32 sampled pin values, and an
+ * 8bit repeat count for these sampled pin values). A sequence number
+ * follows the packets within the transfer, allows to detect missing or
+ * out of order reception.
+ *
+ * Memory layout for 16-channel models:
+ * - 16 bytes per transfer
+ * - 5x (u16 pins, and u8 count)
+ * - 1x u8 sequence number
+ *
+ * Memory layout for 32-channel models:
+ * - 32 bytes per transfer
+ * - 6x (u32 pins, and u8 count)
+ * - 2x u8 sequence number (inverted, and normal)
+ *
+ * This implementation silently ignores the (weak) sequence number.
+ */
+static void send_chunk(struct sr_dev_inst *sdi,
+       const uint8_t *data_buffer, size_t data_length)
+{
+       struct dev_context *devc;
+       size_t num_xfers, num_pkts, num_seqs;
+       const uint8_t *rp;
+       uint32_t sample_value;
+       size_t repetitions;
+       uint8_t sample_buff[sizeof(sample_value)];
+
+       devc = sdi->priv;
+
+       /* Ignore incoming USB data after complete sample data download. */
+       if (devc->download_finished)
+               return;
 
-       if ((ret = libusb_submit_transfer(devc->transfer)) != 0) {
-               sr_err("Failed to submit transfer: %s.", libusb_error_name(ret));
-               libusb_free_transfer(devc->transfer);
-               devc->transfer = NULL;
-               g_free(buffer);
-               return SR_ERR;
+       if (devc->trigger_involved && !devc->trigger_marked && devc->info.n_rep_packets_before_trigger == 0) {
+               feed_queue_logic_send_trigger(devc->feed_queue);
+               devc->trigger_marked = TRUE;
        }
 
-       return SR_OK;
+       /*
+        * Adjust the number of remaining bytes to read from the device
+        * before the processing of the currently received chunk affects
+        * the variable which holds the number of received bytes.
+        */
+       if (data_length > devc->n_bytes_to_read)
+               devc->n_bytes_to_read = 0;
+       else
+               devc->n_bytes_to_read -= data_length;
+
+       /* Process the received chunk of capture data. */
+       sample_value = 0;
+       rp = data_buffer;
+       num_xfers = data_length / devc->transfer_size;
+       while (num_xfers--) {
+               num_pkts = devc->packets_per_chunk;
+               while (num_pkts--) {
+
+                       if (devc->model->channel_count == 32)
+                               sample_value = read_u32le_inc(&rp);
+                       else if (devc->model->channel_count == 16)
+                               sample_value = read_u16le_inc(&rp);
+                       repetitions = read_u8_inc(&rp);
+
+                       devc->total_samples += repetitions;
+
+                       write_u32le(sample_buff, sample_value);
+                       feed_queue_logic_submit_one(devc->feed_queue,
+                               sample_buff, repetitions);
+                       sr_sw_limits_update_samples_read(&devc->sw_limits,
+                               repetitions);
+
+                       if (devc->trigger_involved && !devc->trigger_marked) {
+                               if (!--devc->n_reps_until_trigger) {
+                                       feed_queue_logic_send_trigger(devc->feed_queue);
+                                       devc->trigger_marked = TRUE;
+                                       sr_dbg("Trigger position after %" PRIu64 " samples, %.6fms.",
+                                               devc->total_samples,
+                                               (double)devc->total_samples / devc->samplerate * 1e3);
+                               }
+                       }
+               }
+               /* Skip the sequence number bytes. */
+               num_seqs = devc->sequence_size;
+               while (num_seqs--)
+                       (void)read_u8_inc(&rp);
+       }
+
+       /*
+        * Check for several conditions which shall terminate the
+        * capture data download: When the amount of capture data in
+        * the device is exhausted. When the user specified samples
+        * count limit is reached.
+        */
+       if (!devc->n_bytes_to_read) {
+               devc->download_finished = TRUE;
+       } else {
+               sr_dbg("%" PRIu32 " more bytes to download from the device.",
+                       devc->n_bytes_to_read);
+       }
+       if (!devc->download_finished && sr_sw_limits_check(&devc->sw_limits)) {
+               sr_dbg("Acquisition limit reached.");
+               devc->download_finished = TRUE;
+       }
+       if (devc->download_finished) {
+               sr_dbg("Download finished, flushing session feed queue.");
+               feed_queue_logic_flush(devc->feed_queue);
+       }
+       sr_dbg("Total samples after chunk: %" PRIu64 ".", devc->total_samples);
 }
 
-SR_PRIV int la2016_init_device(const struct sr_dev_inst *sdi)
+/*
+ * Process a chunk of capture data in streaming mode. The memory layout
+ * is rather different from "normal mode" (see the send_chunk() routine
+ * above). In streaming mode data is not compressed, and memory cells
+ * neither contain raw sampled pin values at a given point in time. The
+ * memory content needs transformation.
+ *
+ * All enabled channels get iterated over. Disabled channels will not
+ * occupy space in the streamed sample data. Per channel chunk there is
+ * one 16bit entity which carries samples that were taken at different
+ * times. The least significant bit was sampled first, higher bits were
+ * sampled later. After all 16bit entities for all enabled channels
+ * were seen, the first enabled channel's next chunk follows.
+ *
+ * Implementor's note: This routine is inspired by convert_sample_data()
+ * in the https://github.com/AlexUg/sigrok implementation. Which in turn
+ * appears to have been derived from the saleae-logic16 sigrok driver.
+ * The code is phrased conservatively to verify the layout as discussed
+ * above, performance was not a priority. Operation was verified with an
+ * LA2016 device. The LA5032 reportedly shares the 16 samples per channel
+ * layout, just round-robins through a potentially larger set of enabled
+ * channels before returning to the first of the channels.
+ */
+static void stream_data(struct sr_dev_inst *sdi,
+       const uint8_t *data_buffer, size_t data_length)
 {
        struct dev_context *devc;
-       uint16_t state;
-       uint8_t buf[8];
-       int16_t purchase_date_bcd[2];
-       uint8_t magic;
+       struct stream_state_t *stream;
+       size_t bit_count;
+       const uint8_t *rp;
+       uint32_t sample_value;
+       uint8_t sample_buff[sizeof(sample_value)];
+       size_t bit_idx;
+       uint32_t ch_mask;
+
+       devc = sdi->priv;
+       stream = &devc->stream;
+
+       /* Ignore incoming USB data after complete sample data download. */
+       if (devc->download_finished)
+               return;
+       sr_dbg("Stream mode, got another chunk: %p, length %zu.",
+               data_buffer, data_length);
+
+       /* TODO Add soft trigger support when in stream mode? */
+
+       /* All channels' chunks carry 16 samples for one channel. */
+       bit_count = 16;
+       data_length /= sizeof(uint16_t);
+
+       rp = data_buffer;
+       sample_value = 0;
+       while (data_length--) {
+               /* Get another entity. */
+               sample_value = read_u16le_inc(&rp);
+
+               /* Map the entity's bits to a channel's samples. */
+               ch_mask = stream->channel_masks[stream->channel_index];
+               for (bit_idx = 0; bit_idx < bit_count; bit_idx++) {
+                       if (sample_value & (1UL << bit_idx))
+                               stream->sample_data[bit_idx] |= ch_mask;
+               }
+
+               /*
+                * Advance to the next channel. Submit a block of
+                * samples when all channels' data was seen.
+                */
+               stream->channel_index++;
+               if (stream->channel_index != stream->enabled_count)
+                       continue;
+               for (bit_idx = 0; bit_idx < bit_count; bit_idx++) {
+                       sample_value = stream->sample_data[bit_idx];
+                       write_u32le(sample_buff, sample_value);
+                       feed_queue_logic_submit_one(devc->feed_queue,
+                               sample_buff, 1);
+               }
+               sr_sw_limits_update_samples_read(&devc->sw_limits, bit_count);
+               devc->total_samples += bit_count;
+               memset(stream->sample_data, 0, sizeof(stream->sample_data));
+               stream->channel_index = 0;
+       }
+
+       /*
+        * Need we count empty or failed USB transfers? This version
+        * doesn't, assumes that timeouts are perfectly legal because
+        * transfers are started early, and slow samplerates or trigger
+        * support in hardware are plausible causes for empty transfers.
+        *
+        * TODO Maybe a good condition would be (rather large) a timeout
+        * after a previous capture data chunk was seen? So that stalled
+        * streaming gets detected which _is_ an exceptional condition.
+        * We have observed these when "runmode" is set early but bulk
+        * transfers start late with a pause after setting the runmode.
+        */
+       if (sr_sw_limits_check(&devc->sw_limits)) {
+               sr_dbg("Acquisition end reached (sw limits).");
+               devc->download_finished = TRUE;
+       }
+       if (devc->download_finished) {
+               sr_dbg("Stream receive done, flushing session feed queue.");
+               feed_queue_logic_flush(devc->feed_queue);
+       }
+       sr_dbg("Total samples after chunk: %" PRIu64 ".", devc->total_samples);
+}
+
+static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       gboolean was_cancelled, device_gone;
+       int ret;
+
+       sdi = transfer->user_data;
+       devc = sdi->priv;
+
+       was_cancelled = transfer->status == LIBUSB_TRANSFER_CANCELLED;
+       device_gone = transfer->status == LIBUSB_TRANSFER_NO_DEVICE;
+       sr_dbg("receive_transfer(): status %s received %d bytes.",
+               libusb_error_name(transfer->status), transfer->actual_length);
+       if (device_gone) {
+               sr_warn("Lost communication to USB device.");
+               devc->download_finished = TRUE;
+               return;
+       }
+
+       /*
+        * Implementation detail: A USB transfer timeout is not fatal
+        * here. We just process whatever was received, empty input is
+        * perfectly acceptable. Reaching (or exceeding) the sw limits
+        * or exhausting the device's captured data will complete the
+        * sample data download.
+        */
+       if (devc->continuous)
+               stream_data(sdi, transfer->buffer, transfer->actual_length);
+       else
+               send_chunk(sdi, transfer->buffer, transfer->actual_length);
+
+       /*
+        * Re-submit completed transfers (regardless of timeout or
+        * data reception), unless the transfer was cancelled when
+        * the acquisition was terminated or has completed.
+        */
+       if (!was_cancelled && !devc->download_finished) {
+               ret = la2016_usbxfer_resubmit(sdi, transfer);
+               if (ret == SR_OK)
+                       return;
+               devc->download_finished = TRUE;
+       }
+}
+
+SR_PRIV int la2016_receive_data(int fd, int revents, void *cb_data)
+{
+       const struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct drv_context *drvc;
+       struct timeval tv;
        int ret;
 
+       (void)fd;
+       (void)revents;
+
+       sdi = cb_data;
        devc = sdi->priv;
+       drvc = sdi->driver->context;
+
+       /* Arrange for the start of stream mode when requested. */
+       if (devc->continuous && !devc->frame_begin_sent) {
+               sr_dbg("First receive callback in stream mode.");
+               devc->download_finished = FALSE;
+               devc->trigger_marked = FALSE;
+               devc->total_samples = 0;
+
+               std_session_send_df_frame_begin(sdi);
+               devc->frame_begin_sent = TRUE;
+
+               ret = set_run_mode(sdi, RUNMODE_RUN);
+               if (ret != SR_OK) {
+                       sr_err("Cannot set 'runmode' to 'run'.");
+                       return FALSE;
+               }
+
+               ret = ctrl_out(sdi, CMD_BULK_START, 0x00, 0, NULL, 0);
+               if (ret != SR_OK) {
+                       sr_err("Cannot start USB bulk transfers.");
+                       return FALSE;
+               }
+               sr_dbg("Stream data reception initiated.");
+       }
+
+       /*
+        * Wait for the acquisition to complete in hardware.
+        * Periodically check a potentially configured msecs timeout.
+        */
+       if (!devc->continuous && !devc->completion_seen) {
+               if (!la2016_is_idle(sdi)) {
+                       if (sr_sw_limits_check(&devc->sw_limits)) {
+                               devc->sw_limits.limit_msec = 0;
+                               sr_dbg("Limit reached. Stopping acquisition.");
+                               la2016_stop_acquisition(sdi);
+                       }
+                       /* Not yet ready for sample data download. */
+                       return TRUE;
+               }
+               sr_dbg("Acquisition completion seen (hardware).");
+               devc->sw_limits.limit_msec = 0;
+               devc->completion_seen = TRUE;
+               devc->download_finished = FALSE;
+               devc->trigger_marked = FALSE;
+               devc->total_samples = 0;
+
+               la2016_dump_fpga_registers(sdi, "acquisition complete", 0, 0);
+
+               /* Initiate the download of acquired sample data. */
+               std_session_send_df_frame_begin(sdi);
+               devc->frame_begin_sent = TRUE;
+               ret = la2016_start_download(sdi);
+               if (ret != SR_OK) {
+                       sr_err("Cannot start acquisition data download.");
+                       return FALSE;
+               }
+               sr_dbg("Acquisition data download started.");
+
+               return TRUE;
+       }
 
-       /* Four bytes of eeprom at 0x20 are purchase year & month in BCD format, with 16bit
-        * complemented checksum; e.g. 2004DFFB = 2020-April.
-        * This helps to identify the age of devices if unknown magic numbers occur.
+       /* Handle USB reception. Drives sample data download. */
+       memset(&tv, 0, sizeof(tv));
+       libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv);
+
+       /*
+        * Periodically flush acquisition data in streaming mode.
+        * Without this nudge, previously received and accumulated data
+        * keeps sitting in queues and is not seen by applications.
         */
-       if ((ret = ctrl_in(sdi, CMD_EEPROM, 0x20, 0, purchase_date_bcd, sizeof(purchase_date_bcd))) != SR_OK) {
-               sr_err("failed to read eeprom purchase_date_bcd");
+       if (devc->continuous && devc->stream.flush_period_ms) {
+               uint64_t now, elapsed;
+               now = g_get_monotonic_time();
+               if (!devc->stream.last_flushed)
+                       devc->stream.last_flushed = now;
+               elapsed = now - devc->stream.last_flushed;
+               elapsed /= 1000;
+               if (elapsed >= devc->stream.flush_period_ms) {
+                       sr_dbg("Stream mode, flushing.");
+                       feed_queue_logic_flush(devc->feed_queue);
+                       devc->stream.last_flushed = now;
+               }
+       }
+
+       /* Postprocess completion of sample data download. */
+       if (devc->download_finished) {
+               sr_dbg("Download finished, post processing.");
+
+               la2016_stop_acquisition(sdi);
+               usb_source_remove(sdi->session, drvc->sr_ctx);
+
+               la2016_usbxfer_cancel_all(sdi);
+               memset(&tv, 0, sizeof(tv));
+               libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv);
+
+               feed_queue_logic_flush(devc->feed_queue);
+               feed_queue_logic_free(devc->feed_queue);
+               devc->feed_queue = NULL;
+               if (devc->frame_begin_sent) {
+                       std_session_send_df_frame_end(sdi);
+                       devc->frame_begin_sent = FALSE;
+               }
+               std_session_send_df_end(sdi);
+
+               sr_dbg("Download finished, done post processing.");
        }
-       else {
-               sr_dbg("purchase date: 20%02hx-%02hx", (purchase_date_bcd[0]) & 0x00ff, (purchase_date_bcd[0] >> 8) & 0x00ff);
-               if (purchase_date_bcd[0] != (0x0ffff & ~purchase_date_bcd[1])) {
-                       sr_err("purchase date: checksum failure");
+
+       return TRUE;
+}
+
+SR_PRIV int la2016_identify_device(const struct sr_dev_inst *sdi,
+       gboolean show_message)
+{
+       struct dev_context *devc;
+       uint8_t buf[8]; /* Larger size of manuf date and device type magic. */
+       size_t rdoff, rdlen;
+       const uint8_t *rdptr;
+       uint8_t date_yy, date_mm;
+       uint8_t dinv_yy, dinv_mm;
+       uint8_t magic, magic2;
+       size_t model_idx;
+       const struct kingst_model *model;
+       int ret;
+
+       devc = sdi->priv;
+
+       /*
+        * Four EEPROM bytes at offset 0x20 are the manufacturing date,
+        * year and month in BCD format, followed by inverted values for
+        * consistency checks. For example bytes 20 04 df fb translate
+        * to 2020-04. This information can help identify the vintage of
+        * devices when unknown magic numbers are seen.
+        */
+       rdoff = 0x20;
+       rdlen = 4 * sizeof(uint8_t);
+       ret = ctrl_in(sdi, CMD_EEPROM, rdoff, 0, buf, rdlen);
+       if (ret != SR_OK && !show_message) {
+               /* Non-fatal weak attempt during probe. Not worth logging. */
+               sr_dbg("Cannot access EEPROM.");
+               return SR_ERR_IO;
+       } else if (ret != SR_OK) {
+               /* Failed attempt in regular use. Non-fatal. Worth logging. */
+               sr_err("Cannot read manufacture date in EEPROM.");
+       } else {
+               if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+                       GString *txt;
+                       txt = sr_hexdump_new(buf, rdlen);
+                       sr_spew("Manufacture date bytes %s.", txt->str);
+                       sr_hexdump_free(txt);
                }
+               rdptr = &buf[0];
+               date_yy = read_u8_inc(&rdptr);
+               date_mm = read_u8_inc(&rdptr);
+               dinv_yy = read_u8_inc(&rdptr);
+               dinv_mm = read_u8_inc(&rdptr);
+               sr_info("Manufacture date: 20%02hx-%02hx.", date_yy, date_mm);
+               if ((date_mm ^ dinv_mm) != 0xff || (date_yy ^ dinv_yy) != 0xff)
+                       sr_warn("Manufacture date fails checksum test.");
        }
 
        /*
-        * There are four known kingst logic analyser devices which use this same usb vid and pid:
-        * LA2016, LA1016 and the older revision of each of these. They all use the same hardware
-        * and the same FX2 mcu firmware but each requires a different fpga bitstream. They are
-        * differentiated by a 'magic' byte within the 8 bytes of EEPROM from address 0x08.
-        * For example;
+        * Several Kingst logic analyzer devices share the same USB VID
+        * and PID. The product ID determines which MCU firmware to load.
+        * The MCU firmware provides access to EEPROM content which then
+        * allows to identify the device model. Which in turn determines
+        * which FPGA bitstream to load. Eight bytes at offset 0x08 are
+        * to get inspected.
         *
-        * magic=0x08
-        *  | ~magic=0xf7
-        *  | |
-        * 08F7000008F710EF
-        *          | |
-        *          | ~magic-backup
-        *          magic-backup
+        * EEPROM content for model identification is kept redundantly
+        * in memory. The values are stored in verbatim and in inverted
+        * form, multiple copies are kept at different offsets. Example
+        * data:
         *
-        * It seems that only these magic bytes are used, other bytes shown above are 'don't care'.
-        * Changing the magic byte on newer device to older magic causes OEM software to load
-        * the older fpga bitstream. The device then functions but has channels out of order.
-        * It's likely the bitstreams were changed to move input channel pins due to PCB changes.
+        *   magic 0x08
+        *    | ~magic 0xf7
+        *    | |
+        *   08f7000008f710ef
+        *            | |
+        *            | ~magic backup
+        *            magic backup
         *
-        * magic 9 == LA1016a using "kingst-la1016a1-fpga.bitstream" (latest v1.3.0 PCB, perhaps others)
-        * magic 8 == LA2016a using "kingst-la2016a1-fpga.bitstream" (latest v1.3.0 PCB, perhaps others)
-        * magic 3 == LA1016 using "kingst-la1016-fpga.bitstream"
-        * magic 2 == LA2016 using "kingst-la2016-fpga.bitstream"
+        * Exclusively inspecting the magic byte appears to be sufficient,
+        * other fields seem to be 'don't care'.
         *
-        * This was all determined by altering the eeprom contents of an LA2016 and LA1016 and observing
-        * the vendor software actions, either raising errors or loading specific bitstreams.
+        *   magic 2 == LA2016 using "kingst-la2016-fpga.bitstream"
+        *   magic 3 == LA1016 using "kingst-la1016-fpga.bitstream"
+        *   magic 8 == LA2016a using "kingst-la2016a1-fpga.bitstream"
+        *              (latest v1.3.0 PCB, perhaps others)
+        *   magic 9 == LA1016a using "kingst-la1016a1-fpga.bitstream"
+        *              (latest v1.3.0 PCB, perhaps others)
         *
-        * Note:
-        * An LA1016 cannot be converted to an LA2016 by changing the magic number - the bitstream
-        * will not authenticate with ic U10, which has different security coding for each device type.
+        * When EEPROM content does not match the hardware configuration
+        * (the board layout), the software may load but yield incorrect
+        * results (like swapped channels). The FPGA bitstream itself
+        * will authenticate with IC U10 and fail when its capabilities
+        * do not match the hardware model. An LA1016 won't become a
+        * LA2016 by faking its EEPROM content.
         */
-
-       if ((ret = ctrl_in(sdi, CMD_EEPROM, 0x08, 0, &buf, sizeof(buf))) != SR_OK) {
-               sr_err("failed to read eeprom device identifier bytes");
+       devc->identify_magic = 0;
+       rdoff = 0x08;
+       rdlen = 8 * sizeof(uint8_t);
+       ret = ctrl_in(sdi, CMD_EEPROM, rdoff, 0, &buf, rdlen);
+       if (ret != SR_OK) {
+               sr_err("Cannot read EEPROM device identifier bytes.");
                return ret;
        }
-
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *txt;
+               txt = sr_hexdump_new(buf, rdlen);
+               sr_spew("EEPROM magic bytes %s.", txt->str);
+               sr_hexdump_free(txt);
+       }
        magic = 0;
-       if (buf[0] == (0x0ff & ~buf[1])) {
-               /* primary copy of magic passes complement check */
+       magic2 = 0;
+       if ((buf[0] ^ buf[1]) == 0xff && (buf[2] ^ buf[3]) == 0xff) {
+               /* Primary copy of magic passes complement check (4 bytes). */
                magic = buf[0];
-       }
-       else if (buf[4] == (0x0ff & ~buf[5])) {
-               /* backup copy of magic passes complement check */
-               sr_dbg("device_type: using backup copy of magic number");
+               magic2 = buf[2];
+               sr_dbg("Using primary magic 0x%hhx (0x%hhx).", magic, magic2);
+       } else if ((buf[4] ^ buf[5]) == 0xff && (buf[6] ^ buf[7]) == 0xff) {
+               /* Backup copy of magic passes complement check (4 bytes). */
+               magic = buf[4];
+               magic2 = buf[6];
+               sr_dbg("Using secondary magic 0x%hhx (0x%hhx).", magic, magic2);
+       } else if ((buf[0] ^ buf[1]) == 0xff) {
+               /* Primary copy of magic passes complement check (2 bytes). */
+               magic = buf[0];
+               sr_dbg("Using primary magic 0x%hhx.", magic);
+       } else if ((buf[4] ^ buf[5]) == 0xff) {
+               /* Backup copy of magic passes complement check (2 bytes). */
                magic = buf[4];
+               sr_dbg("Using secondary magic 0x%hhx.", magic);
+       } else {
+               sr_err("Cannot find consistent device type identification.");
        }
-
-       sr_dbg("device_type: magic number is %hhu", magic);
-
-       /* select the correct fpga bitstream for this device */
-       switch (magic) {
-       case 2:
-               ret = upload_fpga_bitstream(sdi, FPGA_FW_LA2016);
-               devc->max_samplerate = MAX_SAMPLE_RATE_LA2016;
-               break;
-       case 3:
-               ret = upload_fpga_bitstream(sdi, FPGA_FW_LA1016);
-               devc->max_samplerate = MAX_SAMPLE_RATE_LA1016;
-               break;
-       case 8:
-               ret = upload_fpga_bitstream(sdi, FPGA_FW_LA2016A);
-               devc->max_samplerate = MAX_SAMPLE_RATE_LA2016;
-               break;
-       case 9:
-               ret = upload_fpga_bitstream(sdi, FPGA_FW_LA1016A);
-               devc->max_samplerate = MAX_SAMPLE_RATE_LA1016;
+       devc->identify_magic = magic;
+       devc->identify_magic2 = magic2;
+
+       devc->model = NULL;
+       for (model_idx = 0; model_idx < ARRAY_SIZE(models); model_idx++) {
+               model = &models[model_idx];
+               if (model->magic != magic)
+                       continue;
+               if (model->magic2 && model->magic2 != magic2)
+                       continue;
+               devc->model = model;
+               sr_info("Model '%s', %zu channels, max %" PRIu64 "MHz.",
+                       model->name, model->channel_count,
+                       model->samplerate / SR_MHZ(1));
+               devc->fpga_bitstream = g_strdup_printf(FPGA_FWFILE_FMT,
+                       model->fpga_stem);
+               sr_info("FPGA bitstream file '%s'.", devc->fpga_bitstream);
+               if (!model->channel_count) {
+                       sr_warn("Device lacks logic channels. Not supported.");
+                       devc->model = NULL;
+               }
                break;
-       default:
-               sr_err("device_type: device not supported; magic number indicates this is not a LA2016 or LA1016");
-               return SR_ERR;
        }
+       if (!devc->model) {
+               sr_err("Cannot identify as one of the supported models.");
+               return SR_ERR_DATA;
+       }
+
+       return SR_OK;
+}
+
+SR_PRIV int la2016_init_hardware(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       const char *bitstream_fn;
+       int ret;
+       uint16_t state;
+
+       devc = sdi->priv;
+       bitstream_fn = devc ? devc->fpga_bitstream : "";
 
+       ret = check_fpga_bitstream(sdi);
+       if (ret != SR_OK) {
+               ret = upload_fpga_bitstream(sdi, bitstream_fn);
+               if (ret != SR_OK) {
+                       sr_err("Cannot upload FPGA bitstream.");
+                       return ret;
+               }
+       }
+       ret = enable_fpga_bitstream(sdi);
        if (ret != SR_OK) {
-               sr_err("failed to upload fpga bitstream");
+               sr_err("Cannot enable FPGA bitstream after upload.");
                return ret;
        }
 
        state = run_state(sdi);
-       if (state != 0x85e9) {
-               sr_warn("expect run state to be 0x85e9, but it reads 0x%04x", state);
+       if ((state & 0xfff0) != 0x85e0) {
+               sr_warn("Unexpected run state, want 0x85eX, got 0x%04x.", state);
        }
 
-       if ((ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0)) != SR_OK) {
-               sr_err("failed to send CMD_BULK_RESET");
+       ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0);
+       if (ret != SR_OK) {
+               sr_err("Cannot reset USB bulk transfer.");
                return ret;
        }
 
-       sr_dbg("device should be initialized");
+       sr_dbg("Device should be initialized.");
 
-       return set_defaults(sdi);
+       return SR_OK;
 }
 
-SR_PRIV int la2016_deinit_device(const struct sr_dev_inst *sdi)
+SR_PRIV int la2016_deinit_hardware(const struct sr_dev_inst *sdi)
 {
        int ret;
 
-       if ((ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x00, 0, NULL, 0)) != SR_OK) {
-               sr_err("failed to send deinit command");
+       ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x00, 0, NULL, 0);
+       if (ret != SR_OK) {
+               sr_err("Cannot deinitialize device's FPGA.");
                return ret;
        }
 
        return SR_OK;
 }
+
+SR_PRIV void la2016_release_resources(const struct sr_dev_inst *sdi)
+{
+       (void)la2016_usbxfer_release(sdi);
+}
+
+SR_PRIV int la2016_write_pwm_config(const struct sr_dev_inst *sdi, size_t idx)
+{
+       return set_pwm_config(sdi, idx);
+}
index 09e12be6577d8739b860745a152f7f67648b0a4c..371a3ccd8e710255a866ef1907f0b1caad029155 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * This file is part of the libsigrok project.
  *
+ * Copyright (C) 2022 Gerhard Sittig <gerhard.sittig@gmx.net>
  * Copyright (C) 2020 Florian Schmidt <schmidt_florian@gmx.de>
  * Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se>
  * Copyright (C) 2013 Bert Vermeulen <bert@biot.com>
 #ifndef LIBSIGROK_HARDWARE_KINGST_LA2016_PROTOCOL_H
 #define LIBSIGROK_HARDWARE_KINGST_LA2016_PROTOCOL_H
 
-#include <stdint.h>
-#include <glib.h>
 #include <libsigrok/libsigrok.h>
-#include "libsigrok-internal.h"
+#include <stdint.h>
 
-#define LOG_PREFIX "kingst-la2016"
+#define LOG_PREFIX     "kingst-la2016"
 
 #define LA2016_VID             0x77a1
 #define LA2016_PID             0x01a2
+#define LA2016_IPRODUCT_INDEX  2
 #define USB_INTERFACE          0
 #define USB_CONFIGURATION      1
-
-#define LA2016_BULK_MAX         8388608
+#define USB_EP_FPGA_BITSTREAM  2
+#define USB_EP_CAPTURE_DATA    6
 
 /*
  * On Windows sigrok uses WinUSB RAW_IO policy which requires the
  * USB transfer buffer size to be a multiple of the endpoint max packet
- * size, which is 512 bytes in this case. Also, the maximum allowed size of
- * the transfer buffer is normally read from WinUSB_GetPipePolicy API but
- * libusb does not expose this function. Typically, max size is 2MB.
+ * size, which is 512 bytes in this case. Also, the maximum allowed size
+ * of the transfer buffer is normally read from WinUSB_GetPipePolicy API
+ * but libusb does not expose this function. Typically, max size is 2MB.
+ */
+#define LA2016_EP6_PKTSZ       512 /* Max packet size of USB endpoint 6. */
+#define LA2016_USB_BUFSZ       (512 * 1024) /* 512KiB buffer. */
+#define LA2016_USB_XFER_COUNT  8 /* Size of USB bulk transfers pool. */
+
+/* USB communication timeout during regular operation. */
+#define DEFAULT_TIMEOUT_MS     200
+#define CAPTURE_TIMEOUT_MS     500
+
+/*
+ * Check for MCU firmware to take effect after upload. Check the device
+ * presence for a maximum period of time, delay between checks in that
+ * phase. Allow for the device to vanish after upload and before checks,
+ * to not mistake its earlier incarnation for the successful operation
+ * of the most recently loaded firmware.
  */
-#define LA2016_EP6_PKTSZ       512 /* endpoint 6 max packet size */
-#define LA2016_USB_BUFSZ       (256 * 2 * LA2016_EP6_PKTSZ) /* 256KB buffer */
+#define RENUM_CHECK_PERIOD_MS  3000
+#define RENUM_GONE_DELAY_MS    1800
+#define RENUM_POLL_INTERVAL_MS 200
 
-#define MAX_RENUM_DELAY_MS     3000
-#define DEFAULT_TIMEOUT_MS      200
+/*
+ * The device expects some zero padding to follow the content of the
+ * file which contains the FPGA bitstream. Specify the chunk size here.
+ */
+#define LA2016_EP2_PADDING     4096
 
-#define LA2016_THR_VOLTAGE_MIN  0.40
-#define LA2016_THR_VOLTAGE_MAX  4.00
+/*
+ * Whether the logic input threshold voltage is a config item of the
+ * "Logic" channel group or a global config item of the device. Ideally
+ * it would be the former (being strictly related to the Logic channels)
+ * but mainline applications work better with the latter, and many other
+ * device drivers implement it that way, too.
+ */
+#define WITH_THRESHOLD_DEVCFG  1
 
-#define LA2016_NUM_SAMPLES_MIN  256
-#define LA2016_NUM_SAMPLES_MAX  (10UL * 1000 * 1000 * 1000)
+#define LA2016_THR_VOLTAGE_MIN 0.40
+#define LA2016_THR_VOLTAGE_MAX 4.00
 
-typedef struct pwm_setting_dev {
-       uint32_t period;
-       uint32_t duty;
-} pwm_setting_dev_t;
+#define LA2016_NUM_SAMPLES_MAX (UINT64_C(10 * 1000 * 1000 * 1000))
 
-typedef struct trigger_cfg {
-       uint32_t channels;
-       uint32_t enabled;
-       uint32_t level;
-       uint32_t high_or_falling;
-} trigger_cfg_t;
+/* Maximum device capabilities. May differ between models. */
+#define MAX_PWM_FREQ           SR_MHZ(20)
+#define PWM_CLOCK              SR_MHZ(200)     /* 200MHz for both LA2016 and LA1016 */
 
-typedef struct capture_info {
-       uint32_t n_rep_packets;
-       uint32_t n_rep_packets_before_trigger;
-       uint32_t write_pos;
-} capture_info_t;
+#define LA2016_NUM_PWMCH_MAX   2
 
-#define NUM_PACKETS_IN_CHUNK 5
-#define TRANSFER_PACKET_LENGTH 16
+/* Streaming mode related thresholds. Not enforced, used for warnings. */
+#define LA2016_STREAM_MBPS_MAX 200     /* In units of Mbps. */
+#define LA2016_STREAM_PUSH_THR 16      /* In units of Mbps. */
+#define LA2016_STREAM_PUSH_IVAL        250     /* In units of ms. */
 
-typedef struct pwm_setting {
-       uint8_t enabled;
-       float freq;
-       float duty;
-} pwm_setting_t;
+/*
+ * Whether to de-initialize the device hardware in the driver's close
+ * callback. It is desirable to e.g. configure PWM channels and leave
+ * the generator running after the application shuts down. Users can
+ * always disable channels on their way out if they want to.
+ */
+#define WITH_DEINIT_IN_CLOSE   0
+
+#define LA2016_CONVBUFFER_SIZE (4 * 1024 * 1024)
+
+struct kingst_model {
+       uint8_t magic, magic2;  /* EEPROM magic byte values. */
+       const char *name;       /* User perceived model name. */
+       const char *fpga_stem;  /* Bitstream filename stem. */
+       uint64_t samplerate;    /* Max samplerate in Hz. */
+       size_t channel_count;   /* Max channel count (16, 32). */
+       uint64_t memory_bits;   /* RAM capacity in Gbit (1, 2, 4). */
+       uint64_t baseclock;     /* Base clock to derive samplerate from. */
+};
 
 struct dev_context {
-       struct sr_context *ctx;
-
-       int64_t fw_updated;
-       pwm_setting_t pwm_setting[2];
-       unsigned int threshold_voltage_idx;
-       float threshold_voltage;
-       uint64_t max_samplerate;
-       uint64_t cur_samplerate;
-       uint64_t limit_samples;
+       uint16_t usb_pid;
+       char *mcu_firmware;
+       char *fpga_bitstream;
+       uint64_t fw_uploaded; /* Timestamp of most recent FW upload. */
+       uint8_t identify_magic, identify_magic2;
+       const struct kingst_model *model;
+       char **channel_names_logic;
+       struct sr_channel_group *cg_logic, *cg_pwm;
+
+       /* User specified parameters. */
+       struct pwm_setting {
+               gboolean enabled;
+               float freq;
+               float duty;
+       } pwm_setting[LA2016_NUM_PWMCH_MAX];
+       size_t threshold_voltage_idx;
+       uint64_t samplerate;
+       struct sr_sw_limits sw_limits;
        uint64_t capture_ratio;
-       uint16_t cur_channels;
-       int num_channels;
-
-       uint32_t bitstream_size;
-
-       /* derived stuff */
-       uint64_t pre_trigger_size;
-
-       /* state after sampling */
-       int had_triggers_configured;
-       int have_trigger;
-       int transfer_finished;
-       capture_info_t info;
-       unsigned int n_transfer_packets_to_read; /* each with 5 acq packets */
-       unsigned int n_bytes_to_read;
-       unsigned int n_reps_until_trigger;
-       unsigned int reading_behind_trigger;
+       gboolean continuous;
+
+       /* Internal acquisition and download state. */
+       gboolean trigger_involved;
+       gboolean frame_begin_sent;
+       gboolean completion_seen;
+       gboolean download_finished;
+       size_t transfer_size;
+       size_t sequence_size;
+       uint32_t packets_per_chunk;
+       struct capture_info {
+               uint32_t n_rep_packets;
+               uint32_t n_rep_packets_before_trigger;
+               uint32_t write_pos;
+       } info;
+       uint32_t n_transfer_packets_to_read; /* each with 5 acq packets */
+       uint32_t n_bytes_to_read;
+       uint32_t n_reps_until_trigger;
+       gboolean trigger_marked;
        uint64_t total_samples;
        uint32_t read_pos;
 
-       unsigned int convbuffer_size;
-       uint8_t *convbuffer;
-       struct libusb_transfer *transfer;
+       struct feed_queue_logic *feed_queue;
+       GSList *transfers;
+       size_t transfer_bufsize;
+       struct stream_state_t {
+               size_t enabled_count;
+               uint32_t enabled_mask;
+               uint32_t channel_masks[32];
+               size_t channel_index;
+               uint32_t sample_data[32];
+               uint64_t flush_period_ms;
+               uint64_t last_flushed;
+       } stream;
 };
 
-SR_PRIV int la2016_upload_firmware(struct sr_context *sr_ctx, libusb_device *dev, uint16_t product_id);
-SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV int la2016_upload_firmware(const struct sr_dev_inst *sdi,
+       struct sr_context *sr_ctx, libusb_device *dev, gboolean skip_upload);
+SR_PRIV int la2016_identify_device(const struct sr_dev_inst *sdi,
+       gboolean show_message);
+SR_PRIV int la2016_init_hardware(const struct sr_dev_inst *sdi);
+SR_PRIV int la2016_deinit_hardware(const struct sr_dev_inst *sdi);
+SR_PRIV int la2016_write_pwm_config(const struct sr_dev_inst *sdi, size_t idx);
+SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi,
+       double voltage);
 SR_PRIV int la2016_start_acquisition(const struct sr_dev_inst *sdi);
-SR_PRIV int la2016_stop_acquisition(const struct sr_dev_inst *sdi);
 SR_PRIV int la2016_abort_acquisition(const struct sr_dev_inst *sdi);
-SR_PRIV int la2016_has_triggered(const struct sr_dev_inst *sdi);
-SR_PRIV int la2016_start_retrieval(const struct sr_dev_inst *sdi, libusb_transfer_cb_fn cb);
-SR_PRIV int la2016_init_device(const struct sr_dev_inst *sdi);
-SR_PRIV int la2016_deinit_device(const struct sr_dev_inst *sdi);
+SR_PRIV int la2016_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV void la2016_release_resources(const struct sr_dev_inst *sdi);
 
 #endif
index d5237441c42420cf44c46573adb02063209bf0d9..82e43797484ab29a8f247a2b3e17a4b932ea9638 100644 (file)
@@ -19,6 +19,9 @@
  */
 
 #include <config.h>
+
+#include <ctype.h>
+
 #include "protocol.h"
 
 static const uint32_t scanopts[] = {
@@ -46,55 +49,179 @@ static const uint32_t devopts[] = {
        SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED | SR_CONF_GET | SR_CONF_SET,
 };
 
+/* Voltage and current ranges. Values are: Min, max, step. */
+static const double volts_30[] = { 0, 31, 0.01, };
+static const double volts_60[] = { 0, 61, 0.01, };
+static const double amps_3[] = { 0, 3.1, 0.001, };
+static const double amps_5[] = { 0, 5.1, 0.001, };
+
 static const struct korad_kaxxxxp_model models[] = {
-       /* Device enum, vendor, model, ID reply, channels, voltage, current */
-       {VELLEMAN_PS3005D, "Velleman", "PS3005D",
-               "VELLEMANPS3005DV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {VELLEMAN_LABPS3005D, "Velleman", "LABPS3005D",
-               "VELLEMANLABPS3005DV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {KORAD_KA3005P, "Korad", "KA3005P",
-               "KORADKA3005PV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       /* Sometimes the KA3005P has an extra 0x01 after the ID. */
-       {KORAD_KA3005P_0X01, "Korad", "KA3005P",
-               "KORADKA3005PV2.0\x01", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       /* Sometimes the KA3005P has an extra 0xBC after the ID. */
-       {KORAD_KA3005P_0XBC, "Korad", "KA3005P",
-               "KORADKA3005PV2.0\xBC", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {KORAD_KA3005P_V42, "Korad", "KA3005P",
-               "KORAD KA3005P V4.2", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {KORAD_KA3005P_V55, "Korad", "KA3005P",
-               "KORAD KA3005P V5.5", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {KORAD_KD3005P, "Korad", "KD3005P",
-               "KORAD KD3005P V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {KORAD_KD3005P_V20_NOSP, "Korad", "KD3005P",
-               "KORADKD3005PV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {KORAD_KD3005P_V21_NOSP, "Korad", "KD3005P",
-               "KORADKD3005PV2.1", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {RND_320_KD3005P, "RND", "KD3005P",
-               "RND 320-KD3005P V4.2", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {RND_320_KA3005P, "RND", "KA3005P",
-               "RND 320-KA3005P V5.5", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {RND_320K30PV, "RND", "KA3005P",
-               "RND 320-KA3005P V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {TENMA_72_2550_V2, "Tenma", "72-2550",
-               "TENMA72-2550V2.0", 1, {0, 61, 0.01}, {0, 3.1, 0.001}},
-       {TENMA_72_2540_V20, "Tenma", "72-2540",
-               "TENMA72-2540V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {TENMA_72_2540_V21, "Tenma", "72-2540",
-               "TENMA 72-2540 V2.1", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {TENMA_72_2540_V52, "Tenma", "72-2540",
-               "TENMA 72-2540 V5.2", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {TENMA_72_2535_V21, "Tenma", "72-2535",
-               "TENMA 72-2535 V2.1", 1, {0, 31, 0.01}, {0, 3.1, 0.001}},
-       {TENMA_72_2710_V66, "Tenma", "72-2710",
-               "TENMA 72-2710 V6.6", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {STAMOS_SLS31_V20, "Stamos Soldering", "S-LS-31",
-               "S-LS-31 V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
-       {KORAD_KD6005P, "Korad", "KD6005P",
-               "KORAD KD6005P V2.2", 1, {0, 61, 0.01}, {0, 5.1, 0.001}},
+       /* Vendor, model name, ID reply, channels, voltage, current, quirks. */
+       {"Korad", "KA3005P", "", 1, volts_30, amps_5,
+               KORAD_QUIRK_ID_TRAILING},
+       {"Korad", "KD3005P", "", 1, volts_30, amps_5, 0},
+       {"Korad", "KD6005P", "", 1, volts_60, amps_5, 0},
+       {"RND", "KA3005P", "RND 320-KA3005P", 1, volts_30, amps_5,
+               KORAD_QUIRK_ID_OPT_VERSION},
+       {"RND", "KD3005P", "RND 320-KD3005P", 1, volts_30, amps_5,
+               KORAD_QUIRK_ID_OPT_VERSION},
+       {"Stamos Soldering", "S-LS-31", "", 1, volts_30, amps_5,
+               KORAD_QUIRK_ID_NO_VENDOR},
+       {"Tenma", "72-2535", "", 1, volts_30, amps_3, 0},
+       {"Tenma", "72-2540", "", 1, volts_30, amps_5, 0},
+       {"Tenma", "72-2550", "", 1, volts_60, amps_3, 0},
+       {"Tenma", "72-2705", "", 1, volts_30, amps_3, 0},
+       {"Tenma", "72-2710", "", 1, volts_30, amps_5, 0},
+       {"Velleman", "LABPS3005D", "", 1, volts_30, amps_5,
+               KORAD_QUIRK_LABPS_OVP_EN},
+       {"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5,
+               KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING},
+       {"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0},
        ALL_ZERO
 };
 
+/*
+ * Bump this when adding new models[] above. Make sure the text buffer
+ * for the ID response can hold the longest sequence that we expect in
+ * the field which consists of: vendor + model [ + version ][ + serno ].
+ * Don't be too generous here, the maximum receive buffer size affects
+ * the timeout within which the first response character is expected.
+ */
+static const size_t id_text_buffer_size = 48;
+
+/*
+ * Check whether the device's "*IDN?" response matches a supported model.
+ * The caller already stripped off the optional serial number.
+ */
+static gboolean model_matches(const struct korad_kaxxxxp_model *model,
+       const char *id_text)
+{
+       gboolean matches;
+       gboolean opt_version, skip_vendor, accept_trail;
+       const char *want;
+
+       if (!model)
+               return FALSE;
+
+       /*
+        * When the models[] entry contains a specific response text,
+        * then expect to see this very text in literal form. This
+        * lets the driver map weird and untypical responses to a
+        * specific set of display texts for vendor and model names.
+        * Accept an optionally trailing version if models[] says so.
+        */
+       if (model->id && model->id[0]) {
+               opt_version = model->quirks & KORAD_QUIRK_ID_OPT_VERSION;
+               if (!opt_version) {
+                       matches = g_strcmp0(id_text, model->id) == 0;
+                       if (!matches)
+                               return FALSE;
+                       sr_dbg("Matches expected ID text: '%s'.", model->id);
+                       return TRUE;
+               }
+               matches = g_str_has_prefix(id_text, model->id);
+               if (!matches)
+                       return FALSE;
+               id_text += strlen(model->id);
+               while (isspace((int)*id_text))
+                       id_text++;
+               if (*id_text == 'V') {
+                       id_text++;
+                       while (*id_text == '.' || isdigit((int)*id_text))
+                               id_text++;
+                       while (isspace((int)*id_text))
+                               id_text++;
+               }
+               if (*id_text)
+                       return FALSE;
+               sr_dbg("Matches expected ID text [vers]: '%s'.", model->id);
+               return TRUE;
+       }
+
+       /*
+        * A more generic approach which covers most devices: Check
+        * for the very vendor and model names which also are shown
+        * to users (the display texts). Weakened to match responses
+        * more widely: Case insensitive checks, optional whitespace
+        * in responses, optional version details. Optional trailing
+        * garbage. Optional omission of the vendor name. Shall match
+        * all the devices which were individually listed in earlier
+        * implementations of the driver, and shall also match firmware
+        * versions that were not listed before.
+        */
+       skip_vendor = model->quirks & KORAD_QUIRK_ID_NO_VENDOR;
+       accept_trail = model->quirks & KORAD_QUIRK_ID_TRAILING;
+       if (!skip_vendor) {
+               want = model->vendor;
+               matches = g_ascii_strncasecmp(id_text, want, strlen(want)) == 0;
+               if (!matches)
+                       return FALSE;
+               id_text += strlen(want);
+               while (isspace((int)*id_text))
+                       id_text++;
+       }
+       want = model->name;
+       matches = g_ascii_strncasecmp(id_text, want, strlen(want)) == 0;
+       if (!matches)
+               return FALSE;
+       id_text += strlen(want);
+       while (isspace((int)*id_text))
+               id_text++;
+       if (*id_text == 'V') {
+               /* TODO Isolate and (also) return version details? */
+               id_text++;
+               while (*id_text == '.' || isdigit((int)*id_text))
+                       id_text++;
+               while (isspace((int)*id_text))
+                       id_text++;
+       }
+       if (accept_trail) {
+               /*
+                * TODO Determine how many non-printables to accept here
+                * and how strict to check for "known" garbage variants.
+                */
+               switch (*id_text) {
+               case '\x01':
+               case '\xbc':
+                       id_text++;
+                       break;
+               case '\x00':
+                       /* EMPTY */
+                       break;
+               default:
+                       return FALSE;
+               }
+       }
+       if (*id_text)
+               return FALSE;
+       sr_dbg("Matches generic '[vendor] model [vers] [trail]' pattern.");
+       return TRUE;
+}
+
+/* Lookup a model from an ID response text. */
+static const struct korad_kaxxxxp_model *model_lookup(const char *id_text)
+{
+       size_t idx;
+       const struct korad_kaxxxxp_model *check;
+
+       if (!id_text || !*id_text)
+               return NULL;
+       sr_dbg("Looking up: [%s].", id_text);
+
+       for (idx = 0; idx < ARRAY_SIZE(models); idx++) {
+               check = &models[idx];
+               if (!check->name || !check->name[0])
+                       continue;
+               if (!model_matches(check, id_text))
+                       continue;
+               sr_dbg("Found: [%s] [%s]", check->vendor, check->name);
+               return check;
+       }
+       sr_dbg("Not found");
+
+       return NULL;
+}
+
 static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
        static const char *serno_prefix = " SN:";
@@ -107,7 +234,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        const char *force_detect;
        struct sr_serial_dev_inst *serial;
        char reply[50];
-       int ret, i, model_id;
+       int ret;
+       const struct korad_kaxxxxp_model *model;
        size_t len;
        char *serno;
 
@@ -144,17 +272,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        if (serial_open(serial, SERIAL_RDWR) != SR_OK)
                return NULL;
 
-       /*
-        * Prepare a receive buffer for the identification response that
-        * is large enough to hold the longest known model name, and an
-        * optional serial number. Communicate the identification request.
-        */
-       len = 0;
-       for (i = 0; models[i].id; i++) {
-               if (len < strlen(models[i].id))
-                       len = strlen(models[i].id);
-       }
-       len += strlen(serno_prefix) + 12;
+       /* Communicate the identification request. */
+       len = id_text_buffer_size;
        if (len > sizeof(reply) - 1)
                len = sizeof(reply) - 1;
        sr_dbg("Want max %zu bytes.", len);
@@ -178,35 +297,25 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                serno += strlen(serno_prefix);
        }
 
-       model_id = -1;
-       for (i = 0; models[i].id; i++) {
-               if (g_strcmp0(models[i].id, reply) != 0)
-                       continue;
-               model_id = i;
-               break;
-       }
-       if (model_id < 0 && force_detect) {
-               sr_warn("Found model ID '%s' is unknown, trying '%s' spec.",
+       model = model_lookup(reply);
+       if (!model && force_detect) {
+               sr_warn("Could not find model ID '%s', trying '%s'.",
                        reply, force_detect);
-               for (i = 0; models[i].id; i++) {
-                       if (strcmp(models[i].id, force_detect) != 0)
-                               continue;
+               model = model_lookup(force_detect);
+               if (model)
                        sr_info("Found replacement, using it instead.");
-                       model_id = i;
-                       break;
-               }
        }
-       if (model_id < 0) {
-               sr_err("Unknown model ID '%s' detected, aborting.", reply);
+       if (!model) {
+               sr_err("Unsupported model ID '%s', aborting.", reply);
                return NULL;
        }
-       sr_dbg("Found: %s %s (idx %d, ID '%s').", models[model_id].vendor,
-               models[model_id].name, model_id, models[model_id].id);
+       sr_dbg("Found: %s %s (idx %zu).", model->vendor, model->name,
+               model - &models[0]);
 
-       sdi = g_malloc0(sizeof(struct sr_dev_inst));
+       sdi = g_malloc0(sizeof(*sdi));
        sdi->status = SR_ST_INACTIVE;
-       sdi->vendor = g_strdup(models[model_id].vendor);
-       sdi->model = g_strdup(models[model_id].name);
+       sdi->vendor = g_strdup(model->vendor);
+       sdi->model = g_strdup(model->name);
        if (serno)
                sdi->serial_num = g_strdup(serno);
        sdi->inst_type = SR_INST_SERIAL;
@@ -216,11 +325,11 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V");
        sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I");
 
-       devc = g_malloc0(sizeof(struct dev_context));
+       devc = g_malloc0(sizeof(*devc));
        sr_sw_limits_init(&devc->limits);
        g_mutex_init(&devc->rw_mutex);
-       devc->model = &models[model_id];
-       devc->req_sent_at = 0;
+       devc->model = model;
+       devc->next_req_time = 0;
        devc->cc_mode_1_changed = FALSE;
        devc->cc_mode_2_changed = FALSE;
        devc->output_enabled_changed = FALSE;
@@ -409,7 +518,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi)
        sr_sw_limits_acquisition_start(&devc->limits);
        std_session_send_df_header(sdi);
 
-       devc->req_sent_at = 0;
+       devc->next_req_time = 0;
        serial = sdi->conn;
        serial_source_add(sdi->session, serial, G_IO_IN,
                        KAXXXXP_POLL_INTERVAL_MS,
index 1407d51a6d80804fdc75d52d2abd5a2a6984cbfe..bac8c8ef705fe06b8d5428fd66436431109e27be 100644 (file)
 #include "protocol.h"
 
 #define DEVICE_PROCESSING_TIME_MS 80
+#define EXTRA_PROCESSING_TIME_MS  450
 
 SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial,
-                               const char *cmd)
+       const char *cmd)
 {
        int ret;
 
@@ -155,96 +156,112 @@ static void give_device_time_to_process(struct dev_context *devc)
 {
        int64_t sleeping_time;
 
-       sleeping_time = devc->req_sent_at + (DEVICE_PROCESSING_TIME_MS * 1000);
-       sleeping_time -= g_get_monotonic_time();
+       if (!devc->next_req_time)
+               return;
 
+       sleeping_time = devc->next_req_time - g_get_monotonic_time();
        if (sleeping_time > 0) {
                g_usleep(sleeping_time);
                sr_spew("Sleeping for processing %" PRIi64 " usec", sleeping_time);
        }
 }
 
+static int64_t next_req_time(struct dev_context *devc,
+       gboolean is_set, int target)
+{
+       gboolean is_slow_device, is_long_command;
+       int64_t processing_time_us;
+
+       is_slow_device = devc->model->quirks & KORAD_QUIRK_SLOW_PROCESSING;
+       is_long_command = is_set;
+       is_long_command |= target == KAXXXXP_STATUS;
+
+       processing_time_us = DEVICE_PROCESSING_TIME_MS;
+       if (is_slow_device && is_long_command)
+               processing_time_us += EXTRA_PROCESSING_TIME_MS;
+       processing_time_us *= 1000;
+
+       return g_get_monotonic_time() + processing_time_us;
+}
+
 SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
-                               int target, struct dev_context *devc)
+       int target, struct dev_context *devc)
 {
-       char *msg;
-       const char *cmd;
-       float value;
+       char msg[20];
        int ret;
 
        g_mutex_lock(&devc->rw_mutex);
        give_device_time_to_process(devc);
 
+       msg[0] = '\0';
+       ret = SR_OK;
        switch (target) {
        case KAXXXXP_CURRENT:
        case KAXXXXP_VOLTAGE:
        case KAXXXXP_STATUS:
-               sr_err("Can't set measurable parameter %d.", target);
-               g_mutex_unlock(&devc->rw_mutex);
-               return SR_ERR;
+               sr_err("Can't set measured value %d.", target);
+               ret = SR_ERR;
+               break;
        case KAXXXXP_CURRENT_LIMIT:
-               cmd = "ISET1:%05.3f";
-               value = devc->set_current_limit;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "ISET1:%05.3f", devc->set_current_limit);
                break;
        case KAXXXXP_VOLTAGE_TARGET:
-               cmd = "VSET1:%05.2f";
-               value = devc->set_voltage_target;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "VSET1:%05.2f", devc->set_voltage_target);
                break;
        case KAXXXXP_OUTPUT:
-               cmd = "OUT%01.0f";
-               value = (devc->set_output_enabled) ? 1 : 0;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "OUT%1d", (devc->set_output_enabled) ? 1 : 0);
                /* Set value back to recognize changes */
                devc->output_enabled = devc->set_output_enabled;
                break;
        case KAXXXXP_BEEP:
-               cmd = "BEEP%01.0f";
-               value = (devc->set_beep_enabled) ? 1 : 0;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "BEEP%1d", (devc->set_beep_enabled) ? 1 : 0);
                break;
        case KAXXXXP_OCP:
-               cmd = "OCP%01.0f";
-               value = (devc->set_ocp_enabled) ? 1 : 0;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "OCP%1d", (devc->set_ocp_enabled) ? 1 : 0);
                /* Set value back to recognize changes */
                devc->ocp_enabled = devc->set_ocp_enabled;
                break;
        case KAXXXXP_OVP:
-               cmd = "OVP%01.0f";
-               value = (devc->set_ovp_enabled) ? 1 : 0;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "OVP%1d", (devc->set_ovp_enabled) ? 1 : 0);
                /* Set value back to recognize changes */
                devc->ovp_enabled = devc->set_ovp_enabled;
                break;
        case KAXXXXP_SAVE:
-               cmd = "SAV%01.0f";
                if (devc->program < 1 || devc->program > 5) {
-                       sr_err("Only programs 1-5 supported and %d isn't "
-                              "between them.", devc->program);
-                       g_mutex_unlock(&devc->rw_mutex);
-                       return SR_ERR;
+                       sr_err("Program %d is not in the supported 1-5 range.",
+                              devc->program);
+                       ret = SR_ERR;
+                       break;
                }
-               value = devc->program;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "SAV%1d", devc->program);
                break;
        case KAXXXXP_RECALL:
-               cmd = "RCL%01.0f";
                if (devc->program < 1 || devc->program > 5) {
-                       sr_err("Only programs 1-5 supported and %d isn't "
-                              "between them.", devc->program);
-                       g_mutex_unlock(&devc->rw_mutex);
-                       return SR_ERR;
+                       sr_err("Program %d is not in the supported 1-5 range.",
+                              devc->program);
+                       ret = SR_ERR;
+                       break;
                }
-               value = devc->program;
+               sr_snprintf_ascii(msg, sizeof(msg),
+                       "RCL%1d", devc->program);
                break;
        default:
-               sr_err("Don't know how to set %d.", target);
-               g_mutex_unlock(&devc->rw_mutex);
-               return SR_ERR;
+               sr_err("Don't know how to set target %d.", target);
+               ret = SR_ERR;
+               break;
        }
 
-       msg = g_malloc0(20 + 1);
-       if (cmd)
-               sr_snprintf_ascii(msg, 20, cmd, value);
-
-       ret = korad_kaxxxxp_send_cmd(serial, msg);
-       devc->req_sent_at = g_get_monotonic_time();
-       g_free(msg);
+       if (ret == SR_OK && msg[0]) {
+               ret = korad_kaxxxxp_send_cmd(serial, msg);
+               devc->next_req_time = next_req_time(devc, TRUE, target);
+       }
 
        g_mutex_unlock(&devc->rw_mutex);
 
@@ -252,12 +269,13 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
 }
 
 SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
-                               int target, struct dev_context *devc)
+       int target, struct dev_context *devc)
 {
        int ret, count;
        char reply[6];
        float *value;
        char status_byte;
+       gboolean needs_ovp_quirk;
        gboolean prev_status;
 
        g_mutex_lock(&devc->rw_mutex);
@@ -297,11 +315,14 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
                break;
        default:
                sr_err("Don't know how to query %d.", target);
+               ret = SR_ERR;
+       }
+       if (ret < 0) {
                g_mutex_unlock(&devc->rw_mutex);
-               return SR_ERR;
+               return ret;
        }
 
-       devc->req_sent_at = g_get_monotonic_time();
+       devc->next_req_time = next_req_time(devc, FALSE, target);
 
        if ((ret = korad_kaxxxxp_read_chars(serial, count, reply)) < 0) {
                g_mutex_unlock(&devc->rw_mutex);
@@ -342,9 +363,8 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
                devc->output_enabled_changed = devc->output_enabled != prev_status;
 
                /* OVP enabled, special handling for Velleman LABPS3005 quirk. */
-               if ((devc->model->model_id == VELLEMAN_LABPS3005D && devc->output_enabled) ||
-                       devc->model->model_id != VELLEMAN_LABPS3005D) {
-
+               needs_ovp_quirk = devc->model->quirks & KORAD_QUIRK_LABPS_OVP_EN;
+               if (!needs_ovp_quirk || devc->output_enabled) {
                        prev_status = devc->ovp_enabled;
                        devc->ovp_enabled = status_byte & (1 << 7);
                        devc->ovp_enabled_changed = devc->ovp_enabled != prev_status;
@@ -374,7 +394,7 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
 }
 
 SR_PRIV int korad_kaxxxxp_get_all_values(struct sr_serial_dev_inst *serial,
-                               struct dev_context *devc)
+       struct dev_context *devc)
 {
        int ret, target;
 
index 315961b1ebbba5855c7ba430a257c6ad1fbf7f2c..005d94ab15f7757ade08b9fecf05f12079ae549a 100644 (file)
 
 #define KAXXXXP_POLL_INTERVAL_MS 80
 
-enum {
-       VELLEMAN_PS3005D,
-       VELLEMAN_LABPS3005D,
-       KORAD_KA3005P,
-       KORAD_KA3005P_0X01,
-       KORAD_KA3005P_0XBC,
-       KORAD_KA3005P_V42,
-       KORAD_KA3005P_V55,
-       KORAD_KD3005P,
-       KORAD_KD3005P_V20_NOSP,
-       KORAD_KD3005P_V21_NOSP,
-       RND_320_KD3005P,
-       RND_320_KA3005P,
-       RND_320K30PV,
-       TENMA_72_2550_V2,
-       TENMA_72_2540_V20,
-       TENMA_72_2540_V21,
-       TENMA_72_2540_V52,
-       TENMA_72_2535_V21,
-       TENMA_72_2710_V66,
-       STAMOS_SLS31_V20,
-       KORAD_KD6005P,
-       /* Support for future devices with this protocol. */
+enum korad_quirks_flag {
+       KORAD_QUIRK_NONE = 0,
+       KORAD_QUIRK_LABPS_OVP_EN = 1UL << 0,
+       KORAD_QUIRK_ID_NO_VENDOR = 1UL << 1,
+       KORAD_QUIRK_ID_TRAILING = 1UL << 2,
+       KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3,
+       KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4,
+       KORAD_QUIRK_ALL = (1UL << 5) - 1,
 };
 
 /* Information on single model */
 struct korad_kaxxxxp_model {
-       int model_id; /**< Model info */
        const char *vendor; /**< Vendor name */
        const char *name; /**< Model name */
        const char *id; /**< Model ID, as delivered by interface */
        int channels; /**< Number of channels */
-       double voltage[3]; /**< Min, max, step */
-       double current[3]; /**< Min, max, step */
+       const double *voltage; /**< References: Min, max, step */
+       const double *current; /**< References: Min, max, step */
+       enum korad_quirks_flag quirks;
 };
 
 /* Reply targets */
@@ -86,7 +71,7 @@ struct dev_context {
        const struct korad_kaxxxxp_model *model; /**< Model information. */
 
        struct sr_sw_limits limits;
-       int64_t req_sent_at;
+       int64_t next_req_time;
        GMutex rw_mutex;
 
        float current;          /**< Last current value [A] read from device. */
@@ -118,15 +103,15 @@ struct dev_context {
 };
 
 SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial,
-                                       const char *cmd);
+               const char *cmd);
 SR_PRIV int korad_kaxxxxp_read_chars(struct sr_serial_dev_inst *serial,
-                                       size_t count, char *buf);
+               size_t count, char *buf);
 SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
-                                       int target, struct dev_context *devc);
+               int target, struct dev_context *devc);
 SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
-                                       int target, struct dev_context *devc);
+               int target, struct dev_context *devc);
 SR_PRIV int korad_kaxxxxp_get_all_values(struct sr_serial_dev_inst *serial,
-                                       struct dev_context *devc);
+               struct dev_context *devc);
 SR_PRIV int korad_kaxxxxp_receive_data(int fd, int revents, void *cb_data);
 
 #endif
index a05ff618d5e4c8eeaa1877f6fc46b6ada7ac7875..8cc2bbd3b060c03b99f9fb49465c7fc5000848bd 100644 (file)
@@ -527,14 +527,9 @@ SR_PRIV int lecroy_xstream_init_device(struct sr_dev_inst *sdi)
                ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, channel_enabled,
                        (*scope_models[model_index].analog_names)[i]);
 
-               devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group));
-
-               devc->analog_groups[i]->name = g_strdup(
-                       (char *)(*scope_models[model_index].analog_names)[i]);
+               devc->analog_groups[i] = sr_channel_group_new(sdi,
+                       (*scope_models[model_index].analog_names)[i], NULL);
                devc->analog_groups[i]->channels = g_slist_append(NULL, ch);
-
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                       devc->analog_groups[i]);
        }
 
        devc->model_config = &scope_models[model_index];
index 54444d623c6d1484bfbe31f3da3d99d512a91c33..20a922fa215177a139587c94121693ec6bac45a0 100644 (file)
@@ -145,9 +145,7 @@ static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus)
        sdi->driver = &maynuo_m97_driver_info;
        sdi->inst_type = SR_INST_MODBUS;
 
-       cg = g_malloc0(sizeof(struct sr_channel_group));
-       cg->name = g_strdup("1");
-       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+       cg = sr_channel_group_new(sdi, "1", NULL);
 
        ch = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V1");
        cg->channels = g_slist_append(cg->channels, ch);
index 4eb755d274d057e3eae731e378c8f815b9589e4e..97d5b13cd96392c70a5fc0eb382004e00a540d57 100644 (file)
@@ -72,6 +72,7 @@ static const char *channel_names[] = {
 
 static const uint32_t scanopts[] = {
        SR_CONF_CONN,
+       SR_CONF_PROBE_NAMES,
 };
 
 static const uint32_t drvopts[] = {
@@ -119,6 +120,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
        struct drv_context *drvc;
        const char *conn;
+       const char *probe_names;
        GSList *l, *devices, *usb_devices;
        struct sr_config *cfg;
        struct sr_usb_dev_inst *usb;
@@ -131,12 +133,16 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        drvc = di->context;
 
        conn = PICKIT2_DEFAULT_ADDRESS;
+       probe_names = NULL;
        for (l = options; l; l = l->next) {
                cfg = l->data;
                switch (cfg->key) {
                case SR_CONF_CONN:
                        conn = g_variant_get_string(cfg->data, NULL);
                        break;
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(cfg->data, NULL);
+                       break;
                }
        }
 
@@ -158,17 +164,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                sdi->conn = usb;
                sdi->connection_id = g_strdup(conn);
 
-               /* Create the logic channels group. */
-               cg = g_malloc0(sizeof(*cg));
-               sdi->channel_groups = g_slist_append(NULL, cg);
-               cg->name = g_strdup("Logic");
-               ch_count = ARRAY_SIZE(channel_names);
-               for (ch_idx = 0; ch_idx < ch_count; ch_idx++) {
-                       ch = sr_channel_new(sdi, ch_idx, SR_CHANNEL_LOGIC,
-                               TRUE, channel_names[ch_idx]);
-                       cg->channels = g_slist_append(cg->channels, ch);
-               }
-
                /*
                 * Create the device context. Pre-select the highest
                 * samplerate and the deepest sample count available.
@@ -182,6 +177,17 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                devc->num_captureratios = ARRAY_SIZE(captureratios);
                devc->curr_captureratio_idx = 0;
                devc->sw_limits.limit_samples = PICKIT2_SAMPLE_COUNT;
+               devc->channel_names = sr_parse_probe_names(probe_names,
+                       channel_names, ARRAY_SIZE(channel_names),
+                       ARRAY_SIZE(channel_names), &ch_count);
+
+               /* Create the logic channels group. */
+               cg = sr_channel_group_new(sdi, "Logic", NULL);
+               for (ch_idx = 0; ch_idx < ch_count; ch_idx++) {
+                       ch = sr_channel_new(sdi, ch_idx, SR_CHANNEL_LOGIC,
+                               TRUE, devc->channel_names[ch_idx]);
+                       cg->channels = g_slist_append(cg->channels, ch);
+               }
        }
 
        return std_scan_complete(di, devices);
@@ -269,21 +275,27 @@ static int config_get(uint32_t key, GVariant **data,
        (void)cg;
 
        devc = sdi ? sdi->priv : NULL;
+       usb = sdi ? sdi->conn : NULL;
 
        switch (key) {
        case SR_CONF_CONN:
-               if (!sdi->conn)
+               if (!usb)
                        return SR_ERR_ARG;
-               usb = sdi->conn;
                *data = g_variant_new_printf("%d.%d", usb->bus, usb->address);
                return SR_OK;
        case SR_CONF_SAMPLERATE:
+               if (!devc)
+                       return SR_ERR_ARG;
                rate = devc->samplerates[devc->curr_samplerate_idx];
                *data = g_variant_new_uint64(rate);
                return SR_OK;
        case SR_CONF_LIMIT_SAMPLES:
+               if (!devc)
+                       return SR_ERR_ARG;
                return sr_sw_limits_config_get(&devc->sw_limits, key, data);
        case SR_CONF_CAPTURE_RATIO:
+               if (!devc)
+                       return SR_ERR_ARG;
                ratio = devc->captureratios[devc->curr_captureratio_idx];
                *data = g_variant_new_uint64(ratio);
                return SR_OK;
index 500964730980b5b392bc0f01cec6f897a475c963..28b152f00ea1f9ef3cdd12be1972556c148f9907 100644 (file)
@@ -40,6 +40,7 @@ enum pickit_state {
 };
 
 struct dev_context {
+       char **channel_names;
        enum pickit_state state;
        const uint64_t *samplerates;
        size_t num_samplerates;
index b1b940683038400e6e77b6de94e3b2628e02f310..6e0b01847217790298a061da498cb042ddb7b453 100644 (file)
@@ -111,7 +111,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
          *         descr - handle: 0x0016, uuid: 00002902-0000-1000-8000-00805f9b34fb
          *         descr - handle: 0x0017, uuid: 00002901-0000-1000-8000-00805f9b34fb
         */
-       ret = sr_bt_config_notify(desc, 0x0015, 0x0012, 0x0016, 0x0001);
+       ret = sr_bt_config_notify(desc, 0x0015, 0x0012, 0x0016, 0x0001, 0);
        if (ret < 0)
                goto err;
 
index 31bbfa43827f6c75fecb628bb461f093592fd51e..b29346ca1cbcab166b6e37d6210ed08b11bec310 100644 (file)
@@ -438,13 +438,9 @@ static GSList *do_scan(lps_modelid modelid, struct sr_dev_driver *drv, GSList *o
 
                devc->channel_status[cnt].info = g_slist_append(NULL, ch);
 
-               cg = g_malloc(sizeof(struct sr_channel_group));
                snprintf(channel, sizeof(channel), "CG%d", cnt + 1);
-               cg->name = g_strdup(channel);
-               cg->priv = NULL;
+               cg = sr_channel_group_new(sdi, channel, NULL);
                cg->channels = g_slist_append(NULL, ch);
-
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 
        /* Query status */
index 788d4b8098c7a1ca4c5dc8276d8adb93183c6849..09ef065939c22e6511775c1bc3f25fa62ea6bd3a 100644 (file)
@@ -25,6 +25,7 @@
 static const uint32_t scanopts[] = {
        SR_CONF_CONN,
        SR_CONF_SERIALCOMM,
+       SR_CONF_PROBE_NAMES,
 };
 
 static const uint32_t drvopts[] = {
@@ -32,6 +33,7 @@ static const uint32_t drvopts[] = {
 };
 
 static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
        SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
        SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
        SR_CONF_TRIGGER_MATCH | SR_CONF_LIST,
@@ -99,10 +101,13 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        GSList *l;
        int num_read;
        unsigned int i;
-       const char *conn, *serialcomm;
+       const char *conn, *serialcomm, *probe_names;
        char buf[4] = { 0, 0, 0, 0 };
+       struct dev_context *devc;
+       size_t ch_max;
 
        conn = serialcomm = NULL;
+       probe_names = NULL;
        for (l = options; l; l = l->next) {
                src = l->data;
                switch (src->key) {
@@ -112,6 +117,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                case SR_CONF_SERIALCOMM:
                        serialcomm = g_variant_get_string(src->data, NULL);
                        break;
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(src->data, NULL);
+                       break;
                }
        }
        if (!conn)
@@ -160,37 +168,59 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
                sr_hexdump_free(id);
                return NULL;
+       } else {
+               sr_dbg("Successful detection, got '%c%c%c%c' (0x%02x 0x%02x 0x%02x 0x%02x).",
+                      buf[0], buf[1], buf[2], buf[3],
+                      buf[0], buf[1], buf[2], buf[3]);
        }
 
-       /* Definitely using the OLS protocol, check if it supports
-        * the metadata command.
+       /*
+        * Create common data structures (sdi, devc) here in the common
+        * code path. These further get filled in either from metadata
+        * which is gathered from the device, or from open coded generic
+        * fallback data which is kept in the driver source code.
+        */
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->status = SR_ST_INACTIVE;
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->conn = serial;
+       sdi->connection_id = g_strdup(serial->port);
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+       devc->trigger_at_smpl = OLS_NO_TRIGGER;
+       devc->channel_names = sr_parse_probe_names(probe_names,
+               ols_channel_names, ARRAY_SIZE(ols_channel_names),
+               ARRAY_SIZE(ols_channel_names), &ch_max);
+
+       /*
+        * Definitely using the OLS protocol, check if it supports
+        * the metadata command. Otherwise assign generic values.
+        * Create as many sigrok channels as was determined when
+        * the device was probed.
         */
        send_shortcommand(serial, CMD_METADATA);
-
        g_usleep(RESPONSE_DELAY_US);
-
        if (serial_has_receive_data(serial) != 0) {
                /* Got metadata. */
-               sdi = get_metadata(serial);
+               (void)ols_get_metadata(sdi);
        } else {
-               /* Not an OLS -- some other board that uses the sump protocol. */
+               /* Not an OLS -- some other board using the SUMP protocol. */
                sr_info("Device does not support metadata.");
-               sdi = g_malloc0(sizeof(struct sr_dev_inst));
-               sdi->status = SR_ST_INACTIVE;
                sdi->vendor = g_strdup("Sump");
                sdi->model = g_strdup("Logic Analyzer");
                sdi->version = g_strdup("v1.0");
-               for (i = 0; i < ARRAY_SIZE(ols_channel_names); i++)
-                       sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE,
-                                      ols_channel_names[i]);
-               sdi->priv = ols_dev_new();
+               devc->max_channels = ch_max;
+       }
+       if (devc->max_channels && ch_max > devc->max_channels)
+               ch_max = devc->max_channels;
+       for (i = 0; i < ch_max; i++) {
+               sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE,
+                       devc->channel_names[i]);
        }
        /* Configure samplerate and divider. */
        if (ols_set_samplerate(sdi, DEFAULT_SAMPLERATE) != SR_OK)
                sr_dbg("Failed to set default samplerate (%" PRIu64 ").",
                       DEFAULT_SAMPLERATE);
-       sdi->inst_type = SR_INST_SERIAL;
-       sdi->conn = serial;
 
        serial_close(serial);
 
@@ -211,6 +241,11 @@ static int config_get(uint32_t key, GVariant **data,
        devc = sdi->priv;
 
        switch (key) {
+       case SR_CONF_CONN:
+               if (!sdi->conn || !sdi->connection_id)
+                       return SR_ERR_NA;
+               *data = g_variant_new_string(sdi->connection_id);
+               break;
        case SR_CONF_SAMPLERATE:
                *data = g_variant_new_uint64(devc->cur_samplerate);
                break;
index c747753abde3a0c01794a5636c728c688df4700e..125c279d0f8f11cb455089a2e41c3df937a68d75 100644 (file)
@@ -139,28 +139,6 @@ static int convert_trigger(const struct sr_dev_inst *sdi,
        return SR_OK;
 }
 
-SR_PRIV struct dev_context *ols_dev_new(void)
-{
-       struct dev_context *devc;
-
-       devc = g_malloc0(sizeof(struct dev_context));
-       devc->trigger_at_smpl = OLS_NO_TRIGGER;
-
-       return devc;
-}
-
-static void ols_channel_new(struct sr_dev_inst *sdi, int num_chan)
-{
-       struct dev_context *devc = sdi->priv;
-       int i;
-
-       for (i = 0; i < num_chan; i++)
-               sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE,
-                              ols_channel_names[i]);
-
-       devc->max_channels = num_chan;
-}
-
 static void ols_metadata_quirks(struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
@@ -175,7 +153,7 @@ static void ols_metadata_quirks(struct sr_dev_inst *sdi)
        is_shrimp = sdi->model && strcmp(sdi->model, "Shrimp1.0") == 0;
        if (is_shrimp) {
                if (!devc->max_channels)
-                       ols_channel_new(sdi, 4);
+                       devc->max_channels = 4;
                if (!devc->max_samples)
                        devc->max_samples = 256 * 1024;
                if (!devc->max_samplerate)
@@ -186,9 +164,9 @@ static void ols_metadata_quirks(struct sr_dev_inst *sdi)
                devc->device_flags |= DEVICE_FLAG_IS_DEMON_CORE;
 }
 
-SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial)
+SR_PRIV int ols_get_metadata(struct sr_dev_inst *sdi)
 {
-       struct sr_dev_inst *sdi;
+       struct sr_serial_dev_inst *serial;
        struct dev_context *devc;
        uint32_t tmp_int;
        uint8_t key, type;
@@ -196,10 +174,8 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial)
        GString *tmp_str, *devname, *version;
        guchar tmp_c;
 
-       sdi = g_malloc0(sizeof(struct sr_dev_inst));
-       sdi->status = SR_ST_INACTIVE;
-       devc = ols_dev_new();
-       sdi->priv = devc;
+       serial = sdi->conn;
+       devc = sdi->priv;
 
        devname = g_string_new("");
        version = g_string_new("");
@@ -264,7 +240,7 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial)
                        switch (key) {
                        case METADATA_TOKEN_NUM_PROBES_LONG:
                                /* Number of usable channels */
-                               ols_channel_new(sdi, tmp_int);
+                               devc->max_channels = tmp_int;
                                break;
                        case METADATA_TOKEN_SAMPLE_MEMORY_BYTES:
                                /* Amount of sample memory available (bytes) */
@@ -298,7 +274,7 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial)
                        switch (key) {
                        case METADATA_TOKEN_NUM_PROBES_SHORT:
                                /* Number of usable channels */
-                               ols_channel_new(sdi, tmp_c);
+                               devc->max_channels = tmp_c;
                                break;
                        case METADATA_TOKEN_PROTOCOL_VERSION_SHORT:
                                /* protocol version */
@@ -316,15 +292,13 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial)
                }
        }
 
-       sdi->model = devname->str;
-       sdi->version = version->str;
-       g_string_free(devname, FALSE);
-       g_string_free(version, FALSE);
+       sdi->model = g_string_free(devname, FALSE);
+       sdi->version = g_string_free(version, FALSE);
 
        /* Optionally amend received metadata, model specific quirks. */
        ols_metadata_quirks(sdi);
 
-       return sdi;
+       return SR_OK;
 }
 
 SR_PRIV int ols_set_samplerate(const struct sr_dev_inst *sdi,
index 01a3821366a593e684e046ed804bd7700e9ebf8c..00ed35bed177fdba2b031b8c5d579cbdd04b15fb 100644 (file)
 #define OLS_NO_TRIGGER (-1)
 
 struct dev_context {
+       char **channel_names;
+
        /* constant device properties: */
-       int max_channels;
+       size_t max_channels;
        uint32_t max_samples;
        uint32_t max_samplerate;
        uint32_t protocol_version;
@@ -135,8 +137,7 @@ SR_PRIV int send_longcommand(struct sr_serial_dev_inst *serial, uint8_t command,
 SR_PRIV int ols_send_reset(struct sr_serial_dev_inst *serial);
 SR_PRIV int ols_prepare_acquisition(const struct sr_dev_inst *sdi);
 SR_PRIV uint32_t ols_channel_mask(const struct sr_dev_inst *sdi);
-SR_PRIV struct dev_context *ols_dev_new(void);
-SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial);
+SR_PRIV int ols_get_metadata(struct sr_dev_inst *sdi);
 SR_PRIV int ols_set_samplerate(const struct sr_dev_inst *sdi,
                               uint64_t samplerate);
 SR_PRIV void abort_acquisition(const struct sr_dev_inst *sdi);
index 1b54dd164e23075f4fb3c77c53fb3a810d5bf081..8f6f97f7d716e5aac585a72f49cf70a667517b6a 100644 (file)
@@ -325,10 +325,8 @@ SR_PRIV struct sr_dev_inst *p_ols_get_metadata(uint8_t *buf, int bytes_read, str
                }
        }
 
-       sdi->model = devname->str;
-       sdi->version = version->str;
-       g_string_free(devname, FALSE);
-       g_string_free(version, FALSE);
+       sdi->model = g_string_free(devname, FALSE);
+       sdi->version = g_string_free(version, FALSE);
 
        return sdi;
 }
diff --git a/src/hardware/raspberrypi-pico/api.c b/src/hardware/raspberrypi-pico/api.c
new file mode 100644 (file)
index 0000000..171b5ea
--- /dev/null
@@ -0,0 +1,860 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2022 Shawn Walker <ac0bi00@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#include <config.h>
+#include <fcntl.h>
+#include <glib.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include "protocol.h"
+
+/* Baud rate is really a don't care because we run USB CDC, dtr must be 1.
+ * flow should be zero since we don't use xon/xoff */
+#define SERIALCOMM "115200/8n1/dtr=1/rts=0/flow=0"
+
+/* Use the force_detect scan option as a way to pass user information to the
+ * device the string must use only 0-9,a-z,A-Z,'.','=' and '-'* and be less than
+ * 60 characters */
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,           /* Required OS name for the port, i.e. /dev/ttyACM0 */
+       SR_CONF_SERIALCOMM,     /* Optional config of the port, i.e. 115200/8n1 */
+       SR_CONF_FORCE_DETECT
+};
+
+/* Sample rate can either provide a std_gvar_samplerates_steps or a
+ * std_gvar_samplerates. The latter is just a long list of every supported rate.
+ * For the steps, pulseview/pv/toolbars/mainbar.cpp will do a min,max,step. If
+ * step is 1 then it provides a 1,2,5,10 select otherwise it allows a spin box.
+ * Going with the full list because while the spin box is more flexible, it is
+ * harder to read */
+static const uint64_t samplerates[] = {
+       SR_KHZ(5),
+       SR_KHZ(6),
+       SR_KHZ(8),
+       SR_KHZ(10),
+       SR_KHZ(20),
+       SR_KHZ(30),
+       SR_KHZ(40),
+       SR_KHZ(50),
+       SR_KHZ(60),
+       SR_KHZ(80),
+       SR_KHZ(100),
+       SR_KHZ(125),
+       SR_KHZ(150),
+       SR_KHZ(160), /* max rate of 3 ADC chans that has integer divisor/dividend */
+       SR_KHZ(200),
+       SR_KHZ(250), /* max rate of 2 ADC chans */
+       SR_KHZ(300),
+       SR_KHZ(400),
+       SR_KHZ(500),
+       SR_KHZ(600),
+       SR_KHZ(800),
+       /* Give finer granularity near the thresholds of RLE effectiveness ~1-4Msps
+        * Also use 1.2 and 2.4 as likely max values for ADC overclocking */
+       SR_MHZ(1),
+       SR_MHZ(1.2),
+       SR_MHZ(1.5),
+       SR_MHZ(2),
+       SR_MHZ(2.4),
+       SR_MHZ(3),
+       SR_MHZ(4),
+       SR_MHZ(5),
+       SR_MHZ(6),
+       SR_MHZ(8),
+       SR_MHZ(10),
+       SR_MHZ(15),
+       SR_MHZ(20),
+       SR_MHZ(30),
+       SR_MHZ(40),
+       SR_MHZ(60),
+       /* The baseline 120Mhz PICO clock won't support an 80 or 100
+        * with non fractional divisor, but an overclocked version or one
+        * that modified sysclk could */
+       SR_MHZ(80),
+       SR_MHZ(100),
+       SR_MHZ(120),
+       /* These may not be practically useful, but someone might want to
+        * try to make it work with overclocking */
+       SR_MHZ(150),
+       SR_MHZ(200),
+       SR_MHZ(240),
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_OSCILLOSCOPE,
+       SR_CONF_LOGIC_ANALYZER,
+};
+
+static const int32_t trigger_matches[] = {
+       SR_TRIGGER_ZERO,
+       SR_TRIGGER_ONE,
+       SR_TRIGGER_RISING,
+       SR_TRIGGER_FALLING,
+       SR_TRIGGER_EDGE,
+};
+
+
+static const uint32_t devopts[] = {
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_TRIGGER_MATCH | SR_CONF_LIST,
+       SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+static struct sr_dev_driver raspberrypi_pico_driver_info;
+
+
+static GSList *scan(struct sr_dev_driver *di, GSList * options)
+{
+       struct sr_config *src;
+       struct sr_dev_inst *sdi;
+       struct sr_serial_dev_inst *serial;
+       struct dev_context *devc;
+       struct sr_channel *ch;
+       GSList *l;
+       int num_read;
+       int i;
+       const char *conn, *serialcomm, *force_detect;
+       char buf[32];
+       char ustr[64];
+       int len;
+       uint8_t num_a, num_d, a_size;
+       gchar *channel_name;
+
+       conn = serialcomm = force_detect = NULL;
+       for (l = options; l; l = l->next) {
+               src = l->data;
+               switch (src->key) {
+               case SR_CONF_CONN:
+                       conn = g_variant_get_string(src->data, NULL);
+                       break;
+               case SR_CONF_SERIALCOMM:
+                       serialcomm = g_variant_get_string(src->data, NULL);
+                       break;
+               case SR_CONF_FORCE_DETECT:
+                       force_detect = g_variant_get_string(src->data, NULL);
+                       sr_info("Force detect string %s", force_detect);
+                       break;
+               }
+       }
+       if (!conn)
+               return NULL;
+
+       if (!serialcomm)
+               serialcomm = SERIALCOMM;
+
+       serial = sr_serial_dev_inst_new(conn, serialcomm);
+       sr_info("Opening %s.", conn);
+       if (serial_open(serial, SERIAL_RDWR) != SR_OK) {
+               sr_err("1st serial open fail");
+               return NULL;
+       }
+
+       sr_info("Resetting device with *s at %s.", conn);
+       send_serial_char(serial, '*');
+       g_usleep(10000);
+       do {
+               sr_warn("Drain reads");
+               len = serial_read_blocking(serial, buf, 32, 100);
+               sr_warn("Drain reads done");
+               if (len)
+                       sr_dbg("Dropping in flight serial data");
+       } while (len > 0);
+       /* Send the user string with the identify */
+       if (force_detect && (strlen(force_detect) <= 60)) {
+               sprintf(ustr,"i%s\n", force_detect);
+               sr_info("User string %s", ustr);
+               num_read = send_serial_w_resp(serial, ustr, buf, 17);
+       } else {
+               num_read = send_serial_w_resp(serial, "i\n", buf, 17);
+       }
+       if (num_read < 16) {
+               sr_err("1st identify failed");
+               serial_close(serial);
+               g_usleep(100000);
+               if (serial_open(serial, SERIAL_RDWR) != SR_OK) {
+                       sr_err("2nd serial open fail");
+                       return NULL;
+               }
+               g_usleep(100000);
+               sr_err("Send second *");
+               send_serial_char(serial, '*');
+               g_usleep(100000);
+               num_read = send_serial_w_resp(serial, "i\n", buf, 17);
+               if (num_read < 10) {
+                       sr_err("Second attempt failed");
+                       return NULL;
+               }
+       }
+
+       /* Expected ID response is SRPICO,AxxyDzz,VV
+        * where xx are number of analog channels, y is bytes per analog sample
+        * (7 bits per byte), zz is number of digital channels, and VV is two digit
+        * version# which must be 02 */
+       if ((num_read < 16) || (strncmp(buf, "SRPICO,A", 8)) \
+               || (buf[11] != 'D') || (buf[15] != '0') || (buf[16] != '2')) {
+               sr_err("ERROR: Bad response string %s %d", buf, num_read);
+               return NULL;
+       }
+
+       a_size = buf[10] - '0';
+       buf[10] = '\0';         /*Null to end the str for atois */
+       buf[14] = '\0';         
+       num_a = atoi(&buf[8]);
+       num_d = atoi(&buf[12]);
+
+       sdi = g_malloc0(sizeof(struct sr_dev_inst));
+       sdi->status = SR_ST_INACTIVE;
+       sdi->vendor = g_strdup("Raspberry Pi");
+       sdi->model = g_strdup("PICO");
+       sdi->version = g_strdup("00");
+       sdi->conn = serial;
+       sdi->driver = &raspberrypi_pico_driver_info;
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->serial_num = g_strdup("N/A");
+
+       if (((num_a == 0) && (num_d == 0)) \
+               || (num_a > MAX_ANALOG_CHANNELS) || (num_d > MAX_DIGITAL_CHANNELS)
+               || (a_size < 1) || (a_size > 4)) {
+               sr_err("ERROR: invalid channel config a %d d %d asz %d",
+                       num_a, num_d, a_size);
+               return NULL;
+       }
+
+       devc = g_malloc0(sizeof(struct dev_context));
+       devc->a_size = a_size;
+       devc->num_a_channels = num_a;
+       devc->num_d_channels = num_d;
+       devc->a_chan_mask = ((1 << num_a) - 1);
+       devc->d_chan_mask = ((1 << num_d) - 1);
+
+       /* The number of bytes that each digital sample in the buffers sent to the
+        * session. All logical channels are packed together, where a slice of N
+        * channels takes roundup(N/8) bytes. This never changes even if channels
+        * are disabled because PV expects disabled channels to still be accounted
+        * for in the packing */
+       devc->dig_sample_bytes = ((devc->num_d_channels + 7) / 8);
+       /* These are the slice sizes of the data on the wire
+        * 1 7 bit field per byte */
+       devc->bytes_per_slice = (devc->num_a_channels * devc->a_size);
+
+       if (devc->num_d_channels > 0) {
+               /* logic sent in groups of 7*/
+               devc->bytes_per_slice += (devc->num_d_channels + 6) / 7;
+       }
+       sr_dbg("num channels a %d d %d bps %d dsb %d", num_a, num_d,
+               devc->bytes_per_slice, devc->dig_sample_bytes);
+
+       /* Each analog channel is its own group; digital are just channels;
+        * Grouping of channels is rather arbitrary as parameters like sample rate
+        * and number of samples apply to all channels. Analog channels do have a
+        * scale and offset, but that is applied automatically. */
+       devc->analog_groups = g_malloc0(sizeof(struct sr_channel_group *) *
+               devc->num_a_channels);
+       for (i = 0; i < devc->num_a_channels; i++) {
+               channel_name = g_strdup_printf("A%d", i);
+               ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_name);
+               devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group));
+               devc->analog_groups[i]->name = channel_name;
+               devc->analog_groups[i]->channels = g_slist_append(NULL, ch);
+               sdi->channel_groups = g_slist_append(sdi->channel_groups,
+                       devc->analog_groups[i]);
+       }
+
+       if (devc->num_d_channels > 0) {
+               for (i = 0; i < devc->num_d_channels; i++) {
+                       /* Name digital channels starting at D2 to match pico board names */
+                       channel_name = g_strdup_printf("D%d", i + 2);
+                       sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name);
+                       g_free(channel_name);
+               }
+       }
+
+       /* In large sample usages we get the call to receive with large transfers.
+        * Since the CDC serial implemenation can silenty lose data as it gets close
+        * to full, allocate storage for a half buffer which in a worst case
+        * scenario has 2x ratio of transmitted bytes to storage bytes.
+        * Note: The intent of making this buffer large is to prevent CDC serial
+        * buffer overflows. However, it is likely that if the host is running slow
+        * (i.e. it's a raspberry pi model 3) that it becomes compute bound and
+        * doesn't service CDC serial responses in time to not overflow the internal
+        * CDC buffers. Thus no serial buffer is large enough.
+        * But, it's only 32K... */
+       devc->serial_buffer_size = 32000;
+       devc->buffer = NULL;
+       sr_dbg("Setting serial buffer size: %i.", devc->serial_buffer_size);
+
+       devc->cbuf_wrptr = 0;
+       /* While slices are sent as a group of one sample across all channels,
+        * sigrok wants analog channel data sent as separate packets. Logical trace
+        * values are packed together. An RLE byte in normal mode can represent up
+        * to 1640 samples. In D4 an RLE byte can represent up to 640 samples.
+        * Rather than making the sample_buf_size 1640x the size of serial buffer,
+        * we require that the process loops push samples to the session as we get
+        * anywhere close to full. */
+
+       devc->sample_buf_size = devc->serial_buffer_size;
+       for (i = 0; i < devc->num_a_channels; i++) {
+               devc->a_data_bufs[i] = NULL;
+               devc->a_pretrig_bufs[i] = NULL;
+       }
+       devc->d_data_buf = NULL;
+       devc->sample_rate = 5000;
+       devc->capture_ratio = 10;
+       devc->rxstate = RX_IDLE;
+       /*Set an initial value as various code relies on an inital value. */
+       devc->limit_samples = 1000;
+
+       sdi->priv = devc;
+
+       if (raspberrypi_pico_get_dev_cfg(sdi) != SR_OK) {
+               return NULL;
+       };
+
+       serial_close(serial);
+       return std_scan_complete(di, g_slist_append(NULL, sdi));
+}
+
+/* Note that on the initial driver load we pull all values into local storage.
+ * Thus gets can return local data, but sets have to issue commands to device. */
+static int config_set(uint32_t key, GVariant * data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       int ret;
+       (void) cg;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+
+       devc = sdi->priv;
+       ret = SR_OK;
+
+       sr_dbg("Got config_set key %d \n", key);
+       switch (key) {
+       case SR_CONF_SAMPLERATE:
+               devc->sample_rate = g_variant_get_uint64(data);
+               sr_dbg("config_set sr %lu\n", devc->sample_rate);
+               break;
+       case SR_CONF_LIMIT_SAMPLES:
+               devc->limit_samples = g_variant_get_uint64(data);
+               sr_dbg("config_set slimit %" PRIu64 "\n", devc->limit_samples);
+               break;
+       case SR_CONF_CAPTURE_RATIO:
+               devc->capture_ratio = g_variant_get_uint64(data);
+               break;
+
+       default:
+               sr_err("ERROR: config_set given undefined key %d\n", key);
+               ret = SR_ERR_NA;
+       }
+
+       return ret;
+}
+
+static int config_get(uint32_t key, GVariant ** data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+
+       sr_dbg("config_get given key %d", key);
+
+       (void) cg;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+
+       devc = sdi->priv;
+       switch (key) {
+       case SR_CONF_SAMPLERATE:
+               *data = g_variant_new_uint64(devc->sample_rate);
+               sr_spew("sample rate get of %" PRIu64 "", devc->sample_rate);
+               break;
+       case SR_CONF_CAPTURE_RATIO:
+               if (!sdi)
+                       return SR_ERR;
+               devc = sdi->priv;
+               *data = g_variant_new_uint64(devc->capture_ratio);
+               break;
+       case SR_CONF_LIMIT_SAMPLES:
+               sr_spew("config_get limit_samples of %lu", devc->limit_samples);
+               *data = g_variant_new_uint64(devc->limit_samples);
+               break;
+       default:
+               sr_spew("unsupported config_get key %d", key);
+               return SR_ERR_NA;
+       }
+       return SR_OK;
+}
+
+static int config_list(uint32_t key, GVariant ** data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       (void) cg;
+
+       /* Scan or device options are the only ones that can be called without a
+        * defined instance */
+       if ((key == SR_CONF_SCAN_OPTIONS) || (key == SR_CONF_DEVICE_OPTIONS)) {
+               return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+       }
+
+       if (!sdi) {
+               sr_err("ERROR: Call to config list with null sdi");
+               return SR_ERR_ARG;
+       }
+
+       sr_dbg("Start config_list with key %X", key);
+       switch (key) {
+       case SR_CONF_SAMPLERATE:
+               *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates));
+               break;
+       /* This must be set to get SW trigger support */
+       case SR_CONF_TRIGGER_MATCH:
+               *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches));
+               break;
+       case SR_CONF_LIMIT_SAMPLES:
+               /* Really this limit is up to the memory capacity of the host,
+                * and users that pick huge values deserve what they get.
+                * But setting this limit to prevent really crazy things. */
+               *data = std_gvar_tuple_u64(1LL, 1000000000LL);
+               break;
+       default:
+               sr_dbg("Reached default statement of config_list");
+
+               return SR_ERR_NA;
+       }
+
+       return SR_OK;
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+       struct sr_serial_dev_inst *serial;
+       struct dev_context *devc;
+       struct sr_channel *ch;
+       struct sr_trigger *trigger;
+       char tmpstr[20];
+       char buf[32];
+       GSList *l;
+       int a_enabled = 0, d_enabled = 0, len;
+       serial = sdi->conn;
+       int i, num_read;
+
+       devc = sdi->priv;
+       sr_dbg("Enter acq start");
+       sr_dbg("dsbstart %d", devc->dig_sample_bytes);
+
+       devc->buffer = g_malloc(devc->serial_buffer_size);
+       if (!(devc->buffer)) {
+               sr_err("ERROR: serial buffer malloc fail");
+               return SR_ERR_MALLOC;
+       }
+
+       /* Get device in idle state */
+       if (serial_drain(serial) != SR_OK) {
+               sr_err("Initial Drain Failed");
+               return SR_ERR;
+       }
+
+       send_serial_char(serial, '*');
+       if (serial_drain(serial) != SR_OK) {
+               sr_err("Second Drain Failed");
+               return SR_ERR;
+       }
+
+       for (l = sdi->channels; l; l = l->next) {
+               ch = l->data;
+               sr_dbg("c %d enabled %d name %s\n", ch->index, ch->enabled, ch->name);
+
+               if (ch->name[0] == 'A') {
+                       devc->a_chan_mask &= ~(1 << ch->index);
+                       if (ch->enabled) {
+                               devc->a_chan_mask |= (ch->enabled << ch->index);
+                               a_enabled++;
+                       }
+               }
+               if (ch->name[0] == 'D') {
+                       devc->d_chan_mask &= ~(1 << ch->index);
+                       if (ch->enabled) {
+                               devc->d_chan_mask |= (ch->enabled << ch->index);
+                               d_enabled++;
+                       }
+               }
+
+               sr_info("Channel enable masks D 0x%X A 0x%X",
+                       devc->d_chan_mask, devc->a_chan_mask);
+               sprintf(tmpstr, "%c%d%d\n", ch->name[0], ch->enabled, ch->index);
+               if (send_serial_w_ack(serial, tmpstr) != SR_OK) {
+                       sr_err("ERROR: Channel enable fail");
+                       return SR_ERR;
+               }
+       }
+
+       /* Ensure data channels are continuous */
+       int invalid = 0;
+       for (i = 0; i < 32; i++) {
+               if ((devc->d_chan_mask >> i) & 1) {
+                       if (invalid) {
+                               sr_err("Digital channel mask 0x%X not continous",
+                                       devc->d_chan_mask);
+                               return SR_ERR;
+                       }
+               } else
+                       invalid = 1;
+       }
+
+       /* Recalculate bytes_per_slice based on which analog channels are enabled */
+       devc->bytes_per_slice = (a_enabled * devc->a_size);
+
+       for (i = 0; i < devc->num_d_channels; i += 7)
+               if (((devc->d_chan_mask) >> i) & (0x7F))
+                       (devc->bytes_per_slice)++;
+
+       if ((a_enabled == 0) && (d_enabled == 0)) {
+               sr_err("ERROR:No channels enabled");
+               return SR_ERR;
+       }
+
+       sr_dbg("bps %d\n", devc->bytes_per_slice);
+
+       /* Apply sample rate limits; while earlier versions forced a lower sample
+        * rate, the PICO seems to allow ADC overclocking, and by not enforcing
+        * these limits it may support other devices. Thus call sr_err to get
+        * something into the device logs, but allowing it to progress. */
+       if ((a_enabled == 3) && (devc->sample_rate > 160000))
+               sr_err("WARN: 3 channel ADC sample rate above 160khz");
+       if ((a_enabled == 2) && (devc->sample_rate > 250000))
+               sr_err("WARN: 2 channel ADC sample rate above 250khz");
+       if ((a_enabled == 1) && (devc->sample_rate > 500000))
+               sr_err("WARN: 1 channel ADC sample rate above 500khz");
+
+       /* Depending on channel configs, rates below 5ksps are possible but such a
+        * low rate can easily stream and this eliminates a lot of special cases. */
+       if (devc->sample_rate < 5000) {
+               sr_err("Sample rate override to min of 5ksps");
+               devc->sample_rate = 5000;
+       }
+
+       /* While PICO specs a max clock ~120-125Mhz, it does overclock in many cases
+        * so leaving a warning. */
+       if (devc->sample_rate > 120000000)
+               sr_warn("WARN: Sample rate above 120Msps");
+
+       /* It may take a very large number of samples to notice, but if digital and
+        * analog are enabled and either PIO or ADC are fractional the samples will
+        * skew over time. 24Mhz is the max common divisor to the 120Mhz and 48Mhz
+        * ADC clock so force an integer divisor to 24Mhz. */
+       if ((a_enabled > 0) && (d_enabled > 0)) {
+               if (24000000ULL % (devc->sample_rate)) {
+                       uint32_t commondivint = 24000000ULL / (devc->sample_rate);
+                       /* Always increment the divisor so that we go down in frequency to
+                        * avoid max sample rate issues */
+                       commondivint++;
+                       devc->sample_rate = 24000000ULL / commondivint;
+                       /* Make sure the divisor increment didn't make us go too low. */
+                       if (devc->sample_rate < 5000)
+                               devc->sample_rate = 50000;
+                       sr_warn("WARN: Forcing common integer divisor sample rate of " \
+                               "%lu div %u", devc->sample_rate, commondivint);
+               }
+       }
+
+       /* If we are only digital or only analog print a warning that the fractional
+        * divisors aren't a true PLL fractional feedback loop and thus could have
+        * sample to sample variation. These warnings of course assume that the
+        * device is programmed with the expected ratios but non PICO
+        * implementations, or PICO implementations that use different divisors
+        * could avoid. This generally won't be a problem because most of the
+        * sample_rate pulldown values are integer divisors. */
+       if ((a_enabled > 0) && (48000000ULL % (devc->sample_rate * a_enabled)))
+               sr_warn("WARN: Non integer ADC divisor of 48Mhz clock for sample " \
+                       "rate %lu may cause sample to sample variability.",
+                       devc->sample_rate);
+       if ((d_enabled > 0) && (120000000ULL % (devc->sample_rate)))
+               sr_warn("WARN: Non integer PIO divisor of 120Mhz for sample rate " \
+                       "%lu may cause sample to sample variability.", devc->sample_rate);
+
+       sprintf(tmpstr, "L%" PRIu64 "\n", devc->limit_samples);
+       if (send_serial_w_ack(serial, tmpstr) != SR_OK) {
+               sr_err("Sample limit to device failed");
+               return SR_ERR;
+       }
+
+       /* To support future devices that may allow the analog scale/offset to
+        * change, call get_dev_cfg again to get new values */
+       if (raspberrypi_pico_get_dev_cfg(sdi) != SR_OK) {
+               sr_err("get_dev_cfg failure on start");
+               return SR_ERR;
+       }
+
+       /* With all other params set, we use the final sample rate setting as an
+        * opportunity for the device to communicate any errors in configuration.
+        * A single  "*" indicates success.
+        * A "*" with subsequent data is success, but allows for the device to
+        * print something to the error console without aborting.
+        * A non "*" in the first character blocks the start. */
+       sprintf(tmpstr, "R%lu\n", devc->sample_rate);
+       num_read = send_serial_w_resp(serial, tmpstr, buf, 30);
+       buf[num_read] = 0;
+       if ((num_read > 1) && (buf[0] == '*'))
+               sr_dbg("Sample rate to device success with resp %s", buf);
+       else if (!((num_read == 1) && (buf[0] == '*'))) {
+               sr_err("Sample rate to device failed");
+               if (num_read > 0) {
+                       buf[num_read]=0;
+                       sr_err("sample_rate error string %s",buf);
+               }
+               return SR_ERR;
+       }
+
+       devc->sent_samples = 0;
+       devc->byte_cnt = 0;
+       devc->bytes_avail = 0;
+       devc->wrptr = 0;
+       devc->cbuf_wrptr = 0;
+       len = serial_read_blocking(serial, devc->buffer, devc->serial_buffer_size,
+               serial_timeout(serial, 4));
+
+       if (len > 0) {
+               sr_info("Pre-ARM drain had %d characters:", len);
+               devc->buffer[len] = 0;
+               sr_info("%s", devc->buffer);
+       }
+
+       for (i = 0; i < devc->num_a_channels; i++) {
+               devc->a_data_bufs[i] = g_malloc(devc->sample_buf_size * sizeof(float));
+               if (!(devc->a_data_bufs[i])) {
+                       sr_err("ERROR: analog buffer malloc fail");
+                       return SR_ERR_MALLOC;
+               }
+       }
+
+       if (devc->num_d_channels > 0) {
+               devc->d_data_buf = g_malloc(devc->sample_buf_size *
+                       devc->dig_sample_bytes);
+               if (!(devc->d_data_buf)) {
+                       sr_err("ERROR: logic buffer malloc fail");
+                       return SR_ERR_MALLOC;
+               }
+       }
+
+       devc->pretrig_entries = (devc->capture_ratio * devc->limit_samples) / 100;
+       /* While the driver supports the passing of trigger info to the device
+        * it has been found that the sw overhead of supporting triggering and
+        * pretrigger buffer entries etc.. ends up slowing the cores down enough
+        * that the effective continous sample rate isn't much higher than that of
+        * sending untriggered samples across USB.  Thus this code will remain but
+        * likely may not be used by the device, unless HW based triggers are
+        * implemented */
+       if ((trigger = sr_session_trigger_get(sdi->session))) {
+               if (g_slist_length(trigger->stages) > 1)
+                       return SR_ERR_NA;
+
+               struct sr_trigger_stage *stage;
+               struct sr_trigger_match *match;
+               GSList *l;
+               stage = g_slist_nth_data(trigger->stages, 0);
+               if (!stage)
+                       return SR_ERR_ARG;
+               for (l = stage->matches; l; l = l->next) {
+                       match = l->data;
+                       if (!match->match)
+                               continue;
+                       if (!match->channel->enabled)
+                               continue;
+                       int idx = match->channel->index;
+                       int8_t val;
+                       switch(match->match) {
+                       case SR_TRIGGER_ZERO:
+                               val = 0; break;
+                       case SR_TRIGGER_ONE:
+                               val = 1; break;
+                       case SR_TRIGGER_RISING:
+                               val = 2; break;
+                       case SR_TRIGGER_FALLING:
+                               val = 3; break;
+                       case SR_TRIGGER_EDGE:
+                               val = 4; break;
+                       default:
+                               val = -1;
+                       }
+                       sr_info("Trigger value idx %d match %d", idx, match->match);
+                       /* Only set trigger on enabled channels */
+                       if ((val >= 0) && ((devc->d_chan_mask >> idx) & 1)) {
+                               sprintf(&tmpstr[0], "t%d%02d\n", val, idx+2);
+                               if (send_serial_w_ack(serial, tmpstr) != SR_OK) {
+                                       sr_err("Trigger cfg to device failed");
+                                       return SR_ERR;
+                               }
+                       }
+               }
+
+               sprintf(&tmpstr[0], "p%d\n", devc->pretrig_entries);
+               if (send_serial_w_ack(serial, tmpstr) != SR_OK) {
+                       sr_err("Pretrig to device failed");
+                       return SR_ERR;
+               }
+
+               devc->stl = soft_trigger_logic_new(sdi, trigger, devc->pretrig_entries);
+               if (!devc->stl)
+                       return SR_ERR_MALLOC;
+
+               devc->trigger_fired = FALSE;
+               if (devc->pretrig_entries > 0) {
+                       sr_dbg("Allocating pretrig buffers size %d", devc->pretrig_entries);
+                       for (i = 0; i < devc->num_a_channels; i++) {
+                               if ((devc->a_chan_mask >> i) & 1) {
+                                       devc->a_pretrig_bufs[i] = g_malloc0(sizeof(float) *
+                                               devc->pretrig_entries);
+                                       if (!devc->a_pretrig_bufs[i]) {
+                                               sr_err("ERROR:Analog pretrigger buffer malloc " \
+                                                       "failure, disabling");
+                                               devc->trigger_fired = TRUE;
+                                       }
+                               }
+                       }
+               }
+
+               sr_info("Entering sw triggered mode");
+               /* Post the receive before starting the device to ensure we are ready
+                * to receive data ASAP */
+               serial_source_add(sdi->session, serial, G_IO_IN, 200,
+                       raspberrypi_pico_receive, (void*)sdi);
+
+               sprintf(tmpstr, "C\n");
+               if (send_serial_str(serial, tmpstr) != SR_OK)
+                       return SR_ERR;
+
+       } else {
+               devc->trigger_fired = TRUE;
+               devc->pretrig_entries = 0;
+               sr_info("Entering fixed sample mode");
+               serial_source_add(sdi->session, serial, G_IO_IN, 200,
+                       raspberrypi_pico_receive, (void*)sdi);
+
+               sprintf(tmpstr, "F\n");
+               if (send_serial_str(serial, tmpstr) != SR_OK)
+                       return SR_ERR;
+       }
+
+       std_session_send_df_header(sdi);
+
+       sr_dbg("dsbstartend %d", devc->dig_sample_bytes);
+
+       if (devc->trigger_fired)
+               std_session_send_df_trigger(sdi);
+
+       /* Keep this at the end as we don't want to be RX_ACTIVE unless everything
+        * is ok */
+       devc->rxstate = RX_ACTIVE;
+
+       return SR_OK;
+}
+
+/* This function is called either by the protocol code if we reached all of the
+ * samples or an error condition, and also by the user clicking stop in
+ * pulseview. It must always be called for any acquistion that was started to
+ * free memory. */
+static int dev_acquisition_stop(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       int len;
+       devc = sdi->priv;
+       serial = sdi->conn;
+
+       sr_dbg("At dev_acquisition_stop");
+
+       std_session_send_df_end(sdi);
+
+       /* If we reached this while still active it is likely because the stop
+        * button was pushed in pulseview. That is generally some kind of error
+        * condition, so we don't try to check the bytenct */
+       if (devc->rxstate == RX_ACTIVE)
+               sr_err("Reached dev_acquisition_stop in RX_ACTIVE");
+
+       if (devc->rxstate != RX_IDLE) {
+               sr_err("Sending plus to stop device stream");
+               send_serial_char(serial, '+');
+       }
+
+       /* In case we get calls to receive force it to exit */
+       devc->rxstate = RX_IDLE;
+
+       /* Drain data from device so that it doesn't confuse subsequent commands */
+       do {
+               len = serial_read_blocking(serial, devc->buffer,
+                       devc->serial_buffer_size, 100);
+               if (len)
+                       sr_err("Dropping %d device bytes", len);
+       } while (len > 0);
+
+       if (devc->buffer) {
+               g_free(devc->buffer);
+               devc->buffer = NULL;
+       }
+
+       for (int i = 0; i < devc->num_a_channels; i++) {
+               if (devc->a_data_bufs[i]) {
+                       g_free(devc->a_data_bufs[i]);
+                       devc->a_data_bufs[i] = NULL;
+               }
+       }
+       if (devc->d_data_buf) {
+               g_free(devc->d_data_buf);
+               devc->d_data_buf = NULL;
+       }
+
+       for (int i = 0; i < devc->num_a_channels; i++) {
+               if (devc->a_pretrig_bufs[i])
+                       g_free(devc->a_pretrig_bufs[i]);
+               devc->a_pretrig_bufs[i] = NULL;
+       }
+
+       serial = sdi->conn;
+       serial_source_remove(sdi->session, serial);
+
+       return SR_OK;
+}
+
+static struct sr_dev_driver raspberrypi_pico_driver_info = {
+       .name = "raspberrypi-pico",
+       .longname = "RaspberryPI PICO",
+       .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 = std_serial_dev_open,
+       .dev_close = std_serial_dev_close,
+       .dev_acquisition_start = dev_acquisition_start,
+       .dev_acquisition_stop = dev_acquisition_stop,
+       .context = NULL,
+};
+
+SR_REGISTER_DEV_DRIVER(raspberrypi_pico_driver_info);
diff --git a/src/hardware/raspberrypi-pico/protocol.c b/src/hardware/raspberrypi-pico/protocol.c
new file mode 100644 (file)
index 0000000..d3132d8
--- /dev/null
@@ -0,0 +1,864 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2022 Shawn Walker <ac0bi00@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#define _GNU_SOURCE
+
+#include <config.h>
+#include <errno.h>
+#include <glib.h>
+#include <math.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include "protocol.h"
+
+SR_PRIV int send_serial_str(struct sr_serial_dev_inst *serial, char *str)
+{
+       int len = strlen(str);
+       if ((len > 15) || (len < 1)) {
+               sr_err("ERROR: Serial string len %d invalid ", len);
+               return SR_ERR;
+       }
+
+       /* 100ms timeout. With USB CDC serial we can't define the timeout based
+        * on link rate, so just pick something large as we shouldn't normally
+        * see them */
+       if (serial_write_blocking(serial, str, len, 100) != len) {
+               sr_err("ERROR: Serial str write failed");
+               return SR_ERR;
+       }
+
+       return SR_OK;
+}
+
+SR_PRIV int send_serial_char(struct sr_serial_dev_inst *serial, char ch)
+{
+       char buf[1];
+       buf[0] = ch;
+
+       if (serial_write_blocking(serial, buf, 1, 100) != 1) {  /* 100ms */
+               sr_err("ERROR: Serial char write failed");
+               return SR_ERR;
+       }
+
+       return SR_OK;
+}
+
+/* Issue a command that expects a string return that is less than 30 characters.
+ * Returns the length of string */
+int send_serial_w_resp(struct sr_serial_dev_inst *serial, char *str,
+       char *resp, size_t cnt)
+{
+       int num_read, i;
+       send_serial_str(serial, str);
+
+       /* Using the serial_read_blocking function when reading a response of
+        * unknown length requires a long worst case timeout to always be taken.
+        * So, instead loop waiting for a first byte, and then a final small delay
+        * for the rest. */
+       for (i = 0; i < 1000; i++) {    /* wait up to 1 second in ms increments */
+               num_read = serial_read_blocking(serial, resp, cnt, 1);
+               if (num_read > 0)
+                       break;
+       }
+
+       /* Since the serial port is USB CDC we can't calculate timeouts based on
+        * baud rate but even if the response is split between two USB transfers,
+        * 10ms should be plenty. */
+       num_read += serial_read_blocking(serial, &(resp[num_read]), cnt - num_read,
+               10);
+       if ((num_read < 1) || (num_read > 30)) {
+               sr_err("ERROR: Serial_w_resp failed (%d).", num_read);
+               return -1;
+       } else
+               return num_read;
+}
+
+/* Issue a command that expects a single char ack */
+SR_PRIV int send_serial_w_ack(struct sr_serial_dev_inst *serial, char *str)
+{
+       char buf[2];
+       int num_read;
+
+       /* In case we have left over transfer from the device, drain them.
+        * These should not exist in normal operation */
+       while ((num_read = serial_read_blocking(serial, buf, 2, 10)))
+               sr_dbg("swack drops 2 bytes %d %d", buf[0], buf[1]);
+
+       send_serial_str(serial, str);
+
+       /* 1000ms timeout */
+       num_read = serial_read_blocking(serial, buf, 1, 1000);
+
+       if ((num_read == 1) && (buf[0] == '*')) {
+               return SR_OK;
+       } else {
+               sr_err("ERROR: Serial_w_ack %s failed (%d).", str, num_read);
+               if (num_read)
+                       sr_err("ack resp char %c d %d", buf[0], buf[0]);
+               return SR_ERR;
+       }
+}
+
+/* Process incoming data stream assuming it is optimized packing of 4 channels
+ * or less.
+ * Each byte is 4 channels of data and a 3 bit rle value, or a larger rle value,
+ * or a control signal. This also checks for aborts and ends.
+ * If an end is seen we stop processing but do not check the byte_cnt
+ * The output is a set of samples fed to process group to perform sw triggering
+ * and sending of data to the session as well as maintenance of the serial rx
+ * byte cnt.
+ * Since we can get huge rle values we chop them up for processing into smaller
+ * groups.
+ * In this mode we can always consume all bytes because there are no cases where
+ * the processing of one byte requires the one after it. */
+void process_D4(struct sr_dev_inst *sdi, struct dev_context *d)
+{
+       uint32_t j;
+       uint8_t cbyte, cval;
+       uint32_t rlecnt = 0;
+
+       while (d->ser_rdptr < d->bytes_avail) {
+               cbyte = d->buffer[(d->ser_rdptr)];
+
+               /*RLE only byte */
+               if ((cbyte >= 48) && (cbyte <= 127)) {
+                       rlecnt += (cbyte - 47) * 8;
+                       d->byte_cnt++;
+               } else if (cbyte >= 0x80) {     /* sample with possible rle */
+                       rlecnt += (cbyte & 0x70) >> 4;
+                       if (rlecnt) {
+                               /* On a value change, duplicate the previous values first. */
+                               rle_memset(d, rlecnt);
+                               rlecnt = 0;
+                       }
+                       /* Finally add in the new values */
+                       cval = cbyte & 0xF;
+                       uint32_t didx = (d->cbuf_wrptr) * (d->dig_sample_bytes);
+                       d->d_data_buf[didx] = cval;
+
+                       /* Pad in all other bytes since the sessions even wants disabled
+                        * channels reported */
+                       for (j = 1; j < d->dig_sample_bytes; j++)
+                               d->d_data_buf[didx+j] = 0;
+
+                       d->byte_cnt++;
+                       sr_spew("Dchan4 rdptr %d wrptr %d bytein 0x%X rle %d cval 0x%X didx %d",
+                               (d->ser_rdptr) - 1, d->cbuf_wrptr, cbyte, rlecnt, cval, didx);
+                       d->cbuf_wrptr++;
+                       rlecnt = 0;
+                       d->d_last[0] = cval;
+               } else {
+                       /* Any other character ends parsing - it could be a frame error or a
+                        * start of the final byte cnt */
+                       if (cbyte == '$') {
+                               sr_info("D4 Data stream stops with cbyte %d char %c rdidx %d cnt %lu",
+                                       cbyte, cbyte, d->ser_rdptr, d->byte_cnt);
+                               d->rxstate = RX_STOPPED;
+                       } else {
+                               sr_err("D4 Data stream aborts with cbyte %d char %c rdidx %d cnt %lu",
+                                       cbyte, cbyte, d->ser_rdptr, d->byte_cnt);
+                               d->rxstate = RX_ABORT;
+                       }
+                       break;  /* break from while loop */
+               }
+
+               (d->ser_rdptr)++;
+               /* To ensure we don't overflow the sample buffer, but still send it
+                * large chunks of data (to make the packet sends to the session
+                * efficient) only call process group after a large number of samples
+                * have been seen. cbuf_wrptr counts slices, so shift right by 2 to
+                * create a worst case x4 multiple ratio of cbuf_wrptr value to the
+                * depth of the sample buffer.
+                * Likely we could use the max rle value of 640 but 1024 gives some
+                * extra room. Also do a simple check of rlecnt>2000 since that is a
+                * reasonable minimal value to send to the session */
+               if ((rlecnt >= 2000) || \
+                       ((rlecnt + ((d->cbuf_wrptr) <<2 ))) > (d->sample_buf_size - 1024)) {
+                       sr_spew("D4 preoverflow wrptr %d bufsize %d rlecnt %d\n\r",
+                               d->cbuf_wrptr, d->sample_buf_size, rlecnt);
+                       rle_memset(d, rlecnt);
+                       process_group(sdi, d, d->cbuf_wrptr);
+                       rlecnt = 0;
+               }
+
+       } /*while rdptr < wrptr*/
+
+       sr_spew("D4 while done rdptr %d", d->ser_rdptr);
+
+       /* If we reach the end of the serial input stream send any remaining values
+        * or rles to the session */
+       if (rlecnt) {
+               sr_spew("Residual D4 slice rlecnt %d", rlecnt);
+               rle_memset(d, rlecnt);
+       }
+       if (d->cbuf_wrptr) {
+               sr_spew("Residual D4 data wrptr %d", d->cbuf_wrptr);
+               process_group(sdi, d, d->cbuf_wrptr);
+       }
+}
+
+/* Process incoming data stream and forward to trigger processing with
+ * process_group
+ * The final value of ser_rdptr indicates how many bytes were processed.
+ * This version handles all other enabled channel configurations that
+ * Process_D4 doesn't */
+void process_slice(struct sr_dev_inst *sdi, struct dev_context *devc)
+{
+       int32_t i;
+       uint32_t tmp32, cword;
+       uint8_t cbyte;
+       uint32_t slice_bytes;   /* Number of bytes that have legal slice values including RLE */
+
+       /* Only process legal data values for this mode which are 0x32-0x7F for RLE and 0x80 to 0xFF for data*/
+       for (slice_bytes = 1; (slice_bytes < devc->bytes_avail)
+               && (devc->buffer[slice_bytes - 1] >= 0x30); slice_bytes++);
+
+       if (slice_bytes != devc->bytes_avail) {
+               cbyte = devc->buffer[slice_bytes - 1];
+               slice_bytes--;  /* Don't process the ending character */
+               if (cbyte == '$') {
+                       sr_info("Data stream stops with cbyte %d char %c rdidx %d sbytes %d cnt %lu",
+                               cbyte, cbyte, devc->ser_rdptr, slice_bytes, devc->byte_cnt);
+                       devc->rxstate = RX_STOPPED;
+               } else {
+                       sr_err("Data stream aborts with cbyte %d char %c rdidx %d sbytes %d cnt %lu",
+                               cbyte, cbyte, devc->ser_rdptr, slice_bytes, devc->byte_cnt);
+                       devc->rxstate = RX_ABORT;
+               }
+       }
+
+       /* If the wrptr is non-zero due to a residual from the previous serial
+        * transfer, don't double count it towards byte_cnt*/
+       devc->byte_cnt += slice_bytes - (devc->wrptr);
+
+       sr_spew("process slice avail %d rdptr %d sb %d byte_cnt %" PRIu64 "",
+               devc->bytes_avail, devc->ser_rdptr, slice_bytes, devc->byte_cnt);
+
+       /* Must have a full slice or one rle byte */
+       while (((devc->ser_rdptr + devc->bytes_per_slice) <= slice_bytes)
+               || ((devc->ser_rdptr < slice_bytes) &&
+                       (devc->buffer[devc->ser_rdptr] < 0x80))) {
+
+       if (devc->buffer[devc->ser_rdptr] < 0x80) {
+               int16_t rlecnt;
+               if (devc->buffer[devc->ser_rdptr] <= 79)
+                       rlecnt = devc->buffer[devc->ser_rdptr] - 47;
+               else
+                       rlecnt = (devc->buffer[devc->ser_rdptr] - 78) * 32;
+
+               sr_info("RLEcnt of %d in %d", rlecnt, devc->buffer[devc->ser_rdptr]);
+               if ((rlecnt < 1) || (rlecnt > 1568))
+                       sr_err("Bad rlecnt val %d in %d",
+                               rlecnt, devc->buffer[devc->ser_rdptr]);
+               else
+                       rle_memset(devc,rlecnt);
+
+               devc->ser_rdptr++;
+
+       } else {
+               cword = 0;
+               /* Build up a word 7 bits at a time, using only enabled channels */
+               for (i = 0; i < devc->num_d_channels; i += 7) {
+                       if (((devc->d_chan_mask) >> i) & 0x7F) {
+                               cword |= ((devc->buffer[devc->ser_rdptr]) & 0x7F) << i;
+                               (devc->ser_rdptr)++;
+                       }
+               }
+               /* And then distribute 8 bits at a time to all possible channels
+                * but first save of cword for rle */
+               devc->d_last[0] =  cword        & 0xFF;
+               devc->d_last[1] = (cword >> 8)  & 0xFF;
+               devc->d_last[2] = (cword >> 16) & 0xFF;
+               devc->d_last[3] = (cword >> 24) & 0xFF;
+
+               for (i = 0; i < devc->num_d_channels; i += 8) {
+                       uint32_t idx = ((devc->cbuf_wrptr) * devc->dig_sample_bytes) +
+                               (i >> 3);
+                       devc->d_data_buf[idx] = cword & 0xFF;
+                       sr_spew("Dchan i %d wrptr %d idx %d char 0x%X cword 0x%X",
+                               i, devc->cbuf_wrptr, idx, devc->d_data_buf[idx], cword);
+                       cword >>= 8;
+               }
+
+               /* Each analog value is one or more 7 bit values */
+               for (i = 0; i < devc->num_a_channels; i++) {
+                       if ((devc->a_chan_mask >> i) & 1) {
+
+                               tmp32 =
+                                   devc->buffer[devc->ser_rdptr] - 0x80;
+                               for(int a=1;a<devc->a_size;a++){
+                                    tmp32+=(devc->buffer[(devc->ser_rdptr)+a] - 0x80)<<(7*a);
+                                }
+                               devc->a_data_bufs[i][devc->cbuf_wrptr] =
+                                   ((float) tmp32 * devc->a_scale[i]) +
+                                   devc->a_offset[i];
+                               devc->a_last[i] =
+                                   devc->a_data_bufs[i][devc->cbuf_wrptr];
+                               sr_spew
+                                   ("AChan %d t32 %d value %f wrptr %d rdptr %d sc %f off %f",
+                                    i, tmp32,
+                                    devc->
+                                    a_data_bufs[i][devc->cbuf_wrptr],
+                                    devc->cbuf_wrptr, devc->ser_rdptr,
+                                    devc->a_scale[i], devc->a_offset[i]);
+                               devc->ser_rdptr+=devc->a_size;
+                       }       /*if channel enabled*/
+               }               /*for num_a_channels*/
+               devc->cbuf_wrptr++;
+         }/*Not an RLE */
+           /*RLEs can create a large number of samples relative to the incoming serial buffer
+           To prevent overflow of the sample data buffer we call process_group.
+           cbuf_wrptr and sample_buf_size are both in terms of slices
+           2048 is more than needed for a max rle of 1640 on the next incoming character */
+           if((devc->cbuf_wrptr +2048) >  devc->sample_buf_size){
+              sr_spew("Drain large buff %d %d\n\r",devc->cbuf_wrptr,devc->sample_buf_size);
+              process_group(sdi, devc, devc->cbuf_wrptr);
+
+           }
+       }/* While another slice or RLE available */
+       if (devc->cbuf_wrptr){
+               process_group(sdi, devc, devc->cbuf_wrptr);
+       }
+
+}
+
+/* Send the processed analog values to the session */
+int send_analog(struct sr_dev_inst *sdi, struct dev_context *devc,
+               uint32_t num_samples, uint32_t offset)
+{
+       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;
+       uint32_t i;
+       float *fptr;
+
+       sr_analog_init(&analog, &encoding, &meaning, &spec, ANALOG_DIGITS);
+       for (i = 0; i < devc->num_a_channels; i++) {
+               if ((devc->a_chan_mask >> i) & 1) {
+                       ch = devc->analog_groups[i]->channels->data;
+                       analog.meaning->channels =
+                           g_slist_append(NULL, ch);
+                       analog.num_samples = num_samples;
+                       analog.data = (devc->a_data_bufs[i]) + offset;
+                       fptr = analog.data;
+                       sr_spew
+                           ("send analog num %d offset %d first %f 2 %f",
+                            num_samples, offset, *(devc->a_data_bufs[i]),
+                            *fptr);
+                       analog.meaning->mq = SR_MQ_VOLTAGE;
+                       analog.meaning->unit = SR_UNIT_VOLT;
+                       analog.meaning->mqflags = 0;
+                       packet.type = SR_DF_ANALOG;
+                       packet.payload = &analog;
+                       sr_session_send(sdi, &packet);
+                       g_slist_free(analog.meaning->channels);
+               }/* if enabled */
+       }/* for channels */
+       return 0;
+
+}
+
+/*Send the ring buffer of pre-trigger analog samples.
+  The entire buffer is sent (as long as it filled once), but need send two payloads split at the 
+  the writeptr  */
+int send_analog_ring(struct sr_dev_inst *sdi, struct dev_context *devc,
+                    uint32_t num_samples)
+{
+       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 i;
+       uint32_t num_pre, start_pre;
+       uint32_t num_post, start_post;
+       num_pre =
+           (num_samples >=
+            devc->pretrig_wr_ptr) ? devc->pretrig_wr_ptr : num_samples;
+       start_pre = devc->pretrig_wr_ptr - num_pre;
+       num_post = num_samples - num_pre;
+       start_post = devc->pretrig_entries - num_post;
+       sr_spew
+           ("send_analog ring wrptr %u ns %d npre %u spre %u npost %u spost %u",
+            devc->pretrig_wr_ptr, num_samples, num_pre, start_pre,
+            num_post, start_post);
+       float *fptr;
+       sr_analog_init(&analog, &encoding, &meaning, &spec, ANALOG_DIGITS);
+       for (i = 0; i < devc->num_a_channels; i++) {
+               if ((devc->a_chan_mask >> i) & 1) {
+                       ch = devc->analog_groups[i]->channels->data;
+                       analog.meaning->channels =
+                           g_slist_append(NULL, ch);
+                       analog.meaning->mq = SR_MQ_VOLTAGE;
+                       analog.meaning->unit = SR_UNIT_VOLT;
+                       analog.meaning->mqflags = 0;
+                       packet.type = SR_DF_ANALOG;
+                       packet.payload = &analog;
+                       /*First send what is after the write pointer because it is oldest */
+                       if (num_post) {
+                               analog.num_samples = num_post;
+                               analog.data =
+                                   (devc->a_pretrig_bufs[i]) + start_post;
+                               for (uint32_t j = 0;
+                                    j < analog.num_samples; j++) {
+                                       fptr =
+                                           analog.data +
+                                           (j * sizeof(float));
+                               }
+                               sr_session_send(sdi, &packet);
+                       }
+                       if (num_pre) {
+                               analog.num_samples = num_pre;
+                               analog.data =
+                                   (devc->a_pretrig_bufs[i]) + start_pre;
+                               sr_dbg("Sending A%d ring buffer newest ",
+                                      i);
+                               for (uint32_t j = 0;
+                                    j < analog.num_samples; j++) {
+                                       fptr =
+                                           analog.data +
+                                           (j * sizeof(float));
+                                       sr_spew("RNGDCW%d j %d %f %p", i,
+                                               j, *fptr, (void *) fptr);
+                               }
+                               sr_session_send(sdi, &packet);
+                       }
+                       g_slist_free(analog.meaning->channels);
+                       sr_dbg("Sending A%d ring buffer done ", i);
+               }/*if enabled */
+       }/* for channels */
+       return 0;
+
+}
+
+/* Given a chunk of slices forward to trigger check or session as appropriate and update state
+   these could be real slices or those generated by rles */
+int process_group(struct sr_dev_inst *sdi, struct dev_context *devc,
+                 uint32_t num_slices)
+{
+       int trigger_offset;
+       int pre_trigger_samples;
+       /*  These are samples sent to session and are less than num_slices if we reach limit_samples */
+       size_t num_samples;
+       struct sr_datafeed_logic logic;
+       struct sr_datafeed_packet packet;
+       int i;
+       size_t cbuf_wrptr_cpy;
+       cbuf_wrptr_cpy = devc->cbuf_wrptr;
+       /*regardless of whether we forward samples on or not (because we aren't triggered), always reset the 
+         pointer into the device data buffers  */
+       devc->cbuf_wrptr = 0;
+       if (devc->trigger_fired) {      /*send directly to session */
+               if (devc->limit_samples &&
+                   num_slices >
+                   devc->limit_samples - devc->sent_samples) {
+                       num_samples =
+                           devc->limit_samples - devc->sent_samples;
+               } else {
+                       num_samples = num_slices;
+               }
+               if (num_samples > 0) {
+                       sr_spew("Process_group sending %lu post trig samples dsb %d",
+                               num_samples, devc->dig_sample_bytes);
+                       if (devc->num_d_channels) {
+                               packet.type = SR_DF_LOGIC;
+                               packet.payload = &logic;
+                               /* The number of bytes required to fit all of the channels */
+                               logic.unitsize = devc->dig_sample_bytes;
+                               /* The total length of the array sent */
+                               logic.length = num_samples * logic.unitsize;
+                               logic.data = devc->d_data_buf;
+                               sr_session_send(sdi, &packet);
+                       }
+                       send_analog(sdi, devc, num_samples, 0);
+               }
+
+               devc->sent_samples += num_samples;
+               return 0;
+
+       } else {
+               /* Trigger_fired */
+               size_t num_ring_samples;
+               size_t sptr, eptr;
+               size_t numtail, numwrap;
+               size_t srcptr;
+               /* The trigger_offset is -1 if no trigger is found, but if a trigger is
+                * found then trigger_offset is the offset into the data buffer sent to
+                * it. The pre_trigger_samples is the total number of samples before
+                * the trigger, but limited to the size of the ring buffer set by the
+                * capture_ratio. So the pre_trigger_samples can include both the new
+                * samples and the ring buffer, but trigger_offset is only in relation
+                * to the new samples */
+               trigger_offset = soft_trigger_logic_check(devc->stl, devc->d_data_buf,
+                       num_slices * devc->dig_sample_bytes, &pre_trigger_samples);
+
+               /* A trigger offset >=0 indicates a trigger was seen. The stl will issue
+                * the trigger to the session and will forward all pre trigger logic
+                * samples, but we must send any post trigger logic and all pre and post
+                * trigger analog signals */
+               if (trigger_offset > -1) {
+                       devc->trigger_fired = TRUE;
+                       devc->sent_samples += pre_trigger_samples;
+                       packet.type = SR_DF_LOGIC;
+                       packet.payload = &logic;
+                       num_samples = num_slices - trigger_offset;
+
+                       /* Since we are in continuous mode for SW triggers it is possible to
+                        * get more samples than limit_samples, so once the trigger fires,
+                        * make sure we don't get beyond limit samples. At this point
+                        * sent_samples should be equal to pre_trigger_samples (just added
+                        * above) because without being triggered we'd never increment
+                        * sent_samples.
+                        * This number is the number of post trigger logic samples to send
+                        * to the session, the number of floats is larger because of the
+                        * analog ring buffer we track. */
+                       if (devc->limit_samples && \
+                               (num_samples > devc->limit_samples - devc->sent_samples))
+                               num_samples = devc->limit_samples - devc->sent_samples;
+
+                       /* The soft trigger logic issues the trigger and sends packets for
+                        * all logic data that was pretrigger so only send what is left */
+                       if (num_samples > 0) {
+                               sr_dbg("Sending post trigger logical remainder of %lu",
+                                       num_samples);
+                               logic.length = num_samples * devc->dig_sample_bytes;
+                               logic.unitsize = devc->dig_sample_bytes;
+                               logic.data = devc->d_data_buf +
+                                       (trigger_offset * devc->dig_sample_bytes);
+                               devc->sent_samples += num_samples;
+                               sr_session_send(sdi, &packet);
+                       }
+
+                       size_t new_start, new_end, new_samples, ring_samples;
+                       /* Figure out the analog data to send. We might need to send:
+                        * -some or all of incoming data
+                        * -all of incoming data and some of ring buffer
+                        * -all of incoming data and all of ring buffer (and still might be
+                        * short)
+                        * We don't need to compare to limit_samples because pretrig_entries
+                        * can never be more than limit_samples trigger offset indicatese
+                        * where in the new samples the trigger was, but we need to go back
+                        * pretrig_entries before it */
+                       new_start = (trigger_offset > (int)devc->pretrig_entries) ?
+                               trigger_offset - devc->pretrig_entries : 0;
+
+                       /* Note that we might not have gotten all the pre triggerstore data
+                        * we were looking for. In such a case the sw trigger logic seems to
+                        * fill up to the limit_samples and thus the ratio is off, but we
+                        * get the full number of samples.
+                        * The number of entries in the ring buffer is
+                        * pre_trigger_samples-trigger_offset so subtract that from limit
+                        * samples as a threshold */
+                       new_end = MIN(num_slices - 1,
+                               devc->limit_samples - (pre_trigger_samples - trigger_offset) - 1);
+
+                       /* This includes pre and post trigger storage. */
+                       new_samples = new_end - new_start + 1;
+
+                       /* pre_trigger_samples can never be greater than trigger_offset by
+                        * more than the ring buffer depth (pretrig entries) */
+                       ring_samples = (pre_trigger_samples > trigger_offset) ?
+                               pre_trigger_samples - trigger_offset : 0;
+                       sr_spew("SW trigger float info newstart %zu new_end %zu " \
+                                       "new_samp %zu ring_samp %zu",
+                               new_start, new_end, new_samples, ring_samples);
+
+                       if (ring_samples > 0)
+                               send_analog_ring(sdi, devc, ring_samples);
+                       if (new_samples)
+                               send_analog(sdi, devc, new_samples, new_start);
+               } else {
+                       /* We didn't trigger but need to copy to ring buffer */
+                       if ((devc->a_chan_mask) && (devc->pretrig_entries)) {
+                               /*The incoming data buffer could be much larger than the ring
+                                * buffer, so never copy more than the size of the ring buffer */
+                               num_ring_samples = num_slices > devc->pretrig_entries ?
+                                       devc->pretrig_entries : num_slices;
+                               sptr = devc->pretrig_wr_ptr;    /* Starting pointer to copy to */
+
+                               /* endptr can't go past the end */
+                               eptr = (sptr + num_ring_samples) >= devc-> pretrig_entries ?
+                                       devc->pretrig_entries - 1 : sptr + num_ring_samples - 1;
+
+                               /* Number of samples to copy to the tail of ring buffer without
+                                * wrapping */
+                               numtail = (eptr - sptr) + 1;
+
+                               numwrap = (num_ring_samples > numtail) ?
+                                       num_ring_samples - numtail : 0;
+
+                               /* cbuf_wrptr points to where the next write should go,
+                                * not the actual write data */
+                               srcptr = cbuf_wrptr_cpy - num_ring_samples;
+                               sr_spew("RNG num %zu sptr %zu eptr %zu ",
+                                       num_ring_samples, sptr, eptr);
+
+                               /* Copy tail */
+                               for (i = 0; i < devc->num_a_channels; i++)
+                                       if ((devc->a_chan_mask >> i) & 1)
+                                               for (uint32_t j = 0; j < numtail; j++)
+                                                       devc->a_pretrig_bufs[i][sptr + j] =
+                                                               devc->a_data_bufs[i][srcptr + j];
+
+                               /* Copy wrap */
+                               srcptr += numtail;
+                               for (i = 0; i < devc->num_a_channels; i++)
+                                       if ((devc->a_chan_mask >> i) & 1)
+                                               for (uint32_t j = 0; j < numwrap; j++)
+                                                       devc->a_pretrig_bufs[i][j] =
+                                                               devc->a_data_bufs[i][srcptr + j];
+
+                               devc->pretrig_wr_ptr = (numwrap) ?
+                                       numwrap : (eptr + 1) % devc->pretrig_entries;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+/* Duplicate previous sample values
+ * This function relies on the caller to ensure d_data_buf has samples to handle
+ * the full value of the rle */
+void rle_memset(struct dev_context *devc, uint32_t num_slices)
+{
+       uint32_t j, k, didx;
+       sr_spew("rle_memset vals 0x%X, 0x%X, 0x%X slices %d dsb %d",
+               devc->d_last[0], devc->d_last[1], devc->d_last[2],
+               num_slices, devc->dig_sample_bytes);
+
+       /* Even if a channel is disabled, PV expects the same location and size for
+        * the enabled channels as if the channel were enabled. */
+       for (j = 0; j < num_slices; j++) {
+               didx = devc->cbuf_wrptr * devc->dig_sample_bytes;
+               for (k = 0; k < devc->dig_sample_bytes; k++)
+                       devc->d_data_buf[didx + k] =  devc->d_last[k];
+               /* cbuf_wrptr always counts slices/samples (and not the bytes in the
+                * buffer) regardless of mode */
+               devc->cbuf_wrptr++;
+       }
+}
+
+/* This callback function is mapped from api.c with serial_source_add and is
+ * created after a capture has been setup and is responsible for querying the
+ * device trigger status, downloading data and forwarding packets */
+SR_PRIV int raspberrypi_pico_receive(int fd, int revents, void *cb_data)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       int len;
+       uint32_t i, bytes_rem, residual_bytes;
+       (void) fd;
+
+       if (!(sdi = cb_data))
+               return TRUE;
+
+       if (!(devc = sdi->priv))
+               return TRUE;
+
+       if (devc->rxstate != RX_ACTIVE) {
+               /* This condition is normal operation and expected to happen
+                * but printed as information */
+               sr_dbg("Reached non active state in receive %d", devc->rxstate);
+               /* Don't return - we may be waiting for a final bytecnt */
+       }
+
+       if (devc->rxstate == RX_IDLE) {
+               /* This is the normal end condition where we do one more receive
+                * to make sure we get the full byte_cnt */
+               sr_dbg("Reached idle state in receive %d", devc->rxstate);
+               return FALSE;
+       }
+
+       serial = sdi->conn;
+
+       /* Return true if it is some kind of event we don't handle */
+       if (!(revents == G_IO_IN || revents == 0))
+               return TRUE;
+
+       /* Fill the buffer, note the end may have partial slices */
+       bytes_rem = devc->serial_buffer_size - devc->wrptr;
+
+       /* Read one byte less so that we can null it and print as a string. Do a
+        * small 10ms timeout, if we get nothing, we'll always come back again */
+       len = serial_read_blocking(serial, &(devc->buffer[devc->wrptr]),
+               bytes_rem - 1, 10);
+       sr_spew("Entry wrptr %u bytes_rem %u len %d", devc->wrptr, bytes_rem, len);
+
+       if (len > 0) {
+               devc->buffer[devc->wrptr + len] = 0;
+               /* Add the "#" so that spaces in the string are clearly seen */
+               sr_dbg("rx string %s#", devc->buffer);
+               devc->bytes_avail = (devc->wrptr + len);
+               sr_spew("rx len %d bytes_avail %ul sent_samples %ul wrptr %u",
+                       len, devc->bytes_avail, devc->sent_samples, devc->wrptr);
+       } else {
+               if (len == 0) {
+                       return TRUE;
+               } else {
+                       sr_err("ERROR: Negative serial read code %d", len);
+                       sdi->driver->dev_acquisition_stop(sdi);
+                       return FALSE;
+               }
+       }
+
+       /* Process the serial read data */
+       devc->ser_rdptr = 0;
+       if (devc->rxstate == RX_ACTIVE) {
+               if ((devc->a_chan_mask == 0) \
+                       && ((devc->d_chan_mask & 0xFFFFFFF0) == 0))
+                       process_D4(sdi, devc);
+               else
+                       process_slice(sdi, devc);
+       }
+
+       /* process_slice/process_D4 increment ser_rdptr as bytes of the serial
+        * buffer are used. But they may not use all of it, and thus the residual
+        * unused bytes are shifted to the start of the buffer for the next call. */
+       residual_bytes = devc->bytes_avail - devc->ser_rdptr;
+       if (residual_bytes) {
+               for (i = 0; i < residual_bytes; i++)
+                       devc->buffer[i] = devc->buffer[i + devc->ser_rdptr];
+
+               devc->ser_rdptr = 0;
+               devc->wrptr = residual_bytes;
+               sr_spew("Residual shift rdptr %u wrptr %u", devc->ser_rdptr, devc->wrptr);
+       } else {
+               /* If there are no residuals shifted then zero the wrptr since all data
+                * is used */
+               devc->wrptr = 0;
+       }
+
+       /* ABORT ends immediately */
+       if (devc->rxstate == RX_ABORT) {
+               sr_err("Ending receive on abort");
+               sdi->driver->dev_acquisition_stop(sdi);
+               return FALSE;   
+       }
+
+       /* If stopped, look for final '+' indicating the full byte_cnt is received */
+       if (devc->rxstate == RX_STOPPED) {
+               sr_dbg("Stopped, checking byte_cnt");
+               if (devc->buffer[0] != '$') {
+                       /* If this happens it means that we got a set of data that was not
+                        * processed as whole groups of slice bytes. So either we lost data
+                        * or are not parsing it correctly. */
+                       sr_err("ERROR: Stop marker should be byte zero");
+                       devc->rxstate = RX_ABORT;
+                       sdi->driver->dev_acquisition_stop(sdi);
+                       return FALSE;
+               }
+
+               for (i = 1; i < devc->wrptr; i++) {
+                       if (devc->buffer[i] == '+') {
+                               devc->buffer[i] = 0;
+                               uint64_t rxbytecnt;
+                               rxbytecnt = atol((char*)&(devc->buffer[1]));
+                               sr_dbg("Byte_cnt check device cnt %lu host cnt %lu",
+                                       rxbytecnt, devc->byte_cnt);
+                               if (rxbytecnt != devc->byte_cnt)
+                                       sr_err("ERROR: received %lu and counted %lu bytecnts " \
+                                                       "don't match, data may be lost",
+                                               rxbytecnt, devc->byte_cnt);
+
+                               /* Since we got the bytecnt we know the device is done
+                                * sending data */
+                               devc->rxstate = RX_IDLE;
+
+                               /* We must always call acquisition_stop on all completed runs */
+                               sdi->driver->dev_acquisition_stop(sdi);
+                               return TRUE;
+                       }
+               }
+
+               /*It's possible we need one more serial transfer to get the byte_cnt,
+                * so print that here */
+               sr_dbg("Haven't seen byte_cnt + yet");
+       }
+       /* If at the sample limit, send a "+" in case we are in continuous mode and
+        * need to stop the device.  Not that even in non continous mode there might
+        * be cases where get an extra sample or two... */
+
+       if ((devc->sent_samples >= devc->limit_samples) \
+               && (devc->rxstate == RX_ACTIVE)) {
+               sr_dbg("Ending: sent %u of limit %lu samples byte_cnt %lu",
+                       devc->sent_samples, devc->limit_samples, devc->byte_cnt);
+               send_serial_char(serial, '+');
+       }
+
+       sr_spew("Receive function done: sent %u limit %lu wrptr %u len %d",
+               devc->sent_samples, devc->limit_samples, devc->wrptr, len);
+
+       return TRUE;
+}
+
+/* Read device specific information from the device */
+SR_PRIV int raspberrypi_pico_get_dev_cfg(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       char *cmd, response[20];
+       gchar **tokens;
+       unsigned int i;
+       int ret, num_tokens;
+
+       devc = sdi->priv;
+       sr_dbg("At get_dev_cfg");
+       serial = sdi->conn;
+       for (i = 0; i < devc->num_a_channels; i++) {
+               cmd = g_strdup_printf("a%d\n", i);
+               ret = send_serial_w_resp(serial, cmd, response, 20);
+               if (ret <= 0) {
+                       sr_err("ERROR: No response from device for analog channel query");
+                       return SR_ERR;
+               }
+               response[ret] = 0;
+               tokens = NULL;
+               tokens = g_strsplit(response, "x", 0);
+               num_tokens = g_strv_length(tokens);
+
+               if (num_tokens == 2) {
+                       devc->a_scale[i] = ((float) atoi(tokens[0])) / 1000000.0;
+                       devc->a_offset[i] = ((float) atoi(tokens[1])) / 1000000.0;
+                       sr_dbg("A%d scale %f offset %f response #%s# tokens #%s# #%s#",
+                               i, devc->a_scale[i], devc->a_offset[i],
+                               response, tokens[0], tokens[1]);
+               } else {
+                       sr_err("ERROR: Ascale read c%d got unparseable response %s tokens %d",
+                               i, response, num_tokens);
+                       /* Force a legal fixed value assuming a 3.3V scale */
+                       devc->a_scale[i] = 0.0257;
+                       devc->a_offset[i] = 0.0;
+               }
+
+               g_strfreev(tokens);
+               g_free(cmd);
+       }
+
+       return SR_OK;
+}
diff --git a/src/hardware/raspberrypi-pico/protocol.h b/src/hardware/raspberrypi-pico/protocol.h
new file mode 100644 (file)
index 0000000..45a8a2a
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2022 Shawn Walker <ac0bi00@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_RASPBERRYPI_PICO_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_RASPBERRYPI_PICO_PROTOCOL_H
+
+#include <stdint.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+/* This is used by sr_dbg/log etc to indicate where a printout came from */
+#define LOG_PREFIX "srpico"
+
+/* Number of bytes between markers */
+#define MRK_STRIDE 128
+
+/* These must be 32 or or less since many channel enable/disable masks and other
+ * elements may be only 32 bits wide. Setting values larger than what a PICO can
+ * support to enable other devices, or possibly modes where channels are created
+ * from internal values rather than external pins */
+#define MAX_ANALOG_CHANNELS 8
+#define MAX_DIGITAL_CHANNELS 32
+
+/* Digits input to sr_analog_init */
+#define ANALOG_DIGITS 4
+
+SR_PRIV int send_serial_str(struct sr_serial_dev_inst *serial, char *str);
+SR_PRIV int send_serial_char(struct sr_serial_dev_inst *serial, char ch);
+int send_serial_w_resp(struct sr_serial_dev_inst *serial, char *str,
+       char *resp, size_t cnt);
+SR_PRIV int send_serial_w_ack(struct sr_serial_dev_inst *serial, char *str);
+
+typedef enum rxstate {
+       RX_IDLE = 0,            /* Not receiving */
+       RX_ACTIVE = 1,          /* Receiving data */
+       RX_STOPPED = 2,         /* Received stop marker, waiting for byte cnt */
+       RX_ABORT = 3,           /* Received aborted marker or other error */
+} rxstate_t;
+
+struct dev_context {
+       /* Configuration Parameters
+        * It is up to the user to understand sample rates and serial download speed
+        * etc and do the right thing. i.e. don't expect continuous streaming
+        * bandwidth greater than serial link speed etc... */
+       /* The number of samples the user expects to see. */
+       uint64_t limit_samples;
+       uint64_t sample_rate;
+       /* Number of samples that have been received and processed */
+       uint32_t num_samples;
+       /* Initial Number of analog and digital channels.
+        * This is set by initial device config. Channels can be disabled/enabled,
+        * but can not be added/removed once driver is loaded. */
+       uint16_t num_a_channels;
+       uint16_t num_d_channels;
+       /* Masks of enabled channels based on user input */
+       uint32_t a_chan_mask;
+       uint32_t d_chan_mask;
+       /* Channel groups - each analog channel is its own group */
+       struct sr_channel_group **analog_groups;
+       struct sr_channel_group *digital_group;
+       /* Data size in bytes for each analog channel in bytes must be 1 as only
+        * single byte samples are supported in this version */
+       uint8_t a_size;
+       /* Offset and scale for each analog channel to covert bytes to float */
+       float a_offset[MAX_ANALOG_CHANNELS];
+       float a_scale[MAX_ANALOG_CHANNELS];
+       /* % ratio of pre-trigger to post trigger samples */
+       uint64_t capture_ratio;
+       /* Total number of bytes of data sent for one sample across all channels */
+       uint16_t bytes_per_slice;
+       /* The number of bytes needed to store all channels for one sample in the
+        * device data buffer */
+       uint32_t dig_sample_bytes;
+
+       /* Tracking/status once started */
+       /* Number of bytes in the current serial input stream */
+       uint32_t bytes_avail;
+       /* Samples sent to the session */
+       uint32_t sent_samples;
+       /* count total received bytes to detect lost info */
+       uint64_t byte_cnt;
+       /* For SW-based triggering we put the device into continuous transmit and
+        * stop when we detect a sample and capture all the samples we need.
+        * trigger_fired is thus set when the sw trigger logic detects a trigger.
+        * For non triggered modes we send a start and a number of samples and the
+        * device transmits that much. trigger_fired is set immediately at the
+        * start. */
+       gboolean trigger_fired;
+       rxstate_t rxstate;
+
+       /* Serial Related */
+       /* Serial data buffer */
+       unsigned char *buffer;
+       /* Size of incoming serial buffer*/
+       uint32_t serial_buffer_size;
+       /* Current byte in serial read stream that is being processed */
+       uint32_t ser_rdptr;
+       /* Write pointer into the serial input buffer */
+       uint32_t wrptr;
+
+       /* Buffering Related */
+       /* Parsed serial read data is split into each channels dedicated buffer
+        * for analog */
+       float *a_data_bufs[MAX_ANALOG_CHANNELS];
+       /* Digital samples are stored packed together since cli/pulseview want it
+        * that way */
+       uint8_t *d_data_buf;
+       /* Write pointer for the the per channel data buffers */
+       uint32_t cbuf_wrptr;
+       /* Size of packet data buffers for each channel */
+       uint32_t sample_buf_size;
+
+       /* RLE related*/
+       /* Previous sample values to duplicate for rle */
+       float a_last[MAX_ANALOG_CHANNELS];
+       uint8_t d_last[4];
+
+       /* SW trigger related */
+       struct soft_trigger_logic *stl;
+       /* Maximum number of entries to store pre-trigger */
+       uint32_t pretrig_entries;
+       /* Analog pre-trigger storage for software based triggering
+        * because sw based only has internal storage for logic */
+       float *a_pretrig_bufs[MAX_ANALOG_CHANNELS];
+       uint32_t pretrig_wr_ptr;
+};
+
+SR_PRIV int raspberrypi_pico_receive(int fd, int revents, void *cb_data);
+SR_PRIV int raspberrypi_pico_get_dev_cfg(const struct sr_dev_inst *sdi);
+
+void process_D4(struct sr_dev_inst *sdi, struct dev_context *d);
+void process_slice(struct sr_dev_inst *sdi, struct dev_context *devc);
+
+int send_analog(struct sr_dev_inst *sdi, struct dev_context *devc,
+       uint32_t num_samples, uint32_t offset);
+int send_analog_ring(struct sr_dev_inst *sdi, struct dev_context *devc,
+       uint32_t num_samples);
+
+int process_group(struct sr_dev_inst *sdi, struct dev_context *devc,
+       uint32_t num_slices);
+void rle_memset(struct dev_context *devc, uint32_t num_slices);
+SR_PRIV int check_marker(struct dev_context *d, int *len);
+
+
+#endif
index 1c67ea49d1715d5b32cb3b18b445288623438cdc..c55b589655cd579c2e40ca578082f439be6acaac 100644 (file)
@@ -19,9 +19,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
 
-#include <math.h>
 #include <string.h>
 
 #include "protocol.h"
@@ -52,18 +51,95 @@ static const uint32_t devopts[] = {
        SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET,
 };
 
-/* Model ID, model name, max current/voltage/power, current/voltage digits. */
+static const uint32_t devopts_w_range[] = {
+       SR_CONF_CONTINUOUS,
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_VOLTAGE | SR_CONF_GET,
+       SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_CURRENT | SR_CONF_GET,
+       SR_CONF_CURRENT_LIMIT | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_REGULATION | SR_CONF_GET,
+       SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET,
+       SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET,
+       SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_RANGE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+/* Range name, max current/voltage/power, current/voltage digits. */
+static const struct rdtech_dps_range ranges_dps3005[] = {
+       {  "5A",  5, 30,  160, 3, 2 }
+};
+
+static const struct rdtech_dps_range ranges_dps5005[] = {
+       {  "5A",  5, 50,  250, 3, 2 }
+};
+
+static const struct rdtech_dps_range ranges_dps5015[] = {
+       { "15A", 15, 50,  750, 2, 2 }
+};
+
+static const struct rdtech_dps_range ranges_dps5020[] = {
+       { "20A", 20, 50, 1000, 2, 2 }
+};
+
+static const struct rdtech_dps_range ranges_dps8005[] = {
+       {  "5A",  5, 80,  408, 3, 2 }
+};
+
+static const struct rdtech_dps_range ranges_rd6006[] = {
+       {  "6A",  6, 60,  360, 3, 2 }
+};
+
+static const struct rdtech_dps_range ranges_rd6006p[] = {
+       {  "6A",  6, 60,  360, 4, 3 }
+};
+
+static const struct rdtech_dps_range ranges_rd6012[] = {
+       { "12A", 12, 60,  720, 2, 2 }
+};
+
+/*
+ * RD6012P supports multiple current ranges with differing resolution.
+ * Up to 6A with 4 digits (when RTU reg 20 == 0), up to 12A with 3 digits
+ * (when RTU reg 20 == 1).
+ */
+static const struct rdtech_dps_range ranges_rd6012p[] = {
+       {  "6A",  6, 60,  360, 4, 3 },
+       { "12A", 12, 60,  720, 3, 3 }
+};
+
+static const struct rdtech_dps_range ranges_rd6018[] = {
+       { "18A", 18, 60, 1080, 2, 2 }
+};
+
+static const struct rdtech_dps_range ranges_rd6024[] = {
+       { "24A", 24, 60, 1440, 2, 2 }
+};
+
+/* Model ID, model name, model dependent ranges. */
 static const struct rdtech_dps_model supported_models[] = {
-       { MODEL_DPS, 3005, "DPS3005",  5, 30,  160, 3, 2 },
-       { MODEL_DPS, 5005, "DPS5005",  5, 50,  250, 3, 2 },
-       { MODEL_DPS, 5205, "DPH5005",  5, 50,  250, 3, 2 },
-       { MODEL_DPS, 5015, "DPS5015", 15, 50,  750, 2, 2 },
-       { MODEL_DPS, 5020, "DPS5020", 20, 50, 1000, 2, 2 },
-       { MODEL_DPS, 8005, "DPS8005",  5, 80,  408, 3, 2 },
-       /* All RD specs taken from the 2020.12.2 instruction manual. */
-       { MODEL_RD , 6006, "RD6006" ,  6, 60,  360, 3, 2 },
-       { MODEL_RD , 6012, "RD6012" , 12, 60,  720, 2, 2 },
-       { MODEL_RD , 6018, "RD6018" , 18, 60, 1080, 2, 2 },
+       { MODEL_DPS, 3005, "DPS3005", ARRAY_AND_SIZE(ranges_dps3005), },
+       { MODEL_DPS, 5005, "DPS5005", ARRAY_AND_SIZE(ranges_dps5005), },
+       { MODEL_DPS, 5205, "DPH5005", ARRAY_AND_SIZE(ranges_dps5005), },
+       { MODEL_DPS, 5015, "DPS5015", ARRAY_AND_SIZE(ranges_dps5015), },
+       { MODEL_DPS, 5020, "DPS5020", ARRAY_AND_SIZE(ranges_dps5020), },
+       { MODEL_DPS, 8005, "DPS8005", ARRAY_AND_SIZE(ranges_dps8005), },
+       /*
+        * Specs for models RD60nn taken from the 2020.12.2 instruction manual,
+        * specs for RD6006P from the 2021.2.26 (english) manual,
+        * specs for RD6012P from the 2021.10.26 (english) manual,
+        * and specs for RD6024P from the 2021.1.7 (english) manual.
+        */
+       { MODEL_RD, 60061, "RD6006" , ARRAY_AND_SIZE(ranges_rd6006),  },
+       { MODEL_RD, 60062, "RD6006" , ARRAY_AND_SIZE(ranges_rd6006),  },
+       { MODEL_RD, 60065, "RD6006P", ARRAY_AND_SIZE(ranges_rd6006p), },
+       { MODEL_RD, 60121, "RD6012" , ARRAY_AND_SIZE(ranges_rd6012),  },
+       { MODEL_RD, 60125, "RD6012P", ARRAY_AND_SIZE(ranges_rd6012p), },
+       { MODEL_RD, 60181, "RD6018" , ARRAY_AND_SIZE(ranges_rd6018),  },
+       { MODEL_RD, 60241, "RD6024" , ARRAY_AND_SIZE(ranges_rd6024),  },
 };
 
 static struct sr_dev_driver rdtech_dps_driver_info;
@@ -137,12 +213,12 @@ static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus,
        sr_channel_new(sdi, 2, SR_CHANNEL_ANALOG, TRUE, "P");
 
        devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
        sr_sw_limits_init(&devc->limits);
        devc->model = model;
-       devc->current_multiplier = pow(10.0, model->current_digits);
-       devc->voltage_multiplier = pow(10.0, model->voltage_digits);
-
-       sdi->priv = devc;
+       ret = rdtech_dps_update_range(sdi);
+       if (ret != SR_OK)
+               return NULL;
 
        return sdi;
 }
@@ -266,7 +342,7 @@ static int config_get(uint32_t key, GVariant **data,
        struct dev_context *devc;
        struct rdtech_dps_state state;
        int ret;
-       const char *cc_text;
+       const char *cc_text, *range_text;
 
        (void)cg;
 
@@ -375,6 +451,15 @@ static int config_get(uint32_t key, GVariant **data,
                        return SR_ERR_DATA;
                *data = g_variant_new_double(state.ocp_threshold);
                break;
+       case SR_CONF_RANGE:
+               ret = rdtech_dps_get_state(sdi, &state, ST_CTX_CONFIG);
+               if (ret != SR_OK)
+                       return ret;
+               if (!(state.mask & STATE_RANGE))
+                       return SR_ERR_DATA;
+               range_text = devc->model->ranges[state.range].range_str;
+               *data = g_variant_new_string(range_text);
+               break;
        default:
                return SR_ERR_NA;
        }
@@ -387,6 +472,9 @@ static int config_set(uint32_t key, GVariant *data,
 {
        struct dev_context *devc;
        struct rdtech_dps_state state;
+       const char *range_str;
+       const struct rdtech_dps_range *range;
+       size_t i;
 
        (void)cg;
 
@@ -417,6 +505,17 @@ static int config_set(uint32_t key, GVariant *data,
                state.ocp_threshold = g_variant_get_double(data);
                state.mask |= STATE_OCP_THRESHOLD;
                return rdtech_dps_set_state(sdi, &state);
+       case SR_CONF_RANGE:
+               range_str = g_variant_get_string(data, NULL);
+               for (i = 0; i < devc->model->n_ranges; i++) {
+                       range = &devc->model->ranges[i];
+                       if (g_strcmp0(range->range_str, range_str) != 0)
+                               continue;
+                       state.range = i;
+                       state.mask |= STATE_RANGE;
+                       return rdtech_dps_set_state(sdi, &state);
+               }
+               return SR_ERR_NA;
        default:
                return SR_ERR_NA;
        }
@@ -428,21 +527,43 @@ static int config_list(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        struct dev_context *devc;
+       const struct rdtech_dps_range *range;
+       GVariantBuilder gvb;
+       size_t i;
+       const char *s;
 
        devc = (sdi) ? sdi->priv : NULL;
 
        switch (key) {
        case SR_CONF_SCAN_OPTIONS:
        case SR_CONF_DEVICE_OPTIONS:
-               return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+               if (devc && devc->model->n_ranges > 1) {
+                       return STD_CONFIG_LIST(key, data, sdi, cg,
+                               scanopts, drvopts, devopts_w_range);
+               } else {
+                       return STD_CONFIG_LIST(key, data, sdi, cg,
+                               scanopts, drvopts, devopts);
+               }
        case SR_CONF_VOLTAGE_TARGET:
-               *data = std_gvar_min_max_step(0.0, devc->model->max_voltage,
+               rdtech_dps_update_range(sdi);
+               range = &devc->model->ranges[devc->curr_range];
+               *data = std_gvar_min_max_step(0.0, range->max_voltage,
                        1 / devc->voltage_multiplier);
                break;
        case SR_CONF_CURRENT_LIMIT:
-               *data = std_gvar_min_max_step(0.0, devc->model->max_current,
+               rdtech_dps_update_range(sdi);
+               range = &devc->model->ranges[devc->curr_range];
+               *data = std_gvar_min_max_step(0.0, range->max_current,
                        1 / devc->current_multiplier);
                break;
+       case SR_CONF_RANGE:
+               g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+               for (i = 0; i < devc->model->n_ranges; i++) {
+                       s = devc->model->ranges[i].range_str;
+                       g_variant_builder_add(&gvb, "s", s);
+               }
+               *data = g_variant_builder_end(&gvb);
+               break;
        default:
                return SR_ERR_NA;
        }
@@ -459,16 +580,22 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi)
        modbus = sdi->conn;
        devc = sdi->priv;
 
+       devc->acquisition_started = TRUE;
+
        /* Seed internal state from current data. */
        ret = rdtech_dps_seed_receive(sdi);
-       if (ret != SR_OK)
+       if (ret != SR_OK) {
+               devc->acquisition_started = FALSE;
                return ret;
+       }
 
        /* Register the periodic data reception callback. */
        ret = sr_modbus_source_add(sdi->session, modbus, G_IO_IN, 10,
                        rdtech_dps_receive_data, (void *)sdi);
-       if (ret != SR_OK)
+       if (ret != SR_OK) {
+               devc->acquisition_started = FALSE;
                return ret;
+       }
 
        sr_sw_limits_acquisition_start(&devc->limits);
        std_session_send_df_header(sdi);
@@ -478,9 +605,13 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 
 static int dev_acquisition_stop(struct sr_dev_inst *sdi)
 {
+       struct dev_context *devc;
        struct sr_modbus_dev_inst *modbus;
 
+       devc = sdi->priv;
+
        std_session_send_df_end(sdi);
+       devc->acquisition_started = FALSE;
 
        modbus = sdi->conn;
        sr_modbus_source_remove(sdi->session, modbus);
index 9ce2eb66e3a855337dc63f0bed452ba165f511f1..dfcffbb16c2f8582924f77a37f49569f5ba66582 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
 
+#include <math.h>
 #include <string.h>
 
 #include "protocol.h"
 
+/* These are the Modbus RTU registers for the DPS family of devices. */
 enum rdtech_dps_register {
        REG_DPS_USET       = 0x00, /* Mirror of 0x50 */
        REG_DPS_ISET       = 0x01, /* Mirror of 0x51 */
@@ -69,6 +71,11 @@ enum rdtech_dps_regulation_mode {
        MODE_CC      = 1,
 };
 
+/*
+ * These are the Modbus RTU registers for the RD family of devices.
+ * Some registers are device specific, like REG_RD_RANGE of RD6012P
+ * which could be battery related in other devices.
+ */
 enum rdtech_rd_register {
        REG_RD_MODEL = 0, /* u16 */
        REG_RD_SERIAL = 1, /* u32 */
@@ -85,6 +92,8 @@ enum rdtech_rd_register {
        REG_RD_PROTECT = 16, /* u16 */
        REG_RD_REGULATION = 17, /* u16 */
        REG_RD_ENABLE = 18, /* u16 */
+       REG_RD_PRESET = 19, /* u16 */
+       REG_RD_RANGE = 20, /* u16 */
        /*
         * Battery at 32 == 0x20 pp:
         * Mode, voltage, temperature, capacity, energy.
@@ -132,7 +141,7 @@ static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
        modbus = sdi->conn;
 
        wrptr = (void *)registers;
-       write_u16le(wrptr, value);
+       write_u16be(wrptr, value);
 
        g_mutex_lock(&devc->rw_mutex);
        ret = sr_modbus_write_multiple_registers(modbus, address,
@@ -187,8 +196,8 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
                if (ret != SR_OK)
                        return ret;
                rdptr = (void *)registers;
-               *model = read_u16le_inc(&rdptr);
-               *version = read_u16le_inc(&rdptr);
+               *model = read_u16be_inc(&rdptr);
+               *version = read_u16be_inc(&rdptr);
                *serno = 0;
                sr_info("RDTech DPS/DPH model: %u version: %u",
                        *model, *version);
@@ -200,7 +209,7 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
                if (ret != SR_OK)
                        return ret;
                rdptr = (void *)registers;
-               *model = read_u16be_inc(&rdptr) / 10;
+               *model = read_u16be_inc(&rdptr);
                *serno = read_u32be_inc(&rdptr);
                *version = read_u16be_inc(&rdptr);
                sr_info("RDTech RD model: %u version: %u, serno %u",
@@ -213,6 +222,49 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
        /* UNREACH */
 }
 
+SR_PRIV void rdtech_dps_update_multipliers(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       const struct rdtech_dps_range *range;
+
+       devc = sdi->priv;
+       range = &devc->model->ranges[devc->curr_range];
+       devc->current_multiplier = pow(10.0, range->current_digits);
+       devc->voltage_multiplier = pow(10.0, range->voltage_digits);
+}
+
+/*
+ * Determine range of connected device. Don't do anything once
+ * acquisition has started (since the range will then be tracked).
+ */
+SR_PRIV int rdtech_dps_update_range(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       uint16_t range;
+       int ret;
+
+       devc = sdi->priv;
+
+       /*
+        * Only update range if there are multiple ranges and data
+        * acquisition hasn't started.
+        */
+       if (devc->model->n_ranges <= 1 || devc->acquisition_started)
+               return SR_OK;
+       if (devc->model->model_type != MODEL_RD)
+               return SR_ERR;
+
+       ret = rdtech_dps_read_holding_registers(sdi->conn,
+               REG_RD_RANGE, 1, &range);
+       if (ret != SR_OK)
+               return ret;
+       range = range ? 1 : 0;
+       devc->curr_range = range;
+       rdtech_dps_update_multipliers(sdi);
+
+       return SR_OK;
+}
+
 /* Send a measured value to the session feed. */
 static int send_value(const struct sr_dev_inst *sdi,
        struct sr_channel *ch, float value,
@@ -259,13 +311,15 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
        struct dev_context *devc;
        struct sr_modbus_dev_inst *modbus;
        gboolean get_config, get_init_state, get_curr_meas;
-       uint16_t registers[12];
+       uint16_t registers[14];
        int ret;
        const uint8_t *rdptr;
        uint16_t uset_raw, iset_raw, uout_raw, iout_raw, power_raw;
        uint16_t reg_val, reg_state, out_state, ovpset_raw, ocpset_raw;
        gboolean is_lock, is_out_enabled, is_reg_cc;
        gboolean uses_ovp, uses_ocp;
+       gboolean have_range;
+       uint16_t range;
        float volt_target, curr_limit;
        float ovp_threshold, ocp_threshold;
        float curr_voltage, curr_current, curr_power;
@@ -308,6 +362,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
        (void)get_init_state;
        (void)get_curr_meas;
 
+       have_range = devc->model->n_ranges > 1;
+       if (!have_range)
+               range = 0;
+
        switch (devc->model->model_type) {
        case MODEL_DPS:
                /*
@@ -320,32 +378,33 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
                 */
                g_mutex_lock(&devc->rw_mutex);
                ret = rdtech_dps_read_holding_registers(modbus,
-                       REG_DPS_USET, 10, registers);
+                       REG_DPS_USET, REG_DPS_ENABLE - REG_DPS_USET + 1,
+                       registers);
                g_mutex_unlock(&devc->rw_mutex);
                if (ret != SR_OK)
                        return ret;
 
                /* Interpret the registers' values. */
                rdptr = (const void *)registers;
-               uset_raw = read_u16le_inc(&rdptr);
+               uset_raw = read_u16be_inc(&rdptr);
                volt_target = uset_raw / devc->voltage_multiplier;
-               iset_raw = read_u16le_inc(&rdptr);
+               iset_raw = read_u16be_inc(&rdptr);
                curr_limit = iset_raw / devc->current_multiplier;
-               uout_raw = read_u16le_inc(&rdptr);
+               uout_raw = read_u16be_inc(&rdptr);
                curr_voltage = uout_raw / devc->voltage_multiplier;
-               iout_raw = read_u16le_inc(&rdptr);
+               iout_raw = read_u16be_inc(&rdptr);
                curr_current = iout_raw / devc->current_multiplier;
-               power_raw = read_u16le_inc(&rdptr);
+               power_raw = read_u16be_inc(&rdptr);
                curr_power = power_raw / 100.0f;
-               (void)read_u16le_inc(&rdptr); /* UIN */
-               reg_val = read_u16le_inc(&rdptr); /* LOCK */
+               (void)read_u16be_inc(&rdptr); /* UIN */
+               reg_val = read_u16be_inc(&rdptr); /* LOCK */
                is_lock = reg_val != 0;
-               reg_val = read_u16le_inc(&rdptr); /* PROTECT */
+               reg_val = read_u16be_inc(&rdptr); /* PROTECT */
                uses_ovp = reg_val == STATE_OVP;
                uses_ocp = reg_val == STATE_OCP;
-               reg_state = read_u16le_inc(&rdptr); /* CV_CC */
+               reg_state = read_u16be_inc(&rdptr); /* CV_CC */
                is_reg_cc = reg_state == MODE_CC;
-               out_state = read_u16le_inc(&rdptr); /* ENABLE */
+               out_state = read_u16be_inc(&rdptr); /* ENABLE */
                is_out_enabled = out_state != 0;
 
                /* Transfer another chunk of registers in a single call. */
@@ -358,9 +417,9 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
 
                /* Interpret the second registers chunk's values. */
                rdptr = (const void *)registers;
-               ovpset_raw = read_u16le_inc(&rdptr); /* PRE OVPSET */
+               ovpset_raw = read_u16be_inc(&rdptr); /* PRE OVPSET */
                ovp_threshold = ovpset_raw * devc->voltage_multiplier;
-               ocpset_raw = read_u16le_inc(&rdptr); /* PRE OCPSET */
+               ocpset_raw = read_u16be_inc(&rdptr); /* PRE OCPSET */
                ocp_threshold = ocpset_raw * devc->current_multiplier;
 
                break;
@@ -369,7 +428,11 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
                /* Retrieve a set of adjacent registers. */
                g_mutex_lock(&devc->rw_mutex);
                ret = rdtech_dps_read_holding_registers(modbus,
-                       REG_RD_VOLT_TGT, 11, registers);
+                       REG_RD_VOLT_TGT,
+                       devc->model->n_ranges > 1
+                               ? REG_RD_RANGE - REG_RD_VOLT_TGT + 1
+                               : REG_RD_ENABLE - REG_RD_VOLT_TGT + 1,
+                       registers);
                g_mutex_unlock(&devc->rw_mutex);
                if (ret != SR_OK)
                        return ret;
@@ -396,6 +459,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
                is_reg_cc = reg_state == MODE_CC;
                out_state = read_u16be_inc(&rdptr); /* ENABLE */
                is_out_enabled = out_state != 0;
+               if (have_range) {
+                       (void)read_u16be_inc(&rdptr); /* PRESET */
+                       range = read_u16be_inc(&rdptr) ? 1 : 0; /* RANGE */
+               }
 
                /* Retrieve a set of adjacent registers. */
                g_mutex_lock(&devc->rw_mutex);
@@ -456,6 +523,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
        state->mask |= STATE_CURRENT;
        state->power = curr_power;
        state->mask |= STATE_POWER;
+       if (have_range) {
+               state->range = range;
+               state->mask |= STATE_RANGE;
+       }
 
        return SR_OK;
 }
@@ -575,6 +646,42 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi,
                        return SR_ERR_ARG;
                }
        }
+       if (state->mask & STATE_RANGE) {
+               reg_value = state->range;
+               switch (devc->model->model_type) {
+               case MODEL_DPS:
+                       /* DPS models don't support current ranges at all. */
+                       if (reg_value > 0)
+                               return SR_ERR_ARG;
+                       break;
+               case MODEL_RD:
+                       /*
+                        * Reject unsupported range indices.
+                        * Need not set the range when the device only
+                        * supports a single fixed range.
+                        */
+                       if (reg_value >= devc->model->n_ranges)
+                               return SR_ERR_NA;
+                       if (devc->model->n_ranges <= 1)
+                               return SR_OK;
+                       ret = rdtech_rd_set_reg(sdi, REG_RD_RANGE, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       /*
+                        * Immediately update internal state outside of
+                        * an acquisition. Assume that in-acquisition
+                        * activity will update internal state. This is
+                        * essential for meta package emission.
+                        */
+                       if (!devc->acquisition_started) {
+                               devc->curr_range = reg_value;
+                               rdtech_dps_update_multipliers(sdi);
+                       }
+                       break;
+               default:
+                       return SR_ERR_ARG;
+               }
+       }
 
        return SR_OK;
 }
@@ -602,6 +709,10 @@ SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi)
                devc->curr_cc_state = state.regulation_cc;
        if (state.mask & STATE_OUTPUT_ENABLED)
                devc->curr_out_state = state.output_enabled;
+       if (state.mask & STATE_RANGE) {
+               devc->curr_range = state.range;
+               rdtech_dps_update_multipliers(sdi);
+       }
 
        return SR_OK;
 }
@@ -614,7 +725,7 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data)
        struct rdtech_dps_state state;
        int ret;
        struct sr_channel *ch;
-       const char *regulation_text;
+       const char *regulation_text, *range_text;
 
        (void)fd;
        (void)revents;
@@ -635,11 +746,11 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data)
        ch = g_slist_nth_data(sdi->channels, 0);
        send_value(sdi, ch, state.voltage,
                SR_MQ_VOLTAGE, SR_MQFLAG_DC, SR_UNIT_VOLT,
-               devc->model->voltage_digits);
+               devc->model->ranges[devc->curr_range].voltage_digits);
        ch = g_slist_nth_data(sdi->channels, 1);
        send_value(sdi, ch, state.current,
                SR_MQ_CURRENT, SR_MQFLAG_DC, SR_UNIT_AMPERE,
-               devc->model->current_digits);
+               devc->model->ranges[devc->curr_range].current_digits);
        ch = g_slist_nth_data(sdi->channels, 2);
        send_value(sdi, ch, state.power,
                SR_MQ_POWER, 0, SR_UNIT_WATT, 2);
@@ -669,6 +780,13 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data)
                        g_variant_new_boolean(state.output_enabled));
                devc->curr_out_state = state.output_enabled;
        }
+       if (devc->curr_range != state.range) {
+               range_text = devc->model->ranges[state.range].range_str;
+               (void)sr_session_send_meta(sdi, SR_CONF_RANGE,
+                       g_variant_new_string(range_text));
+               devc->curr_range = state.range;
+               rdtech_dps_update_multipliers(sdi);
+       }
 
        /* Check optional acquisition limits. */
        sr_sw_limits_update_samples_read(&devc->limits, 1);
index 456e20419bf6f785de425588111ccaa195ebe920..1170dbf99baabb7d7d8b158f703c8f3e920fa3b1 100644 (file)
@@ -22,7 +22,7 @@
 #ifndef LIBSIGROK_HARDWARE_RDTECH_DPS_PROTOCOL_H
 #define LIBSIGROK_HARDWARE_RDTECH_DPS_PROTOCOL_H
 
-#include <config.h>
+#include "config.h"
 
 #include <glib.h>
 #include <libsigrok/libsigrok.h>
@@ -38,10 +38,8 @@ enum rdtech_dps_model_type {
        MODEL_RD,
 };
 
-struct rdtech_dps_model {
-       enum rdtech_dps_model_type model_type;
-       unsigned int id;
-       const char *name;
+struct rdtech_dps_range {
+       const char *range_str;
        unsigned int max_current;
        unsigned int max_voltage;
        unsigned int max_power;
@@ -49,6 +47,14 @@ struct rdtech_dps_model {
        unsigned int voltage_digits;
 };
 
+struct rdtech_dps_model {
+       enum rdtech_dps_model_type model_type;
+       unsigned int id;
+       const char *name;
+       const struct rdtech_dps_range *ranges;
+       size_t n_ranges;
+};
+
 struct dev_context {
        const struct rdtech_dps_model *model;
        double current_multiplier;
@@ -59,6 +65,8 @@ struct dev_context {
        gboolean curr_ocp_state;
        gboolean curr_cc_state;
        gboolean curr_out_state;
+       size_t curr_range;
+       gboolean acquisition_started;
 };
 
 /* Container to get and set parameter values. */
@@ -77,6 +85,7 @@ struct rdtech_dps_state {
                STATE_VOLTAGE = 1 << 10,
                STATE_CURRENT = 1 << 11,
                STATE_POWER = 1 << 12,
+               STATE_RANGE = 1 << 13,
        } mask;
        gboolean lock;
        gboolean output_enabled, regulation_cc;
@@ -84,6 +93,7 @@ struct rdtech_dps_state {
        float voltage_target, current_limit;
        float ovp_threshold, ocp_threshold;
        float voltage, current, power;
+       size_t range;
 };
 
 enum rdtech_dps_state_context {
@@ -100,6 +110,8 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi,
 SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
        enum rdtech_dps_model_type model_type,
        uint16_t *model, uint16_t *version, uint32_t *serno);
+SR_PRIV void rdtech_dps_update_multipliers(const struct sr_dev_inst *sdi);
+SR_PRIV int rdtech_dps_update_range(const struct sr_dev_inst *sdi);
 SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi);
 SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data);
 
index eacd17d3bb104f0af15992b86850b8322dbf192b..ae86fccf5c4bb74bbeba8881afa2ea41b2a9d87b 100644 (file)
  */
 
 #include <config.h>
+
+#include <fcntl.h>
 #include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <fcntl.h>
-#include <string.h>
-#include <libsigrok/libsigrok.h>
+
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
@@ -40,23 +42,27 @@ static const uint32_t drvopts[] = {
 
 static const uint32_t devopts[] = {
        SR_CONF_CONTINUOUS,
-       SR_CONF_LIMIT_SAMPLES | SR_CONF_SET,
-       SR_CONF_LIMIT_MSEC | SR_CONF_SET,
+       SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
 };
 
-static GSList *rdtech_tc_scan(struct sr_dev_driver *di, const char *conn,
-                             const char *serialcomm)
+static GSList *rdtech_tc_scan(struct sr_dev_driver *di,
+       const char *conn, const char *serialcomm)
 {
        struct sr_serial_dev_inst *serial;
-       GSList *devices = NULL;
-       struct dev_context *devc = NULL;
-       struct sr_dev_inst *sdi = NULL;
+       GSList *devices;
+       struct dev_context *devc;
+       struct sr_dev_inst *sdi;
+       size_t i;
+       const struct rdtech_tc_channel_desc *pch;
+       struct sr_channel *ch;
+       struct feed_queue_analog *feed;
 
        serial = sr_serial_dev_inst_new(conn, serialcomm);
        if (serial_open(serial, SERIAL_RDWR) != SR_OK)
                goto err_out;
 
-       devc = g_malloc0(sizeof(struct dev_context));
+       devc = g_malloc0(sizeof(*devc));
        sr_sw_limits_init(&devc->limits);
 
        if (rdtech_tc_probe(serial, devc) != SR_OK) {
@@ -64,7 +70,8 @@ static GSList *rdtech_tc_scan(struct sr_dev_driver *di, const char *conn,
                goto err_out_serial;
        }
 
-       sdi = g_malloc0(sizeof(struct sr_dev_inst));
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->priv = devc;
        sdi->status = SR_ST_INACTIVE;
        sdi->vendor = g_strdup("RDTech");
        sdi->model = g_strdup(devc->dev_info.model_name);
@@ -72,12 +79,18 @@ static GSList *rdtech_tc_scan(struct sr_dev_driver *di, const char *conn,
        sdi->serial_num = g_strdup_printf("%08" PRIu32, devc->dev_info.serial_num);
        sdi->inst_type = SR_INST_SERIAL;
        sdi->conn = serial;
-       sdi->priv = devc;
 
-       for (int i = 0; devc->channels[i].name; i++)
-               sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, devc->channels[i].name);
+       devc->feeds = g_malloc0(devc->channel_count * sizeof(devc->feeds[0]));
+       for (i = 0; i < devc->channel_count; i++) {
+               pch = &devc->channels[i];
+               ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, pch->name);
+               feed = feed_queue_analog_alloc(sdi, 1, pch->digits, ch);
+               feed_queue_analog_mq_unit(feed, pch->mq, 0, pch->unit);
+               feed_queue_analog_scale_offset(feed, &pch->scale, NULL);
+               devc->feeds[i] = feed;
+       }
 
-       devices = g_slist_append(devices, sdi);
+       devices = g_slist_append(NULL, sdi);
        serial_close(serial);
        if (!devices)
                sr_serial_dev_inst_free(serial);
@@ -93,31 +106,53 @@ err_out:
        return NULL;
 }
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
+static void clear_helper(struct dev_context *devc)
 {
-       struct sr_config *src;
-       const char *conn = NULL;
-       const char *serialcomm = RDTECH_TC_SERIALCOMM;
-
-       for (GSList *l = options; l; l = l->next) {
-               src = l->data;
-               switch (src->key) {
-               case SR_CONF_CONN:
-                       conn = g_variant_get_string(src->data, NULL);
-                       break;
-               case SR_CONF_SERIALCOMM:
-                       serialcomm = g_variant_get_string(src->data, NULL);
-                       break;
-               }
+       size_t idx;
+
+       if (!devc)
+               return;
+
+       if (devc->feeds) {
+               for (idx = 0; idx < devc->channel_count; idx++)
+                       feed_queue_analog_free(devc->feeds[idx]);
+               g_free(devc->feeds);
        }
+}
+
+static int dev_clear(const struct sr_dev_driver *driver)
+{
+       return std_dev_clear_with_callback(driver, (std_dev_clear_callback)clear_helper);
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       const char *conn;
+       const char *serialcomm;
+
+       conn = NULL;
+       serialcomm = RDTECH_TC_SERIALCOMM;
+       (void)sr_serial_extract_options(options, &conn, &serialcomm);
        if (!conn)
                return NULL;
 
        return rdtech_tc_scan(di, conn, serialcomm);
 }
 
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+
+       (void)cg;
+
+       devc = sdi->priv;
+
+       return sr_sw_limits_config_get(&devc->limits, key, data);
+}
+
 static int config_set(uint32_t key, GVariant *data,
-                     const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        struct dev_context *devc;
 
@@ -129,23 +164,25 @@ 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)
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
 }
 
 static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 {
-       struct dev_context *devc = sdi->priv;
-       struct sr_serial_dev_inst *serial = sdi->conn;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
 
+       devc = sdi->priv;
        sr_sw_limits_acquisition_start(&devc->limits);
        std_session_send_df_header(sdi);
 
+       serial = sdi->conn;
        serial_source_add(sdi->session, serial, G_IO_IN, 50,
-                         rdtech_tc_receive_data, (void *)sdi);
+               rdtech_tc_receive_data, (void *)sdi);
 
-       return rdtech_tc_poll(sdi);
+       return rdtech_tc_poll(sdi, TRUE);
 }
 
 static struct sr_dev_driver rdtech_tc_driver_info = {
@@ -156,8 +193,8 @@ static struct sr_dev_driver rdtech_tc_driver_info = {
        .cleanup = std_cleanup,
        .scan = scan,
        .dev_list = std_dev_list,
-       .dev_clear = std_dev_clear,
-       .config_get = NULL,
+       .dev_clear = dev_clear,
+       .config_get = config_get,
        .config_set = config_set,
        .config_list = config_list,
        .dev_open = std_serial_dev_open,
index 3b94890d43b30f8926b3321d4533a3602c0a3e7e..291604a0c524e6887381d77d9a837263751793c2 100644 (file)
  */
 
 #include <config.h>
-#include <stdlib.h>
-#include <math.h>
-#include <string.h>
+
 #include <glib.h>
-#include <nettle/aes.h>
 #include <libsigrok/libsigrok.h>
+#include <math.h>
+#include <nettle/aes.h>
+#include <stdlib.h>
+#include <string.h>
+
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
-#define SERIAL_WRITE_TIMEOUT_MS 1
-
-#define TC_POLL_LEN 192
-#define TC_POLL_PERIOD_MS 100
-#define TC_TIMEOUT_MS 1000
+#define PROBE_TO_MS    1000
+#define WRITE_TO_MS    1
+#define POLL_PERIOD_MS 100
 
-static const char POLL_CMD[] = "getva";
+/*
+ * Response data (raw sample data) consists of three adjacent chunks
+ * of 64 bytes each. These chunks start with their magic string, and
+ * end in a 32bit checksum field. Measurement values are scattered
+ * across these 192 bytes total size. All multi-byte integer values
+ * are represented in little endian format. Typical size is 32 bits.
+ */
 
-#define MAGIC_PAC1 0x31636170UL
-#define MAGIC_PAC2 0x32636170UL
-#define MAGIC_PAC3 0x33636170UL
+#define MAGIC_PAC1     0x70616331      /* 'pac1' */
+#define MAGIC_PAC2     0x70616332      /* 'pac2' */
+#define MAGIC_PAC3     0x70616333      /* 'pac3' */
 
-/* Length of PAC block excluding CRC */
-#define PAC_DATA_LEN 60
-/* Length of PAC block including CRC */
 #define PAC_LEN 64
+#define PAC_CRC_POS (PAC_LEN - sizeof(uint32_t))
 
 /* Offset to PAC block from start of poll data */
 #define OFF_PAC1 (0 * PAC_LEN)
 #define OFF_PAC2 (1 * PAC_LEN)
 #define OFF_PAC3 (2 * PAC_LEN)
+#define TC_POLL_LEN (3 * PAC_LEN)
+#if TC_POLL_LEN > RDTECH_TC_RSPBUFSIZE
+#  error "response length exceeds receive buffer space"
+#endif
 
 #define OFF_MODEL 4
 #define LEN_MODEL 4
@@ -57,76 +65,115 @@ static const char POLL_CMD[] = "getva";
 
 #define OFF_SERIAL 12
 
-static const uint8_t AES_KEY[] = {
+static const uint8_t aes_key[] = {
        0x58, 0x21, 0xfa, 0x56, 0x01, 0xb2, 0xf0, 0x26,
        0x87, 0xff, 0x12, 0x04, 0x62, 0x2a, 0x4f, 0xb0,
        0x86, 0xf4, 0x02, 0x60, 0x81, 0x6f, 0x9a, 0x0b,
        0xa7, 0xf1, 0x06, 0x61, 0x9a, 0xb8, 0x72, 0x88,
 };
 
-static const struct binary_analog_channel rdtech_tc_channels[] = {
-       { "V",  {   0 + 48, BVT_LE_UINT32, 1e-4, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "I",  {   0 + 52, BVT_LE_UINT32, 1e-5, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE },
-       { "D+", {  64 + 32, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "D-", {  64 + 36, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "E0", {  64 + 12, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       { "E1", {  64 + 20, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       ALL_ZERO,
+static const struct rdtech_tc_channel_desc rdtech_tc_channels[] = {
+       { "V",  {   0 + 48, BVT_LE_UINT32, }, { 100, 1e6, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I",  {   0 + 52, BVT_LE_UINT32, }, {  10, 1e6, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", {  64 + 32, BVT_LE_UINT32, }, {  10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", {  64 + 36, BVT_LE_UINT32, }, {  10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "E0", {  64 + 12, BVT_LE_UINT32, }, {   1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
+       { "E1", {  64 + 20, BVT_LE_UINT32, }, {   1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
 };
 
-static int check_pac_crc(uint8_t *data)
+static gboolean check_pac_crc(uint8_t *data)
 {
-       uint16_t crc;
-       uint32_t crc_field;
-
-       crc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_DATA_LEN);
-       crc_field = RL32(data + PAC_DATA_LEN);
+       uint16_t crc_calc;
+       uint32_t crc_recv;
 
-       if (crc != crc_field) {
+       crc_calc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_CRC_POS);
+       crc_recv = read_u32le(&data[PAC_CRC_POS]);
+       if (crc_calc != crc_recv) {
                sr_spew("CRC error. Calculated: %0x" PRIx16 ", expected: %0x" PRIx32,
-                       crc, crc_field);
-               return 0;
-       } else {
-               return 1;
+                       crc_calc, crc_recv);
+               return FALSE;
        }
+
+       return TRUE;
 }
 
-static int process_poll_pkt(struct dev_context  *devc, uint8_t *dst)
+static int process_poll_pkt(struct dev_context *devc, uint8_t *dst)
 {
        struct aes256_ctx ctx;
+       gboolean ok;
 
-       aes256_set_decrypt_key(&ctx, AES_KEY);
+       aes256_set_decrypt_key(&ctx, aes_key);
        aes256_decrypt(&ctx, TC_POLL_LEN, dst, devc->buf);
 
-       if (RL32(dst + OFF_PAC1) != MAGIC_PAC1 ||
-           RL32(dst + OFF_PAC2) != MAGIC_PAC2 ||
-           RL32(dst + OFF_PAC3) != MAGIC_PAC3) {
-               sr_err("Invalid poll packet magic values!");
-               return SR_ERR;
+       ok = TRUE;
+       ok &= read_u32be(&dst[OFF_PAC1]) == MAGIC_PAC1;
+       ok &= read_u32be(&dst[OFF_PAC2]) == MAGIC_PAC2;
+       ok &= read_u32be(&dst[OFF_PAC3]) == MAGIC_PAC3;
+       if (!ok) {
+               sr_err("Invalid poll response packet (magic values).");
+               return SR_ERR_DATA;
        }
 
-       if (!check_pac_crc(dst + OFF_PAC1) ||
-           !check_pac_crc(dst + OFF_PAC2) ||
-           !check_pac_crc(dst + OFF_PAC3)) {
-               sr_err("Invalid poll checksum!");
-               return SR_ERR;
+       ok &= check_pac_crc(&dst[OFF_PAC1]);
+       ok &= check_pac_crc(&dst[OFF_PAC2]);
+       ok &= check_pac_crc(&dst[OFF_PAC3]);
+       if (!ok) {
+               sr_err("Invalid poll response packet (checksum).");
+               return SR_ERR_DATA;
+       }
+
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               static const size_t chunk_max = 32;
+
+               const uint8_t *rdptr;
+               size_t rdlen, chunk_addr, chunk_len;
+               GString *txt;
+
+               sr_spew("check passed on decrypted receive data");
+               rdptr = dst;
+               rdlen = TC_POLL_LEN;
+               chunk_addr = 0;
+               while (rdlen) {
+                       chunk_len = rdlen;
+                       if (chunk_len > chunk_max)
+                               chunk_len = chunk_max;
+                       txt = sr_hexdump_new(rdptr, chunk_len);
+                       sr_spew("%04zx  %s", chunk_addr, txt->str);
+                       sr_hexdump_free(txt);
+                       chunk_addr += chunk_len;
+                       rdptr += chunk_len;
+                       rdlen -= chunk_len;
+               }
        }
 
        return SR_OK;
 }
 
-SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context  *devc)
+SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc)
 {
+       static const char *poll_cmd_cdc = "getva";
+       static const char *poll_cmd_ble = "bgetva\r\n";
+
        int len;
        uint8_t poll_pkt[TC_POLL_LEN];
 
-       if (serial_write_blocking(serial, &POLL_CMD, sizeof(POLL_CMD) - 1,
-                                  SERIAL_WRITE_TIMEOUT_MS) < 0) {
-               sr_err("Unable to send probe request.");
+       /* Construct the request text. Which differs across transports. */
+       devc->is_bluetooth = ser_name_is_bt(serial);
+       snprintf(devc->req_text, sizeof(devc->req_text), "%s",
+               devc->is_bluetooth ? poll_cmd_ble : poll_cmd_cdc);
+       sr_dbg("is bluetooth %d -> poll request '%s'.",
+               devc->is_bluetooth, devc->req_text);
+
+       /* Transmit the request. */
+       len = serial_write_blocking(serial,
+               devc->req_text, strlen(devc->req_text), WRITE_TO_MS);
+       if (len < 0) {
+               sr_err("Failed to send probe request.");
                return SR_ERR;
        }
 
-       len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, TC_TIMEOUT_MS);
+       /* Receive a response. */
+       len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, PROBE_TO_MS);
        if (len != TC_POLL_LEN) {
                sr_err("Failed to read probe response.");
                return SR_ERR;
@@ -138,73 +185,139 @@ SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_contex
        }
 
        devc->channels = rdtech_tc_channels;
-       devc->dev_info.model_name = g_strndup((const char *)poll_pkt + OFF_MODEL, LEN_MODEL);
-       devc->dev_info.fw_ver = g_strndup((const char *)poll_pkt + OFF_FW_VER, LEN_FW_VER);
-       devc->dev_info.serial_num = RL32(poll_pkt + OFF_SERIAL);
+       devc->channel_count = ARRAY_SIZE(rdtech_tc_channels);
+       devc->dev_info.model_name = g_strndup((const char *)&poll_pkt[OFF_MODEL], LEN_MODEL);
+       devc->dev_info.fw_ver = g_strndup((const char *)&poll_pkt[OFF_FW_VER], LEN_FW_VER);
+       devc->dev_info.serial_num = read_u32le(&poll_pkt[OFF_SERIAL]);
 
        return SR_OK;
 }
 
-SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi)
+SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi, gboolean force)
 {
-       struct dev_context *devc = sdi->priv;
-       struct sr_serial_dev_inst *serial = sdi->conn;
+       struct dev_context *devc;
+       int64_t now, elapsed;
+       struct sr_serial_dev_inst *serial;
+       int len;
+
+       /*
+        * Don't send the request while receive data is being accumulated.
+        * Defer request transmission when a previous request has not yet
+        * seen any response data at all (more probable to happen shortly
+        * after connecting to the peripheral).
+        */
+       devc = sdi->priv;
+       if (!force) {
+               if (devc->rdlen)
+                       return SR_OK;
+               if (!devc->rx_after_tx)
+                       return SR_OK;
+       }
+
+       /*
+        * Send the request when the transmit interval was reached. Or
+        * when the caller forced the transmission.
+        */
+       now = g_get_monotonic_time() / 1000;
+       elapsed = now - devc->cmd_sent_at;
+       if (!force && elapsed < POLL_PERIOD_MS)
+               return SR_OK;
 
-       if (serial_write_blocking(serial, &POLL_CMD, sizeof(POLL_CMD) - 1,
-                                  SERIAL_WRITE_TIMEOUT_MS) < 0) {
+       /*
+        * Transmit another measurement request. Only advance the
+        * interval after successful transmission.
+        */
+       serial = sdi->conn;
+       len = serial_write_blocking(serial,
+               devc->req_text, strlen(devc->req_text), WRITE_TO_MS);
+       if (len < 0) {
                sr_err("Unable to send poll request.");
                return SR_ERR;
        }
-
-       devc->cmd_sent_at = g_get_monotonic_time() / 1000;
+       devc->cmd_sent_at = now;
+       devc->rx_after_tx = 0;
 
        return SR_OK;
 }
 
-static void handle_poll_data(const struct sr_dev_inst *sdi)
+static int handle_poll_data(struct sr_dev_inst *sdi)
 {
-       struct dev_context *devc = sdi->priv;
+       struct dev_context *devc;
        uint8_t poll_pkt[TC_POLL_LEN];
-       int i;
-       GSList *ch;
-
-       sr_spew("Received poll packet (len: %d).", devc->buflen);
-       if (devc->buflen != TC_POLL_LEN) {
-               sr_err("Unexpected poll packet length: %i", devc->buflen);
-               return;
+       size_t ch_idx;
+       const struct rdtech_tc_channel_desc *pch;
+       int ret;
+       float v;
+
+       devc = sdi->priv;
+       sr_spew("Received poll packet (len: %zu).", devc->rdlen);
+       if (devc->rdlen < TC_POLL_LEN) {
+               sr_err("Insufficient poll packet length: %zu", devc->rdlen);
+               return SR_ERR_DATA;
        }
 
        if (process_poll_pkt(devc, poll_pkt) != SR_OK) {
                sr_err("Failed to process poll packet.");
-               return;
+               return SR_ERR_DATA;
+       }
+
+       ret = SR_OK;
+       std_session_send_df_frame_begin(sdi);
+       for (ch_idx = 0; ch_idx < devc->channel_count; ch_idx++) {
+               pch = &devc->channels[ch_idx];
+               ret = bv_get_value_len(&v, &pch->spec, poll_pkt, TC_POLL_LEN);
+               if (ret != SR_OK)
+                       break;
+               ret = feed_queue_analog_submit_one(devc->feeds[ch_idx], v, 1);
+               if (ret != SR_OK)
+                       break;
        }
+       std_session_send_df_frame_end(sdi);
 
-       for (ch = sdi->channels, i = 0; ch; ch = g_slist_next(ch), i++) {
-               bv_send_analog_channel(sdi, ch->data,
-                                      &devc->channels[i], poll_pkt, TC_POLL_LEN);
-        }
+       sr_sw_limits_update_frames_read(&devc->limits, 1);
+       if (sr_sw_limits_check(&devc->limits))
+               sr_dev_acquisition_stop(sdi);
 
-       sr_sw_limits_update_samples_read(&devc->limits, 1);
+       return ret;
 }
 
-static void recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
+static int recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
 {
-       struct dev_context *devc = sdi->priv;
+       struct dev_context *devc;
+       size_t space;
        int len;
-
-       /* Serial data arrived. */
-       while (devc->buflen < TC_POLL_LEN) {
-               len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1);
-               if (len < 1)
-                       return;
-
-               devc->buflen++;
+       int ret;
+
+       /* Receive data became available. Drain the transport layer. */
+       devc = sdi->priv;
+       while (devc->rdlen < TC_POLL_LEN) {
+               space = sizeof(devc->buf) - devc->rdlen;
+               len = serial_read_nonblocking(serial,
+                       &devc->buf[devc->rdlen], space);
+               if (len < 0)
+                       return SR_ERR_IO;
+               if (len == 0)
+                       return SR_OK;
+               devc->rdlen += len;
+               devc->rx_after_tx += len;
        }
 
-       if (devc->buflen == TC_POLL_LEN)
-               handle_poll_data(sdi);
+       /*
+        * TODO Want to (re-)synchronize to the packet stream? The
+        * 'pac1' string literal would be a perfect match for that.
+        */
+
+       /* Process packets when their reception has completed. */
+       while (devc->rdlen >= TC_POLL_LEN) {
+               ret = handle_poll_data(sdi);
+               if (ret != SR_OK)
+                       return ret;
+               devc->rdlen -= TC_POLL_LEN;
+               if (devc->rdlen)
+                       memmove(devc->buf, &devc->buf[TC_POLL_LEN], devc->rdlen);
+       }
 
-       devc->buflen = 0;
+       return SR_OK;
 }
 
 SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data)
@@ -212,30 +325,31 @@ SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data)
        struct sr_dev_inst *sdi;
        struct dev_context *devc;
        struct sr_serial_dev_inst *serial;
-       int64_t now, elapsed;
+       int ret;
 
        (void)fd;
 
        if (!(sdi = cb_data))
                return TRUE;
-
        if (!(devc = sdi->priv))
                return TRUE;
 
+       /* Handle availability of receive data. */
        serial = sdi->conn;
-       if (revents == G_IO_IN)
-               recv_poll_data(sdi, serial);
+       if (revents == G_IO_IN) {
+               ret = recv_poll_data(sdi, serial);
+               if (ret != SR_OK)
+                       sr_dev_acquisition_stop(sdi);
+       }
 
+       /* Check configured acquisition limits. */
        if (sr_sw_limits_check(&devc->limits)) {
                sr_dev_acquisition_stop(sdi);
                return TRUE;
        }
 
-       now = g_get_monotonic_time() / 1000;
-       elapsed = now - devc->cmd_sent_at;
-
-       if (elapsed > TC_POLL_PERIOD_MS)
-               rdtech_tc_poll(sdi);
+       /* Periodically retransmit measurement requests. */
+       (void)rdtech_tc_poll(sdi, FALSE);
 
        return TRUE;
 }
index 963c7765623bf654514a7c273660b5022da341f4..e495375ca673dc7b46bef478fbec0c964e237be4 100644 (file)
 
 #define LOG_PREFIX "rdtech-tc"
 
-#define RDTECH_TC_BUFSIZE 256
+/*
+ * Keep request and response buffers of sufficient size. The maximum
+ * request text currently involved is "bgetva\r\n" which translates
+ * to 9 bytes. The poll response (a measurement, the largest amount
+ * of data that is currently received) is 192 bytes in length. Add
+ * some slack for alignment, and for in-flight messages or adjacent
+ * data during synchronization to the data stream.
+ */
+#define RDTECH_TC_MAXREQLEN 12
+#define RDTECH_TC_RSPBUFSIZE 256
 
 struct rdtech_dev_info {
        char *model_name;
@@ -33,18 +42,31 @@ struct rdtech_dev_info {
        uint32_t serial_num;
 };
 
+struct rdtech_tc_channel_desc {
+       const char *name;
+       struct binary_value_spec spec;
+       struct sr_rational scale;
+       int digits;
+       enum sr_mq mq;
+       enum sr_unit unit;
+};
+
 struct dev_context {
+       gboolean is_bluetooth;
+       char req_text[RDTECH_TC_MAXREQLEN];
        struct rdtech_dev_info dev_info;
-       const struct binary_analog_channel *channels;
+       const struct rdtech_tc_channel_desc *channels;
+       size_t channel_count;
+       struct feed_queue_analog **feeds;
        struct sr_sw_limits limits;
-
-       uint8_t buf[RDTECH_TC_BUFSIZE];
-       int buflen;
+       uint8_t buf[RDTECH_TC_RSPBUFSIZE];
+       size_t rdlen;
        int64_t cmd_sent_at;
+       size_t rx_after_tx;
 };
 
-SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context  *devc);
+SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc);
 SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data);
-SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi);
+SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi, gboolean force);
 
 #endif
index fbc38b1aadcbd1570608a7456bcb8d1fdce61367..67ff37d87b6b675cc20ea826f73f4271c099080f 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
 #include <config.h>
+
 #include <glib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
 #include <fcntl.h>
+#include <libsigrok/libsigrok.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
-#include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
-
 #include "protocol.h"
 
 #define RDTECH_UM_SERIALCOMM "115200/8n1"
@@ -43,18 +42,22 @@ static const uint32_t drvopts[] = {
 
 static const uint32_t devopts[] = {
        SR_CONF_CONTINUOUS,
-       SR_CONF_LIMIT_SAMPLES | SR_CONF_SET,
-       SR_CONF_LIMIT_MSEC | SR_CONF_SET,
+       SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
 };
 
-static GSList *rdtech_um_scan(struct sr_dev_driver *di, const char *conn,
-                             const char *serialcomm)
+static GSList *rdtech_um_scan(struct sr_dev_driver *di,
+       const char *conn, const char *serialcomm)
 {
        struct sr_serial_dev_inst *serial;
-       const struct rdtech_um_profile *p = NULL;
-       GSList *devices = NULL;
-       struct dev_context *devc = NULL;
-       struct sr_dev_inst *sdi = NULL;
+       const struct rdtech_um_profile *p;
+       GSList *devices;
+       struct dev_context *devc;
+       struct sr_dev_inst *sdi;
+       size_t ch_idx;
+       const struct rdtech_um_channel_desc *pch;
+       struct sr_channel *ch;
+       struct feed_queue_analog *feed;
 
        serial = sr_serial_dev_inst_new(conn, serialcomm);
        if (serial_open(serial, SERIAL_RDWR) != SR_OK)
@@ -66,25 +69,31 @@ static GSList *rdtech_um_scan(struct sr_dev_driver *di, const char *conn,
                goto err_out_serial;
        }
 
-       devc = g_malloc0(sizeof(struct dev_context));
-       sdi = g_malloc0(sizeof(struct sr_dev_inst));
-
+       devc = g_malloc0(sizeof(*devc));
        sr_sw_limits_init(&devc->limits);
        devc->profile = p;
 
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->priv = devc;
        sdi->status = SR_ST_INACTIVE;
        sdi->vendor = g_strdup("RDTech");
        sdi->model = g_strdup(p->model_name);
        sdi->version = NULL;
        sdi->inst_type = SR_INST_SERIAL;
        sdi->conn = serial;
-       sdi->priv = devc;
 
-       for (int i = 0; p->channels[i].name; i++)
-               sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE,
-                              p->channels[i].name);
+       devc->feeds = g_malloc0(p->channel_count * sizeof(devc->feeds[0]));
+       for (ch_idx = 0; ch_idx < p->channel_count; ch_idx++) {
+               pch = &p->channels[ch_idx];
+               ch = sr_channel_new(sdi, ch_idx,
+                       SR_CHANNEL_ANALOG, TRUE, pch->name);
+               feed = feed_queue_analog_alloc(sdi, 1, pch->digits, ch);
+               feed_queue_analog_mq_unit(feed, pch->mq, 0, pch->unit);
+               feed_queue_analog_scale_offset(feed, &pch->scale, NULL);
+               devc->feeds[ch_idx] = feed;
+       }
 
-       devices = g_slist_append(devices, sdi);
+       devices = g_slist_append(NULL, sdi);
        serial_close(serial);
        if (!devices)
                sr_serial_dev_inst_free(serial);
@@ -99,31 +108,55 @@ err_out:
        return NULL;
 }
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
+static void clear_helper(struct dev_context *devc)
 {
-       struct sr_config *src;
-       const char *conn = NULL;
-       const char *serialcomm = RDTECH_UM_SERIALCOMM;
-
-       for (GSList *l = options; l; l = l->next) {
-               src = l->data;
-               switch (src->key) {
-               case SR_CONF_CONN:
-                       conn = g_variant_get_string(src->data, NULL);
-                       break;
-               case SR_CONF_SERIALCOMM:
-                       serialcomm = g_variant_get_string(src->data, NULL);
-                       break;
-               }
+       const struct rdtech_um_profile *p;
+       size_t ch_idx;
+
+       if (!devc)
+               return;
+
+       p = devc->profile;
+       if (p && devc->feeds) {
+               for (ch_idx = 0; ch_idx < p->channel_count; ch_idx++)
+                       feed_queue_analog_free(devc->feeds[ch_idx]);
+               g_free(devc->feeds);
        }
+}
+
+static int dev_clear(const struct sr_dev_driver *di)
+{
+       return std_dev_clear_with_callback(di, (std_dev_clear_callback)clear_helper);
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       const char *conn;
+       const char *serialcomm;
+
+       conn = NULL;
+       serialcomm = RDTECH_UM_SERIALCOMM;
+       (void)sr_serial_extract_options(options, &conn, &serialcomm);
        if (!conn)
                return NULL;
 
        return rdtech_um_scan(di, conn, serialcomm);
 }
 
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+
+       (void)cg;
+
+       devc = sdi->priv;
+
+       return sr_sw_limits_config_get(&devc->limits, key, data);
+}
+
 static int config_set(uint32_t key, GVariant *data,
-                     const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        struct dev_context *devc;
 
@@ -135,23 +168,25 @@ 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)
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
        return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
 }
 
 static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 {
-       struct dev_context *devc = sdi->priv;
-       struct sr_serial_dev_inst *serial = sdi->conn;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
 
+       devc = sdi->priv;
        sr_sw_limits_acquisition_start(&devc->limits);
        std_session_send_df_header(sdi);
 
+       serial = sdi->conn;
        serial_source_add(sdi->session, serial, G_IO_IN, 50,
-                         rdtech_um_receive_data, (void *)sdi);
+               rdtech_um_receive_data, (void *)sdi);
 
-       return rdtech_um_poll(sdi);
+       return rdtech_um_poll(sdi, TRUE);
 }
 
 static struct sr_dev_driver rdtech_um_driver_info = {
@@ -162,8 +197,8 @@ static struct sr_dev_driver rdtech_um_driver_info = {
        .cleanup = std_cleanup,
        .scan = scan,
        .dev_list = std_dev_list,
-       .dev_clear = std_dev_clear,
-       .config_get = NULL,
+       .dev_clear = dev_clear,
+       .config_get = config_get,
        .config_set = config_set,
        .config_list = config_list,
        .dev_open = std_serial_dev_open,
index 04de768713ac04d2d6c039454e1b7982610466cb..806f9c2d49f0dc7b38691f2c69b823a1f3e2b12c 100644 (file)
  */
 
 #include <config.h>
-#include <stdlib.h>
-#include <math.h>
-#include <string.h>
+
 #include <glib.h>
 #include <libsigrok/libsigrok.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
-#define SERIAL_WRITE_TIMEOUT_MS 1
+/* Read/write timeouts, poll request intervals. */
+#define PROBE_TO_MS 1000
+#define WRITE_TO_MS 1
+#define POLL_PERIOD_MS 100
 
-#define UM_POLL_LEN 130
-#define UM_POLL_PERIOD_MS 100
-#define UM_TIMEOUT_MS 1000
+/* Expected receive data size for poll responses. */
+#define POLL_RECV_LEN 130
 
+/* Command code to request another poll response. */
 #define UM_CMD_POLL 0xf0
 
-static const struct binary_analog_channel rdtech_default_channels[] = {
-       { "V",  { 2, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "I",  { 4, BVT_BE_UINT16, 0.001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
-       { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "T", { 10, BVT_BE_UINT16, 1.0, },  0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+static const struct rdtech_um_channel_desc default_channels[] = {
+       { "V", { 2, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I", { 4, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
        /* Threshold-based recording (mWh) */
-       { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       ALL_ZERO,
+       { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
 };
 
-static const struct binary_analog_channel rdtech_um25c_channels[] = {
-       { "V", { 2, BVT_BE_UINT16, 0.001, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "I", { 4, BVT_BE_UINT16, 0.0001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
-       { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+static const struct rdtech_um_channel_desc um25c_channels[] = {
+       { "V", { 2, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I", { 4, BVT_BE_UINT16, }, { 100, 1e6, }, 4, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
        /* Threshold-based recording (mWh) */
-       { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       ALL_ZERO,
+       { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
 };
 
-static int poll_csum_fff1(char buf[], int len)
+static gboolean csum_ok_fff1(const uint8_t *buf, size_t len)
 {
-       if (len != UM_POLL_LEN)
-               return 0;
-       else
-               return RB16(&buf[len - 2]) == 0xFFF1;
+       uint16_t csum_recv;
+
+       if (len != POLL_RECV_LEN)
+               return FALSE;
+
+       csum_recv = read_u16be(&buf[len - sizeof(uint16_t)]);
+       if (csum_recv != 0xfff1)
+               return FALSE;
+
+       return TRUE;
 }
 
-static int poll_csum_um34c(char buf[], int len)
+static gboolean csum_ok_um34c(const uint8_t *buf, size_t len)
 {
        static const int positions[] = {
                1, 3, 7, 9, 15, 17, 19, 23, 31, 39, 41, 45, 49, 53,
                55, 57, 59, 63, 67, 69, 73, 79, 83, 89, 97, 99, 109,
                111, 113, 119, 121, 127,
        };
-       unsigned int i;
-       uint8_t csum = 0;
 
-       if (len != UM_POLL_LEN)
-               return 0;
+       size_t i;
+       uint8_t csum_calc, csum_recv;
+
+       if (len != POLL_RECV_LEN)
+               return FALSE;
 
+       csum_calc = 0;
        for (i = 0; i < ARRAY_SIZE(positions); i++)
-               csum ^= buf[positions[i]];
+               csum_calc ^= buf[positions[i]];
+       csum_recv = read_u8(&buf[len - sizeof(uint8_t)]);
+       if (csum_recv != csum_calc)
+               return FALSE;
 
-       return csum == (uint8_t)buf[len - 1];
+       return TRUE;
 }
 
 static const struct rdtech_um_profile um_profiles[] = {
-       { "UM24C", RDTECH_UM24C, rdtech_default_channels, &poll_csum_fff1, },
-       { "UM25C", RDTECH_UM25C, rdtech_um25c_channels, &poll_csum_fff1, },
-       { "UM34C", RDTECH_UM34C, rdtech_default_channels, &poll_csum_um34c, },
+       { "UM24C", RDTECH_UM24C, ARRAY_AND_SIZE(default_channels), csum_ok_fff1, },
+       { "UM25C", RDTECH_UM25C, ARRAY_AND_SIZE(um25c_channels), csum_ok_fff1, },
+       { "UM34C", RDTECH_UM34C, ARRAY_AND_SIZE(default_channels), csum_ok_um34c, },
 };
 
 static const struct rdtech_um_profile *find_profile(uint16_t id)
 {
-       unsigned int i;
+       size_t i;
+       const struct rdtech_um_profile *profile;
+
        for (i = 0; i < ARRAY_SIZE(um_profiles); i++) {
-               if (um_profiles[i].model_id == id)
-                       return &um_profiles[i];
+               profile = &um_profiles[i];
+               if (profile->model_id == id)
+                       return profile;
        }
        return NULL;
 }
@@ -102,103 +119,213 @@ static const struct rdtech_um_profile *find_profile(uint16_t id)
 SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial)
 {
        const struct rdtech_um_profile *p;
-       static const uint8_t request = UM_CMD_POLL;
-       char buf[RDTECH_UM_BUFSIZE];
-       int len;
-
-       if (serial_write_blocking(serial, &request, sizeof(request),
-                                  SERIAL_WRITE_TIMEOUT_MS) < 0) {
-               sr_err("Unable to send probe request.");
+       uint8_t req;
+       int ret;
+       uint8_t buf[RDTECH_UM_BUFSIZE];
+       int rcvd;
+       uint16_t model_id;
+
+       req = UM_CMD_POLL;
+       ret = serial_write_blocking(serial, &req, sizeof(req), WRITE_TO_MS);
+       if (ret < 0) {
+               sr_err("Failed to send probe request.");
                return NULL;
        }
 
-       len = serial_read_blocking(serial, buf, UM_POLL_LEN, UM_TIMEOUT_MS);
-       if (len != UM_POLL_LEN) {
+       rcvd = serial_read_blocking(serial, buf, POLL_RECV_LEN, PROBE_TO_MS);
+       if (rcvd != POLL_RECV_LEN) {
                sr_err("Failed to read probe response.");
                return NULL;
        }
 
-       p = find_profile(RB16(&buf[0]));
+       model_id = read_u16be(&buf[0]);
+       p = find_profile(model_id);
        if (!p) {
-               sr_err("Unrecognized UM device (0x%.4" PRIx16 ")!", RB16(&buf[0]));
+               sr_err("Unrecognized UM device (0x%.4" PRIx16 ").", model_id);
                return NULL;
        }
 
-       if (!p->poll_csum(buf, len)) {
-               sr_err("Probe response contains illegal checksum or end marker.\n");
+       if (!p->csum_ok(buf, rcvd)) {
+               sr_err("Probe response fails checksum verification.");
                return NULL;
        }
 
        return p;
 }
 
-SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi)
+SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi, gboolean force)
 {
-       struct dev_context *devc = sdi->priv;
-       struct sr_serial_dev_inst *serial = sdi->conn;
-       static const uint8_t request = UM_CMD_POLL;
+       struct dev_context *devc;
+       int64_t now, elapsed;
+       struct sr_serial_dev_inst *serial;
+       uint8_t req;
+       int ret;
 
-       if (serial_write_blocking(serial, &request, sizeof(request),
-                                  SERIAL_WRITE_TIMEOUT_MS) < 0) {
+       /* Don't send request when receive data is being accumulated. */
+       devc = sdi->priv;
+       if (!force && devc->buflen)
+               return SR_OK;
+
+       /* Check for expired intervals or forced requests. */
+       now = g_get_monotonic_time() / 1000;
+       elapsed = now - devc->cmd_sent_at;
+       if (!force && elapsed < POLL_PERIOD_MS)
+               return SR_OK;
+
+       /* Send another poll request. Update interval only on success. */
+       serial = sdi->conn;
+       req = UM_CMD_POLL;
+       ret = serial_write_blocking(serial, &req, sizeof(req), WRITE_TO_MS);
+       if (ret < 0) {
                sr_err("Unable to send poll request.");
                return SR_ERR;
        }
-
-       devc->cmd_sent_at = g_get_monotonic_time() / 1000;
+       devc->cmd_sent_at = now;
 
        return SR_OK;
 }
 
-static void handle_poll_data(const struct sr_dev_inst *sdi)
+static int process_data(struct sr_dev_inst *sdi,
+       const uint8_t *data, size_t dlen)
 {
-       struct dev_context *devc = sdi->priv;
-       int i;
-       GSList *ch;
-
-       sr_spew("Received poll packet (len: %d).", devc->buflen);
-       if (devc->buflen != UM_POLL_LEN) {
-               sr_err("Unexpected poll packet length: %i", devc->buflen);
-               return;
+       struct dev_context *devc;
+       const struct rdtech_um_profile *p;
+       size_t ch_idx;
+       float v;
+       int ret;
+
+       devc = sdi->priv;
+       p = devc->profile;
+
+       sr_spew("Received poll packet (len: %zu).", dlen);
+       if (dlen < POLL_RECV_LEN) {
+               sr_err("Insufficient response data length: %zu", dlen);
+               return SR_ERR_DATA;
        }
 
-       for (ch = sdi->channels, i = 0; ch; ch = g_slist_next(ch), i++)
-               bv_send_analog_channel(sdi, ch->data,
-                                      &devc->profile->channels[i],
-                                      devc->buf, devc->buflen);
+       if (!p->csum_ok(data, POLL_RECV_LEN)) {
+               sr_err("Packet checksum verification failed.");
+               return SR_ERR_DATA;
+       }
+
+       ret = SR_OK;
+       std_session_send_df_frame_begin(sdi);
+       for (ch_idx = 0; ch_idx < p->channel_count; ch_idx++) {
+               ret = bv_get_value_len(&v, &p->channels[ch_idx].spec, data, dlen);
+               if (ret != SR_OK)
+                       break;
+               ret = feed_queue_analog_submit_one(devc->feeds[ch_idx], v, 1);
+               if (ret != SR_OK)
+                       break;
+       }
+       std_session_send_df_frame_end(sdi);
 
-       sr_sw_limits_update_samples_read(&devc->limits, 1);
+       sr_sw_limits_update_frames_read(&devc->limits, 1);
+       if (sr_sw_limits_check(&devc->limits))
+               sr_dev_acquisition_stop(sdi);
+
+       return ret;
 }
 
-static void recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
+static int accum_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
 {
-       struct dev_context *devc = sdi->priv;
-       const struct rdtech_um_profile *p = devc->profile;
-       int len;
-
-       /* Serial data arrived. */
-       while (devc->buflen < UM_POLL_LEN) {
-               len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1);
-               if (len < 1)
-                       return;
-
-               devc->buflen++;
-
-               /* Check if the poll model ID matches the profile. */
-               if (devc->buflen == 2 && RB16(devc->buf) != p->model_id) {
-                       sr_warn("Illegal model ID in poll response (0x%.4" PRIx16 "),"
-                               " skipping 1 byte.",
-                               RB16(devc->buf));
-                       devc->buflen--;
-                       memmove(devc->buf, devc->buf + 1, devc->buflen);
-               }
+       struct dev_context *devc;
+       const struct rdtech_um_profile *p;
+       uint8_t *rdptr;
+       size_t space, rcvd, rdlen;
+       int ret;
+       gboolean do_sync_check;
+       size_t sync_len, sync_idx;
+
+       /*
+        * Receive data became available. Drain the serial transport.
+        * Grab incoming data in as large a chunk as possible. Also
+        * copes with zero receive data length, as some transports may
+        * trigger periodically without data really being available.
+        */
+       devc = sdi->priv;
+       p = devc->profile;
+       rdptr = &devc->buf[devc->buflen];
+       space = sizeof(devc->buf) - devc->buflen;
+       do_sync_check = FALSE;
+       sync_len = sizeof(uint16_t);
+       while (space) {
+               ret = serial_read_nonblocking(serial, rdptr, space);
+               if (ret < 0)
+                       return SR_ERR_IO;
+               rcvd = (size_t)ret;
+               if (rcvd == 0)
+                       break;
+               if (rcvd > space)
+                       return SR_ERR_BUG;
+               if (devc->buflen < sync_len)
+                       do_sync_check = TRUE;
+               devc->buflen += rcvd;
+               if (devc->buflen < sync_len)
+                       do_sync_check = FALSE;
+               space -= rcvd;
+               rdptr += rcvd;
        }
 
-       if (devc->buflen == UM_POLL_LEN && p->poll_csum(devc->buf, devc->buflen))
-               handle_poll_data(sdi);
-       else
-               sr_warn("Skipping packet with illegal checksum / end marker.");
+       /*
+        * Synchronize to the packetized input stream. Check the model
+        * ID at the start of receive data. Which is a weak condition,
+        * but going out of sync should be rare, and repeated attempts
+        * to synchronize should eventually succeed. Try to rate limit
+        * the emission of diagnostics messages. (Re-)run this logic
+        * at the first reception which makes enough data available,
+        * but not during subsequent accumulation of more data.
+        *
+        * Reducing redundancy in the implementation at the same time as
+        * increasing robustness would involve the creation of a checker
+        * routine, which just gets called for every byte position until
+        * it succeeds. Similar to what a previous implementation of the
+        * read loop did, which was expensive on the serial transport.
+        */
+       sync_idx = 0;
+       if (do_sync_check && read_u16be(&devc->buf[sync_idx]) != p->model_id)
+               sr_warn("Unexpected response data, trying to synchronize.");
+       while (do_sync_check) {
+               if (sync_idx + sync_len >= devc->buflen)
+                       break;
+               if (read_u16be(&devc->buf[sync_idx]) == p->model_id)
+                       break;
+               sync_idx++;
+       }
+       if (do_sync_check && sync_idx) {
+               sr_dbg("Skipping %zu bytes in attempt to sync.", sync_idx);
+               sync_len = devc->buflen - sync_idx;
+               if (sync_len)
+                       memmove(&devc->buf[0], &devc->buf[sync_idx], sync_len);
+               devc->buflen -= sync_idx;
+       }
+
+       /*
+        * Process packets as their reception completes. Periodically
+        * re-transmit poll requests. Discard consumed data after all
+        * processing has completed.
+        */
+       rdptr = devc->buf;
+       rdlen = devc->buflen;
+       ret = SR_OK;
+       while (ret == SR_OK && rdlen >= POLL_RECV_LEN) {
+               ret = process_data(sdi, rdptr, rdlen);
+               if (ret != SR_OK) {
+                       sr_err("Processing response packet failed.");
+                       break;
+               }
+               rdptr += POLL_RECV_LEN;
+               rdlen -= POLL_RECV_LEN;
 
-       devc->buflen = 0;
+               if (0 && !sr_sw_limits_check(&devc->limits))
+                       (void)rdtech_um_poll(sdi, FALSE);
+       }
+       rcvd = rdptr - devc->buf;
+       devc->buflen -= rcvd;
+       if (devc->buflen)
+               memmove(&devc->buf[0], rdptr, devc->buflen);
+
+       return ret;
 }
 
 SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data)
@@ -206,30 +333,36 @@ SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data)
        struct sr_dev_inst *sdi;
        struct dev_context *devc;
        struct sr_serial_dev_inst *serial;
-       int64_t now, elapsed;
+       int ret;
 
        (void)fd;
 
        if (!(sdi = cb_data))
                return TRUE;
-
        if (!(devc = sdi->priv))
                return TRUE;
 
+       /*
+        * Drain and process receive data as it becomes available.
+        * Terminate acquisition upon receive or processing error.
+        */
        serial = sdi->conn;
-       if (revents == G_IO_IN)
-               recv_poll_data(sdi, serial);
+       if (revents == G_IO_IN) {
+               ret = accum_data(sdi, serial);
+               if (ret != SR_OK) {
+                       sr_dev_acquisition_stop(sdi);
+                       return TRUE;
+               }
+       }
 
+       /* Check configured acquisition limits. */
        if (sr_sw_limits_check(&devc->limits)) {
                sr_dev_acquisition_stop(sdi);
                return TRUE;
        }
 
-       now = g_get_monotonic_time() / 1000;
-       elapsed = now - devc->cmd_sent_at;
-
-       if (elapsed > UM_POLL_PERIOD_MS)
-               rdtech_um_poll(sdi);
+       /* Periodically retransmit measurement requests. */
+       (void)rdtech_um_poll(sdi, FALSE);
 
        return TRUE;
 }
index f344f1b9eb2960e10a1664e2944a3d46f1bfae25..ac147d8c026af5eca945103aa3c47a02862caad2 100644 (file)
@@ -33,32 +33,34 @@ enum rdtech_um_model_id {
        RDTECH_UM34C = 0x0d4c,
 };
 
-enum rdtech_um_checksum {
-       RDTECH_CSUM_STATIC_FFF1,
-       RDTECH_CSUM_UM34C,
+struct rdtech_um_channel_desc {
+       const char *name;
+       struct binary_value_spec spec;
+       struct sr_rational scale;
+       int digits;
+       enum sr_mq mq;
+       enum sr_unit unit;
 };
 
-/* Supported device profiles */
 struct rdtech_um_profile {
        const char *model_name;
        enum rdtech_um_model_id model_id;
-       const struct binary_analog_channel *channels;
-
-       /* Verify poll packet checksum; return 1 if OK, 0 otherwise. */
-       int (*poll_csum)(char buf[], int len);
+       const struct rdtech_um_channel_desc *channels;
+       size_t channel_count;
+       gboolean (*csum_ok)(const uint8_t *buf, size_t len);
 };
 
 struct dev_context {
        const struct rdtech_um_profile *profile;
        struct sr_sw_limits limits;
-
-       char buf[RDTECH_UM_BUFSIZE];
-       int buflen;
+       struct feed_queue_analog **feeds;
+       uint8_t buf[RDTECH_UM_BUFSIZE];
+       size_t buflen;
        int64_t cmd_sent_at;
 };
 
 SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial);
 SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data);
-SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi);
+SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi, gboolean force);
 
 #endif
index e8a3aadc8acd4ea2fd8edaa2824bb114abb42f31..a173243264576e2a0ff6e8859d03e240f464f080 100644 (file)
@@ -380,12 +380,9 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
        for (i = 0; i < device->num_channels; i++) {
                ch = sr_channel_new(sdi, ch_idx++, SR_CHANNEL_ANALOG, TRUE,
                                device->channels[i].name);
-               cg = g_malloc0(sizeof(*cg));
                snprintf(tmp, sizeof(tmp), "%u", i + 1);
-               cg->name = g_strdup(tmp);
+               cg = sr_channel_group_new(sdi, tmp, NULL);
                cg->channels = g_slist_append(cg->channels, ch);
-
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 
        /* Create channels for the frequency counter output. */
index 632c95963cb75e517e6d7abe3cbff783e6cbee70..6703d1cea3145a4ccffaf53ef102677662cd8f8b 100644 (file)
@@ -273,14 +273,15 @@ static const struct rigol_ds_model supported_models[] = {
        {SERIES(DS1000Z), "DS1104Z", {5, 1000000000}, CH_INFO(4, false), std_cmd},
        {SERIES(DS1000Z), "DS1074Z-S", {5, 1000000000}, CH_INFO(4, false), std_cmd},
        {SERIES(DS1000Z), "DS1104Z-S", {5, 1000000000}, CH_INFO(4, false), std_cmd},
-       {SERIES(DS1000Z), "DS1074Z Plus", {5, 1000000000}, CH_INFO(4, false), std_cmd},
-       {SERIES(DS1000Z), "DS1104Z Plus", {5, 1000000000}, CH_INFO(4, false), std_cmd},
+       {SERIES(DS1000Z), "DS1074Z Plus", {5, 1000000000}, CH_INFO(4, true), std_cmd},
+       {SERIES(DS1000Z), "DS1104Z Plus", {5, 1000000000}, CH_INFO(4, true), std_cmd},
        {SERIES(DS1000Z), "DS1102Z-E", {2, 1000000000}, CH_INFO(2, false), std_cmd},
        {SERIES(DS1000Z), "DS1202Z-E", {2, 1000000000}, CH_INFO(2, false), std_cmd},
        {SERIES(DS1000Z), "MSO1074Z", {5, 1000000000}, CH_INFO(4, true), std_cmd},
        {SERIES(DS1000Z), "MSO1104Z", {5, 1000000000}, CH_INFO(4, true), std_cmd},
        {SERIES(DS1000Z), "MSO1074Z-S", {5, 1000000000}, CH_INFO(4, true), std_cmd},
        {SERIES(DS1000Z), "MSO1104Z-S", {5, 1000000000}, CH_INFO(4, true), std_cmd},
+       {SERIES(DS4000), "DS4014", {1, 1000000000}, CH_INFO(4, false), std_cmd},
        {SERIES(DS4000), "DS4024", {1, 1000000000}, CH_INFO(4, false), std_cmd},
        {SERIES(MSO5000), "MSO5072", {1, 1000000000}, CH_INFO(2, true), std_cmd},
        {SERIES(MSO5000), "MSO5074", {1, 1000000000}, CH_INFO(4, true), std_cmd},
@@ -395,17 +396,13 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
                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] = g_malloc0(sizeof(struct sr_channel_group));
-
-               devc->analog_groups[i]->name = channel_name;
+               devc->analog_groups[i] = sr_channel_group_new(sdi,
+                       channel_name, NULL);
                devc->analog_groups[i]->channels = g_slist_append(NULL, ch);
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                               devc->analog_groups[i]);
        }
 
        if (devc->model->has_digital) {
-               devc->digital_group = g_malloc0(sizeof(struct sr_channel_group));
-
+               devc->digital_group = sr_channel_group_new(sdi, "LA", NULL);
                for (i = 0; i < ARRAY_SIZE(devc->digital_channels); i++) {
                        channel_name = g_strdup_printf("D%d", i);
                        ch = sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name);
@@ -413,9 +410,6 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
                        devc->digital_group->channels = g_slist_append(
                                        devc->digital_group->channels, ch);
                }
-               devc->digital_group->name = g_strdup("LA");
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                               devc->digital_group);
        }
 
        for (i = 0; i < ARRAY_SIZE(timebases); i++) {
index 520dc7068a7c11c647f159b49167587aa626ce5f..d0a448d783fbe65e6af7f0c1b33eace757fa962f 100644 (file)
@@ -50,6 +50,7 @@ static const uint32_t devopts_generic_range[] = {
 
 static const struct scpi_command cmdset_agilent[] = {
        { DMM_CMD_SETUP_REMOTE, "\n", },
+       { DMM_CMD_SETUP_LOCAL, "SYST:LOC", },
        { DMM_CMD_SETUP_FUNC, "CONF:%s", },
        { DMM_CMD_QUERY_FUNC, "CONF?", },
        { DMM_CMD_START_ACQ, "INIT", },
@@ -198,6 +199,20 @@ static const struct mqopt_item mqopts_owon_xdm2041[] = {
        { SR_MQ_CAPACITANCE, 0, "CAP", "CAP", NO_DFLT_PREC, FLAGS_NONE, },
 };
 
+static const struct mqopt_item mqopts_siglent_sdm3055[] = {
+       { SR_MQ_VOLTAGE, SR_MQFLAG_DC, "VOLT:DC", "VOLT ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_VOLTAGE, SR_MQFLAG_AC, "VOLT:AC", "VOLT:AC ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_CURRENT, SR_MQFLAG_DC, "CURR:DC", "CURR ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_CURRENT, SR_MQFLAG_AC, "CURR:AC", "CURR:AC ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_RESISTANCE, 0, "RES", "RES ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, "FRES", "FRES ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, FLAGS_NONE, },
+       { SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, FLAGS_NONE, },
+       { SR_MQ_FREQUENCY, 0, "FREQ", "FREQ ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_TIME, 0, "PER", "PER ", NO_DFLT_PREC, FLAGS_NONE, },
+       { SR_MQ_CAPACITANCE, 0, "CAP", "CAP", NO_DFLT_PREC, FLAGS_NONE, },
+};
+
 SR_PRIV const struct scpi_dmm_model models[] = {
        {
                "Agilent", "34405A",
@@ -272,6 +287,14 @@ SR_PRIV const struct scpi_dmm_model models[] = {
                0, 0, 10 * 1000, 0, FALSE,
                scpi_dmm_get_range_text, scpi_dmm_set_range_from_text, NULL,
        },
+       {
+               "OWON", "XDM1041",
+               1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm2041),
+               scpi_dmm_get_meas_gwinstek,
+               ARRAY_AND_SIZE(devopts_generic),
+               0, 0, 0, 1e9, TRUE,
+               NULL, NULL, NULL,
+       },
        {
                "OWON", "XDM2041",
                1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm2041),
@@ -280,6 +303,14 @@ SR_PRIV const struct scpi_dmm_model models[] = {
                0, 0, 0, 1e9, TRUE,
                NULL, NULL, NULL,
        },
+       {
+               "Siglent", "SDM3055",
+               1, 5, cmdset_hp, ARRAY_AND_SIZE(mqopts_siglent_sdm3055),
+               scpi_dmm_get_meas_agilent,
+               ARRAY_AND_SIZE(devopts_generic),
+               0, 0, 0, 0, FALSE,
+               NULL, NULL, NULL,
+       },
 };
 
 static const struct scpi_dmm_model *is_compatible(const char *vendor, const char *model)
index f413fcd730fbcaad79d58be38fbc2de3ed74d4fd..a13db8415162e6f4ea1b89314997edd28a46eedb 100644 (file)
@@ -396,7 +396,7 @@ SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch)
        if (ret != SR_OK)
                return ret;
        g_strstrip(response);
-       use_double = devc->model->digits > 6;
+       use_double = devc->model->digits >= 6;
        ret = sr_atod_ascii(response, &info->d_value);
        if (ret != SR_OK) {
                g_free(response);
index aa5b945a3349cee0573491709d304efa424ccdc7..116a5d467c5bde9b182f69f71e5adca181e88e8d 100644 (file)
@@ -145,8 +145,7 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi,
 
        for (i = 0; i < num_channel_groups; i++) {
                cgs = &channel_groups[i];
-               cg = g_malloc0(sizeof(struct sr_channel_group));
-               cg->name = g_strdup(cgs->name);
+               cg = sr_channel_group_new(sdi, cgs->name, NULL);
                for (j = 0, mask = 1; j < 64; j++, mask <<= 1) {
                        if (cgs->channel_index_mask & mask) {
                                for (l = sdi->channels; l; l = l->next) {
@@ -167,7 +166,6 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi,
                pcg = g_malloc0(sizeof(struct pps_channel_group));
                pcg->features = cgs->features;
                cg->priv = pcg;
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 
        sr_scpi_hw_info_free(hw_info);
@@ -439,6 +437,10 @@ static int config_get(uint32_t key, GVariant **data,
                gvtype = G_VARIANT_TYPE_DOUBLE;
                cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_THRESHOLD;
                break;
+       case SR_CONF_OVER_CURRENT_PROTECTION_DELAY:
+               gvtype = G_VARIANT_TYPE_DOUBLE;
+               cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_DELAY;
+               break;
        case SR_CONF_OVER_TEMPERATURE_PROTECTION:
                if (devc->device->dialect == SCPI_DIALECT_HMP) {
                        /* OTP is always enabled. */
@@ -700,6 +702,12 @@ static int config_set(uint32_t key, GVariant *data,
                                channel_group_cmd, channel_group_name,
                                SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD, d);
                break;
+       case SR_CONF_OVER_CURRENT_PROTECTION_DELAY:
+               d = g_variant_get_double(data);
+               ret = sr_scpi_cmd(sdi, devc->device->commands,
+                               channel_group_cmd, channel_group_name,
+                               SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DELAY, d);
+               break;
        case SR_CONF_OVER_TEMPERATURE_PROTECTION:
                if (g_variant_get_boolean(data))
                        ret = sr_scpi_cmd(sdi, devc->device->commands,
@@ -796,6 +804,9 @@ static int config_list(uint32_t key, GVariant **data,
                case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD:
                        *data = std_gvar_min_max_step_array(ch_spec->ocp);
                        break;
+               case SR_CONF_OVER_CURRENT_PROTECTION_DELAY:
+                       *data = std_gvar_min_max_step_array(ch_spec->ocp_delay);
+                       break;
                default:
                        return SR_ERR_NA;
                }
index e947f744a0dab2d71e3db594735740a0a50ac178..2b759e5d8187cccf66438238f44de31bf2092cfe 100644 (file)
@@ -29,6 +29,7 @@
 #define FREQ_DC_ONLY {0, 0, 0, 0, 0}
 #define NO_OVP_LIMITS {0, 0, 0, 0, 0}
 #define NO_OCP_LIMITS {0, 0, 0, 0, 0}
+#define NO_OCP_DELAY {0, 0, 0, 0, 0}
 
 /* Agilent/Keysight N5700A series */
 static const uint32_t agilent_n5700a_devopts[] = {
@@ -52,11 +53,11 @@ static const struct channel_group_spec agilent_n5700a_cg[] = {
 };
 
 static const struct channel_spec agilent_n5767a_ch[] = {
-       { "1", { 0, 60, 0.0072, 3, 4 }, { 0, 25, 0.003, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 60, 0.0072, 3, 4 }, { 0, 25, 0.003, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec agilent_n5763a_ch[] = {
-       { "1", { 0, 12.5, 0.0015, 3, 4 }, { 0, 120, 0.0144, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 12.5, 0.0015, 3, 4 }, { 0, 120, 0.0144, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 /*
@@ -103,9 +104,9 @@ static const uint32_t bk_9130_devopts_cg[] = {
 };
 
 static const struct channel_spec bk_9130_ch[] = {
-       { "1", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "3", { 0,  5, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 15, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "2", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "3", { 0,  5, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 15, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec bk_9130_cg[] = {
@@ -152,7 +153,7 @@ static const uint32_t chroma_61604_devopts_cg[] = {
 };
 
 static const struct channel_spec chroma_61604_ch[] = {
-       { "1", { 0, 300, 0.1, 1, 1 }, { 0, 16, 0.1, 2, 2 }, { 0, 2000, 0, 1, 1 }, { 1.0, 1000.0, 0.01 }, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 300, 0.1, 1, 1 }, { 0, 16, 0.1, 2, 2 }, { 0, 2000, 0, 1, 1 }, { 1.0, 1000.0, 0.01 }, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec chroma_61604_cg[] = {
@@ -468,11 +469,11 @@ static const uint32_t rigol_dp700_devopts_cg[] = {
 };
 
 static const struct channel_spec rigol_dp711_ch[] = {
-       { "1", { 0, 30, 0.01, 3, 3 }, { 0, 5, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 33, 0.01}, { 0.01, 5.5, 0.01 } },
+       { "1", { 0, 30, 0.01, 3, 3 }, { 0, 5, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 33, 0.01}, { 0.01, 5.5, 0.01 }, NO_OCP_DELAY },
 };
 
 static const struct channel_spec rigol_dp712_ch[] = {
-       { "1", { 0, 50, 0.01, 3, 3 }, { 0, 3, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 55, 0.01}, { 0.01, 3.3, 0.01 } },
+       { "1", { 0, 50, 0.01, 3, 3 }, { 0, 3, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 55, 0.01}, { 0.01, 3.3, 0.01 }, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec rigol_dp700_cg[] = {
@@ -537,20 +538,20 @@ static const uint32_t rigol_dp800_devopts_cg[] = {
 };
 
 static const struct channel_spec rigol_dp821a_ch[] = {
-       { "1", { 0, 60, 0.001, 3, 3 }, { 0, 1, 0.0001, 4, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0,  8, 0.001, 3, 3 }, { 0, 10, 0.001, 3, 3 }, { 0, 80, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 60, 0.001, 3, 3 }, { 0, 1, 0.0001, 4, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "2", { 0,  8, 0.001, 3, 3 }, { 0, 10, 0.001, 3, 3 }, { 0, 80, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec rigol_dp831_ch[] = {
-       { "1", { 0,   8, 0.001, 3, 4 }, { 0, 5, 0.0003, 3, 4 }, { 0, 40, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0,  30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "3", { 0, -30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0,   8, 0.001, 3, 4 }, { 0, 5, 0.0003, 3, 4 }, { 0, 40, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "2", { 0,  30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "3", { 0, -30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec rigol_dp832_ch[] = {
-       { "1", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "3", { 0,  5, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "2", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "3", { 0,  5, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec rigol_dp820_cg[] = {
@@ -622,15 +623,15 @@ static const uint32_t hp_6630a_devopts_cg[] = {
 };
 
 static const struct channel_spec hp_6632a_ch[] = {
-       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
+       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6633a_ch[] = {
-       { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS },
+       { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6634a_ch[] = {
-       { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS },
+       { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec hp_6630a_cg[] = {
@@ -764,43 +765,43 @@ static const uint32_t hp_6630b_devopts_cg[] = {
 };
 
 static const struct channel_spec hp_6611c_ch[] = {
-       { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 41.92297 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS },
+       { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 41.92297 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6612c_ch[] = {
-       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
+       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6613c_ch[] = {
-       { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 52.40627 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS },
+       { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 52.40627 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6614c_ch[] = {
-       { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 0.5118, 0.000125, 4, 5 }, { 0, 52.39808 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS },
+       { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 0.5118, 0.000125, 4, 5 }, { 0, 52.39808 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6631b_ch[] = {
-       { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 10.237, 0.00263, 4, 5 }, { 0, 83.84103 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS },
+       { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 10.237, 0.00263, 4, 5 }, { 0, 83.84103 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6632b_ch[] = {
-       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
+       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_66312a_ch[] = {
-       { "1", { 0, 20.475, 0.0001, 4, 5 }, { 0, 2.0475, 0.0001, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.01 }, NO_OCP_LIMITS },
+       { "1", { 0, 20.475, 0.0001, 4, 5 }, { 0, 2.0475, 0.0001, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.01 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_66332a_ch[] = {
-       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
+       { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6633b_ch[] = {
-       { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.000526, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS },
+       { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.000526, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec hp_6634b_ch[] = {
-       { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.000263, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS },
+       { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.000263, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec hp_6630b_cg[] = {
@@ -1025,11 +1026,11 @@ static const uint32_t owon_p4000_devopts_cg[] = {
 };
 
 static const struct channel_spec owon_p4603_ch[] = {
-       { "1", { 0.01, 60, 0.001, 3, 3 }, { 0.001, 3, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 61, 0.001}, { 0.001, 3.1, 0.001} },
+       { "1", { 0.01, 60, 0.001, 3, 3 }, { 0.001, 3, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 61, 0.001}, { 0.001, 3.1, 0.001}, NO_OCP_DELAY },
 };
 
 static const struct channel_spec owon_p4305_ch[] = {
-       { "1", { 0.01, 30, 0.001, 3, 3 }, { 0.001, 5, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 31, 0.001}, { 0.001, 3.1, 0.001} },
+       { "1", { 0.01, 30, 0.001, 3, 3 }, { 0.001, 5, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 31, 0.001}, { 0.001, 3.1, 0.001}, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec owon_p4000_cg[] = {
@@ -1054,6 +1055,32 @@ static const struct scpi_command owon_p4000_cmd[] = {
        ALL_ZERO
 };
 
+/* Owon SPE series*/
+
+static const struct channel_spec owon_spe6103_ch[] = {
+       { "1", { 0.01, 60, 0.01, 3, 3 }, { 0.001, 10, 0.001, 3, 3 }, { 0, 300, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 61, 0.01}, { 0.001, 10.1, 0.001}, NO_OCP_DELAY },
+};
+
+static const struct scpi_command owon_spe6103_cmd[] = {
+       { SCPI_CMD_REMOTE, "SYST:REM" },
+       { SCPI_CMD_LOCAL, "SYST:LOC" },
+       { SCPI_CMD_GET_MEAS_VOLTAGE, "MEAS:VOLT?" },
+       { SCPI_CMD_GET_MEAS_CURRENT, "MEAS:CURR?" },
+       { SCPI_CMD_GET_MEAS_POWER, "MEAS:POW?" },
+       { SCPI_CMD_GET_VOLTAGE_TARGET, "VOLT?" },
+       { SCPI_CMD_SET_VOLTAGE_TARGET, "VOLT %.6f" },
+       { SCPI_CMD_GET_CURRENT_LIMIT, "CURR?" },
+       { SCPI_CMD_SET_CURRENT_LIMIT, "CURR %.6f" },
+       { SCPI_CMD_GET_OUTPUT_ENABLED, "OUTP?" },
+       { SCPI_CMD_SET_OUTPUT_ENABLE, "OUTP 1" },
+       { SCPI_CMD_SET_OUTPUT_DISABLE, "OUTP 0" },
+       { SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_THRESHOLD, "VOLT:LIM?" },
+       { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_THRESHOLD, "VOLT:LIM %.6f" },
+       { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_THRESHOLD, "CURR:LIM?" },
+       { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD, "CURR:LIM %.6f" },
+       ALL_ZERO
+};
+
 /* Philips/Fluke PM2800 series */
 static const uint32_t philips_pm2800_devopts[] = {
        SR_CONF_CONTINUOUS,
@@ -1216,6 +1243,9 @@ static const uint32_t rs_hmc8043_devopts_cg[] = {
        SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED | SR_CONF_GET | SR_CONF_SET,
        SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET,
        SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_OVER_CURRENT_PROTECTION_ENABLED | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET,
+       SR_CONF_OVER_CURRENT_PROTECTION_DELAY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
        SR_CONF_VOLTAGE | SR_CONF_GET,
        SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
        SR_CONF_CURRENT | SR_CONF_GET,
@@ -1224,15 +1254,15 @@ static const uint32_t rs_hmc8043_devopts_cg[] = {
 };
 
 static const struct channel_spec rs_hmc8043_ch[] = {
-       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, { 0.01, 10, 0.001, 3, 4}  },
+       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, { 0.01, 10, 0.001, 3, 4} },
+       { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, { 0.01, 10, 0.001, 3, 4} },
 };
 
 static const struct channel_group_spec rs_hmc8043_cg[] = {
-       { "1", CH_IDX(0), PPS_OVP, SR_MQFLAG_DC },
-       { "2", CH_IDX(1), PPS_OVP, SR_MQFLAG_DC },
-       { "3", CH_IDX(2), PPS_OVP, SR_MQFLAG_DC },
+       { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
+       { "2", CH_IDX(1), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
+       { "3", CH_IDX(2), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
 };
 
 static const struct scpi_command rs_hmc8043_cmd[] = {
@@ -1252,6 +1282,12 @@ static const struct scpi_command rs_hmc8043_cmd[] = {
        { SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ENABLED, "VOLT:PROT:STAT?" },
        { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_ENABLE, "VOLT:PROT:STAT ON" },
        { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_DISABLE, "VOLT:PROT:STAT OFF" },
+       { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE, "FUSE:TRIP?" },
+       { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ENABLED, "FUSE:STAT?" },
+       { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_ENABLE, "FUSE:STAT ON" },
+       { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DISABLE, "FUSE:STAT OFF" },
+       { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_DELAY, "FUSE:DEL?" },
+       { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DELAY, "FUSE:DEL %.03f" },
        ALL_ZERO
 };
 
@@ -1275,21 +1311,21 @@ static const uint32_t rs_hmp4040_devopts_cg[] = {
 };
 
 static const struct channel_spec rs_hmp2020_ch[] = {
-       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec rs_hmp2030_ch[] = {
-       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001,  5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_spec rs_hmp4040_ch[] = {
-       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
-       { "4", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+       { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
+       { "4", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY },
 };
 
 static const struct channel_group_spec rs_hmp4040_cg[] = {
@@ -1683,6 +1719,17 @@ SR_PRIV const struct scpi_pps pps_profiles[] = {
                .update_status = NULL,
        },
 
+       { "OWON", "^SPE6103$", SCPI_DIALECT_UNKNOWN, 0,
+               ARRAY_AND_SIZE(owon_p4000_devopts),
+               ARRAY_AND_SIZE(owon_p4000_devopts_cg),
+               ARRAY_AND_SIZE(owon_spe6103_ch),
+               ARRAY_AND_SIZE(owon_p4000_cg),
+               owon_spe6103_cmd,
+               .probe_channels = NULL,
+               .init_acquisition = NULL,
+               .update_status = NULL,
+       },
+
        /* Philips/Fluke PM2800 series */
        { "Philips", "^PM28[13][123]/[01234]{1,2}$", SCPI_DIALECT_PHILIPS, 0,
                ARRAY_AND_SIZE(philips_pm2800_devopts),
@@ -1695,6 +1742,18 @@ SR_PRIV const struct scpi_pps pps_profiles[] = {
                .update_status = NULL,
        },
 
+       /* Rohde & Schwarz HMC8042 */
+       { "Rohde&Schwarz", "HMC8042", SCPI_DIALECT_UNKNOWN, 0,
+               ARRAY_AND_SIZE(rs_hmc8043_devopts),
+               ARRAY_AND_SIZE(rs_hmc8043_devopts_cg),
+               rs_hmc8043_ch, 2,
+               rs_hmc8043_cg, 2,
+               rs_hmc8043_cmd,
+               .probe_channels = NULL,
+               .init_acquisition = NULL,
+               .update_status = NULL,
+       },
+
        /* Rohde & Schwarz HMC8043 */
        { "Rohde&Schwarz", "HMC8043", SCPI_DIALECT_UNKNOWN, 0,
                ARRAY_AND_SIZE(rs_hmc8043_devopts),
index 279977f1d3de106b94d3787a126258f0f87bc071..b75feb2ec2a1d0fcc75abe445ac5671aa60985f9 100644 (file)
@@ -66,6 +66,8 @@ enum pps_scpi_cmds {
        SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE,
        SCPI_CMD_GET_OVER_CURRENT_PROTECTION_THRESHOLD,
        SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD,
+       SCPI_CMD_GET_OVER_CURRENT_PROTECTION_DELAY,
+       SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DELAY,
 };
 
 /* Defines the SCPI dialect */
@@ -121,6 +123,7 @@ struct channel_spec {
        double frequency[5];
        double ovp[5];
        double ocp[5];
+       double ocp_delay[5];
 };
 
 struct channel_group_spec {
index 483bb009b1295fac7464dd9c2c7ff31005c0f0c0..4cbec2677f0a04626b0aca8575d1b3c0809fd9c3 100644 (file)
@@ -199,7 +199,7 @@ static void handle_new_data(struct sr_dev_inst *sdi, void *info)
                        pkt_size = dmm->packet_size;
                }
 
-               /* Process the package. */
+               /* Process the packet. */
                sr_dbg("Valid packet, size %zu, processing", pkt_size);
                handle_packet(sdi, check_ptr, pkt_size, info);
                check_pos += pkt_size;
index 846399b678c4a8a3af56864e99ffbf27173b9d87..76955e6da058c211a173b36cfd5beebf40a2bed4 100644 (file)
@@ -305,17 +305,13 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
                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] = g_malloc0(sizeof(struct sr_channel_group));
-
-               devc->analog_groups[i]->name = channel_name;
+               devc->analog_groups[i] = sr_channel_group_new(sdi,
+                       channel_name, NULL);
                devc->analog_groups[i]->channels = g_slist_append(NULL, ch);
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                       devc->analog_groups[i]);
        }
 
        if (devc->model->has_digital) {
-               devc->digital_group = g_malloc0(sizeof(struct sr_channel_group));
-
+               devc->digital_group = sr_channel_group_new(sdi, "LA", NULL);
                for (i = 0; i < ARRAY_SIZE(devc->digital_channels); i++) {
                        channel_name = g_strdup_printf("D%d", i);
                        ch = sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name);
@@ -323,9 +319,6 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
                        devc->digital_group->channels = g_slist_append(
                                devc->digital_group->channels, ch);
                }
-               devc->digital_group->name = g_strdup("LA");
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                       devc->digital_group);
        }
 
        for (i = 0; i < ARRAY_SIZE(timebases); i++) {
index 919df9b27c30830ff16ff3f41c73b918abebc1a5..d59e52cbc1841365d88673492d951ef3d1985ac5 100644 (file)
@@ -364,71 +364,71 @@ static int siglent_sds_get_digital(const struct sr_dev_inst *sdi, struct sr_chan
        for (l = sdi->channels; l; l = l->next) {
                ch = l->data;
                samples_index = 0;
-               if (ch->type == SR_CHANNEL_LOGIC) {
-                       if (ch->enabled) {
-                               if (sr_scpi_send(sdi->conn, "D%d:WF? DAT2", ch->index) != SR_OK)
-                                       return SR_ERR;
-                               if (sr_scpi_read_begin(scpi) != SR_OK)
-                                       return TRUE;
-                               len = sr_scpi_read_data(scpi, buf, -1);
-                               if (len < 0)
-                                       return TRUE;
-                               len -= 15;
-                               buffdata = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len);
-                               buf += 15; /* Skipping the data header. */
-                               g_array_append_vals(buffdata, buf, len);
-                               tmp_samplebuf = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); /* New temp buffer. */
-                               for (uint64_t cur_sample_index = 0; cur_sample_index < devc->memory_depth_digital; cur_sample_index++) {
-                                       char sample = (char)g_array_index(buffdata, uint8_t, cur_sample_index);
-                                       for (int ii = 0; ii < 8; ii++, sample >>= 1) {
-                                               if (ch->index < 8) {
-                                                       channel_index = ch->index;
-                                                       if (data_low_channels->len <= samples_index) {
-                                                               tmp_value = 0; /* New sample. */
-                                                               low_channels = TRUE; /* We have at least one enabled low channel. */
-                                                       } else {
-                                                               /* Get previous stored sample from low channel buffer. */
-                                                               tmp_value = g_array_index(data_low_channels, uint8_t, samples_index);
-                                                       }
-                                               } else {
-                                                       channel_index = ch->index - 8;
-                                                       if (data_high_channels->len <= samples_index) {
-                                                               tmp_value = 0; /* New sample. */
-                                                               high_channels = TRUE; /* We have at least one enabled high channel. */
-                                                       } else {
-                                                               /* Get previous stored sample from high channel buffer. */
-                                                               tmp_value = g_array_index(data_high_channels, uint8_t, samples_index);
-                                                       }
-                                               }
-                                               /* Check if the current scope sample bit is set. */
-                                               if (sample & 0x1)
-                                                       tmp_value |= 1UL << channel_index; /* Set current scope sample bit based on channel index. */
-                                               g_array_append_val(tmp_samplebuf, tmp_value);
-                                               samples_index++;
-                                       }
-                               }
-
-                               /* Clear the buffers to prepare for the new samples */
+               if (ch->type != SR_CHANNEL_LOGIC)
+                       continue;
+               if (!ch->enabled)
+                       continue;
+               if (sr_scpi_send(sdi->conn, "D%d:WF? DAT2", ch->index) != SR_OK)
+                       return SR_ERR;
+               if (sr_scpi_read_begin(scpi) != SR_OK)
+                       return TRUE;
+               len = sr_scpi_read_data(scpi, buf, -1);
+               if (len < 0)
+                       return TRUE;
+               len -= 15;
+               buffdata = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len);
+               buf += 15; /* Skipping the data header. */
+               g_array_append_vals(buffdata, buf, len);
+               tmp_samplebuf = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); /* New temp buffer. */
+               for (uint64_t cur_sample_index = 0; cur_sample_index < devc->memory_depth_digital; cur_sample_index++) {
+                       char sample = (char)g_array_index(buffdata, uint8_t, cur_sample_index);
+                       for (int ii = 0; ii < 8; ii++, sample >>= 1) {
                                if (ch->index < 8) {
-                                       g_array_free(data_low_channels, FALSE);
-                                       data_low_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t));
+                                       channel_index = ch->index;
+                                       if (data_low_channels->len <= samples_index) {
+                                               tmp_value = 0; /* New sample. */
+                                               low_channels = TRUE; /* We have at least one enabled low channel. */
+                                       } else {
+                                               /* Get previous stored sample from low channel buffer. */
+                                               tmp_value = g_array_index(data_low_channels, uint8_t, samples_index);
+                                       }
                                } else {
-                                       g_array_free(data_high_channels, FALSE);
-                                       data_high_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t));
-                               }
-
-                               /* Storing the converted temp values from the the scope into the buffers. */
-                               for (uint64_t index = 0; index < tmp_samplebuf->len; index++) {
-                                       uint8_t value = g_array_index(tmp_samplebuf, uint8_t, index);
-                                       if (ch->index < 8)
-                                               g_array_append_val(data_low_channels, value);
-                                       else
-                                               g_array_append_val(data_high_channels, value);
+                                       channel_index = ch->index - 8;
+                                       if (data_high_channels->len <= samples_index) {
+                                               tmp_value = 0; /* New sample. */
+                                               high_channels = TRUE; /* We have at least one enabled high channel. */
+                                       } else {
+                                               /* Get previous stored sample from high channel buffer. */
+                                               tmp_value = g_array_index(data_high_channels, uint8_t, samples_index);
+                                       }
                                }
-                               g_array_free(tmp_samplebuf, TRUE);
-                               g_array_free(buffdata, TRUE);
+                               /* Check if the current scope sample bit is set. */
+                               if (sample & 0x1)
+                                       tmp_value |= 1UL << channel_index; /* Set current scope sample bit based on channel index. */
+                               g_array_append_val(tmp_samplebuf, tmp_value);
+                               samples_index++;
                        }
                }
+
+               /* Clear the buffers to prepare for the new samples */
+               if (ch->index < 8) {
+                       g_array_free(data_low_channels, TRUE);
+                       data_low_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t));
+               } else {
+                       g_array_free(data_high_channels, TRUE);
+                       data_high_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t));
+               }
+
+               /* Storing the converted temp values from the the scope into the buffers. */
+               for (uint64_t index = 0; index < tmp_samplebuf->len; index++) {
+                       uint8_t value = g_array_index(tmp_samplebuf, uint8_t, index);
+                       if (ch->index < 8)
+                               g_array_append_val(data_low_channels, value);
+                       else
+                               g_array_append_val(data_high_channels, value);
+               }
+               g_array_free(tmp_samplebuf, TRUE);
+               g_array_free(buffdata, TRUE);
        }
 
        /* Combining the lower and higher channel buffers into one buffer for sigrok. */
index 150be80379aa377f729d1137dcd0662daa390197..972a8e9ffa68cb9c921d0e24f8760f59547664a2 100644 (file)
@@ -177,6 +177,33 @@ static int dev_acquisition_stop(struct sr_dev_inst *sdi)
        }).di
 
 SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers,
+       /* {{{ es519xx */
+       DMM(
+               "tenma-72-7750", es519xx,
+               /* The baudrate is actually 19230, see "Note 1" below. */
+               "Tenma", "72-7750", 19200,
+               ES519XX_11B_PACKET_SIZE,
+               sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse,
+               NULL
+       ),
+       DMM(
+               "uni-t-ut60g", es519xx,
+               /* The baudrate is actually 19230, see "Note 1" below. */
+               "UNI-T", "UT60G", 19200,
+               ES519XX_11B_PACKET_SIZE,
+               sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse,
+               NULL
+       ),
+       DMM(
+               "uni-t-ut61e", es519xx,
+               /* The baudrate is actually 19230, see "Note 1" below. */
+               "UNI-T", "UT61E", 19200,
+               ES519XX_14B_PACKET_SIZE,
+               sr_es519xx_19200_14b_packet_valid, sr_es519xx_19200_14b_parse,
+               NULL
+       ),
+       /* }}} */
+       /* {{{ fs9721 */
        DMM(
                "tecpel-dmm-8061", fs9721,
                "Tecpel", "DMM-8061", 2400,
@@ -185,11 +212,11 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers,
                sr_fs9721_00_temp_c
        ),
        DMM(
-               "uni-t-ut372", ut372,
-               "UNI-T", "UT372", 2400,
-               UT372_PACKET_SIZE,
-               sr_ut372_packet_valid, sr_ut372_parse,
-               NULL
+               "tenma-72-7745", fs9721,
+               "Tenma", "72-7745", 2400,
+               FS9721_PACKET_SIZE,
+               sr_fs9721_packet_valid, sr_fs9721_parse,
+               sr_fs9721_00_temp_c
        ),
        DMM(
                "uni-t-ut60a", fs9721,
@@ -206,13 +233,21 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers,
                sr_fs9721_00_temp_c
        ),
        DMM(
-               "uni-t-ut60g", es519xx,
-               /* The baudrate is actually 19230, see "Note 1" below. */
-               "UNI-T", "UT60G", 19200,
-               ES519XX_11B_PACKET_SIZE,
-               sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse,
+               "voltcraft-vc820", fs9721,
+               "Voltcraft", "VC-820", 2400,
+               FS9721_PACKET_SIZE,
+               sr_fs9721_packet_valid, sr_fs9721_parse,
                NULL
        ),
+       DMM(
+               "voltcraft-vc840", fs9721,
+               "Voltcraft", "VC-840", 2400,
+               FS9721_PACKET_SIZE,
+               sr_fs9721_packet_valid, sr_fs9721_parse,
+               sr_fs9721_00_temp_c
+       ),
+       /* }}} */
+       /* {{{ fs9922 */
        DMM(
                "uni-t-ut61b", fs9922,
                "UNI-T", "UT61B", 2400,
@@ -235,13 +270,46 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers,
                NULL
        ),
        DMM(
-               "uni-t-ut61e", es519xx,
-               /* The baudrate is actually 19230, see "Note 1" below. */
-               "UNI-T", "UT61E", 19200,
-               ES519XX_14B_PACKET_SIZE,
-               sr_es519xx_19200_14b_packet_valid, sr_es519xx_19200_14b_parse,
+               "voltcraft-vc830", fs9922,
+               /*
+                * Note: The VC830 doesn't set the 'volt' and 'diode' bits of
+                * the FS9922 protocol. Instead, it only sets the user-defined
+                * bit "z1" to indicate "diode mode" and "voltage".
+                */
+               "Voltcraft", "VC-830", 2400,
+               FS9922_PACKET_SIZE,
+               sr_fs9922_packet_valid, sr_fs9922_parse,
+               &sr_fs9922_z1_diode
+       ),
+       /* }}} */
+       /* {{{ ut372 */
+       DMM(
+               "uni-t-ut372", ut372,
+               "UNI-T", "UT372", 2400,
+               UT372_PACKET_SIZE,
+               sr_ut372_packet_valid, sr_ut372_parse,
                NULL
        ),
+       /* }}} */
+       /* {{{ ut71x */
+       DMM(
+               "tenma-72-7730", ut71x,
+               "Tenma", "72-7730", 2400,
+               UT71X_PACKET_SIZE,
+               sr_ut71x_packet_valid, sr_ut71x_parse, NULL
+       ),
+       DMM(
+               "tenma-72-7732", ut71x,
+               "Tenma", "72-7732", 2400,
+               UT71X_PACKET_SIZE,
+               sr_ut71x_packet_valid, sr_ut71x_parse, NULL
+       ),
+       DMM(
+               "tenma-72-9380a", ut71x,
+               "Tenma", "72-9380A", 2400,
+               UT71X_PACKET_SIZE,
+               sr_ut71x_packet_valid, sr_ut71x_parse, NULL
+       ),
        DMM(
                "uni-t-ut71a", ut71x,
                "UNI-T", "UT71A", 2400, UT71X_PACKET_SIZE,
@@ -272,37 +340,6 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers,
                "UNI-T", "UT804", 2400, UT71X_PACKET_SIZE,
                sr_ut71x_packet_valid, sr_ut71x_parse, NULL
        ),
-       DMM(
-               "voltcraft-vc820", fs9721,
-               "Voltcraft", "VC-820", 2400,
-               FS9721_PACKET_SIZE,
-               sr_fs9721_packet_valid, sr_fs9721_parse,
-               NULL
-       ),
-       DMM(
-               "voltcraft-vc830", fs9922,
-               /*
-                * Note: The VC830 doesn't set the 'volt' and 'diode' bits of
-                * the FS9922 protocol. Instead, it only sets the user-defined
-                * bit "z1" to indicate "diode mode" and "voltage".
-                */
-               "Voltcraft", "VC-830", 2400,
-               FS9922_PACKET_SIZE,
-               sr_fs9922_packet_valid, sr_fs9922_parse,
-               &sr_fs9922_z1_diode
-       ),
-       DMM(
-               "voltcraft-vc840", fs9721,
-               "Voltcraft", "VC-840", 2400,
-               FS9721_PACKET_SIZE,
-               sr_fs9721_packet_valid, sr_fs9721_parse,
-               sr_fs9721_00_temp_c
-       ),
-       DMM(
-               "voltcraft-vc870", vc870,
-               "Voltcraft", "VC-870", 9600, VC870_PACKET_SIZE,
-               sr_vc870_packet_valid, sr_vc870_parse, NULL
-       ),
        DMM(
                "voltcraft-vc920", ut71x,
                "Voltcraft", "VC-920", 2400, UT71X_PACKET_SIZE,
@@ -318,37 +355,12 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers,
                "Voltcraft", "VC-960", 2400, UT71X_PACKET_SIZE,
                sr_ut71x_packet_valid, sr_ut71x_parse, NULL
        ),
+       /* }}} */
+       /* {{{ vc870 */
        DMM(
-               "tenma-72-7730", ut71x,
-               "Tenma", "72-7730", 2400,
-               UT71X_PACKET_SIZE,
-               sr_ut71x_packet_valid, sr_ut71x_parse, NULL
-       ),
-       DMM(
-               "tenma-72-7732", ut71x,
-               "Tenma", "72-7732", 2400,
-               UT71X_PACKET_SIZE,
-               sr_ut71x_packet_valid, sr_ut71x_parse, NULL
-       ),
-       DMM(
-               "tenma-72-9380a", ut71x,
-               "Tenma", "72-9380A", 2400,
-               UT71X_PACKET_SIZE,
-               sr_ut71x_packet_valid, sr_ut71x_parse, NULL
-       ),
-       DMM(
-               "tenma-72-7745", fs9721,
-               "Tenma", "72-7745", 2400,
-               FS9721_PACKET_SIZE,
-               sr_fs9721_packet_valid, sr_fs9721_parse,
-               sr_fs9721_00_temp_c
-       ),
-       DMM(
-               "tenma-72-7750", es519xx,
-               /* The baudrate is actually 19230, see "Note 1" below. */
-               "Tenma", "72-7750", 19200,
-               ES519XX_11B_PACKET_SIZE,
-               sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse,
-               NULL
+               "voltcraft-vc870", vc870,
+               "Voltcraft", "VC-870", 9600, VC870_PACKET_SIZE,
+               sr_vc870_packet_valid, sr_vc870_parse, NULL
        ),
+       /* }}} */
 );
index f80e83dceb7e91537252d05b37a516e12e225ac3..c1fe1b019e0a27ae4d1b251a2ef6726c01d862b1 100644 (file)
@@ -53,6 +53,7 @@ static const uint32_t devopts_cg_analog[] = {
 };
 
 static const uint32_t devopts_cg_digital[] = {
+       /* EMPTY */
 };
 
 enum {
@@ -423,12 +424,19 @@ static int config_list(uint32_t key, GVariant **data,
 
        switch (key) {
        case SR_CONF_DEVICE_OPTIONS:
-               if (cg_type == CG_ANALOG)
+               if (cg_type == CG_ANALOG) {
                        *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_analog));
-               else if (cg_type == CG_DIGITAL)
+                       break;
+               }
+               if (cg_type == CG_DIGITAL) {
+                       if (!ARRAY_SIZE(devopts_cg_digital)) {
+                               *data = std_gvar_array_u32(NULL, 0);
+                               break;
+                       }
                        *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_digital));
-               else
-                       *data = std_gvar_array_u32(NULL, 0);
+                       break;
+               }
+               *data = std_gvar_array_u32(NULL, 0);
                break;
        case SR_CONF_COUPLING:
                if (!cg)
index d2c51cdac6d4f5ebfebaf61c02c66dfef262d9cb..06dae50f7cd9a3979692371138df4ad8b91b3f0b 100644 (file)
@@ -790,24 +790,17 @@ SR_PRIV int dlm_device_init(struct sr_dev_inst *sdi, int model_index)
                ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE,
                                (*scope_models[model_index].analog_names)[i]);
 
-               devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group));
-
-               devc->analog_groups[i]->name = g_strdup(
-                               (char *)(*scope_models[model_index].analog_names)[i]);
+               devc->analog_groups[i] = sr_channel_group_new(sdi,
+                       (*scope_models[model_index].analog_names)[i], NULL);
                devc->analog_groups[i]->channels = g_slist_append(NULL, ch);
-
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                               devc->analog_groups[i]);
        }
 
        /* Add digital channel groups. */
        for (i = 0; i < scope_models[model_index].pods; i++) {
-               devc->digital_groups[i] = g_malloc0(sizeof(struct sr_channel_group));
+               devc->digital_groups[i] = sr_channel_group_new(sdi, NULL, NULL);
                if (!devc->digital_groups[i])
                        return SR_ERR_MALLOC;
                devc->digital_groups[i]->name = g_strdup_printf("POD%d", i);
-               sdi->channel_groups = g_slist_append(sdi->channel_groups,
-                               devc->digital_groups[i]);
        }
 
        /* Add digital channels. */
index 9d15fe6c97c13ec7e915e3036861ba348e228f30..49e0e1f9e1dbb7e122e1c5135d62b281be4a560d 100644 (file)
@@ -169,7 +169,10 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        struct libusb_device_handle *hdl;
        libusb_device **devlist;
        GSList *devices;
-       int ret, i, j;
+       int ret;
+       size_t i, j;
+       uint8_t bus, addr;
+       const struct zp_model *check;
        char serial_num[64], connection_id[64];
 
        (void)options;
@@ -180,21 +183,47 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
        /* Find all ZEROPLUS analyzers and add them to device list. */
        libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); /* TODO: Errors. */
-
        for (i = 0; devlist[i]; i++) {
                libusb_get_device_descriptor(devlist[i], &des);
 
-               if ((ret = libusb_open(devlist[i], &hdl)) < 0)
+               /*
+                * Check for expected VID:PID first as soon as we got
+                * the descriptor's content. This avoids access to flaky
+                * unrelated devices which trouble the application even
+                * if they are unrelated to measurement purposes.
+                *
+                * See https://sigrok.org/bugzilla/show_bug.cgi?id=1115
+                * and https://github.com/sigrokproject/libsigrok/pull/165
+                * for a discussion.
+                */
+               prof = NULL;
+               for (j = 0; zeroplus_models[j].vid; j++) {
+                       check = &zeroplus_models[j];
+                       if (des.idVendor != check->vid)
+                               continue;
+                       if (des.idProduct != check->pid)
+                               continue;
+                       prof = check;
+                       break;
+               }
+               if (!prof)
                        continue;
 
-               if (des.iSerialNumber == 0) {
-                       serial_num[0] = '\0';
-               } else if ((ret = libusb_get_string_descriptor_ascii(hdl,
-                               des.iSerialNumber, (unsigned char *) serial_num,
-                               sizeof(serial_num))) < 0) {
-                       sr_warn("Failed to get serial number string descriptor: %s.",
-                               libusb_error_name(ret));
+               /* Get the device's serial number from USB strings. */
+               ret = libusb_open(devlist[i], &hdl);
+               if (ret < 0)
                        continue;
+
+               serial_num[0] = '\0';
+               if (des.iSerialNumber != 0) {
+                       ret = libusb_get_string_descriptor_ascii(hdl,
+                               des.iSerialNumber,
+                               (uint8_t *)serial_num, sizeof(serial_num));
+                       if (ret < 0) {
+                               sr_warn("Cannot get USB serial number: %s.",
+                                       libusb_error_name(ret));
+                               continue;
+                       }
                }
 
                libusb_close(hdl);
@@ -202,26 +231,21 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0)
                        continue;
 
-               prof = NULL;
-               for (j = 0; j < zeroplus_models[j].vid; j++) {
-                       if (des.idVendor == zeroplus_models[j].vid &&
-                               des.idProduct == zeroplus_models[j].pid) {
-                               prof = &zeroplus_models[j];
-                       }
-               }
-
-               if (!prof)
-                       continue;
                sr_info("Found ZEROPLUS %s.", prof->model_name);
 
-               sdi = g_malloc0(sizeof(struct sr_dev_inst));
+               sdi = g_malloc0(sizeof(*sdi));
                sdi->status = SR_ST_INACTIVE;
                sdi->vendor = g_strdup("ZEROPLUS");
                sdi->model = g_strdup(prof->model_name);
                sdi->serial_num = g_strdup(serial_num);
                sdi->connection_id = g_strdup(connection_id);
 
-               devc = g_malloc0(sizeof(struct dev_context));
+               bus = libusb_get_bus_number(devlist[i]);
+               addr = libusb_get_device_address(devlist[i]);
+               sdi->inst_type = SR_INST_USB;
+               sdi->conn = sr_usb_dev_inst_new(bus, addr, NULL);
+
+               devc = g_malloc0(sizeof(*devc));
                sdi->priv = devc;
                devc->prof = prof;
                devc->num_channels = prof->channels;
@@ -234,17 +258,13 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 #endif
                devc->max_samplerate *= SR_MHZ(1);
                devc->memory_size = MEMORY_SIZE_8K;
-               // memset(devc->trigger_buffer, 0, NUM_TRIGGER_STAGES);
 
-               for (j = 0; j < devc->num_channels; j++)
+               for (j = 0; j < devc->num_channels; j++) {
                        sr_channel_new(sdi, j, SR_CHANNEL_LOGIC, TRUE,
                                        channel_names[j]);
+               }
 
                devices = g_slist_append(devices, sdi);
-               sdi->inst_type = SR_INST_USB;
-               sdi->conn = sr_usb_dev_inst_new(
-                       libusb_get_bus_number(devlist[i]),
-                       libusb_get_device_address(devlist[i]), NULL);
        }
        libusb_free_device_list(devlist, 1);
 
index bc517c35acbf526d9c513d11fb50dd9e15417e8a..37fcbdcc2eb04d09d3c15a8f4f18736fcef8fca0 100644 (file)
 #include <math.h>
 #include "protocol.h"
 
-SR_PRIV unsigned int get_memory_size(int type)
+SR_PRIV size_t get_memory_size(int type)
 {
        if (type == MEMORY_SIZE_8K)
                return (8 * 1024);
-       else if (type <= MEMORY_SIZE_8M)
+       if (type <= MEMORY_SIZE_8M)
                return (32 * 1024) << type;
-       else
-               return 0;
+       return 0;
 }
 
 static int clz(unsigned int x)
@@ -59,6 +58,8 @@ static int clz(unsigned int x)
 
 SR_PRIV int set_limit_samples(struct dev_context *devc, uint64_t samples)
 {
+       size_t mem_kb;
+
        if (samples > devc->max_sample_depth)
                samples = devc->max_sample_depth;
 
@@ -71,8 +72,8 @@ SR_PRIV int set_limit_samples(struct dev_context *devc, uint64_t samples)
        else
                devc->memory_size = 19 - clz(samples - 1);
 
-       sr_info("Setting memory size to %dK.",
-               get_memory_size(devc->memory_size) / 1024);
+       mem_kb = get_memory_size(devc->memory_size) / 1024;
+       sr_info("Setting memory size to %zuK.", mem_kb);
 
        analyzer_set_memory_size(devc->memory_size);
 
index e179f1f8a2280b86b89e5f5e1c811ab6f52eaa1e..b0c358be09923a61a86b2ef049d42b75a7a23eb8 100644 (file)
 
 #define LOG_PREFIX "zeroplus-logic-cube"
 
+struct zp_model;
 struct dev_context {
        uint64_t cur_samplerate;
        uint64_t max_samplerate;
        uint64_t limit_samples;
-       int num_channels;
-       int memory_size;
-       unsigned int max_sample_depth;
-       //uint8_t channel_mask;
-       //uint8_t trigger_mask[NUM_TRIGGER_STAGES];
-       //uint8_t trigger_value[NUM_TRIGGER_STAGES];
-       // uint8_t trigger_buffer[NUM_TRIGGER_STAGES];
+       size_t num_channels;
+       size_t memory_size;
+       size_t max_sample_depth;
        int trigger;
        uint64_t capture_ratio;
        double cur_threshold;
        const struct zp_model *prof;
 };
 
-SR_PRIV unsigned int get_memory_size(int type);
+SR_PRIV size_t get_memory_size(int type);
 SR_PRIV int zp_set_samplerate(struct dev_context *devc, uint64_t samplerate);
 SR_PRIV int set_limit_samples(struct dev_context *devc, uint64_t samples);
 SR_PRIV int set_voltage_threshold(struct dev_context *devc, double thresh);
index 2850946ffaf4e8392737f9222b9fa2b6611715a3..068a215ac5f6956a4968e46566d2c3e7ce11b9d7 100644 (file)
@@ -74,6 +74,8 @@ static struct sr_key_info sr_key_info_config[] = {
                "Modbus slave address", NULL},
        {SR_CONF_FORCE_DETECT, SR_T_STRING, "force_detect",
                "Forced detection", NULL},
+       {SR_CONF_PROBE_NAMES, SR_T_STRING, "probe_names",
+               "Names of device's probes", NULL},
 
        /* Device (or channel group) configuration */
        {SR_CONF_SAMPLERATE, SR_T_UINT64, "samplerate",
@@ -212,6 +214,8 @@ static struct sr_key_info sr_key_info_config[] = {
                "Power Target", NULL},
        {SR_CONF_RESISTANCE_TARGET, SR_T_FLOAT, "resistance_target",
                "Resistance Target", NULL},
+       {SR_CONF_OVER_CURRENT_PROTECTION_DELAY, SR_T_FLOAT, "ocp_delay",
+               "Over-current protection delay", NULL},
 
        /* Special stuff */
        {SR_CONF_SESSIONFILE, SR_T_STRING, "sessionfile",
@@ -329,6 +333,8 @@ SR_PRIV const GVariantType *sr_variant_type_get(int datatype)
        switch (datatype) {
        case SR_T_INT32:
                return G_VARIANT_TYPE_INT32;
+       case SR_T_UINT32:
+               return G_VARIANT_TYPE_UINT32;
        case SR_T_UINT64:
                return G_VARIANT_TYPE_UINT64;
        case SR_T_STRING:
index 4dcc77b8eaeb19e13999324e230786d8da19ef9f..66ac41dc5c8a7e4bfebfa74e04031691a0386d39 100644 (file)
@@ -379,8 +379,6 @@ static void set_analog_value(struct context *inc, size_t ch_idx, csv_analog_t va
 {
        if (ch_idx >= inc->analog_channels)
                return;
-       if (!value)
-               return;
        inc->analog_sample_buffer[ch_idx * inc->analog_datafeed_buf_size] = value;
 }
 
@@ -1454,7 +1452,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
                inc->analog_datafeed_buf_size /= sample_size;
                inc->analog_datafeed_buf_size /= inc->analog_channels;
                sample_count = inc->analog_channels * inc->analog_datafeed_buf_size;
-               inc->analog_datafeed_buffer = g_malloc0(sample_count * sample_size);
+               inc->analog_datafeed_buffer = g_malloc(sample_count * sample_size);
                if (!inc->analog_datafeed_buffer) {
                        sr_err("Cannot allocate datafeed send buffer (analog).");
                        ret = SR_ERR_MALLOC;
index b25fa77affdc8b7f90731dafa62ed5ef6b6ab9ab..d62f3f6b71fb000e4018135441920bc502891e33 100644 (file)
@@ -57,14 +57,14 @@ SR_API struct feed_queue_logic *feed_queue_logic_alloc(
        return q;
 }
 
-SR_API int feed_queue_logic_submit(struct feed_queue_logic *q,
-       const uint8_t *data, size_t count)
+SR_API int feed_queue_logic_submit_one(struct feed_queue_logic *q,
+       const uint8_t *data, size_t repeat_count)
 {
        uint8_t *wrptr;
        int ret;
 
        wrptr = &q->data_bytes[q->fill_count * q->unit_size];
-       while (count--) {
+       while (repeat_count--) {
                memcpy(wrptr, data, q->unit_size);
                wrptr += q->unit_size;
                q->fill_count++;
@@ -79,6 +79,35 @@ SR_API int feed_queue_logic_submit(struct feed_queue_logic *q,
        return SR_OK;
 }
 
+SR_API int feed_queue_logic_submit_many(struct feed_queue_logic *q,
+       const uint8_t *data, size_t samples_count)
+{
+       uint8_t *wrptr;
+       size_t space, copy_count;
+       int ret;
+
+       wrptr = &q->data_bytes[q->fill_count * q->unit_size];
+       while (samples_count) {
+               space = q->alloc_count - q->fill_count;
+               copy_count = samples_count;
+               if (copy_count > space)
+                       copy_count = space;
+               memcpy(wrptr, data, copy_count * q->unit_size);
+               data += copy_count * q->unit_size;
+               samples_count -= copy_count;
+               wrptr += copy_count * q->unit_size;
+               q->fill_count += copy_count;
+               if (q->fill_count == q->alloc_count) {
+                       ret = feed_queue_logic_flush(q);
+                       if (ret != SR_OK)
+                               return ret;
+                       wrptr = &q->data_bytes[0];
+               }
+       }
+
+       return SR_OK;
+}
+
 SR_API int feed_queue_logic_flush(struct feed_queue_logic *q)
 {
        int ret;
@@ -162,12 +191,51 @@ SR_API struct feed_queue_analog *feed_queue_analog_alloc(
        return q;
 }
 
-SR_API int feed_queue_analog_submit(struct feed_queue_analog *q,
-       float data, size_t count)
+SR_API int feed_queue_analog_mq_unit(struct feed_queue_analog *q,
+       enum sr_mq mq, enum sr_mqflag mq_flag, enum sr_unit unit)
+{
+       int ret;
+
+       if (!q)
+               return SR_ERR_ARG;
+
+       ret = feed_queue_analog_flush(q);
+       if (ret != SR_OK)
+               return ret;
+
+       q->meaning.mq = mq;
+       q->meaning.mqflags = mq_flag;
+       q->meaning.unit = unit;
+
+       return SR_OK;
+}
+
+SR_API int feed_queue_analog_scale_offset(struct feed_queue_analog *q,
+       const struct sr_rational *scale, const struct sr_rational *offset)
+{
+       int ret;
+
+       if (!q)
+               return SR_ERR_ARG;
+
+       ret = feed_queue_analog_flush(q);
+       if (ret != SR_OK)
+               return ret;
+
+       if (scale)
+               q->encoding.scale = *scale;
+       if (offset)
+               q->encoding.offset = *offset;
+
+       return SR_OK;
+}
+
+SR_API int feed_queue_analog_submit_one(struct feed_queue_analog *q,
+       float data, size_t repeat_count)
 {
        int ret;
 
-       while (count--) {
+       while (repeat_count--) {
                q->data_values[q->fill_count++] = data;
                if (q->fill_count == q->alloc_count) {
                        ret = feed_queue_analog_flush(q);
index b5de532bc58a1c25a4d3430d223a8b5b8ee7299b..cd0db1f15380609d4801958e1fe12877b089e8af 100644 (file)
  */
 
 /** @cond PRIVATE */
+extern SR_PRIV struct sr_input_module input_binary;
 extern SR_PRIV struct sr_input_module input_chronovu_la8;
 extern SR_PRIV struct sr_input_module input_csv;
-extern SR_PRIV struct sr_input_module input_binary;
+extern SR_PRIV struct sr_input_module input_logicport;
+extern SR_PRIV struct sr_input_module input_null;
+extern SR_PRIV struct sr_input_module input_protocoldata;
+extern SR_PRIV struct sr_input_module input_raw_analog;
+extern SR_PRIV struct sr_input_module input_saleae;
 extern SR_PRIV struct sr_input_module input_stf;
 extern SR_PRIV struct sr_input_module input_trace32_ad;
 extern SR_PRIV struct sr_input_module input_vcd;
 extern SR_PRIV struct sr_input_module input_wav;
-extern SR_PRIV struct sr_input_module input_raw_analog;
-extern SR_PRIV struct sr_input_module input_logicport;
-extern SR_PRIV struct sr_input_module input_saleae;
-extern SR_PRIV struct sr_input_module input_null;
 /** @endcond */
 
 static const struct sr_input_module *input_module_list[] = {
        &input_binary,
        &input_chronovu_la8,
        &input_csv,
+       &input_logicport,
+       &input_null,
+       &input_protocoldata,
+       &input_raw_analog,
+       &input_saleae,
 #if defined HAVE_INPUT_STF && HAVE_INPUT_STF
        &input_stf,
 #endif
        &input_trace32_ad,
        &input_vcd,
        &input_wav,
-       &input_raw_analog,
-       &input_logicport,
-       &input_saleae,
-       &input_null,
        NULL,
 };
 
@@ -173,7 +175,7 @@ SR_API const char *const *sr_input_extensions_get(
  *
  * @since 0.4.0
  */
-SR_API const struct sr_input_module *sr_input_find(char *id)
+SR_API const struct sr_input_module *sr_input_find(const char *id)
 {
        int i;
 
index 354330ab453e9f800d5926cc2df081a59906e3c2..ea4cb39b252bb406aaf31bcb4a343128ed2dab9b 100644 (file)
 #include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
 
-/* TODO: Move these helpers to some library API routine group. */
-struct sr_channel_group *sr_channel_group_new(const char *name, void *priv);
-void sr_channel_group_free(struct sr_channel_group *cg);
-
 #define LOG_PREFIX     "input/logicport"
 
 #define MAX_CHANNELS   34
@@ -155,26 +151,6 @@ static void free_signal_group(struct signal_group_desc *desc)
        g_free(desc);
 }
 
-struct sr_channel_group *sr_channel_group_new(const char *name, void *priv)
-{
-       struct sr_channel_group *cg;
-
-       cg = g_malloc0(sizeof(*cg));
-       if (name && *name)
-               cg->name = g_strdup(name);
-       cg->priv = priv;
-
-       return cg;
-}
-
-void sr_channel_group_free(struct sr_channel_group *cg)
-{
-       if (!cg)
-               return;
-       g_free(cg->name);
-       g_slist_free(cg->channels);
-}
-
 /* Wrapper for GDestroyNotify compatibility. */
 static void sg_free(void *p)
 {
@@ -802,10 +778,9 @@ static int create_channels_groups(struct sr_input *in)
        sdi = in->sdi;
        for (l = inc->signal_groups; l; l = l->next) {
                desc = l->data;
-               cg = sr_channel_group_new(desc->name, NULL);
+               cg = sr_channel_group_new(sdi, desc->name, NULL);
                if (!cg)
                        return SR_ERR_MALLOC;
-               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
                mask = UINT64_C(1);
                for (idx = 0; idx < inc->channel_count; idx++, mask <<= 1) {
                        if (!(desc->mask & mask))
diff --git a/src/input/protocoldata.c b/src/input/protocoldata.c
new file mode 100644 (file)
index 0000000..905e33e
--- /dev/null
@@ -0,0 +1,3633 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019-2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This input module reads data values from an input stream, and sends
+ * the corresponding samples to the sigrok session feed which form the
+ * respective waveform, pretending that a logic analyzer had captured
+ * wire traffic. This allows to feed data to protocol decoders which
+ * were recorded by different means (COM port redirection, pcap(3)
+ * recordings, 3rd party bus analyzers). It can also simplify the
+ * initial creation of protocol decoders by generating synthetic
+ * input data, before real world traffic captures become available.
+ *
+ * This input module "assumes ideal traffic" and absence of protocol
+ * errors. Does _not_ inject error conditions, instead generates valid
+ * bit patterns by naively filling blanks to decorate the payload data
+ * which the input file provides. To yield a stream of samples which
+ * successfully decodes at the recipient's, and upper layer decoders
+ * will see valid data which corresponds to the file's content. Edge
+ * positions and minute timnig details are not adjustable either in
+ * this module (no support for setup or hold times or slew rates etc).
+ * The goal is not to emulate a protocol with all its possibilities to
+ * the fullest detail. The module's purpose is to simplify the import
+ * of values while no capture of the wire traffic was available.
+ *
+ * There are several approaches to using the input module:
+ * - Input data can be a mere bytes sequence. While attributes can get
+ *   specified by means of input module options. This is the fastest
+ *   approach to accessing raw data that's externally made available.
+ * - An optional leading magic literal supports automatic file type
+ *   detection, and obsoletes the -I input module selection. Unwanted
+ *   automatic detection is possible but very unlikely. The magic text
+ *   was chosen such that its occurance at the very start of payload
+ *   data is extremely unlikely, and is easy to work around should the
+ *   situation happen. Of course specifying input module options does
+ *   necessitate the selection of the input module.
+ * - When the file type magic is present, an optional header section
+ *   can follow, and can carry parameters which obsolete the necessity
+ *   to specify input module options. The choice of header section
+ *   boundaries again reduces the likelyhood of false detection. When
+ *   input module options were specified, they take precedence over
+ *   input stream content.
+ * - The payload of the input stream (the protocol values) can take
+ *   the form of a mere bytes sequence where every byte is a value
+ *   (this is the default). Or values can be represented in textual
+ *   format when either an input module option or the header section
+ *   specify that the input is text. Individual protocol handlers can
+ *   also prefer one format over another, while file content and
+ *   module options take precedence as usual. Some protocols may not
+ *   usefully be described by values only, or may involve values and
+ *   numbers larger than a byte, which essentially makes text format
+ *   a non-option for these situations.
+ * - The text format supports coments which silently get discarded.
+ *   As well as pseudo comments which can affect the interpretation
+ *   of the input text, and/or can control properties of protocols
+ *   that exceed the mere submission of values. Think chip-select or
+ *   ACK/NAK slots or similar.
+ * - It's understood that the text format is more expensive to process,
+ *   but is also more versatile. It's assumed that the 'protocoldata'
+ *   input format is used for small or mid size capture lengths. The
+ *   input module enables quick access to data that became available
+ *   by other means. For higher fidelity of real world traffic and for
+ *   long captures the native format should be preferred. For error
+ *   injection the VCD format might be a better match.
+ * - It should be obvious that raw bytes or input data in text form,
+ *   as well as header fields can either be the content of a file on
+ *   disk, or can be part of a pipe input. Either the earlier process
+ *   in the pipe which provides the values, or an intermediate filter
+ *   in the pipe, can provide the decoration.
+ *     $ ./gen-values.sh | sigrok-cli -i - ...
+ *     $ ./gen-values.sh | cat header - | sigrok-cli -i - ...
+ * - Since the input format supports automatic detection as well as
+ *   parameter specs by means of input module options as well as in
+ *   file content, the format lends itself equally well to pipelined
+ *   or scripted as well as interactive use in different applications.
+ *   For pipelines, the header as well as the values (as well as any
+ *   mix of these pieces) can be kept in separate locations. Generators
+ *   need not provide all of the input stream in a single invocation.
+ * - As a matter of convenience, especially when targetting upper layer
+ *   protocol decoders, users need not construct "correctly configured"
+ *   from the lower protocol's perspective) waveforms on the wire.
+ *   Instead "naive" waveforms which match the decoders' default options
+ *   can be used, which eliminates the need to configure non-default
+ *   options in decoders (and redundantly do the same thing in the
+ *   input module, just to have them match again).
+ *     $ ./gen-values.sh | sigrok-cli \
+ *       -i - -I protocoldata:protocol=uart:bitrate=57600:frameformat=8e2 \
+ *       -P uart:parity=even:baudrate=57600
+ *     $ ./gen-values.sh | sigrok-cli \
+ *       -i - -I protocoldata:protocol=uart -P uart,midi
+ *
+ * Example invocations:
+ *
+ *   $ sigrok-cli -I protocoldata --show
+ *
+ *   $ echo "Hello sigrok protocol values!" | \
+ *     sigrok-cli \
+ *       -I protocoldata:protocol=uart -i - \
+ *       -P uart:format=ascii -A uart=rx-data
+ *
+ *   $ sigrok-cli -i file.bin -P uart -A uart=rx-data
+ *   $ sigrok-cli -i file.txt -P uart:rx=rxtx -A uart
+ *   $ sigrok-cli -i file.txt --show
+ *   $ sigrok-cli -i file.txt -O ascii:width=4000 | $PAGER
+ *
+ *   $ echo "# -- sigrok protocol data values file --" > header.txt
+ *   $ echo "# -- sigrok protocol data header start --" >> header.txt
+ *   $ echo "protocol=uart" >> header.txt
+ *   $ echo "bitrate=100000" >> header.txt
+ *   $ echo "frameformat=8e2" >> header.txt
+ *   $ echo "textinput=yes" >> header.txt
+ *   $ echo "# -- sigrok protocol data header end --" >> header.txt
+ *   $ echo "# textinput: radix=16" > values.txt
+ *   $ echo "0f  40 a6 28 fa 78 05 19 ee c2 92 70 58 62 09 a9 f1 ca 44 90 d1 07 19  02  00" >> values.txt
+ *   $ head header.txt values.txt
+ *   $ cat values.txt | cat header.txt - | \
+ *     sigrok-cli -i - -P uart:baudrate=100000:parity=even,sbus_futaba -A sbus_futaba
+ *
+ *   $ pulseview -i file-spi-text.txt &
+ *
+ * Known issues:
+ * - Only few protocols are implemented so far. Existing handlers have
+ *   suggested which infrastructure is required for future extension.
+ *   But future handlers may reveal more omissions or assumptions that
+ *   need addressing.
+ * - Terminology may be inconsistent, because this input module supports
+ *   several protocols which often differ in how they use terms. What is
+ *   available:
+ *   - The input module constructs waveforms that span multiple traces.
+ *     Resulting waveforms are said to have a samplerate. Data that is
+ *     kept in that waveform can have a bitrate. Which is essential for
+ *     asynchronous communication, but could be unimportant for clocked
+ *     protocols. Protocol handlers may adjust their output to enforce
+ *     a bitrate, but need not. The timing is an approximation anyway,
+ *     does not reflect pauses or jitter or turnarounds which real world
+ *     traffic would reveal.
+ *   - Protocol handlers can generate an arbitrary number of samples for
+ *     a protocol data value. A maximum number of samples per value is
+ *     assumed. Variable length samples sequences per data value or per
+ *     invocation is supported (and can be considered the typical case).
+ *   - Protocol handlers can configure differing widths for the samples
+ *     that they derived from input data. These quanta get configured
+ *     when the frame format gets interpreted, and are assumed to remain
+ *     as they are across data value processing.
+ *   - Data values can be considered "a frame" (as seen with UART). But
+ *     data values could also be "bytes" or "words" in a protocol, while
+ *     "frames" or "transfers" are implemented by different means (as
+ *     seen with SPI or I2C). The typical approach would be to control a
+ *     "select" signal by means of pseudo comments which are interleaved
+ *     with data values.
+ *   - Data values need not get forwarded to decoders. They might also
+ *     control the processing of the following data values as well as
+ *     the waveform construction. This is at the discretion of protocol
+ *     handlers, think of slave addresses, preceeding field or value
+ *     counts before their data values follow, etc.
+ * - Users may need to specify more options than expected when the file
+ *   content is "incomplete". The sequence of scanning builtin defaults,
+ *   then file content provided specs, then user specified specs, is
+ *   yet to get done. Until then it helps being explicit and thorough.
+ *
+ * TODO (arbitrary order, could partially be outdated)
+ * - Implement the most appropriate order of option scanning. Use
+ *   builtin defaults first, file content then, then user specified
+ *   options (when available). This shall be most robust and correct.
+ * - Switch to "submit one sample" in feed queue API when available.
+ *   The current implementation of this input module uses ugly ifdefs
+ *   to adjust to either feed queue API approach.
+ * - (obsoleted by the introduction of support for text format input?)
+ *   Introduce TLV support for the binary input format? u32be type,
+ *   u64be length, u8[] payload. The complexity of the implementation
+ *   in the input module, combined with the complexity of generating
+ *   the input stream which uses TLV sections, are currently considered
+ *   undesirable for this input module. Do we expect huge files where
+ *   the computational cost of text conversion causes pain?
+ * - Extend the UART protocol handler. Implement separate RX and TX
+ *   traces. Support tx-only, rx-only, and tx-then-rx input orders.
+ * - Add a 'parallel' protocol handler, which grabs a bit pattern and
+ *   derives the waveform in straight forward ways? This would be similar
+ *   to the raw binary input module, but the text format could improve
+ *   readability, and the input module could generate a clock signal
+ *   which isn't part of the input stream. That 'parallel' protocol
+ *   could be used as a vehicle to bitbang any other protocol that is
+ *   unknown to the input module. The approach is only limited by the
+ *   input stream generator's imagination.
+ * - Add other protocol variants. The binary input format was very
+ *   limiting, the text format could cover a lot of more cases:
+ *   - CAN: Pseudo comments can communicate the frame's flags (and
+ *     address type etc). The first data value can be the address. The
+ *     second data value or a pseudo comment can hold the CAN frame's
+ *     data length (bytes count). Other data values are the 0..8 data
+ *     bytes. CAN-FD might be possible with minimal adjustment?
+ *   - W1: Pseudo comments can start a frame (initiate RESET). First
+ *     value can carry frame length. Data bytes follow. Scans can get
+ *     represented as raw bytes (bit count results in full 8bit size).
+ * - Are more than 8 traces desirable? The initial implementation was
+ *   motivated by serial communication (UART). More channels were not
+ *   needed so far. Even QuadSPI and Hitachi displays fit onto 8 lines.
+ *
+ * See the sigrok.org file format wiki page for details about the syntax
+ * that is supported by this input module. Or see the top of the source
+ * file and its preprocessor symbols to quickly get an idea of known
+ * keywords in input files.
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <libsigrok/libsigrok.h>
+#include <string.h>
+#include <strings.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX     "input/protocoldata"
+
+#define CHUNK_SIZE     (4 * 1024 * 1024)
+
+/*
+ * Support optional automatic file type detection. Support optionally
+ * embedded options in a header section after the file detection magic
+ * and before the payload data (bytes or text).
+ */
+#define MAGIC_FILE_TYPE                "# -- sigrok protocol data values file --"
+#define TEXT_HEAD_START                "# -- sigrok protocol data header start --"
+#define TEXT_HEAD_END          "# -- sigrok protocol data header end --"
+#define TEXT_COMM_LEADER       "#"
+
+#define LABEL_SAMPLERATE       "samplerate="
+#define LABEL_BITRATE          "bitrate="
+#define LABEL_PROTOCOL         "protocol="
+#define LABEL_FRAMEFORMAT      "frameformat="
+#define LABEL_TEXTINPUT                "textinput="
+
+/*
+ * Options which are embedded in pseudo comments and are related to
+ * how the input module reads the input text stream. Universally
+ * applicable to all text inputs regardless of protocol choice.
+ */
+#define TEXT_INPUT_PREFIX      "textinput:"
+#define TEXT_INPUT_RADIX       "radix="
+
+/*
+ * Protocol dependent frame formats, the default and absolute limits.
+ * Protocol dependent keywords in pseudo-comments.
+ *
+ * UART assumes 9x2 as the longest useful frameformat. Additional STOP
+ * bits let users insert idle phases between frames, until more general
+ * support for inter-frame gaps is in place. By default the protocol
+ * handler generously adds a few more idle bit times after a UART frame.
+ *
+ * SPI assumes exactly 8 bits per "word". And leaves bit slots around
+ * the byte transmission, to have space where CS asserts or releases.
+ * Including time where SCK changes to its idle level. And requires two
+ * samples per bit time (pos and neg clock phase). The "decoration" also
+ * helps users' interactive exploration of generated waveforms.
+ *
+ * I2C generously assumes six quanta per bit slot, to gracefully allow
+ * for reliable SCL and SDA transitions regardless of samples that result
+ * from prior communication. The longest waveform is a byte (with eight
+ * data bits and an ACK slot). Special symbols like START, and STOP will
+ * fit into that memory while it is not used to communicate a byte.
+ */
+#define UART_HANDLER_NAME      "uart"
+#define UART_DFLT_SAMPLERATE   SR_MHZ(1)
+#define UART_DFLT_BITRATE      115200
+#define UART_DFLT_FRAMEFMT     "8n1"
+#define UART_MIN_DATABITS      5
+#define UART_MAX_DATABITS      9
+#define UART_MAX_STOPBITS      20
+#define UART_ADD_IDLEBITS      2
+#define UART_MAX_WAVELEN       (1 + UART_MAX_DATABITS + 1 + UART_MAX_STOPBITS \
+                               + UART_ADD_IDLEBITS)
+#define UART_FORMAT_INVERT     "inverted"
+/* In addition the usual '8n1' et al are supported. */
+#define UART_PSEUDO_BREAK      "break"
+#define UART_PSEUDO_IDLE       "idle"
+
+#define SPI_HANDLER_NAME       "spi"
+#define SPI_DFLT_SAMPLERATE    SR_MHZ(10)
+#define SPI_DFLT_BITRATE       SR_MHZ(1)
+#define SPI_DFLT_FRAMEFMT      "cs-low,bits=8,mode=0,msb-first"
+#define SPI_MIN_DATABITS       8
+#define SPI_MAX_DATABITS       8
+#define SPI_MAX_WAVELEN                (2 + 2 * SPI_MAX_DATABITS + 3)
+#define SPI_FORMAT_CS_LOW      "cs-low"
+#define SPI_FORMAT_CS_HIGH     "cs-high"
+#define SPI_FORMAT_DATA_BITS   "bits="
+#define SPI_FORMAT_SPI_MODE    "mode="
+#define SPI_FORMAT_MODE_CPOL   "cpol="
+#define SPI_FORMAT_MODE_CPHA   "cpha="
+#define SPI_FORMAT_MSB_FIRST   "msb-first"
+#define SPI_FORMAT_LSB_FIRST   "lsb-first"
+#define SPI_PSEUDO_MOSI_ONLY   "mosi-only"
+#define SPI_PSEUDO_MOSI_FIXED  "mosi-fixed="
+#define SPI_PSEUDO_MISO_ONLY   "miso-only"
+#define SPI_PSEUDO_MISO_FIXED  "miso-fixed="
+#define SPI_PSEUDO_MOSI_MISO   "mosi-then-miso"
+#define SPI_PSEUDO_MISO_MOSI   "miso-then-mosi"
+#define SPI_PSEUDO_CS_ASSERT   "cs-assert"
+#define SPI_PSEUDO_CS_RELEASE  "cs-release"
+#define SPI_PSEUDO_CS_NEXT     "cs-auto-next="
+#define SPI_PSEUDO_IDLE                "idle"
+
+#define I2C_HANDLER_NAME       "i2c"
+#define I2C_DFLT_SAMPLERATE    SR_MHZ(10)
+#define I2C_DFLT_BITRATE       SR_KHZ(400)
+#define I2C_DFLT_FRAMEFMT      "addr-7bit"
+#define I2C_BITTIME_SLOTS      (1 + 8 + 1 + 1)
+#define I2C_BITTIME_QUANTA     6
+#define I2C_ADD_IDLESLOTS      2
+#define I2C_MAX_WAVELEN                (I2C_BITTIME_QUANTA * I2C_BITTIME_SLOTS + I2C_ADD_IDLESLOTS)
+#define I2C_FORMAT_ADDR_7BIT   "addr-7bit"
+#define I2C_FORMAT_ADDR_10BIT  "addr-10bit"
+#define I2C_PSEUDO_START       "start"
+#define I2C_PSEUDO_REP_START   "repeat-start"
+#define I2C_PSEUDO_STOP                "stop"
+#define I2C_PSEUDO_ADDR_WRITE  "addr-write="
+#define I2C_PSEUDO_ADDR_READ   "addr-read="
+#define I2C_PSEUDO_ACK_NEXT    "ack-next="
+#define I2C_PSEUDO_ACK_ONCE    "ack-next"
+
+enum textinput_t {
+       INPUT_UNSPEC,
+       INPUT_BYTES,
+       INPUT_TEXT,
+};
+
+static const char *input_format_texts[] = {
+       [INPUT_UNSPEC] = "from-file",
+       [INPUT_BYTES] = "raw-bytes",
+       [INPUT_TEXT] = "text-format",
+};
+
+struct spi_proto_context_t {
+       gboolean needs_mosi, has_mosi;
+       gboolean needs_miso, has_miso;
+       gboolean mosi_first;
+       gboolean cs_active;
+       size_t auto_cs_remain;
+       uint8_t mosi_byte, miso_byte;
+       uint8_t mosi_fixed_value;
+       gboolean mosi_is_fixed;
+       uint8_t miso_fixed_value;
+       gboolean miso_is_fixed;
+};
+
+struct i2c_proto_context_t {
+       size_t ack_remain;
+};
+
+struct context;
+
+struct proto_handler_t {
+       const char *name;
+       struct {
+               uint64_t samplerate;
+               uint64_t bitrate;
+               const char *frame_format;
+               enum textinput_t textinput;
+       } dflt;
+       struct {
+               size_t count;
+               const char **names;
+       } chans;
+       size_t priv_size;
+       int (*check_opts)(struct context *inc);
+       int (*config_frame)(struct context *inc);
+       int (*proc_pseudo)(struct sr_input *in, char *text);
+       int (*proc_value)(struct context *inc, uint32_t value);
+       int (*get_idle_capture)(struct context *inc,
+               size_t *bits, uint8_t *lvls);
+       int (*get_idle_interframe)(struct context *inc,
+               size_t *samples, uint8_t *lvls);
+};
+
+struct context {
+       /* User provided options. */
+       struct user_opts_t {
+               uint64_t samplerate;
+               uint64_t bitrate;
+               const char *proto_name;
+               const char *fmt_text;
+               enum textinput_t textinput;
+       } user_opts;
+       /* Derived at runtime. */
+       struct {
+               uint64_t samplerate;
+               uint64_t bitrate;
+               uint64_t samples_per_bit;
+               char *proto_name;
+               char *fmt_text;
+               enum textinput_t textinput;
+               enum proto_type_t {
+                       PROTO_TYPE_NONE,
+                       PROTO_TYPE_UART,
+                       PROTO_TYPE_SPI,
+                       PROTO_TYPE_I2C,
+                       PROTO_TYPE_COUNT,
+               } protocol_type;
+               const struct proto_handler_t *prot_hdl;
+               void *prot_priv;
+               union {
+                       struct uart_frame_fmt_opts {
+                               size_t databit_count;
+                               enum {
+                                       UART_PARITY_NONE,
+                                       UART_PARITY_ODD,
+                                       UART_PARITY_EVEN,
+                               } parity_type;
+                               size_t stopbit_count;
+                               gboolean half_stopbit;
+                               gboolean inverted;
+                       } uart;
+                       struct spi_frame_fmt_opts {
+                               uint8_t cs_polarity;
+                               size_t databit_count;
+                               gboolean msb_first;
+                               gboolean spi_mode_cpol;
+                               gboolean spi_mode_cpha;
+                       } spi;
+                       struct i2c_frame_fmt_opts {
+                               gboolean addr_10bit;
+                       } i2c;
+               } frame_format;
+       } curr_opts;
+       /* Module stage. Logic output channels. Session feed. */
+       gboolean scanned_magic;
+       gboolean has_magic;
+       gboolean has_header;
+       gboolean got_header;
+       gboolean started;
+       gboolean meta_sent;
+       size_t channel_count;
+       const char **channel_names;
+       struct feed_queue_logic *feed_logic;
+       /*
+        * Internal state: Allocated space for a theoretical maximum
+        * bit count. Filled in bit pattern for the current data value.
+        * (Stuffing can result in varying bit counts across frames.)
+        *
+        * Keep the bits' width in sample numbers, as well as the bits'
+        * boundaries relative to the start of the protocol frame's
+        * start. Support a number of logic bits per bit time.
+        *
+        * Implementor's note: Due to development history terminology
+        * might slip here. Strictly speaking it's "waveform sections"
+        * that hold samples for a given number of cycles. "A bit" in
+        * the protocol can occupy multiple of these slots to e.g. have
+        * a synchronous clock, or to present setup and hold phases,
+        * etc. Sample data spans several logic signal traces. You get
+        * the idea ...
+        */
+       size_t max_frame_bits;  /* Reserved. */
+       size_t top_frame_bits;  /* Currently filled. */
+       struct {
+               size_t mul;
+               size_t div;
+       } *bit_scale;           /* Quanta scaling. */
+       size_t *sample_edges;
+       size_t *sample_widths;
+       uint8_t *sample_levels; /* Sample data, logic traces. */
+       /* Common support for samples updating by manipulation. */
+       struct {
+               uint8_t idle_levels;
+               uint8_t curr_levels;
+       } samples;
+       /* Internal state of the input text reader. */
+       struct {
+               int base;
+       } read_text;
+       /* Manage state across .reset() calls. Robustness. */
+       struct proto_prev {
+               GSList *sr_channels;
+               GSList *sr_groups;
+       } prev;
+};
+
+/* {{{ frame bits manipulation, waveform construction */
+
+/*
+ * Primitives to construct waveforms for a protocol frame, by sequencing
+ * samples after data values were seen in the input stream. Individual
+ * protocol handlers will use these common routines.
+ *
+ * The general idea is: The protocol handler's options parser determines
+ * the frame format, and derives the maximum number of time slots needed
+ * to represent the waveform. Slots can scale differintly, proportions
+ * get configured once during initialization. All remaining operation
+ * receives arbitrarily interleaved data values and pseudo comments, uses
+ * the pre-allocated and pre-scaled time slots to construct waveforms,
+ * which then get sent to the session bus as if an acquisition device
+ * had captured wire traffic. For clocked signals the "coarse" timing
+ * should never be an issue. Protocol handlers are free to use as many
+ * time slots per bit time as they please or feel necessary.
+ */
+
+static int alloc_frame_storage(struct context *inc)
+{
+       size_t bits, alloc;
+
+       if (!inc)
+               return SR_ERR_ARG;
+
+       if (!inc->max_frame_bits)
+               return SR_ERR_DATA;
+
+       inc->top_frame_bits = 0;
+       bits = inc->max_frame_bits;
+
+       alloc = bits * sizeof(inc->sample_edges[0]);
+       inc->sample_edges = g_malloc0(alloc);
+       alloc = bits * sizeof(inc->sample_widths[0]);
+       inc->sample_widths = g_malloc0(alloc);
+       alloc = bits * sizeof(inc->sample_levels[0]);
+       inc->sample_levels = g_malloc0(alloc);
+       if (!inc->sample_edges || !inc->sample_widths || !inc->sample_levels)
+               return SR_ERR_MALLOC;
+
+       alloc = bits * sizeof(inc->bit_scale[0]);
+       inc->bit_scale = g_malloc0(alloc);
+       if (!inc->bit_scale)
+               return SR_ERR_MALLOC;
+
+       return SR_OK;
+}
+
+/*
+ * Assign an equal bit width to all bits in the frame. Derive the width
+ * from the bitrate and the sampelrate. Protocol handlers optionally can
+ * arrange for "odd bit widths" (either fractions, or multiples, or when
+ * desired any rational at all). Think half-bits, or think quanta within
+ * a bit time, depends on the protocol handler really.
+ *
+ * Implementation note: The input module assumes that the position of
+ * odd length bits will never vary during frame construction. The total
+ * length may vary, 'top' can be smaller than 'max' in every iteration.
+ * It is assumed that frames with odd-length bits have constant layout,
+ * and that stuffing protocols have same-width bits. Odd lengths also
+ * can support bit time quanta, while it's assumed that these always use
+ * the same layout for all generated frames. This constraint is kept in
+ * the implementation, until one of the supported protocols genuinely
+ * requires higher flexibility and the involved complexity and runtime
+ * cost of per-samplepoint adjustment.
+ */
+static int assign_bit_widths(struct context *inc)
+{
+       const struct proto_handler_t *handler;
+       int ret;
+       double bit_edge, bit_time, this_bit_time;
+       uint64_t bit_time_int, bit_time_prev, bit_times_total;
+       size_t idx;
+
+       if (!inc)
+               return SR_ERR_ARG;
+
+       /*
+        * Run the protocol handler's optional configure routine.
+        * It derives the maximum number of "bit slots" that are needed
+        * to represent a protocol frame's waveform.
+        */
+       handler = inc->curr_opts.prot_hdl;
+       if (handler && handler->config_frame) {
+               ret = handler->config_frame(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /* Assign bit widths to the protocol frame's bit positions. */
+       bit_time = inc->curr_opts.samplerate;
+       bit_time /= inc->curr_opts.bitrate;
+       inc->curr_opts.samples_per_bit = bit_time + 0.5;
+       sr_dbg("Samplerate %" PRIu64 ", bitrate %" PRIu64 ".",
+               inc->curr_opts.samplerate, inc->curr_opts.bitrate);
+       sr_dbg("Resulting bit width %.2f samples, int %" PRIu64 ".",
+               bit_time, inc->curr_opts.samples_per_bit);
+       bit_edge = 0.0;
+       bit_time_prev = 0;
+       bit_times_total = 0;
+       for (idx = 0; idx < inc->max_frame_bits; idx++) {
+               this_bit_time = bit_time;
+               if (inc->bit_scale[idx].mul)
+                       this_bit_time *= inc->bit_scale[idx].mul;
+               if (inc->bit_scale[idx].div)
+                       this_bit_time /= inc->bit_scale[idx].div;
+               bit_edge += this_bit_time;
+               bit_time_int = (uint64_t)(bit_edge + 0.5);
+               inc->sample_edges[idx] = bit_time_int;
+               bit_time_int -= bit_time_prev;
+               inc->sample_widths[idx] = bit_time_int;
+               bit_time_prev = inc->sample_edges[idx];
+               bit_times_total += bit_time_int;
+               sr_spew("Bit %zu, width %" PRIu64 ".", idx, bit_time_int);
+       }
+       sr_dbg("Maximum waveform width: %zu slots, %.2f / %" PRIu64 " samples.",
+               inc->max_frame_bits, bit_edge, bit_times_total);
+
+       return SR_OK;
+}
+
+/* Start accumulating the samples for a new part of the waveform. */
+static int wave_clear_sequence(struct context *inc)
+{
+
+       if (!inc)
+               return SR_ERR_ARG;
+
+       inc->top_frame_bits = 0;
+
+       return SR_OK;
+}
+
+/* Append channels' levels to the waveform for another period of samples. */
+static int wave_append_pattern(struct context *inc, uint8_t sample)
+{
+
+       if (!inc)
+               return SR_ERR_ARG;
+
+       if (inc->top_frame_bits >= inc->max_frame_bits)
+               return SR_ERR_DATA;
+
+       inc->sample_levels[inc->top_frame_bits++] = sample;
+
+       return SR_OK;
+}
+
+/* Initially assign idle levels, start the buffer from idle state. */
+static void sample_buffer_preset(struct context *inc, uint8_t idle_sample)
+{
+       inc->samples.idle_levels = idle_sample;
+       inc->samples.curr_levels = idle_sample;
+}
+
+/* Modify the samples buffer by assigning a given traces state. */
+static void sample_buffer_assign(struct context *inc, uint8_t sample)
+{
+       inc->samples.curr_levels = sample;
+}
+
+/* Modify the samples buffer by changing individual traces. */
+static void sample_buffer_modify(struct context *inc,
+       uint8_t set_mask, uint8_t clr_mask)
+{
+       inc->samples.curr_levels |= set_mask;
+       inc->samples.curr_levels &= ~clr_mask;
+}
+
+static void sample_buffer_raise(struct context *inc, uint8_t bits)
+{
+       return sample_buffer_modify(inc, bits, 0);
+}
+
+static void sample_buffer_clear(struct context *inc, uint8_t bits)
+{
+       return sample_buffer_modify(inc, 0, bits);
+}
+
+static void sample_buffer_setclr(struct context *inc,
+       gboolean level, uint8_t mask)
+{
+       if (level)
+               sample_buffer_raise(inc, mask);
+       else
+               sample_buffer_clear(inc, mask);
+}
+
+static void sample_buffer_toggle(struct context *inc, uint8_t mask)
+{
+       inc->samples.curr_levels ^= mask;
+}
+
+/* Reset current sample buffer to idle state. */
+static void sample_buffer_toidle(struct context *inc)
+{
+       inc->samples.curr_levels = inc->samples.idle_levels;
+}
+
+/* Append the buffered samples to the waveform memory. */
+static int wave_append_buffer(struct context *inc)
+{
+       return wave_append_pattern(inc, inc->samples.curr_levels);
+}
+
+/* Send idle level before the first generated frame and at end of capture. */
+static int send_idle_capture(struct context *inc)
+{
+       const struct proto_handler_t *handler;
+       size_t count;
+       uint8_t data;
+       int ret;
+
+       handler = inc->curr_opts.prot_hdl;
+       if (!handler->get_idle_capture)
+               return SR_OK;
+
+       ret = handler->get_idle_capture(inc, &count, &data);
+       if (ret != SR_OK)
+               return ret;
+       count *= inc->curr_opts.samples_per_bit;
+       ret = feed_queue_logic_submit_one(inc->feed_logic, &data, count);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/* Optionally send idle level between protocol frames. */
+static int send_idle_interframe(struct context *inc)
+{
+       const struct proto_handler_t *handler;
+       size_t count;
+       uint8_t data;
+       int ret;
+
+       handler = inc->curr_opts.prot_hdl;
+       if (!handler->get_idle_interframe)
+               return SR_OK;
+
+       ret = handler->get_idle_interframe(inc, &count, &data);
+       if (ret != SR_OK)
+               return ret;
+       ret = feed_queue_logic_submit_one(inc->feed_logic, &data, count);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/* Forward the previously accumulated samples of the waveform. */
+static int send_frame(struct sr_input *in)
+{
+       struct context *inc;
+       size_t count, index;
+       uint8_t data;
+       int ret;
+
+       inc = in->priv;
+
+       for (index = 0; index < inc->top_frame_bits; index++) {
+               data = inc->sample_levels[index];
+               count = inc->sample_widths[index];
+               ret = feed_queue_logic_submit_one(inc->feed_logic,
+                       &data, count);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* }}} frame bits manipulation */
+/* {{{ UART protocol handler */
+
+enum uart_pin_t {
+       UART_PIN_RXTX,
+};
+
+#define UART_PINMASK_RXTX      (1UL << UART_PIN_RXTX)
+
+/* UART specific options and frame format check. */
+static int uart_check_opts(struct context *inc)
+{
+       struct uart_frame_fmt_opts *fmt_opts;
+       const char *fmt_text;
+       char **opts, *opt;
+       size_t opt_count, opt_idx;
+       int ret;
+       unsigned long v;
+       char par_text;
+       char *endp;
+       size_t total_bits;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       fmt_opts = &inc->curr_opts.frame_format.uart;
+
+       /* Apply defaults before reading external spec. */
+       memset(fmt_opts, 0, sizeof(*fmt_opts));
+       fmt_opts->databit_count = 8;
+       fmt_opts->parity_type = UART_PARITY_NONE;
+       fmt_opts->stopbit_count = 1;
+       fmt_opts->half_stopbit = FALSE;
+       fmt_opts->inverted = FALSE;
+
+       /* Provide a default UART frame format. */
+       fmt_text = inc->curr_opts.fmt_text;
+       if (!fmt_text || !*fmt_text)
+               fmt_text = UART_DFLT_FRAMEFMT;
+       sr_dbg("UART frame format: %s.", fmt_text);
+
+       /* Parse the comma separated list of user provided options. */
+       opts = g_strsplit_set(fmt_text, ", ", 0);
+       opt_count = g_strv_length(opts);
+       for (opt_idx = 0; opt_idx < opt_count; opt_idx++) {
+               opt = opts[opt_idx];
+               if (!opt || !*opt)
+                       continue;
+               sr_spew("UART format option: %s", opt);
+               /*
+                * Check for specific keywords. Before falling back to
+                * attempting the "8n1" et al interpretation.
+                */
+               if (strcmp(opt, UART_FORMAT_INVERT) == 0) {
+                       fmt_opts->inverted = TRUE;
+                       continue;
+               }
+               /* Parse an "8n1", "8e2", "7o1", or similar input spec. */
+               /* Get the data bits count. */
+               endp = NULL;
+               ret = sr_atoul_base(opt, &v, &endp, 10);
+               if (ret != SR_OK || !endp)
+                       return SR_ERR_DATA;
+               opt = endp;
+               if (v < UART_MIN_DATABITS || v > UART_MAX_DATABITS)
+                       return SR_ERR_DATA;
+               fmt_opts->databit_count = v;
+               /* Get the parity type. */
+               par_text = tolower((int)*opt++);
+               switch (par_text) {
+               case 'n':
+                       fmt_opts->parity_type = UART_PARITY_NONE;
+                       break;
+               case 'o':
+                       fmt_opts->parity_type = UART_PARITY_ODD;
+                       break;
+               case 'e':
+                       fmt_opts->parity_type = UART_PARITY_EVEN;
+                       break;
+               default:
+                       return SR_ERR_DATA;
+               }
+               /* Get the stop bits count. Supports half bits too. */
+               endp = NULL;
+               ret = sr_atoul_base(opt, &v, &endp, 10);
+               if (ret != SR_OK || !endp)
+                       return SR_ERR_DATA;
+               opt = endp;
+               if (v > UART_MAX_STOPBITS)
+                       return SR_ERR_DATA;
+               fmt_opts->stopbit_count = v;
+               if (g_ascii_strcasecmp(opt, ".5") == 0) {
+                       opt += strlen(".5");
+                       fmt_opts->half_stopbit = TRUE;
+               }
+               /* Incomplete consumption of input text is fatal. */
+               if (*opt) {
+                       sr_err("Unprocessed frame format remainder: %s.", opt);
+                       return SR_ERR_DATA;
+               }
+               continue;
+       }
+       g_strfreev(opts);
+
+       /*
+        * Calculate the total number of bit times in the UART frame.
+        * Add a few more bit times to the reserved space. They usually
+        * are not occupied during data transmission, but are useful to
+        * have for special symbols (BREAK, IDLE).
+        */
+       total_bits = 1; /* START bit, unconditional. */
+       total_bits += fmt_opts->databit_count;
+       total_bits += (fmt_opts->parity_type != UART_PARITY_NONE) ? 1 : 0;
+       total_bits += fmt_opts->stopbit_count;
+       total_bits += fmt_opts->half_stopbit ? 1 : 0;
+       total_bits += UART_ADD_IDLEBITS;
+       sr_dbg("UART frame: total bits %zu.", total_bits);
+       if (total_bits > UART_MAX_WAVELEN)
+               return SR_ERR_DATA;
+       inc->max_frame_bits = total_bits;
+
+       return SR_OK;
+}
+
+/*
+ * Configure the frame's bit widths when not identical across the
+ * complete frame. Think half STOP bits.
+ * Preset the sample data for an idle bus.
+ */
+static int uart_config_frame(struct context *inc)
+{
+       struct uart_frame_fmt_opts *fmt_opts;
+       size_t bit_idx;
+       uint8_t sample;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       fmt_opts = &inc->curr_opts.frame_format.uart;
+
+       /*
+        * Position after the START bit. Advance over DATA, PARITY and
+        * (full) STOP bits. Then set the trailing STOP bit to half if
+        * needed. Make the trailing IDLE period after a UART frame
+        * wider than regular bit times. Add an even wider IDLE period
+        * which is used for special symbols.
+        */
+       bit_idx = 1;
+       bit_idx += fmt_opts->databit_count;
+       bit_idx += (fmt_opts->parity_type == UART_PARITY_NONE) ? 0 : 1;
+       bit_idx += fmt_opts->stopbit_count;
+       if (fmt_opts->half_stopbit) {
+               sr_dbg("Setting bit index %zu to half width.", bit_idx);
+               inc->bit_scale[bit_idx].div = 2;
+               bit_idx++;
+       }
+       inc->bit_scale[bit_idx++].mul = 2;
+       inc->bit_scale[bit_idx++].mul = 4;
+
+       /* Start from idle signal levels (high when not inverted). */
+       sample = 0;
+       if (!fmt_opts->inverted)
+               sample |= UART_PINMASK_RXTX;
+       sample_buffer_preset(inc, sample);
+
+       return SR_OK;
+}
+
+/* Create samples for a special UART frame (IDLE, BREAK). */
+static int uart_write_special(struct context *inc, uint8_t level)
+{
+       struct uart_frame_fmt_opts *fmt_opts;
+       int ret;
+       size_t bits;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       fmt_opts = &inc->curr_opts.frame_format.uart;
+
+       ret = wave_clear_sequence(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Set the same level for all bit slots, covering all of
+        * START and DATA (and PARITY) and STOP. This allows the
+        * simulation of BREAK and IDLE phases.
+        */
+       if (fmt_opts->inverted)
+               level = !level;
+       sample_buffer_setclr(inc, level, UART_PINMASK_RXTX);
+       bits = 1; /* START */
+       bits += fmt_opts->databit_count;
+       bits += (fmt_opts->parity_type != UART_PARITY_NONE) ? 1 : 0;
+       bits += fmt_opts->stopbit_count;
+       bits += fmt_opts->half_stopbit ? 1 : 0;
+       while (bits--) {
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /*
+        * Force a few more idle bit times. This does not affect a
+        * caller requested IDLE symbol. But helps separate (i.e.
+        * robustly detect) several caller requested BREAK symbols.
+        * Also separates those specials from subsequent data bytes.
+        */
+       sample_buffer_toidle(inc);
+       bits = UART_ADD_IDLEBITS;
+       while (bits--) {
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* Process UART protocol specific pseudo comments. */
+static int uart_proc_pseudo(struct sr_input *in, char *line)
+{
+       struct context *inc;
+       char *word;
+       int ret;
+
+       inc = in->priv;
+
+       while (line) {
+               word = sr_text_next_word(line, &line);
+               if (!word)
+                       break;
+               if (!*word)
+                       continue;
+               if (strcmp(word, UART_PSEUDO_BREAK) == 0) {
+                       ret = uart_write_special(inc, 0);
+                       if (ret != SR_OK)
+                               return ret;
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               if (strcmp(word, UART_PSEUDO_IDLE) == 0) {
+                       ret = uart_write_special(inc, 1);
+                       if (ret != SR_OK)
+                               return ret;
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               return SR_ERR_DATA;
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Create the UART frame's waveform for the given data value.
+ *
+ * In theory the protocol handler could setup START and STOP once during
+ * initialization. But the overhead compares to DATA and PARITY is small.
+ * And unconditional START/STOP would break the creation of BREAK and
+ * IDLE frames, or complicate their construction and recovery afterwards.
+ * A future implementation might as well support UART traffic on multiple
+ * traces, including interleaved bidirectional communication. So let's
+ * keep the implementation simple. Execution time is not a priority.
+ */
+static int uart_proc_value(struct context *inc, uint32_t value)
+{
+       struct uart_frame_fmt_opts *fmt_opts;
+       int ret;
+       size_t bits;
+       int par_bit, data_bit;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       fmt_opts = &inc->curr_opts.frame_format.uart;
+
+       ret = wave_clear_sequence(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* START bit, unconditional, always 0. */
+       sample_buffer_clear(inc, UART_PINMASK_RXTX);
+       if (fmt_opts->inverted)
+               sample_buffer_toggle(inc, UART_PINMASK_RXTX);
+       ret = wave_append_buffer(inc);
+
+       /* DATA bits. Track parity here (unconditionally). */
+       par_bit = 0;
+       bits = fmt_opts->databit_count;
+       while (bits--) {
+               data_bit = value & 0x01;
+               value >>= 1;
+               par_bit ^= data_bit;
+               if (fmt_opts->inverted)
+                       data_bit = !data_bit;
+               sample_buffer_setclr(inc, data_bit, UART_PINMASK_RXTX);
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /* PARITY bit. Emission is optional. */
+       switch (fmt_opts->parity_type) {
+       case UART_PARITY_ODD:
+               data_bit = par_bit ? 0 : 1;
+               bits = 1;
+               break;
+       case UART_PARITY_EVEN:
+               data_bit = par_bit ? 1 : 0;
+               bits = 1;
+               break;
+       default:
+               data_bit = 0;
+               bits = 0;
+               break;
+       }
+       if (bits) {
+               if (fmt_opts->inverted)
+                       data_bit = !data_bit;
+               sample_buffer_setclr(inc, data_bit, UART_PINMASK_RXTX);
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /* STOP bits. Optional. */
+       sample_buffer_raise(inc, UART_PINMASK_RXTX);
+       if (fmt_opts->inverted)
+               sample_buffer_toggle(inc, UART_PINMASK_RXTX);
+       bits = fmt_opts->stopbit_count;
+       bits += fmt_opts->half_stopbit ? 1 : 0;
+       while (bits--) {
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /*
+        * Force some idle time after the UART frame.
+        * A little shorter than for special symbols.
+        */
+       sample_buffer_toidle(inc);
+       bits = UART_ADD_IDLEBITS - 1;
+       while (bits--) {
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* Start/end the logic trace with a few bit times of idle level. */
+static int uart_get_idle_capture(struct context *inc,
+       size_t *bitcount, uint8_t *sample)
+{
+
+       /* Describe a UART frame's length of idle level. */
+       if (bitcount)
+               *bitcount = inc->max_frame_bits;
+       if (sample)
+               *sample = inc->samples.idle_levels;
+       return SR_OK;
+}
+
+/* Arrange for a few samples of idle level between UART frames. */
+static int uart_get_idle_interframe(struct context *inc,
+       size_t *samplecount, uint8_t *sample)
+{
+
+       (void)inc;
+
+       /*
+        * Regular waveform creation for UART frames already includes
+        * padding between UART frames. That is why we don't need to
+        * add extra inter-frame samples. Yet prepare the implementation
+        * for when we need or want to add a few more idle samples.
+        */
+       if (samplecount) {
+               *samplecount = inc->curr_opts.samples_per_bit;
+               *samplecount *= 0;
+       }
+       if (sample)
+               *sample = inc->samples.idle_levels;
+       return SR_OK;
+}
+
+/* }}} UART protocol handler */
+/* {{{ SPI protocol handler */
+
+enum spi_pin_t {
+       SPI_PIN_SCK,
+       SPI_PIN_MISO,
+       SPI_PIN_MOSI,
+       SPI_PIN_CS,
+       SPI_PIN_COUNT,
+};
+
+#define SPI_PINMASK_SCK                (1UL << SPI_PIN_SCK)
+#define SPI_PINMASK_MISO       (1UL << SPI_PIN_MISO)
+#define SPI_PINMASK_MOSI       (1UL << SPI_PIN_MOSI)
+#define SPI_PINMASK_CS         (1UL << SPI_PIN_CS)
+
+/* "Forget" data which was seen before. */
+static void spi_value_discard_prev_data(struct context *inc)
+{
+       struct spi_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+       incs->has_mosi = !incs->needs_mosi;
+       incs->has_miso = !incs->needs_miso;
+       incs->mosi_byte = 0;
+       incs->miso_byte = 0;
+}
+
+/* Check whether all required values for the byte time were seen. */
+static gboolean spi_value_is_bytes_complete(struct context *inc)
+{
+       struct spi_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+
+       return incs->has_mosi && incs->has_miso;
+}
+
+/* Arrange for data reception before waveform emission. */
+static void spi_pseudo_data_order(struct context *inc,
+       gboolean needs_mosi, gboolean needs_miso, gboolean mosi_first)
+{
+       struct spi_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+
+       incs->needs_mosi = needs_mosi;
+       incs->needs_miso = needs_miso;
+       incs->mosi_first = mosi_first;
+       if (needs_mosi)
+               incs->mosi_is_fixed = FALSE;
+       if (needs_miso)
+               incs->miso_is_fixed = FALSE;
+       spi_value_discard_prev_data(inc);
+}
+
+static void spi_pseudo_mosi_fixed(struct context *inc, uint8_t v)
+{
+       struct spi_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+
+       incs->mosi_fixed_value = v;
+       incs->mosi_is_fixed = TRUE;
+}
+
+static void spi_pseudo_miso_fixed(struct context *inc, uint8_t v)
+{
+       struct spi_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+
+       incs->miso_fixed_value = v;
+       incs->miso_is_fixed = TRUE;
+}
+
+/* Explicit CS control. Arrange for next CS level, track state to keep it. */
+static void spi_pseudo_select_control(struct context *inc, gboolean cs_active)
+{
+       struct spi_frame_fmt_opts *fmt_opts;
+       struct spi_proto_context_t *incs;
+       uint8_t cs_level, sck_level;
+
+       fmt_opts = &inc->curr_opts.frame_format.spi;
+       incs = inc->curr_opts.prot_priv;
+
+       /* Track current "CS active" state. */
+       incs->cs_active = cs_active;
+       incs->auto_cs_remain = 0;
+
+       /* Derive current "CS pin level". Update sample data buffer. */
+       cs_level = 1 - fmt_opts->cs_polarity;
+       if (incs->cs_active)
+               cs_level = fmt_opts->cs_polarity;
+       sample_buffer_setclr(inc, cs_level, SPI_PINMASK_CS);
+
+       /* Derive the idle "SCK level" from the SPI mode's CPOL. */
+       sck_level = fmt_opts->spi_mode_cpol ? 1 : 0;
+       sample_buffer_setclr(inc, sck_level, SPI_PINMASK_SCK);
+}
+
+/* Arrange for automatic CS release after transfer length. Starts the phase. */
+static void spi_pseudo_auto_select(struct context *inc, size_t length)
+{
+       struct spi_frame_fmt_opts *fmt_opts;
+       struct spi_proto_context_t *incs;
+       uint8_t cs_level;
+
+       fmt_opts = &inc->curr_opts.frame_format.spi;
+       incs = inc->curr_opts.prot_priv;
+
+       /* Track current "CS active" state. */
+       incs->cs_active = TRUE;
+       incs->auto_cs_remain = length;
+
+       /* Derive current "CS pin level". Update sample data buffer. */
+       cs_level = 1 - fmt_opts->cs_polarity;
+       if (incs->cs_active)
+               cs_level = fmt_opts->cs_polarity;
+       sample_buffer_setclr(inc, cs_level, SPI_PINMASK_CS);
+}
+
+/* Check for automatic CS release. Decrements, yields result. No action here. */
+static gboolean spi_auto_select_ends(struct context *inc)
+{
+       struct spi_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+       if (!incs->auto_cs_remain)
+               return FALSE;
+
+       incs->auto_cs_remain--;
+       if (incs->auto_cs_remain)
+               return FALSE;
+
+       /*
+        * DON'T release CS yet. The last data is yet to get sent.
+        * Keep the current "CS pin level", but tell the caller that
+        * CS will be released after transmission of that last data.
+        */
+       return TRUE;
+}
+
+/* Update for automatic CS release after last data was sent. */
+static void spi_auto_select_update(struct context *inc)
+{
+       struct spi_frame_fmt_opts *fmt_opts;
+       struct spi_proto_context_t *incs;
+       uint8_t cs_level;
+
+       fmt_opts = &inc->curr_opts.frame_format.spi;
+       incs = inc->curr_opts.prot_priv;
+
+       /* Track current "CS active" state. */
+       incs->cs_active = FALSE;
+       incs->auto_cs_remain = 0;
+
+       /* Derive current "CS pin level". Map to bits pattern. */
+       cs_level = 1 - fmt_opts->cs_polarity;
+       sample_buffer_setclr(inc, cs_level, SPI_PINMASK_CS);
+}
+
+/*
+ * Create the waveforms for one SPI byte. Also cover idle periods:
+ * Dummy/padding bytes within a frame with clock. Idle lines outside
+ * of frames without clock edges. Optional automatic CS release with
+ * resulting inter-frame gap.
+ */
+static int spi_write_frame_patterns(struct context *inc,
+       gboolean idle, gboolean cs_release)
+{
+       struct spi_proto_context_t *incs;
+       struct spi_frame_fmt_opts *fmt_opts;
+       int ret;
+       uint8_t mosi_bit, miso_bit;
+       size_t bits;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       incs = inc->curr_opts.prot_priv;
+       fmt_opts = &inc->curr_opts.frame_format.spi;
+
+       /* Apply fixed values before drawing the waveform. */
+       if (incs->mosi_is_fixed)
+               incs->mosi_byte = incs->mosi_fixed_value;
+       if (incs->miso_is_fixed)
+               incs->miso_byte = incs->miso_fixed_value;
+
+       ret = wave_clear_sequence(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Provide two samples with idle SCK and current CS. */
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Provide two samples per DATABIT time slot. Keep CS as is.
+        * Toggle SCK according to CPHA specs. Shift out MOSI and MISO
+        * in the configured order.
+        *
+        * Force dummy MOSI/MISO bits for idle bytes within a frame.
+        * Skip SCK toggling for idle "frames" outside of active CS.
+        */
+       bits = fmt_opts->databit_count;
+       while (bits--) {
+               /*
+                * First half-period. Provide next DATABIT values.
+                * Toggle SCK here when CPHA is set.
+                */
+               if (fmt_opts->msb_first) {
+                       mosi_bit = incs->mosi_byte & 0x80;
+                       miso_bit = incs->miso_byte & 0x80;
+                       incs->mosi_byte <<= 1;
+                       incs->miso_byte <<= 1;
+               } else {
+                       mosi_bit = incs->mosi_byte & 0x01;
+                       miso_bit = incs->miso_byte & 0x01;
+                       incs->mosi_byte >>= 1;
+                       incs->miso_byte >>= 1;
+               }
+               if (incs->cs_active && !idle) {
+                       sample_buffer_setclr(inc, mosi_bit, SPI_PINMASK_MOSI);
+                       sample_buffer_setclr(inc, miso_bit, SPI_PINMASK_MISO);
+               }
+               if (fmt_opts->spi_mode_cpha && incs->cs_active)
+                       sample_buffer_toggle(inc, SPI_PINMASK_SCK);
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+               /* Second half-period. Keep DATABIT, toggle SCK. */
+               if (incs->cs_active)
+                       sample_buffer_toggle(inc, SPI_PINMASK_SCK);
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+               /* Toggle SCK again unless done above due to CPHA. */
+               if (!fmt_opts->spi_mode_cpha && incs->cs_active)
+                       sample_buffer_toggle(inc, SPI_PINMASK_SCK);
+       }
+
+       /*
+        * Hold the waveform for another sample period. Happens to
+        * also communicate the most recent SCK pin level.
+        *
+        * Optionally auto-release the CS signal after sending the
+        * last data byte. Update the CS trace's level. Add another
+        * (long) bit slot to present an inter-frame gap.
+        */
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+       if (cs_release)
+               spi_auto_select_update(inc);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+       if (cs_release) {
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* SPI specific options and frame format check. */
+static int spi_check_opts(struct context *inc)
+{
+       struct spi_frame_fmt_opts *fmt_opts;
+       const char *fmt_text;
+       char **opts, *opt;
+       size_t opt_count, opt_idx;
+       int ret;
+       unsigned long v;
+       char *endp;
+       size_t total_bits;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       fmt_opts = &inc->curr_opts.frame_format.spi;
+
+       /* Setup defaults before reading external specs. */
+       fmt_opts->cs_polarity = 0;
+       fmt_opts->databit_count = SPI_MIN_DATABITS;
+       fmt_opts->msb_first = TRUE;
+       fmt_opts->spi_mode_cpol = FALSE;
+       fmt_opts->spi_mode_cpha = FALSE;
+
+       /* Provide a default SPI frame format. */
+       fmt_text = inc->curr_opts.fmt_text;
+       if (!fmt_text || !*fmt_text)
+               fmt_text = SPI_DFLT_FRAMEFMT;
+       sr_dbg("SPI frame format: %s.", fmt_text);
+
+       /* Accept comma separated key=value pairs of specs. */
+       opts = g_strsplit_set(fmt_text, ", ", 0);
+       opt_count = g_strv_length(opts);
+       for (opt_idx = 0; opt_idx < opt_count; opt_idx++) {
+               opt = opts[opt_idx];
+               if (!opt || !*opt)
+                       continue;
+               sr_spew("SPI format option: %s.", opt);
+               if (strcmp(opt, SPI_FORMAT_CS_LOW) == 0) {
+                       sr_spew("SPI chip select: low.");
+                       fmt_opts->cs_polarity = 0;
+                       continue;
+               }
+               if (strcmp(opt, SPI_FORMAT_CS_HIGH) == 0) {
+                       sr_spew("SPI chip select: high.");
+                       fmt_opts->cs_polarity = 1;
+                       continue;
+               }
+               if (g_str_has_prefix(opt, SPI_FORMAT_DATA_BITS)) {
+                       opt += strlen(SPI_FORMAT_DATA_BITS);
+                       endp = NULL;
+                       ret = sr_atoul_base(opt, &v, &endp, 10);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("SPI word size: %lu.", v);
+                       if (v < SPI_MIN_DATABITS || v > SPI_MAX_DATABITS)
+                               return SR_ERR_ARG;
+                       fmt_opts->databit_count = v;
+                       continue;
+               }
+               if (g_str_has_prefix(opt, SPI_FORMAT_SPI_MODE)) {
+                       opt += strlen(SPI_FORMAT_SPI_MODE);
+                       endp = NULL;
+                       ret = sr_atoul_base(opt, &v, &endp, 10);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("SPI mode: %lu.", v);
+                       if (v > 3)
+                               return SR_ERR_ARG;
+                       fmt_opts->spi_mode_cpol = v & (1UL << 1);
+                       fmt_opts->spi_mode_cpha = v & (1UL << 0);
+                       continue;
+               }
+               if (g_str_has_prefix(opt, SPI_FORMAT_MODE_CPOL)) {
+                       opt += strlen(SPI_FORMAT_MODE_CPOL);
+                       endp = NULL;
+                       ret = sr_atoul_base(opt, &v, &endp, 10);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("SPI cpol: %lu.", v);
+                       if (v > 1)
+                               return SR_ERR_ARG;
+                       fmt_opts->spi_mode_cpol = !!v;
+                       continue;
+               }
+               if (g_str_has_prefix(opt, SPI_FORMAT_MODE_CPHA)) {
+                       opt += strlen(SPI_FORMAT_MODE_CPHA);
+                       endp = NULL;
+                       ret = sr_atoul_base(opt, &v, &endp, 10);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("SPI cpha: %lu.", v);
+                       if (v > 1)
+                               return SR_ERR_ARG;
+                       fmt_opts->spi_mode_cpha = !!v;
+                       continue;
+               }
+               if (strcmp(opt, SPI_FORMAT_MSB_FIRST) == 0) {
+                       sr_spew("SPI endianess: MSB first.");
+                       fmt_opts->msb_first = 1;
+                       continue;
+               }
+               if (strcmp(opt, SPI_FORMAT_LSB_FIRST) == 0) {
+                       sr_spew("SPI endianess: LSB first.");
+                       fmt_opts->msb_first = 0;
+                       continue;
+               }
+               return SR_ERR_ARG;
+       }
+       g_strfreev(opts);
+
+       /*
+        * Get the total bit count. Add slack for CS control, and to
+        * visually separate bytes in frames. Multiply data bit count
+        * for the creation of two clock half-periods.
+        */
+       total_bits = 2;
+       total_bits += 2 * fmt_opts->databit_count;
+       total_bits += 3;
+
+       sr_dbg("SPI frame: total bits %zu.", total_bits);
+       if (total_bits > SPI_MAX_WAVELEN)
+               return SR_ERR_DATA;
+       inc->max_frame_bits = total_bits;
+
+       return SR_OK;
+}
+
+/*
+ * Setup half-width slots for the two halves of a DATABIT time. Keep
+ * the "decoration" (CS control) at full width. Setup a rather long
+ * last slot for potential inter-frame gaps.
+ *
+ * Preset CS and SCK from their idle levels according to the frame format
+ * configuration. So that idle times outside of SPI transfers are covered
+ * with simple logic despite the protocol's flexibility.
+ */
+static int spi_config_frame(struct context *inc)
+{
+       struct spi_frame_fmt_opts *fmt_opts;
+       size_t bit_idx, bit_count;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       fmt_opts = &inc->curr_opts.frame_format.spi;
+
+       /* Configure DATABIT positions for half width (for clock period). */
+       bit_idx = 2;
+       bit_count = fmt_opts->databit_count;
+       while (bit_count--) {
+               inc->bit_scale[bit_idx + 0].div = 2;
+               inc->bit_scale[bit_idx + 1].div = 2;
+               bit_idx += 2;
+       }
+       bit_idx += 2;
+       inc->bit_scale[bit_idx].mul = fmt_opts->databit_count;
+
+       /*
+        * Seed the protocol handler's internal state before seeing
+        * first data values. To properly cover idle periods, and to
+        * operate correctly in the absence of pseudo comments.
+        *
+        * Use internal helpers for sample data initialization. Then
+        * grab the resulting pin levels as the idle state.
+        */
+       spi_value_discard_prev_data(inc);
+       spi_pseudo_data_order(inc, TRUE, TRUE, TRUE);
+       spi_pseudo_select_control(inc, FALSE);
+       sample_buffer_preset(inc, inc->samples.curr_levels);
+
+       return SR_OK;
+}
+
+/*
+ * Process protocol dependent pseudo comments. Can affect future frame
+ * construction and submission, or can immediately emit "inter frame"
+ * bit patterns like chip select control.
+ */
+static int spi_proc_pseudo(struct sr_input *in, char *line)
+{
+       struct context *inc;
+       char *word, *endp;
+       int ret;
+       unsigned long v;
+
+       inc = in->priv;
+
+       while (line) {
+               word = sr_text_next_word(line, &line);
+               if (!word)
+                       break;
+               if (!*word)
+                       continue;
+               if (strcmp(word, SPI_PSEUDO_MOSI_ONLY) == 0) {
+                       sr_spew("SPI pseudo: MOSI only");
+                       spi_pseudo_data_order(inc, TRUE, FALSE, TRUE);
+                       continue;
+               }
+               if (g_str_has_prefix(word, SPI_PSEUDO_MOSI_FIXED)) {
+                       word += strlen(SPI_PSEUDO_MOSI_FIXED);
+                       endp = NULL;
+                       ret = sr_atoul_base(word, &v, &endp, inc->read_text.base);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("SPI pseudo: MOSI fixed %lu", v);
+                       spi_pseudo_mosi_fixed(inc, v);
+                       continue;
+               }
+               if (strcmp(word, SPI_PSEUDO_MISO_ONLY) == 0) {
+                       sr_spew("SPI pseudo: MISO only");
+                       spi_pseudo_data_order(inc, FALSE, TRUE, FALSE);
+                       continue;
+               }
+               if (g_str_has_prefix(word, SPI_PSEUDO_MISO_FIXED)) {
+                       word += strlen(SPI_PSEUDO_MISO_FIXED);
+                       endp = NULL;
+                       ret = sr_atoul_base(word, &v, &endp, inc->read_text.base);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("SPI pseudo: MISO fixed %lu", v);
+                       spi_pseudo_miso_fixed(inc, v);
+                       continue;
+               }
+               if (strcmp(word, SPI_PSEUDO_MOSI_MISO) == 0) {
+                       sr_spew("SPI pseudo: MOSI then MISO");
+                       spi_pseudo_data_order(inc, TRUE, TRUE, TRUE);
+                       continue;
+               }
+               if (strcmp(word, SPI_PSEUDO_MISO_MOSI) == 0) {
+                       sr_spew("SPI pseudo: MISO then MOSI");
+                       spi_pseudo_data_order(inc, TRUE, TRUE, FALSE);
+                       continue;
+               }
+               if (strcmp(word, SPI_PSEUDO_CS_ASSERT) == 0) {
+                       sr_spew("SPI pseudo: CS assert");
+                       spi_pseudo_select_control(inc, TRUE);
+                       continue;
+               }
+               if (strcmp(word, SPI_PSEUDO_CS_RELEASE) == 0) {
+                       sr_spew("SPI pseudo: CS release");
+                       /* Release CS. Force IDLE to display the pin change. */
+                       spi_pseudo_select_control(inc, FALSE);
+                       ret = spi_write_frame_patterns(inc, TRUE, FALSE);
+                       if (ret != SR_OK)
+                               return ret;
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               if (g_str_has_prefix(word, SPI_PSEUDO_CS_NEXT)) {
+                       word += strlen(SPI_PSEUDO_CS_NEXT);
+                       endp = NULL;
+                       ret = sr_atoul_base(word, &v, &endp, 0);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("SPI pseudo: CS auto next %lu", v);
+                       spi_pseudo_auto_select(inc, v);
+                       continue;
+               }
+               if (strcmp(word, SPI_PSEUDO_IDLE) == 0) {
+                       sr_spew("SPI pseudo: idle");
+                       ret = spi_write_frame_patterns(inc, TRUE, FALSE);
+                       if (ret != SR_OK)
+                               return ret;
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               return SR_ERR_DATA;
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Create the frame's waveform for the given data value. For bidirectional
+ * communication multiple routine invocations accumulate data bits, while
+ * the last invocation completes the frame preparation.
+ */
+static int spi_proc_value(struct context *inc, uint32_t value)
+{
+       struct spi_proto_context_t *incs;
+       gboolean taken;
+       int ret;
+       gboolean auto_cs_end;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       incs = inc->curr_opts.prot_priv;
+
+       /*
+        * Discard previous data when we get here after having completed
+        * a previous frame. This roundtrip from filling in to clearing
+        * is required to have the caller emit the waveform that we have
+        * constructed after receiving data values.
+        */
+       if (spi_value_is_bytes_complete(inc)) {
+               sr_spew("SPI value: discarding previous data");
+               spi_value_discard_prev_data(inc);
+       }
+
+       /*
+        * Consume the caller provided value. Apply data in the order
+        * that was configured before.
+        */
+       taken = FALSE;
+       if (!taken && incs->mosi_first && !incs->has_mosi) {
+               sr_spew("SPI value: grabbing MOSI value");
+               incs->mosi_byte = value & 0xff;
+               incs->has_mosi = TRUE;
+               taken = TRUE;
+       }
+       if (!taken && !incs->has_miso) {
+               sr_spew("SPI value: grabbing MISO value");
+               incs->miso_byte = value & 0xff;
+               incs->has_miso = TRUE;
+       }
+       if (!taken && !incs->mosi_first && !incs->has_mosi) {
+               sr_spew("SPI value: grabbing MOSI value");
+               incs->mosi_byte = value & 0xff;
+               incs->has_mosi = TRUE;
+               taken = TRUE;
+       }
+
+       /*
+        * Generate the waveform when all data values in a byte time
+        * were seen (all MOSI and MISO including their being optional
+        * or fixed values).
+        *
+        * Optionally automatically release CS after a given number of
+        * data bytes, when requested by the input stream.
+        */
+       if (!spi_value_is_bytes_complete(inc)) {
+               sr_spew("SPI value: need more values");
+               return +1;
+       }
+       auto_cs_end = spi_auto_select_ends(inc);
+       sr_spew("SPI value: frame complete, drawing, auto CS %d", auto_cs_end);
+       ret = spi_write_frame_patterns(inc, FALSE, auto_cs_end);
+       if (ret != SR_OK)
+               return ret;
+       return 0;
+}
+
+/* Start/end the logic trace with a few bit times of idle level. */
+static int spi_get_idle_capture(struct context *inc,
+       size_t *bitcount, uint8_t *sample)
+{
+
+       /* Describe one byte time of idle level. */
+       if (bitcount)
+               *bitcount = inc->max_frame_bits;
+       if (sample)
+               *sample = inc->samples.idle_levels;
+       return SR_OK;
+}
+
+/* Arrange for a few samples of idle level between UART frames. */
+static int spi_get_idle_interframe(struct context *inc,
+       size_t *samplecount, uint8_t *sample)
+{
+
+       /* Describe four bit times, re-use most recent pin levels. */
+       if (samplecount) {
+               *samplecount = inc->curr_opts.samples_per_bit;
+               *samplecount *= 4;
+       }
+       if (sample)
+               *sample = inc->samples.curr_levels;
+       return SR_OK;
+}
+
+/* }}} SPI protocol handler */
+/* {{{ I2C protocol handler */
+
+enum i2c_pin_t {
+       I2C_PIN_SCL,
+       I2C_PIN_SDA,
+       I2C_PIN_COUNT,
+};
+
+#define I2C_PINMASK_SCL                (1UL << I2C_PIN_SCL)
+#define I2C_PINMASK_SDA                (1UL << I2C_PIN_SDA)
+
+/* Arrange for automatic ACK for a given number of data bytes. */
+static void i2c_auto_ack_start(struct context *inc, size_t count)
+{
+       struct i2c_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+       incs->ack_remain = count;
+}
+
+/* Check whether automatic ACK is still applicable. Decrements. */
+static gboolean i2c_auto_ack_avail(struct context *inc)
+{
+       struct i2c_proto_context_t *incs;
+
+       incs = inc->curr_opts.prot_priv;
+       if (!incs->ack_remain)
+               return FALSE;
+
+       if (incs->ack_remain--)
+               return TRUE;
+       return FALSE;
+}
+
+/* Occupy the slots where START/STOP would be. Keep current levels. */
+static int i2c_write_nothing(struct context *inc)
+{
+       size_t reps;
+       int ret;
+
+       reps = I2C_BITTIME_QUANTA;
+       while (reps--) {
+               ret = wave_append_buffer(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Construct a START symbol. Occupy a full bit time in the waveform.
+ * Can also be used as REPEAT START due to its conservative signalling.
+ *
+ * Definition of START: Falling SDA while SCL is high.
+ * Repeated START: A START without a preceeding STOP.
+ */
+static int i2c_write_start(struct context *inc)
+{
+       int ret;
+
+       /*
+        * Important! Assumes that either SDA and SCL already are
+        * high (true when we come here from an idle bus). Or that
+        * SCL already is low before SDA potentially changes (this
+        * is true for preceeding START or REPEAT START or DATA BIT
+        * symbols).
+        *
+        * Implementation detail: This START implementation can be
+        * used for REPEAT START as well. The signalling sequence is
+        * conservatively done.
+        */
+
+       /* Enforce SDA high. */
+       sample_buffer_raise(inc, I2C_PINMASK_SDA);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Enforce SCL high. */
+       sample_buffer_raise(inc, I2C_PINMASK_SCL);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Keep high SCL and high SDA for another period. */
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Falling SDA while SCL is high. */
+       sample_buffer_clear(inc, I2C_PINMASK_SDA);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Keep high SCL and low SDA for one more period. */
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Lower SCL here already. Which kind of prepares DATA BIT
+        * times (fits a data bit's start condition, does not harm).
+        * Improves back to back START and (repeated) START as well
+        * as STOP without preceeding DATA BIT.
+        */
+       sample_buffer_clear(inc, I2C_PINMASK_SCL);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * Construct a STOP symbol. Occupy a full bit time in the waveform.
+ *
+ * Definition of STOP: Rising SDA while SCL is high.
+ */
+static int i2c_write_stop(struct context *inc)
+{
+       int ret;
+
+       /* Enforce SCL low before SDA changes. */
+       sample_buffer_clear(inc, I2C_PINMASK_SCL);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Enforce SDA low (can change while SCL is low). */
+       sample_buffer_clear(inc, I2C_PINMASK_SDA);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Rise SCL high while SDA is low. */
+       sample_buffer_raise(inc, I2C_PINMASK_SCL);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Keep high SCL and low SDA for another period. */
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Rising SDA. */
+       sample_buffer_raise(inc, I2C_PINMASK_SDA);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Keep high SCL and high SDA for one more periods. */
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * Construct a DATA BIT symbol. Occupy a full bit time in the waveform.
+ *
+ * SDA can change while SCL is low. SDA must be kept while SCL is high.
+ */
+static int i2c_write_bit(struct context *inc, uint8_t value)
+{
+       int ret;
+
+       /* Enforce SCL low before SDA changes. */
+       sample_buffer_clear(inc, I2C_PINMASK_SCL);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Setup SDA pin level while SCL is low. */
+       sample_buffer_setclr(inc, value, I2C_PINMASK_SDA);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Rising SCL, starting SDA validity. */
+       sample_buffer_raise(inc, I2C_PINMASK_SCL);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Keep SDA level with high SCL for two more periods. */
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Falling SCL, terminates SDA validity. */
+       sample_buffer_clear(inc, I2C_PINMASK_SCL);
+       ret = wave_append_buffer(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/* Create a waveform for the eight data bits and the ACK/NAK slot. */
+static int i2c_write_byte(struct context *inc, uint8_t value, uint8_t ack)
+{
+       size_t bit_mask, bit_value;
+       int ret;
+
+       /* Keep an empty bit time before the data byte. */
+       ret = i2c_write_nothing(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Send 8 data bits, MSB first. */
+       bit_mask = 0x80;
+       while (bit_mask) {
+               bit_value = value & bit_mask;
+               bit_mask >>= 1;
+               ret = i2c_write_bit(inc, bit_value);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /* Send ACK, which is low active. NAK is recessive, high. */
+       bit_value = !ack;
+       ret = i2c_write_bit(inc, bit_value);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Keep an empty bit time after the data byte. */
+       ret = i2c_write_nothing(inc);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/* Send slave address (7bit or 10bit, 1 or 2 bytes). Consumes one ACK. */
+static int i2c_send_address(struct sr_input *in, uint16_t addr, gboolean read)
+{
+       struct context *inc;
+       struct i2c_frame_fmt_opts *fmt_opts;
+       gboolean with_ack;
+       uint8_t addr_byte, rw_bit;
+       int ret;
+
+       inc = in->priv;
+       fmt_opts = &inc->curr_opts.frame_format.i2c;
+
+       addr &= 0x3ff;
+       rw_bit = read ? 1 : 0;
+       with_ack = i2c_auto_ack_avail(inc);
+
+       if (!fmt_opts->addr_10bit) {
+               /* 7 bit address, the simple case. */
+               addr_byte = addr & 0x7f;
+               addr_byte <<= 1;
+               addr_byte |= rw_bit;
+               sr_spew("I2C 7bit address, byte 0x%" PRIx8, addr_byte);
+               ret = wave_clear_sequence(inc);
+               if (ret != SR_OK)
+                       return ret;
+               ret = i2c_write_byte(inc, addr_byte, with_ack);
+               if (ret != SR_OK)
+                       return ret;
+               ret = send_frame(in);
+               if (ret != SR_OK)
+                       return ret;
+       } else {
+               /*
+                * 10 bit address, need to write two bytes: First byte
+                * with prefix 0xf0, upper most 2 address bits, and R/W.
+                * Second byte with lower 8 address bits.
+                */
+               addr_byte = addr >> 8;
+               addr_byte <<= 1;
+               addr_byte |= 0xf0;
+               addr_byte |= rw_bit;
+               sr_spew("I2C 10bit address, byte 0x%" PRIx8, addr_byte);
+               ret = wave_clear_sequence(inc);
+               if (ret != SR_OK)
+                       return ret;
+               ret = i2c_write_byte(inc, addr_byte, with_ack);
+               if (ret != SR_OK)
+                       return ret;
+               ret = send_frame(in);
+               if (ret != SR_OK)
+                       return ret;
+
+               addr_byte = addr & 0xff;
+               sr_spew("I2C 10bit address, byte 0x%" PRIx8, addr_byte);
+               ret = wave_clear_sequence(inc);
+               if (ret != SR_OK)
+                       return ret;
+               ret = i2c_write_byte(inc, addr_byte, with_ack);
+               if (ret != SR_OK)
+                       return ret;
+               ret = send_frame(in);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* I2C specific options and frame format check. */
+static int i2c_check_opts(struct context *inc)
+{
+       struct i2c_frame_fmt_opts *fmt_opts;
+       const char *fmt_text;
+       char **opts, *opt;
+       size_t opt_count, opt_idx;
+       size_t total_bits;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       fmt_opts = &inc->curr_opts.frame_format.i2c;
+
+       /* Apply defaults before reading external specs. */
+       memset(fmt_opts, 0, sizeof(*fmt_opts));
+       fmt_opts->addr_10bit = FALSE;
+
+       /* Provide a default I2C frame format. */
+       fmt_text = inc->curr_opts.fmt_text;
+       if (!fmt_text || !*fmt_text)
+               fmt_text = I2C_DFLT_FRAMEFMT;
+       sr_dbg("I2C frame format: %s.", fmt_text);
+
+       /* Accept comma separated key=value pairs of specs. */
+       opts = g_strsplit_set(fmt_text, ", ", 0);
+       opt_count = g_strv_length(opts);
+       for (opt_idx = 0; opt_idx < opt_count; opt_idx++) {
+               opt = opts[opt_idx];
+               if (!opt || !*opt)
+                       continue;
+               sr_spew("I2C format option: %s.", opt);
+               if (strcmp(opt, I2C_FORMAT_ADDR_7BIT) == 0) {
+                       sr_spew("I2C address: 7 bit");
+                       fmt_opts->addr_10bit = FALSE;
+                       continue;
+               }
+               if (strcmp(opt, I2C_FORMAT_ADDR_10BIT) == 0) {
+                       sr_spew("I2C address: 10 bit");
+                       fmt_opts->addr_10bit = TRUE;
+                       continue;
+               }
+               return SR_ERR_ARG;
+       }
+       g_strfreev(opts);
+
+       /* Get the total slot count. Leave plenty room for convenience. */
+       total_bits = 0;
+       total_bits += I2C_BITTIME_SLOTS;
+       total_bits *= I2C_BITTIME_QUANTA;
+       total_bits += I2C_ADD_IDLESLOTS;
+
+       sr_dbg("I2C frame: total bits %zu.", total_bits);
+       if (total_bits > I2C_MAX_WAVELEN)
+               return SR_ERR_DATA;
+       inc->max_frame_bits = total_bits;
+
+       return SR_OK;
+}
+
+/*
+ * Don't bother with wide and narrow slots, just assume equal size for
+ * them all. Edges will occupy exactly one sample, then levels are kept.
+ * This protocol handler's oversampling should be sufficient for decoders
+ * to extract the content from generated waveforms.
+ *
+ * Start with high levels on SCL and SDA for an idle bus condition.
+ */
+static int i2c_config_frame(struct context *inc)
+{
+       struct i2c_proto_context_t *incs;
+       size_t bit_idx;
+       uint8_t sample;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       incs = inc->curr_opts.prot_priv;
+
+       memset(incs, 0, sizeof(*incs));
+       incs->ack_remain = 0;
+
+       /*
+        * Adjust all time slots since they represent a smaller quanta
+        * of an I2C bit time.
+        */
+       for (bit_idx = 0; bit_idx < inc->max_frame_bits; bit_idx++) {
+               inc->bit_scale[bit_idx].div = I2C_BITTIME_QUANTA;
+       }
+
+       sample = 0;
+       sample |= I2C_PINMASK_SCL;
+       sample |= I2C_PINMASK_SDA;
+       sample_buffer_preset(inc, sample);
+
+       return SR_OK;
+}
+
+/*
+ * Process protocol dependent pseudo comments. Can affect future frame
+ * construction and submission, or can immediately emit "inter frame"
+ * bit patterns like START/STOP control. Use wide waveforms for these
+ * transfer controls, put the special symbol nicely centered. Supports
+ * users during interactive exploration of generated waveforms.
+ */
+static int i2c_proc_pseudo(struct sr_input *in, char *line)
+{
+       struct context *inc;
+       char *word, *endp;
+       int ret;
+       unsigned long v;
+       size_t bits;
+
+       inc = in->priv;
+
+       while (line) {
+               word = sr_text_next_word(line, &line);
+               if (!word)
+                       break;
+               if (!*word)
+                       continue;
+               sr_spew("I2C pseudo: word %s", word);
+               if (strcmp(word, I2C_PSEUDO_START) == 0) {
+                       sr_spew("I2C pseudo: send START");
+                       ret = wave_clear_sequence(inc);
+                       if (ret != SR_OK)
+                               return ret;
+                       bits = I2C_BITTIME_SLOTS / 2;
+                       while (bits--) {
+                               ret = i2c_write_nothing(inc);
+                               if (ret != SR_OK)
+                                       return ret;
+                       }
+                       ret = i2c_write_start(inc);
+                       if (ret != SR_OK)
+                               return ret;
+                       bits = I2C_BITTIME_SLOTS / 2;
+                       while (bits--) {
+                               ret = i2c_write_nothing(inc);
+                               if (ret != SR_OK)
+                                       return ret;
+                       }
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               if (strcmp(word, I2C_PSEUDO_REP_START) == 0) {
+                       sr_spew("I2C pseudo: send REPEAT START");
+                       ret = wave_clear_sequence(inc);
+                       if (ret != SR_OK)
+                               return ret;
+                       bits = I2C_BITTIME_SLOTS / 2;
+                       while (bits--) {
+                               ret = i2c_write_nothing(inc);
+                               if (ret != SR_OK)
+                                       return ret;
+                       }
+                       ret = i2c_write_start(inc);
+                       if (ret != SR_OK)
+                               return ret;
+                       bits = I2C_BITTIME_SLOTS / 2;
+                       while (bits--) {
+                               ret = i2c_write_nothing(inc);
+                               if (ret != SR_OK)
+                                       return ret;
+                       }
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               if (strcmp(word, I2C_PSEUDO_STOP) == 0) {
+                       sr_spew("I2C pseudo: send STOP");
+                       ret = wave_clear_sequence(inc);
+                       if (ret != SR_OK)
+                               return ret;
+                       bits = I2C_BITTIME_SLOTS / 2;
+                       while (bits--) {
+                               ret = i2c_write_nothing(inc);
+                               if (ret != SR_OK)
+                                       return ret;
+                       }
+                       ret = i2c_write_stop(inc);
+                       if (ret != SR_OK)
+                               return ret;
+                       bits = I2C_BITTIME_SLOTS / 2;
+                       while (bits--) {
+                               ret = i2c_write_nothing(inc);
+                               if (ret != SR_OK)
+                                       return ret;
+                       }
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               if (g_str_has_prefix(word, I2C_PSEUDO_ADDR_WRITE)) {
+                       word += strlen(I2C_PSEUDO_ADDR_WRITE);
+                       endp = NULL;
+                       ret = sr_atoul_base(word, &v, &endp, 0);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("I2C pseudo: addr write %lu", v);
+                       ret = i2c_send_address(in, v, FALSE);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               if (g_str_has_prefix(word, I2C_PSEUDO_ADDR_READ)) {
+                       word += strlen(I2C_PSEUDO_ADDR_READ);
+                       endp = NULL;
+                       ret = sr_atoul_base(word, &v, &endp, 0);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("I2C pseudo: addr read %lu", v);
+                       ret = i2c_send_address(in, v, TRUE);
+                       if (ret != SR_OK)
+                               return ret;
+                       continue;
+               }
+               if (g_str_has_prefix(word, I2C_PSEUDO_ACK_NEXT)) {
+                       word += strlen(I2C_PSEUDO_ACK_NEXT);
+                       endp = NULL;
+                       ret = sr_atoul_base(word, &v, &endp, 0);
+                       if (ret != SR_OK)
+                               return ret;
+                       if (!endp || *endp)
+                               return SR_ERR_ARG;
+                       sr_spew("i2c pseudo: ack next %lu", v);
+                       i2c_auto_ack_start(inc, v);
+                       continue;
+               }
+               if (strcmp(word, I2C_PSEUDO_ACK_ONCE) == 0) {
+                       sr_spew("i2c pseudo: ack once");
+                       i2c_auto_ack_start(inc, 1);
+                       continue;
+               }
+               return SR_ERR_DATA;
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Create the frame's waveform for the given data value. Automatically
+ * track ACK bits, Fallback to NAK when externally specified ACK counts
+ * have expired. The caller sends the waveform that we created.
+ */
+static int i2c_proc_value(struct context *inc, uint32_t value)
+{
+       gboolean with_ack;
+       int ret;
+
+       if (!inc)
+               return SR_ERR_ARG;
+
+       with_ack = i2c_auto_ack_avail(inc);
+
+       ret = wave_clear_sequence(inc);
+       if (ret != SR_OK)
+               return ret;
+       ret = i2c_write_byte(inc, value, with_ack);
+       if (ret != SR_OK)
+               return ret;
+
+       return 0;
+}
+
+/* Start/end the logic trace with a few bit times of idle level. */
+static int i2c_get_idle_capture(struct context *inc,
+       size_t *bitcount, uint8_t *sample)
+{
+
+       /* Describe a byte's time of idle level. */
+       if (bitcount)
+               *bitcount = I2C_BITTIME_SLOTS;
+       if (sample)
+               *sample = inc->samples.idle_levels;
+       return SR_OK;
+}
+
+/* Arrange for a few samples of idle level between UART frames. */
+static int i2c_get_idle_interframe(struct context *inc,
+       size_t *samplecount, uint8_t *sample)
+{
+
+       /*
+        * The space around regular bytes already is sufficient. We
+        * don't need to generate an inter-frame gap, but the code is
+        * prepared to in case we want to in the future.
+        */
+       if (samplecount) {
+               *samplecount = inc->curr_opts.samples_per_bit;
+               *samplecount *= 0;
+       }
+       if (sample)
+               *sample = inc->samples.curr_levels;
+       return SR_OK;
+}
+
+/* }}} I2C protocol handler */
+/* {{{ protocol dispatching */
+
+/*
+ * The list of supported protocols and their handlers, including
+ * protocol specific defaults. The first item after the NONE slot
+ * is the default protocol, and takes effect in the absence of any
+ * user provided or file content provided spec.
+ */
+static const struct proto_handler_t protocols[PROTO_TYPE_COUNT] = {
+       [PROTO_TYPE_UART] = {
+               UART_HANDLER_NAME,
+               {
+                       UART_DFLT_SAMPLERATE,
+                       UART_DFLT_BITRATE, UART_DFLT_FRAMEFMT,
+                       INPUT_BYTES,
+               },
+               {
+                       1, (const char *[]){
+                               [UART_PIN_RXTX] = "rxtx",
+                       },
+               },
+               0,
+               uart_check_opts,
+               uart_config_frame,
+               uart_proc_pseudo,
+               uart_proc_value,
+               uart_get_idle_capture,
+               uart_get_idle_interframe,
+       },
+       [PROTO_TYPE_SPI] = {
+               SPI_HANDLER_NAME,
+               {
+                       SPI_DFLT_SAMPLERATE,
+                       SPI_DFLT_BITRATE, SPI_DFLT_FRAMEFMT,
+                       INPUT_TEXT,
+               },
+               {
+                       4, (const char *[]){
+                               [SPI_PIN_SCK] = "sck",
+                               [SPI_PIN_MISO] = "miso",
+                               [SPI_PIN_MOSI] = "mosi",
+                               [SPI_PIN_CS] = "cs",
+                       },
+               },
+               sizeof(struct spi_proto_context_t),
+               spi_check_opts,
+               spi_config_frame,
+               spi_proc_pseudo,
+               spi_proc_value,
+               spi_get_idle_capture,
+               spi_get_idle_interframe,
+       },
+       [PROTO_TYPE_I2C] = {
+               I2C_HANDLER_NAME,
+               {
+                       I2C_DFLT_SAMPLERATE,
+                       I2C_DFLT_BITRATE, I2C_DFLT_FRAMEFMT,
+                       INPUT_TEXT,
+               },
+               {
+                       2, (const char *[]){
+                               [I2C_PIN_SCL] = "scl",
+                               [I2C_PIN_SDA] = "sda",
+                       },
+               },
+               sizeof(struct i2c_proto_context_t),
+               i2c_check_opts,
+               i2c_config_frame,
+               i2c_proc_pseudo,
+               i2c_proc_value,
+               i2c_get_idle_capture,
+               i2c_get_idle_interframe,
+       },
+};
+
+static int lookup_protocol_name(struct context *inc)
+{
+       const char *name;
+       const struct proto_handler_t *handler;
+       size_t idx;
+       void *priv;
+
+       /*
+        * Silence compiler warnings. Protocol handlers are free to use
+        * several alternative sets of primitives for their operation.
+        * Not using part of the API is nothing worth warning about.
+        */
+       (void)sample_buffer_assign;
+
+       if (!inc)
+               return SR_ERR_ARG;
+       inc->curr_opts.protocol_type = PROTO_TYPE_NONE;
+       inc->curr_opts.prot_hdl = NULL;
+
+       name = inc->curr_opts.proto_name;
+       if (!name || !*name) {
+               /* Fallback to first item after NONE slot. */
+               handler = &protocols[PROTO_TYPE_NONE + 1];
+               name = handler->name;
+       }
+
+       for (idx = 0; idx < ARRAY_SIZE(protocols); idx++) {
+               if (idx == PROTO_TYPE_NONE)
+                       continue;
+               handler = &protocols[idx];
+               if (!handler->name || !*handler->name)
+                       continue;
+               if (strcmp(name, handler->name) != 0)
+                       continue;
+               inc->curr_opts.protocol_type = idx;
+               inc->curr_opts.prot_hdl = handler;
+               if (handler->priv_size) {
+                       priv = g_malloc0(handler->priv_size);
+                       if (!priv)
+                               return SR_ERR_MALLOC;
+                       inc->curr_opts.prot_priv = priv;
+               }
+               return SR_OK;
+       }
+
+       return SR_ERR_DATA;
+}
+
+/* }}} protocol dispatching */
+/* {{{ text/binary input file reader */
+
+/**
+ * Checks for UTF BOM, removes it when found at the start of the buffer.
+ *
+ * @param[in] buf The accumulated input buffer.
+ */
+static void check_remove_bom(GString *buf)
+{
+       static const char *bom_text = "\xef\xbb\xbf";
+
+       if (buf->len < strlen(bom_text))
+               return;
+       if (strncmp(buf->str, bom_text, strlen(bom_text)) != 0)
+               return;
+       g_string_erase(buf, 0, strlen(bom_text));
+}
+
+/**
+ * Checks for presence of a caption, yields the position after its text line.
+ *
+ * @param[in] buf The accumulated input buffer.
+ * @param[in] caption The text to search for (NUL terminated ASCII literal).
+ * @param[in] max_pos The maximum length to search for.
+ *
+ * @returns The position after the text line which contains the caption.
+ *   Or #NULL when either the caption or the end-of-line was not found.
+ */
+static char *have_text_line(GString *buf, const char *caption, size_t max_pos)
+{
+       size_t cap_len, rem_len;
+       char *p_read, *p_found;
+
+       cap_len = strlen(caption);
+       rem_len = buf->len;
+       p_read = buf->str;
+
+       /* Search for the occurance of the caption itself. */
+       if (!max_pos) {
+               /* Caption must be at the start of the buffer. */
+               if (rem_len < cap_len)
+                       return NULL;
+               if (strncmp(p_read, caption, cap_len) != 0)
+                       return NULL;
+       } else {
+               /* Caption can be anywhere up to a max position. */
+               p_found = g_strstr_len(p_read, rem_len, caption);
+               if (!p_found)
+                       return NULL;
+               /* Pretend that caption had been rather long. */
+               cap_len += p_found - p_read;
+       }
+
+       /*
+        * Advance over the caption. Advance over end-of-line. Supports
+        * several end-of-line conditions, but rejects unexpected trailer
+        * after the caption and before the end-of-line. Always wants LF.
+        */
+       p_read += cap_len;
+       rem_len -= cap_len;
+       while (rem_len && *p_read != '\n' && g_ascii_isspace(*p_read)) {
+               p_read++;
+               rem_len--;
+       }
+       if (rem_len && *p_read != '\n' && *p_read == '\r') {
+               p_read++;
+               rem_len--;
+       }
+       if (rem_len && *p_read == '\n') {
+               p_read++;
+               rem_len--;
+               return p_read;
+       }
+
+       return NULL;
+}
+
+/**
+ * Checks for the presence of the magic string at the start of the file.
+ *
+ * @param[in] buf The accumulated input buffer.
+ * @param[out] next_pos The text after the magic text line.
+ *
+ * @returns Boolean whether the magic was found.
+ *
+ * This implementation assumes that the magic file type marker never gets
+ * split across receive chunks.
+ */
+static gboolean have_magic(GString *buf, char **next_pos)
+{
+       char *next_line;
+
+       if (next_pos)
+               *next_pos = NULL;
+
+       next_line = have_text_line(buf, MAGIC_FILE_TYPE, 0);
+       if (!next_line)
+               return FALSE;
+
+       if (next_pos)
+               *next_pos = next_line;
+
+       return TRUE;
+}
+
+/**
+ * Checks for the presence of the header section at the start of the file.
+ *
+ * @param[in] buf The accumulated input buffer.
+ * @param[out] next_pos The text after the header section.
+ *
+ * @returns A negative value when the answer is yet unknown (insufficient
+ *   input data). Or boolean 0/1 when the header was found absent/present.
+ *
+ * The caller is supposed to have checked for and removed the magic text
+ * for the file type. This routine expects to find the header section
+ * boundaries right at the start of the input buffer.
+ *
+ * This implementation assumes that the header start marker never gets
+ * split across receive chunks.
+ */
+static int have_header(GString *buf, char **next_pos)
+{
+       char *after_start, *after_end;
+
+       if (next_pos)
+               *next_pos = NULL;
+
+       after_start = have_text_line(buf, TEXT_HEAD_START, 0);
+       if (!after_start)
+               return 0;
+
+       after_end = have_text_line(buf, TEXT_HEAD_END, buf->len);
+       if (!after_end)
+               return -1;
+
+       if (next_pos)
+               *next_pos = after_end;
+       return 1;
+}
+
+/*
+ * Implementation detail: Most parse routines merely accept an input
+ * string or at most convert text to numbers. Actual processing of the
+ * values or constraints checks are done later when the header section
+ * ended and all data was seen, regardless of order of appearance.
+ */
+
+static int parse_samplerate(struct context *inc, const char *text)
+{
+       uint64_t rate;
+       int ret;
+
+       ret = sr_parse_sizestring(text, &rate);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+
+       inc->curr_opts.samplerate = rate;
+
+       return SR_OK;
+}
+
+static int parse_bitrate(struct context *inc, const char *text)
+{
+       uint64_t rate;
+       int ret;
+
+       ret = sr_parse_sizestring(text, &rate);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+
+       inc->curr_opts.bitrate = rate;
+
+       return SR_OK;
+}
+
+static int parse_protocol(struct context *inc, const char *line)
+{
+
+       if (!line || !*line)
+               return SR_ERR_DATA;
+
+       if (inc->curr_opts.proto_name) {
+               free(inc->curr_opts.proto_name);
+               inc->curr_opts.proto_name = NULL;
+       }
+       inc->curr_opts.proto_name = g_strdup(line);
+       if (!inc->curr_opts.proto_name)
+               return SR_ERR_MALLOC;
+       line = inc->curr_opts.proto_name;
+
+       return SR_OK;
+}
+
+static int parse_frameformat(struct context *inc, const char *line)
+{
+
+       if (!line || !*line)
+               return SR_ERR_DATA;
+
+       if (inc->curr_opts.fmt_text) {
+               free(inc->curr_opts.fmt_text);
+               inc->curr_opts.fmt_text = NULL;
+       }
+       inc->curr_opts.fmt_text = g_strdup(line);
+       if (!inc->curr_opts.fmt_text)
+               return SR_ERR_MALLOC;
+       line = inc->curr_opts.fmt_text;
+
+       return SR_OK;
+}
+
+static int parse_textinput(struct context *inc, const char *text)
+{
+       gboolean is_text;
+
+       if (!text || !*text)
+               return SR_ERR_ARG;
+
+       is_text = sr_parse_boolstring(text);
+       inc->curr_opts.textinput = is_text ? INPUT_TEXT : INPUT_BYTES;
+       return SR_OK;
+}
+
+static int parse_header_line(struct context *inc, const char *line)
+{
+
+       /* Silently ignore comment lines. Also covers start/end markers. */
+       if (strncmp(line, TEXT_COMM_LEADER, strlen(TEXT_COMM_LEADER)) == 0)
+               return SR_OK;
+
+       if (strncmp(line, LABEL_SAMPLERATE, strlen(LABEL_SAMPLERATE)) == 0) {
+               line += strlen(LABEL_SAMPLERATE);
+               return parse_samplerate(inc, line);
+       }
+       if (strncmp(line, LABEL_BITRATE, strlen(LABEL_BITRATE)) == 0) {
+               line += strlen(LABEL_BITRATE);
+               return parse_bitrate(inc, line);
+       }
+       if (strncmp(line, LABEL_PROTOCOL, strlen(LABEL_PROTOCOL)) == 0) {
+               line += strlen(LABEL_PROTOCOL);
+               return parse_protocol(inc, line);
+       }
+       if (strncmp(line, LABEL_FRAMEFORMAT, strlen(LABEL_FRAMEFORMAT)) == 0) {
+               line += strlen(LABEL_FRAMEFORMAT);
+               return parse_frameformat(inc, line);
+       }
+       if (strncmp(line, LABEL_TEXTINPUT, strlen(LABEL_TEXTINPUT)) == 0) {
+               line += strlen(LABEL_TEXTINPUT);
+               return parse_textinput(inc, line);
+       }
+
+       /* Unsupported directive. */
+       sr_err("Unsupported header directive: %s.", line);
+
+       return SR_ERR_DATA;
+}
+
+static int parse_header(struct context *inc, GString *buf, size_t hdr_len)
+{
+       size_t remain;
+       char *curr, *next, *line;
+       int ret;
+
+       ret = SR_OK;
+
+       /* The caller determined where the header ends. Read up to there. */
+       remain = hdr_len;
+       curr = buf->str;
+       while (curr && remain) {
+               /* Get another text line. Skip empty lines. */
+               line = sr_text_next_line(curr, remain, &next, NULL);
+               if (!line)
+                       break;
+               if (next)
+                       remain -= next - curr;
+               else
+                       remain = 0;
+               curr = next;
+               if (!*line)
+                       continue;
+               /* Process the non-empty file header text line. */
+               sr_dbg("Header line: %s", line);
+               ret = parse_header_line(inc, line);
+               if (ret != SR_OK)
+                       break;
+       }
+
+       return ret;
+}
+
+/* Process input text reader specific pseudo comment. */
+static int process_pseudo_textinput(struct sr_input *in, char *line)
+{
+       struct context *inc;
+       char *word;
+       unsigned long v;
+       char *endp;
+       int ret;
+
+       inc = in->priv;
+       while (line) {
+               word = sr_text_next_word(line, &line);
+               if (!word)
+                       break;
+               if (!*word)
+                       continue;
+               if (g_str_has_prefix(word, TEXT_INPUT_RADIX)) {
+                       word += strlen(TEXT_INPUT_RADIX);
+                       endp = NULL;
+                       ret = sr_atoul_base(word, &v, &endp, 10);
+                       if (ret != SR_OK)
+                               return ret;
+                       inc->read_text.base = v;
+                       continue;
+               }
+               return SR_ERR_DATA;
+       }
+
+       return SR_OK;
+}
+
+/* Process a line of input text. */
+static int process_textline(struct sr_input *in, char *line)
+{
+       struct context *inc;
+       const struct proto_handler_t *handler;
+       gboolean is_comm, is_pseudo;
+       char *p, *word, *endp;
+       unsigned long value;
+       int ret;
+
+       inc = in->priv;
+       handler = inc->curr_opts.prot_hdl;
+
+       /*
+        * Check for comments, including pseudo-comments with protocol
+        * specific or text reader specific instructions. It's essential
+        * to check for "# ${PROTO}:" last, because the implementation
+        * of the check advances the read position, cannot rewind when
+        * detection fails. But we know that it is a comment and was not
+        * a pseudo-comment. So any non-matching data just gets discarded.
+        * Matching data gets processed (when handlers exist).
+        */
+       is_comm = g_str_has_prefix(line, TEXT_COMM_LEADER);
+       if (is_comm) {
+               line += strlen(TEXT_COMM_LEADER);
+               while (isspace(*line))
+                       line++;
+               is_pseudo = g_str_has_prefix(line, TEXT_INPUT_PREFIX);
+               if (is_pseudo) {
+                       line += strlen(TEXT_INPUT_PREFIX);
+                       while (isspace(*line))
+                               line++;
+                       sr_dbg("pseudo comment, textinput: %s", line);
+                       line = sr_text_trim_spaces(line);
+                       return process_pseudo_textinput(in, line);
+               }
+               is_pseudo = g_str_has_prefix(line, handler->name);
+               if (is_pseudo) {
+                       line += strlen(handler->name);
+                       is_pseudo = *line == ':';
+                       if (is_pseudo)
+                               line++;
+               }
+               if (is_pseudo) {
+                       while (isspace(*line))
+                               line++;
+                       sr_dbg("pseudo comment, protocol: %s", line);
+                       if (!handler->proc_pseudo)
+                               return SR_OK;
+                       return handler->proc_pseudo(in, line);
+               }
+               sr_spew("comment, skipping: %s", line);
+               return SR_OK;
+       }
+
+       /*
+        * Non-empty non-comment lines carry protocol values.
+        * (Empty lines are handled transparently when they get here.)
+        * Accept comma and semicolon separators for user convenience.
+        * Convert text according to previously received instructions.
+        * Pass the values to the protocol handler. Flush waveforms
+        * when handlers state that their construction has completed.
+        */
+       sr_spew("got values line: %s", line);
+       for (p = line; *p; p++) {
+               if (*p == ',' || *p == ';')
+                       *p = ' ';
+       }
+       while (line) {
+               word = sr_text_next_word(line, &line);
+               if (!word)
+                       break;
+               if (!*word)
+                       continue;
+               /* Get another numeric value. */
+               endp = NULL;
+               ret = sr_atoul_base(word, &value, &endp, inc->read_text.base);
+               if (ret != SR_OK)
+                       return ret;
+               if (!endp || *endp)
+                       return SR_ERR_DATA;
+               sr_spew("got a value, text [%s] -> number [%lu]", word, value);
+               /* Forward the value to the protocol handler. */
+               ret = 0;
+               if (handler->proc_value)
+                       ret = handler->proc_value(inc, value);
+               if (ret < 0)
+                       return ret;
+               /* Flush the waveform when handler signals completion. */
+               if (ret > 0)
+                       continue;
+               ret = send_frame(in);
+               if (ret != SR_OK)
+                       return ret;
+               ret = send_idle_interframe(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* }}} text/binary input file reader */
+
+/*
+ * Consistency check of all previously received information. Combines
+ * the data file's optional header section, as well as user provided
+ * options that were specified during input module creation. User specs
+ * take precedence over file content.
+ */
+static int check_header_user_options(struct context *inc)
+{
+       int ret;
+       const struct proto_handler_t *handler;
+       uint64_t rate;
+       const char *text;
+       enum textinput_t is_text;
+
+       if (!inc)
+               return SR_ERR_ARG;
+
+       /* Prefer user specs over file content. */
+       rate = inc->user_opts.samplerate;
+       if (rate) {
+               sr_dbg("Using user samplerate %" PRIu64 ".", rate);
+               inc->curr_opts.samplerate = rate;
+       }
+       rate = inc->user_opts.bitrate;
+       if (rate) {
+               sr_dbg("Using user bitrate %" PRIu64 ".", rate);
+               inc->curr_opts.bitrate = rate;
+       }
+       text = inc->user_opts.proto_name;
+       if (text && *text) {
+               sr_dbg("Using user protocol %s.", text);
+               ret = parse_protocol(inc, text);
+               if (ret != SR_OK)
+                       return SR_ERR_DATA;
+       }
+       text = inc->user_opts.fmt_text;
+       if (text && *text) {
+               sr_dbg("Using user frame format %s.", text);
+               ret = parse_frameformat(inc, text);
+               if (ret != SR_OK)
+                       return SR_ERR_DATA;
+       }
+       is_text = inc->user_opts.textinput;
+       if (is_text) {
+               sr_dbg("Using user textinput %d.", is_text);
+               inc->curr_opts.textinput = is_text;
+       }
+
+       /* Lookup the protocol (with fallback). Use protocol's defaults. */
+       text = inc->curr_opts.proto_name;
+       ret = lookup_protocol_name(inc);
+       handler = inc->curr_opts.prot_hdl;
+       if (ret != SR_OK || !handler) {
+               sr_err("Unsupported protocol: %s.", text);
+               return SR_ERR_DATA;
+       }
+       text = handler->name;
+       if (!inc->curr_opts.proto_name && text) {
+               sr_dbg("Using protocol handler name %s.", text);
+               ret = parse_protocol(inc, text);
+               if (ret != SR_OK)
+                       return SR_ERR_DATA;
+       }
+       rate = handler->dflt.samplerate;
+       if (!inc->curr_opts.samplerate && rate) {
+               sr_dbg("Using protocol handler samplerate %" PRIu64 ".", rate);
+               inc->curr_opts.samplerate = rate;
+       }
+       rate = handler->dflt.bitrate;
+       if (!inc->curr_opts.bitrate && rate) {
+               sr_dbg("Using protocol handler bitrate %" PRIu64 ".", rate);
+               inc->curr_opts.bitrate = rate;
+       }
+       text = handler->dflt.frame_format;
+       if (!inc->curr_opts.fmt_text && text && *text) {
+               sr_dbg("Using protocol handler frame format %s.", text);
+               ret = parse_frameformat(inc, text);
+               if (ret != SR_OK)
+                       return SR_ERR_DATA;
+       }
+       is_text = handler->dflt.textinput;
+       if (!inc->curr_opts.textinput && is_text) {
+               sr_dbg("Using protocol handler text format %d.", is_text);
+               inc->curr_opts.textinput = is_text;
+       }
+
+       if (!inc->curr_opts.samplerate) {
+               sr_err("Need a samplerate.");
+               return SR_ERR_DATA;
+       }
+       if (!inc->curr_opts.bitrate) {
+               sr_err("Need a protocol bitrate.");
+               return SR_ERR_DATA;
+       }
+
+       if (inc->curr_opts.samplerate < inc->curr_opts.bitrate) {
+               sr_err("Bitrate cannot exceed samplerate.");
+               return SR_ERR_DATA;
+       }
+       if (inc->curr_opts.samplerate / inc->curr_opts.bitrate < 3)
+               sr_warn("Low oversampling, consider higher samplerate.");
+       if (inc->curr_opts.prot_hdl->check_opts) {
+               ret = inc->curr_opts.prot_hdl->check_opts(inc);
+               if (ret != SR_OK) {
+                       sr_err("Options failed the protocol's check.");
+                       return SR_ERR_DATA;
+               }
+       }
+
+       return SR_OK;
+}
+
+static int create_channels(struct sr_input *in)
+{
+       struct context *inc;
+       struct sr_dev_inst *sdi;
+       const struct proto_handler_t *handler;
+       size_t index;
+       const char *name;
+
+       if (!in)
+               return SR_ERR_ARG;
+       inc = in->priv;
+       if (!inc)
+               return SR_ERR_ARG;
+       sdi = in->sdi;
+       handler = inc->curr_opts.prot_hdl;
+
+       for (index = 0; index < handler->chans.count; index++) {
+               name = handler->chans.names[index];
+               sr_dbg("Channel %zu name %s.", index, name);
+               sr_channel_new(sdi, index, SR_CHANNEL_LOGIC, TRUE, name);
+       }
+
+       inc->feed_logic = feed_queue_logic_alloc(in->sdi,
+               CHUNK_SIZE, sizeof(uint8_t));
+       if (!inc->feed_logic) {
+               sr_err("Cannot create session feed.");
+               return SR_ERR_MALLOC;
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Keep track of a previously created channel list, in preparation of
+ * re-reading the input file. Gets called from reset()/cleanup() paths.
+ */
+static void keep_header_for_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       inc = in->priv;
+
+       g_slist_free_full(inc->prev.sr_groups, sr_channel_group_free_cb);
+       inc->prev.sr_groups = in->sdi->channel_groups;
+       in->sdi->channel_groups = NULL;
+
+       g_slist_free_full(inc->prev.sr_channels, sr_channel_free_cb);
+       inc->prev.sr_channels = in->sdi->channels;
+       in->sdi->channels = NULL;
+}
+
+/*
+ * Check whether the input file is being re-read, and refuse operation
+ * when essential parameters of the acquisition have changed in ways
+ * that are unexpected to calling applications. Gets called after the
+ * file header got parsed (again).
+ *
+ * Changing the channel list across re-imports of the same file is not
+ * supported, by design and for valid reasons, see bug #1215 for details.
+ * Users are expected to start new sessions when they change these
+ * essential parameters in the acquisition's setup. When we accept the
+ * re-read file, then make sure to keep using the previous channel list,
+ * applications may still reference them.
+ */
+static gboolean check_header_in_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       if (!in)
+               return FALSE;
+       inc = in->priv;
+       if (!inc)
+               return FALSE;
+       if (!inc->prev.sr_channels)
+               return TRUE;
+
+       if (sr_channel_lists_differ(inc->prev.sr_channels, in->sdi->channels)) {
+               sr_err("Channel list change not supported for file re-read.");
+               return FALSE;
+       }
+
+       g_slist_free_full(in->sdi->channel_groups, sr_channel_group_free_cb);
+       in->sdi->channel_groups = inc->prev.sr_groups;
+       inc->prev.sr_groups = NULL;
+
+       g_slist_free_full(in->sdi->channels, sr_channel_free_cb);
+       in->sdi->channels = inc->prev.sr_channels;
+       inc->prev.sr_channels = NULL;
+
+       return TRUE;
+}
+
+/* Process another chunk of accumulated input data. */
+static int process_buffer(struct sr_input *in, gboolean is_eof)
+{
+       struct context *inc;
+       GVariant *gvar;
+       int ret;
+       GString *buf;
+       const struct proto_handler_t *handler;
+       size_t seen;
+       char *line, *next;
+       uint8_t sample;
+
+       inc = in->priv;
+       buf = in->buf;
+       handler = inc->curr_opts.prot_hdl;
+
+       /*
+        * Send feed header and samplerate once before any sample data.
+        * Communicate an idle period before the first generated frame.
+        */
+       if (!inc->started) {
+               std_session_send_df_header(in->sdi);
+               gvar = g_variant_new_uint64(inc->curr_opts.samplerate);
+               ret = sr_session_send_meta(in->sdi, SR_CONF_SAMPLERATE, gvar);
+               inc->started = TRUE;
+               if (ret != SR_OK)
+                       return ret;
+
+               ret = send_idle_capture(inc);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /*
+        * Force proper line termination when EOF is seen and the data
+        * is in text format. This does not affect binary input, while
+        * properly terminated text input does not suffer from another
+        * line feed, because empty lines are considered acceptable.
+        * Increases robustness for text input from broken generators
+        * (popular editors which don't terminate the last line).
+        */
+       if (inc->curr_opts.textinput == INPUT_TEXT && is_eof) {
+               g_string_append_c(buf, '\n');
+       }
+
+       /*
+        * For text input: Scan for the completion of another text line.
+        * Process its values (or pseudo comments). Skip comment lines.
+        */
+       if (inc->curr_opts.textinput == INPUT_TEXT) do {
+               /* Get another line of text. */
+               seen = 0;
+               line = sr_text_next_line(buf->str, buf->len, &next, &seen);
+               if (!line)
+                       break;
+               /* Process non-empty input lines. */
+               ret = *line ? process_textline(in, line) : 0;
+               if (ret < 0)
+                       return ret;
+               /* Discard processed input text. */
+               g_string_erase(buf, 0, seen);
+       } while (buf->len);
+
+       /*
+        * For binary input: Pass data values (individual bytes) to the
+        * creation of protocol frames. Send the frame's waveform to
+        * logic channels in the session feed when the protocol handler
+        * signals the completion of another waveform (zero return value).
+        * Non-zero positive values translate to "need more input data".
+        * Negative values signal fatal errors. Remove processed input
+        * data from the receive buffer.
+        */
+       if (inc->curr_opts.textinput == INPUT_BYTES) {
+               seen = 0;
+               while (seen < buf->len) {
+                       sample = buf->str[seen++];
+                       ret = 0;
+                       if (handler->proc_value)
+                               ret = handler->proc_value(inc, sample);
+                       if (ret < 0)
+                               return ret;
+                       if (ret > 0)
+                               continue;
+                       ret = send_frame(in);
+                       if (ret != SR_OK)
+                               return ret;
+                       ret = send_idle_interframe(inc);
+                       if (ret != SR_OK)
+                               return ret;
+               }
+               g_string_erase(buf, 0, seen);
+       }
+
+       /* Send idle level, and flush when end of input data is seen. */
+       if (is_eof) {
+               if (buf->len)
+                       sr_warn("Unprocessed input data remains.");
+
+               ret = send_idle_capture(inc);
+               if (ret != SR_OK)
+                       return ret;
+
+               ret = feed_queue_logic_flush(inc->feed_logic);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+static int format_match(GHashTable *metadata, unsigned int *confidence)
+{
+       GString *buf, *tmpbuf;
+       gboolean has_magic;
+
+       buf = g_hash_table_lookup(metadata,
+               GINT_TO_POINTER(SR_INPUT_META_HEADER));
+       tmpbuf = g_string_new_len(buf->str, buf->len);
+
+       check_remove_bom(tmpbuf);
+       has_magic = have_magic(tmpbuf, NULL);
+       g_string_free(tmpbuf, TRUE);
+
+       if (!has_magic)
+               return SR_ERR;
+
+       *confidence = 1;
+       return SR_OK;
+}
+
+static int init(struct sr_input *in, GHashTable *options)
+{
+       struct context *inc;
+       GVariant *gvar;
+       uint64_t rate;
+       char *copy;
+       const char *text;
+
+       in->sdi = g_malloc0(sizeof(*in->sdi));
+       inc = g_malloc0(sizeof(*inc));
+       in->priv = inc;
+
+       /*
+        * Store user specified options for later reference.
+        *
+        * TODO How to most appropriately hook up size strings with the
+        * input module's defaults, and applications and their input
+        * dialogs?
+        */
+       gvar = g_hash_table_lookup(options, "samplerate");
+       if (gvar) {
+               rate = g_variant_get_uint64(gvar);
+               if (rate)
+                       sr_dbg("User samplerate %" PRIu64 ".", rate);
+               inc->user_opts.samplerate = rate;
+       }
+
+       gvar = g_hash_table_lookup(options, "bitrate");
+       if (gvar) {
+               rate = g_variant_get_uint64(gvar);
+               if (rate)
+                       sr_dbg("User bitrate %" PRIu64 ".", rate);
+               inc->user_opts.bitrate = rate;
+       }
+
+       gvar = g_hash_table_lookup(options, "protocol");
+       if (gvar) {
+               copy = g_strdup(g_variant_get_string(gvar, NULL));
+               if (!copy)
+                       return SR_ERR_MALLOC;
+               if (*copy)
+                       sr_dbg("User protocol %s.", copy);
+               inc->user_opts.proto_name = copy;
+       }
+
+       gvar = g_hash_table_lookup(options, "frameformat");
+       if (gvar) {
+               copy = g_strdup(g_variant_get_string(gvar, NULL));
+               if (!copy)
+                       return SR_ERR_MALLOC;
+               if (*copy)
+                       sr_dbg("User frame format %s.", copy);
+               inc->user_opts.fmt_text = copy;
+       }
+
+       inc->user_opts.textinput = INPUT_UNSPEC;
+       gvar = g_hash_table_lookup(options, "textinput");
+       if (gvar) {
+               text = g_variant_get_string(gvar, NULL);
+               if (!text)
+                       return SR_ERR_DATA;
+               if (!*text)
+                       return SR_ERR_DATA;
+               sr_dbg("User text input %s.", text);
+               if (strcmp(text, input_format_texts[INPUT_UNSPEC]) == 0) {
+                       inc->user_opts.textinput = INPUT_UNSPEC;
+               } else if (strcmp(text, input_format_texts[INPUT_BYTES]) == 0) {
+                       inc->user_opts.textinput = INPUT_BYTES;
+               } else if (strcmp(text, input_format_texts[INPUT_TEXT]) == 0) {
+                       inc->user_opts.textinput = INPUT_TEXT;
+               } else {
+                       return SR_ERR_DATA;
+               }
+       }
+
+       return SR_OK;
+}
+
+static int receive(struct sr_input *in, GString *buf)
+{
+       struct context *inc;
+       char *after_magic, *after_header;
+       size_t consumed;
+       int ret;
+
+       inc = in->priv;
+
+       /*
+        * Accumulate all input chunks, potential deferred processing.
+        *
+        * Remove an optional BOM at the very start of the input stream.
+        * BEWARE! This may affect binary input, and we cannot tell if
+        * the input is text or binary at this stage. Though probability
+        * for this issue is rather low. Workarounds are available (put
+        * another values before the first data which happens to match
+        * the BOM pattern, provide text input instead).
+        */
+       g_string_append_len(in->buf, buf->str, buf->len);
+       if (!inc->scanned_magic)
+               check_remove_bom(in->buf);
+
+       /*
+        * Must complete reception of the (optional) header first. Both
+        * end of header and absence of header will: Check options that
+        * were seen so far, then start processing the data part.
+        */
+       if (!inc->got_header) {
+               /* Check for magic file type marker. */
+               if (!inc->scanned_magic) {
+                       inc->has_magic = have_magic(in->buf, &after_magic);
+                       inc->scanned_magic = TRUE;
+                       if (inc->has_magic) {
+                               consumed = after_magic - in->buf->str;
+                               sr_dbg("File format magic found (%zu).", consumed);
+                               g_string_erase(in->buf, 0, consumed);
+                       }
+               }
+
+               /* Complete header reception and processing. */
+               if (inc->has_magic) {
+                       ret = have_header(in->buf, &after_header);
+                       if (ret < 0)
+                               return SR_OK;
+                       inc->has_header = ret;
+                       if (inc->has_header) {
+                               consumed = after_header - in->buf->str;
+                               sr_dbg("File header found (%zu), processing.", consumed);
+                               ret = parse_header(inc, in->buf, consumed);
+                               if (ret != SR_OK)
+                                       return ret;
+                               g_string_erase(in->buf, 0, consumed);
+                       }
+               }
+               inc->got_header = TRUE;
+
+               /*
+                * Postprocess the combination of all options. Create
+                * logic channels, prepare resources for data processing.
+                */
+               ret = check_header_user_options(inc);
+               if (ret != SR_OK)
+                       return ret;
+               ret = create_channels(in);
+               if (ret != SR_OK)
+                       return ret;
+               if (!check_header_in_reread(in))
+                       return SR_ERR_DATA;
+               ret = alloc_frame_storage(inc);
+               if (ret != SR_OK)
+                       return ret;
+               ret = assign_bit_widths(inc);
+               if (ret != SR_OK)
+                       return ret;
+
+               /* Notify the frontend that sdi is ready. */
+               in->sdi_ready = TRUE;
+               return SR_OK;
+       }
+
+       /*
+        * Process the input file's data section after the header section
+        * was received and processed.
+        */
+       ret = process_buffer(in, FALSE);
+
+       return ret;
+}
+
+static int end(struct sr_input *in)
+{
+       struct context *inc;
+       int ret;
+
+       inc = in->priv;
+
+       /* Must complete processing of previously received chunks. */
+       if (in->sdi_ready) {
+               ret = process_buffer(in, TRUE);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       /* Must send DF_END when DF_HEADER was sent before. */
+       if (inc->started) {
+               ret = std_session_send_df_end(in->sdi);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+static void cleanup(struct sr_input *in)
+{
+       struct context *inc;
+
+       inc = in->priv;
+
+       keep_header_for_reread(in);
+
+       g_free(inc->curr_opts.proto_name);
+       inc->curr_opts.proto_name = NULL;
+       g_free(inc->curr_opts.fmt_text);
+       inc->curr_opts.fmt_text = NULL;
+       g_free(inc->curr_opts.prot_priv);
+       inc->curr_opts.prot_priv = NULL;
+       feed_queue_logic_free(inc->feed_logic);
+       inc->feed_logic = NULL;
+       g_free(inc->sample_edges);
+       inc->sample_edges = NULL;
+       g_free(inc->sample_widths);
+       inc->sample_widths = NULL;
+       g_free(inc->sample_levels);
+       inc->sample_levels = NULL;
+       g_free(inc->bit_scale);
+       inc->bit_scale = NULL;
+}
+
+static int reset(struct sr_input *in)
+{
+       struct context *inc;
+       struct user_opts_t save_user_opts;
+       struct proto_prev save_chans;
+
+       inc = in->priv;
+
+       /* Release previously allocated resources. */
+       cleanup(in);
+       g_string_truncate(in->buf, 0);
+
+       /* Restore part of the context, init() won't run again. */
+       save_user_opts = inc->user_opts;
+       save_chans = inc->prev;
+       memset(inc, 0, sizeof(*inc));
+       inc->user_opts = save_user_opts;
+       inc->prev = save_chans;
+
+       return SR_OK;
+}
+
+enum proto_option_t {
+       OPT_SAMPLERATE,
+       OPT_BITRATE,
+       OPT_PROTOCOL,
+       OPT_FRAME_FORMAT,
+       OPT_TEXTINPUT,
+       OPT_MAX,
+};
+
+static struct sr_option options[] = {
+       [OPT_SAMPLERATE] = {
+               "samplerate", "Logic data samplerate",
+               "Samplerate of generated logic traces",
+               NULL, NULL,
+       },
+       [OPT_BITRATE] = {
+               "bitrate", "Protocol bitrate",
+               "Bitrate used in protocol's communication",
+               NULL, NULL,
+       },
+       [OPT_PROTOCOL] = {
+               "protocol", "Protocol type",
+               "The type of protocol to generate waveforms for",
+               NULL, NULL,
+       },
+       [OPT_FRAME_FORMAT] = {
+               "frameformat", "Protocol frame format",
+               "Textual description of the protocol's frame format",
+               NULL, NULL,
+       },
+       [OPT_TEXTINPUT] = {
+               "textinput", "Input data is in text format",
+               "Input is not data bytes, but text formatted values",
+               NULL, NULL,
+       },
+       [OPT_MAX] = ALL_ZERO,
+};
+
+static const struct sr_option *get_options(void)
+{
+       GSList *l;
+       enum proto_type_t p_idx;
+       enum textinput_t t_idx;
+       const char *s;
+
+       if (options[0].def)
+               return options;
+
+       options[OPT_SAMPLERATE].def = g_variant_ref_sink(g_variant_new_uint64(0));
+       options[OPT_BITRATE].def = g_variant_ref_sink(g_variant_new_uint64(0));
+       options[OPT_PROTOCOL].def = g_variant_ref_sink(g_variant_new_string(""));
+       l = NULL;
+       for (p_idx = 0; p_idx < ARRAY_SIZE(protocols); p_idx++) {
+               s = protocols[p_idx].name;
+               if (!s || !*s)
+                       continue;
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string(s)));
+       }
+       options[OPT_PROTOCOL].values = l;
+       options[OPT_FRAME_FORMAT].def = g_variant_ref_sink(g_variant_new_string(""));
+       l = NULL;
+       for (t_idx = INPUT_UNSPEC; t_idx <= INPUT_TEXT; t_idx++) {
+               s = input_format_texts[t_idx];
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string(s)));
+       }
+       options[OPT_TEXTINPUT].values = l;
+       options[OPT_TEXTINPUT].def = g_variant_ref_sink(g_variant_new_string(
+               input_format_texts[INPUT_UNSPEC]));
+       return options;
+}
+
+SR_PRIV struct sr_input_module input_protocoldata = {
+       .id = "protocoldata",
+       .name = "Protocol data",
+       .desc = "Generate logic traces from protocol's data values",
+       .exts = (const char *[]){ "sr-protocol", "protocol", "bin", NULL, },
+       .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
+       .options = get_options,
+       .format_match = format_match,
+       .init = init,
+       .receive = receive,
+       .end = end,
+       .reset = reset,
+};
index 431b0cbc03893ba707cc3469d061d2f14bb72bdf..ddef5de6ee8bc2b72ef92f054329f194485e4c18 100644 (file)
@@ -623,7 +623,7 @@ static void add_sample(const struct sr_input *in, uint16_t data, size_t count)
                count -= inc->submit.samples_to_trigger;
        }
        if (send_first) {
-               (void)feed_queue_logic_submit(inc->submit.feed,
+               (void)feed_queue_logic_submit_one(inc->submit.feed,
                        unit_buffer, send_first);
                inc->submit.submit_count += send_first;
                inc->submit.samples_to_trigger -= send_first;
@@ -632,7 +632,7 @@ static void add_sample(const struct sr_input *in, uint16_t data, size_t count)
                feed_queue_logic_send_trigger(inc->submit.feed);
        }
        if (count) {
-               (void)feed_queue_logic_submit(inc->submit.feed,
+               (void)feed_queue_logic_submit_one(inc->submit.feed,
                        unit_buffer, count);
                inc->submit.submit_count += count;
                if (inc->submit.samples_to_trigger)
index 3a4e1cd479996d91396ddc3d91a9c176f6ac53fd..ba89ad2c41b7479586ae7204762cb1a86a5f7f6b 100644 (file)
@@ -163,7 +163,7 @@ static int init(struct sr_input *in, GHashTable *options)
 
        /* Calculate the desired timestamp scaling factor. */
        inc->samplerate = 1000000 *
-               g_variant_get_uint32(g_hash_table_lookup(options, "samplerate"));
+               g_variant_get_uint64(g_hash_table_lookup(options, "samplerate"));
 
        inc->timestamp_scale = ((1 / TIMESTAMP_RESOLUTION) / (double)inc->samplerate);
 
@@ -891,7 +891,7 @@ static const struct sr_option *get_options(void)
                options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
                options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
                options[11].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
-               options[12].def = g_variant_ref_sink(g_variant_new_uint32(DEFAULT_SAMPLERATE));
+               options[12].def = g_variant_ref_sink(g_variant_new_uint64(DEFAULT_SAMPLERATE));
        }
 
        return options;
index 413712301318787f7201f9389de5748418dbfbeb..d5471a47163236f239fb7bef16d5fc9abcdf620b 100644 (file)
@@ -152,11 +152,6 @@ struct context {
        } conv_bits;
        GString *scope_prefix;
        struct feed_queue_logic *feed_logic;
-       struct split_state {
-               size_t alloced;
-               char **words;
-               gboolean in_use;
-       } split;
        struct ts_stats {
                size_t total_ts_seen;
                uint64_t last_ts_value;
@@ -205,15 +200,6 @@ static void free_channel(void *data)
        g_free(vcd_ch);
 }
 
-/* TODO Drop the local decl when this has become a common helper. */
-void sr_channel_group_free(struct sr_channel_group *cg);
-
-/* Wrapper for GDestroyNotify compatibility. */
-static void cg_free(void *p)
-{
-       sr_channel_group_free(p);
-}
-
 /*
  * Another timestamp delta was observed, update statistics: Update the
  * sorted list of minimum values, and increment the occurance counter.
@@ -466,6 +452,11 @@ static void check_remove_bom(GString *buf)
 /*
  * Reads a single VCD section from input file and parses it to name/contents.
  * e.g. $timescale 1ps $end => "timescale" "1ps"
+ *
+ * The section (its content and its opening/closing markers) can span
+ * multiple text lines. This routine must not modify the caller's input
+ * buffer. Executes potentially multiple times on the same input data,
+ * and executes outside of the processing of the file's data section.
  */
 static gboolean parse_section(GString *buf, char **name, char **contents)
 {
@@ -530,142 +521,6 @@ static gboolean parse_section(GString *buf, char **name, char **contents)
        return status;
 }
 
-/*
- * The glib routine which splits an input text into a list of words also
- * "provides empty strings" which application code then needs to remove.
- * And copies of the input text get allocated for all words.
- *
- * The repeated memory allocation is acceptable for small workloads like
- * parsing the header sections. But the heavy lifting for sample data is
- * done by DIY code to speedup execution. The use of glib routines would
- * severely hurt throughput. Allocated memory gets re-used while a strict
- * ping-pong pattern is assumed (each text line of input data enters and
- * leaves in a strict symmetrical manner, due to the organization of the
- * receive() routine and parse calls).
- */
-
-/* Remove empty parts from an array returned by g_strsplit(). */
-static void remove_empty_parts(gchar **parts)
-{
-       gchar **src, **dest;
-
-       src = dest = parts;
-       while (*src) {
-               if (!**src) {
-                       g_free(*src);
-               } else {
-                       if (dest != src)
-                               *dest = *src;
-                       dest++;
-               }
-               src++;
-       }
-       *dest = NULL;
-}
-
-static char **split_text_line(struct context *inc, char *text, size_t *count)
-{
-       struct split_state *state;
-       size_t counted, alloced, wanted;
-       char **words, *p, **new_words;
-
-       state = &inc->split;
-
-       if (count)
-               *count = 0;
-
-       if (state->in_use) {
-               sr_dbg("coding error, split() called while \"in use\".");
-               return NULL;
-       }
-
-       /*
-        * Seed allocation when invoked for the first time. Assume
-        * simple logic data, start with a few words per line. Will
-        * automatically adjust with subsequent use.
-        */
-       if (!state->alloced) {
-               alloced = 20;
-               words = g_malloc(sizeof(words[0]) * alloced);
-               if (!words)
-                       return NULL;
-               state->alloced = alloced;
-               state->words = words;
-       }
-
-       /* Start with most recently allocated word list space. */
-       alloced = state->alloced;
-       words = state->words;
-       counted = 0;
-
-       /* As long as more input text remains ... */
-       p = text;
-       while (*p) {
-               /* Resize word list if needed. Just double the size. */
-               if (counted + 1 >= alloced) {
-                       wanted = 2 * alloced;
-                       new_words = g_realloc(words, sizeof(words[0]) * wanted);
-                       if (!new_words) {
-                               return NULL;
-                       }
-                       words = new_words;
-                       alloced = wanted;
-                       state->words = words;
-                       state->alloced = alloced;
-               }
-
-               /* Skip leading spaces. */
-               while (g_ascii_isspace(*p))
-                       p++;
-               if (!*p)
-                       break;
-
-               /* Add found word to word list. */
-               words[counted++] = p;
-
-               /* Find end of the word. Terminate loop upon EOS. */
-               while (*p && !g_ascii_isspace(*p))
-                       p++;
-               if (!*p)
-                       break;
-
-               /* More text follows. Terminate the word. */
-               *p++ = '\0';
-       }
-
-       /*
-        * NULL terminate the word list. Provide its length so that
-        * calling code need not re-iterate the list to get the count.
-        */
-       words[counted] = NULL;
-       if (count)
-               *count = counted;
-       state->in_use = TRUE;
-
-       return words;
-}
-
-static void free_text_split(struct context *inc, char **words)
-{
-       struct split_state *state;
-
-       state = &inc->split;
-
-       if (words && words != state->words) {
-               sr_dbg("coding error, free() arg differs from split() result.");
-       }
-
-       /* "Double free" finally releases the memory. */
-       if (!state->in_use) {
-               g_free(state->words);
-               state->words = NULL;
-               state->alloced = 0;
-       }
-
-       /* Mark as no longer in use. */
-       state->in_use = FALSE;
-}
-
 static gboolean have_header(GString *buf)
 {
        static const char *enddef_txt = "$enddefinitions";
@@ -679,7 +534,14 @@ static gboolean have_header(GString *buf)
                return FALSE;
        p += strlen(enddef_txt);
 
-       /* Search for end of section (content expected to be empty). */
+       /*
+        * Search for end of section (content expected to be empty).
+        * Uses DIY logic to scan for the literals' presence including
+        * empty space between keywords. MUST NOT modify the caller's
+        * input data, potentially executes several times on the same
+        * receive buffer, and executes outside of the processing the
+        * file's data section.
+        */
        p_stop = &buf->str[buf->len];
        p_stop -= strlen(end_txt);
        while (p < p_stop && g_ascii_isspace(*p))
@@ -742,8 +604,7 @@ static int parse_timescale(struct context *inc, char *contents)
  */
 static int parse_scope(struct context *inc, char *contents, gboolean is_up)
 {
-       char *sep_pos, *name_pos;
-       char **parts;
+       char *sep_pos, *name_pos, *type_pos;
        size_t length;
 
        /*
@@ -783,15 +644,17 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up)
         * was emitted by libsigrok's VCD output module.
         */
        sr_spew("$scope, got: \"%s\"", contents);
-       parts = g_strsplit_set(contents, " \r\n\t", 0);
-       remove_empty_parts(parts);
-       length = g_strv_length(parts);
-       if (length != 2) {
-               sr_err("Unsupported 'scope' syntax: %s", contents);
-               g_strfreev(parts);
+       type_pos = sr_text_next_word(contents, &contents);
+       if (!type_pos) {
+               sr_err("Cannot parse 'scope' directive");
+               return SR_ERR_DATA;
+       }
+       name_pos = sr_text_next_word(contents, &contents);
+       if (!name_pos || contents) {
+               sr_err("Cannot parse 'scope' directive");
                return SR_ERR_DATA;
        }
-       name_pos = parts[1];
+
        if (strcmp(name_pos, PACKAGE_NAME) == 0) {
                sr_info("Skipping scope with application's package name: %s",
                        name_pos);
@@ -803,7 +666,6 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up)
                g_string_append_printf(inc->scope_prefix,
                        "%s%c%c", name_pos, SCOPE_SEP, '\0');
        }
-       g_strfreev(parts);
        sr_dbg("$scope, prefix now: \"%s\"", inc->scope_prefix->str);
 
        return SR_OK;
@@ -817,8 +679,6 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up)
  */
 static int parse_header_var(struct context *inc, char *contents)
 {
-       char **parts;
-       size_t length;
        char *type, *size_txt, *id, *ref, *idx;
        gboolean is_reg, is_wire, is_real, is_int;
        gboolean is_str;
@@ -830,22 +690,18 @@ static int parse_header_var(struct context *inc, char *contents)
         * Format of $var or $reg header specs:
         * $var type size identifier reference [opt-index] $end
         */
-       parts = g_strsplit_set(contents, " \r\n\t", 0);
-       remove_empty_parts(parts);
-       length = g_strv_length(parts);
-       if (length != 4 && length != 5) {
+       type = sr_text_next_word(contents, &contents);
+       size_txt = sr_text_next_word(contents, &contents);
+       id = sr_text_next_word(contents, &contents);
+       ref = sr_text_next_word(contents, &contents);
+       idx = sr_text_next_word(contents, &contents);
+       if (idx && !*idx)
+               idx = NULL;
+       if (!type || !size_txt || !id || !ref || contents) {
                sr_warn("$var section should have 4 or 5 items");
-               g_strfreev(parts);
                return SR_ERR_DATA;
        }
 
-       type = parts[0];
-       size_txt = parts[1];
-       id = parts[2];
-       ref = parts[3];
-       idx = parts[4];
-       if (idx && !*idx)
-               idx = NULL;
        is_reg = g_strcmp0(type, "reg") == 0;
        is_wire = g_strcmp0(type, "wire") == 0;
        is_real = g_strcmp0(type, "real") == 0;
@@ -861,11 +717,9 @@ static int parse_header_var(struct context *inc, char *contents)
                        id, ref, idx ? idx : "", type);
                inc->ignored_signals = g_slist_append(inc->ignored_signals,
                        g_strdup(id));
-               g_strfreev(parts);
                return SR_OK;
        } else {
                sr_err("Unsupported signal type: '%s'", type);
-               g_strfreev(parts);
                return SR_ERR_DATA;
        }
 
@@ -891,7 +745,6 @@ static int parse_header_var(struct context *inc, char *contents)
        }
        if (!size) {
                sr_warn("Unsupported signal size: '%s'", size_txt);
-               g_strfreev(parts);
                return SR_ERR_DATA;
        }
        if (inc->conv_bits.max_bits < size)
@@ -902,7 +755,6 @@ static int parse_header_var(struct context *inc, char *contents)
                        ref, idx ? idx : "", inc->options.maxchannels);
                inc->ignored_signals = g_slist_append(inc->ignored_signals,
                        g_strdup(id));
-               g_strfreev(parts);
                return SR_OK;
        }
 
@@ -931,7 +783,6 @@ static int parse_header_var(struct context *inc, char *contents)
                vcd_ch->type == SR_CHANNEL_ANALOG ? "A" : "L",
                vcd_ch->array_index);
        inc->channels = g_slist_append(inc->channels, vcd_ch);
-       g_strfreev(parts);
 
        return SR_OK;
 }
@@ -1074,10 +925,8 @@ static void create_channels(const struct sr_input *in,
                if (vcd_ch->type != ch_type)
                        continue;
                cg = NULL;
-               if (vcd_ch->size != 1) {
-                       cg = g_malloc0(sizeof(*cg));
-                       cg->name = g_strdup(vcd_ch->name);
-               }
+               if (vcd_ch->size != 1)
+                       cg = sr_channel_group_new(sdi, vcd_ch->name, NULL);
                for (size_idx = 0; size_idx < vcd_ch->size; size_idx++) {
                        ch_name = get_channel_name(vcd_ch, size_idx);
                        sr_dbg("sigrok channel idx %zu, name %s, type %s, en %d.",
@@ -1089,8 +938,6 @@ static void create_channels(const struct sr_input *in,
                        if (cg)
                                cg->channels = g_slist_append(cg->channels, ch);
                }
-               if (cg)
-                       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
        }
 }
 
@@ -1135,7 +982,7 @@ static void keep_header_for_reread(const struct sr_input *in)
 
        inc = in->priv;
 
-       g_slist_free_full(inc->prev.sr_groups, cg_free);
+       g_slist_free_full(inc->prev.sr_groups, sr_channel_group_free_cb);
        inc->prev.sr_groups = in->sdi->channel_groups;
        in->sdi->channel_groups = NULL;
 
@@ -1174,7 +1021,7 @@ static gboolean check_header_in_reread(const struct sr_input *in)
                return FALSE;
        }
 
-       g_slist_free_full(in->sdi->channel_groups, cg_free);
+       g_slist_free_full(in->sdi->channel_groups, sr_channel_group_free_cb);
        in->sdi->channel_groups = inc->prev.sr_groups;
        inc->prev.sr_groups = NULL;
 
@@ -1189,7 +1036,7 @@ static gboolean check_header_in_reread(const struct sr_input *in)
 static int parse_header(const struct sr_input *in, GString *buf)
 {
        struct context *inc;
-       gboolean status;
+       gboolean enddef_seen, header_valid;
        char *name, *contents;
        size_t size;
        int ret;
@@ -1197,34 +1044,35 @@ static int parse_header(const struct sr_input *in, GString *buf)
        inc = in->priv;
 
        /* Parse sections until complete header was seen. */
-       status = FALSE;
+       enddef_seen = FALSE;
+       header_valid = TRUE;
        name = contents = NULL;
        inc->conv_bits.max_bits = 1;
        while (parse_section(buf, &name, &contents)) {
                sr_dbg("Section '%s', contents '%s'.", name, contents);
 
                if (g_strcmp0(name, "enddefinitions") == 0) {
-                       status = TRUE;
+                       enddef_seen = TRUE;
                        goto done_section;
                }
                if (g_strcmp0(name, "timescale") == 0) {
                        if (parse_timescale(inc, contents) != SR_OK)
-                               status = FALSE;
+                               header_valid = FALSE;
                        goto done_section;
                }
                if (g_strcmp0(name, "scope") == 0) {
                        if (parse_scope(inc, contents, FALSE) != SR_OK)
-                               status = FALSE;
+                               header_valid = FALSE;
                        goto done_section;
                }
                if (g_strcmp0(name, "upscope") == 0) {
                        if (parse_scope(inc, NULL, TRUE) != SR_OK)
-                               status = FALSE;
+                               header_valid = FALSE;
                        goto done_section;
                }
                if (g_strcmp0(name, "var") == 0) {
                        if (parse_header_var(inc, contents) != SR_OK)
-                               status = FALSE;
+                               header_valid = FALSE;
                        goto done_section;
                }
 
@@ -1234,14 +1082,14 @@ done_section:
                g_free(contents);
                contents = NULL;
 
-               if (status)
+               if (enddef_seen)
                        break;
        }
        g_free(name);
        g_free(contents);
 
-       inc->got_header = status;
-       if (!status)
+       inc->got_header = enddef_seen && header_valid;
+       if (!inc->got_header)
                return SR_ERR_DATA;
 
        /* Create sigrok channels here, late, logic before analog. */
@@ -1304,7 +1152,7 @@ static void add_samples(const struct sr_input *in, size_t count, gboolean flush)
        inc = in->priv;
 
        if (inc->logic_count) {
-               feed_queue_logic_submit(inc->feed_logic,
+               feed_queue_logic_submit_one(inc->feed_logic,
                        inc->current_logic, count);
                if (flush)
                        feed_queue_logic_flush(inc->feed_logic);
@@ -1317,7 +1165,7 @@ static void add_samples(const struct sr_input *in, size_t count, gboolean flush)
                if (!q)
                        continue;
                value = inc->current_floats[vcd_ch->array_index];
-               feed_queue_analog_submit(q, value, count);
+               feed_queue_analog_submit_one(q, value, count);
                if (flush)
                        feed_queue_analog_flush(q);
        }
@@ -1609,13 +1457,11 @@ static gboolean vcd_string_valid(const char *s)
 }
 
 /* Parse one text line of the data section. */
-static int parse_textline(const struct sr_input *in, char *lines)
+static int parse_textline(const struct sr_input *in, char *line)
 {
        struct context *inc;
        int ret;
-       char **words;
-       size_t word_count, word_idx;
-       char *curr_word, *next_word, curr_first;
+       char *curr_word, curr_first;
        gboolean is_timestamp, is_section;
        gboolean is_real, is_multibit, is_singlebit, is_string;
        uint64_t timestamp;
@@ -1625,30 +1471,33 @@ static int parse_textline(const struct sr_input *in, char *lines)
        inc = in->priv;
 
        /*
-        * Split the caller's text lines into a list of space separated
-        * words. Note that some of the branches consume the very next
-        * words as well, and assume that both adjacent words will be
-        * available when the first word is seen. This constraint applies
-        * to bit vector data, multi-bit integers and real (float) data,
-        * as well as single-bit data with whitespace before its
-        * identifier (if that's valid in VCD, we'd accept it here).
+        * Consume space separated words from a caller's text line. Note
+        * that many words are self contained, but some require another
+        * word to follow. This implementation assumes that both words
+        * (when involved) become available in the same invocation, that
+        * is that both words reside on the same text line of the file.
         * The fact that callers always pass complete text lines should
-        * make this assumption acceptable.
+        * make this assumption acceptable. No generator is known to
+        * split two corresponding words across text lines.
+        *
+        * This constraint applies to bit vector data, multi-bit integer
+        * and real (float) values, text strings, as well as single-bit
+        * values with whitespace before their identifiers (if that is
+        * valid in VCD, we'd accept it here; if generators don't create
+        * such input, then support for it does not harm).
         */
        ret = SR_OK;
-       words = split_text_line(inc, lines, &word_count);
-       for (word_idx = 0; word_idx < word_count; word_idx++) {
+       while (line) {
                /*
-                * Make the next two words available, to simpilify code
-                * paths below. The second word is optional here.
+                * Lookup one word here which is mandatory. Locations
+                * below conditionally lookup another word as needed.
                 */
-               curr_word = words[word_idx];
-               if (!curr_word && !curr_word[0])
+               curr_word = sr_text_next_word(line, &line);
+               if (!curr_word)
+                       break;
+               if (!*curr_word)
                        continue;
                curr_first = g_ascii_tolower(curr_word[0]);
-               next_word = words[word_idx + 1];
-               if (next_word && !next_word[0])
-                       next_word = NULL;
 
                /*
                 * Optionally skip some sections that can be interleaved
@@ -1831,8 +1680,7 @@ static int parse_textline(const struct sr_input *in, char *lines)
                        float real_val;
 
                        real_text = &curr_word[1];
-                       identifier = next_word;
-                       word_idx++;
+                       identifier = sr_text_next_word(line, &line);
                        if (!*real_text || !identifier || !*identifier) {
                                sr_err("Unexpected real format.");
                                ret = SR_ERR_DATA;
@@ -1866,8 +1714,7 @@ static int parse_textline(const struct sr_input *in, char *lines)
                         * we may never unify code paths at all here.
                         */
                        bits_text = &curr_word[1];
-                       identifier = next_word;
-                       word_idx++;
+                       identifier = sr_text_next_word(line, &line);
 
                        if (!*bits_text || !identifier || !*identifier) {
                                sr_err("Unexpected integer/vector format.");
@@ -1952,10 +1799,8 @@ static int parse_textline(const struct sr_input *in, char *lines)
                                break;
                        }
                        identifier = ++bits_text;
-                       if (!*identifier) {
-                               identifier = next_word;
-                               word_idx++;
-                       }
+                       if (!*identifier)
+                               identifier = sr_text_next_word(line, &line);
                        if (!identifier || !*identifier) {
                                sr_err("Identifier missing.");
                                ret = SR_ERR_DATA;
@@ -1977,8 +1822,7 @@ static int parse_textline(const struct sr_input *in, char *lines)
                        const char *str_value;
 
                        str_value = &curr_word[1];
-                       identifier = next_word;
-                       word_idx++;
+                       identifier = sr_text_next_word(line, &line);
                        if (!vcd_string_valid(str_value)) {
                                sr_err("Invalid string data: %s", str_value);
                                ret = SR_ERR_DATA;
@@ -2005,7 +1849,6 @@ static int parse_textline(const struct sr_input *in, char *lines)
                ret = SR_ERR_DATA;
                break;
        }
-       free_text_split(inc, words);
 
        return ret;
 }
@@ -2016,11 +1859,14 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
        uint64_t samplerate;
        GVariant *gvar;
        int ret;
-       char *rdptr, *endptr, *trimptr;
-       size_t rdlen;
+       char *rdptr, *line;
+       size_t taken, rdlen;
 
        inc = in->priv;
 
+       if (!inc->got_header)
+               return SR_ERR_DATA;
+
        /* Send feed header and samplerate (once) before sample data. */
        if (!inc->started) {
                std_session_send_df_header(in->sdi);
@@ -2045,28 +1891,19 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
        /* Find and process complete text lines in the input data. */
        ret = SR_OK;
        rdptr = in->buf->str;
-       while (TRUE) {
+       taken = 0;
+       while (rdptr) {
                rdlen = &in->buf->str[in->buf->len] - rdptr;
-               endptr = g_strstr_len(rdptr, rdlen, "\n");
-               if (!endptr)
+               line = sr_text_next_line(rdptr, rdlen, &rdptr, &taken);
+               if (!line)
                        break;
-               trimptr = endptr;
-               *endptr++ = '\0';
-               while (g_ascii_isspace(*rdptr))
-                       rdptr++;
-               while (trimptr > rdptr && g_ascii_isspace(trimptr[-1]))
-                       *(--trimptr) = '\0';
-               if (!*rdptr) {
-                       rdptr = endptr;
+               if (!*line)
                        continue;
-               }
-               ret = parse_textline(in, rdptr);
-               rdptr = endptr;
+               ret = parse_textline(in, line);
                if (ret != SR_OK)
                        break;
        }
-       rdlen = rdptr - in->buf->str;
-       g_string_erase(in->buf, 0, rdlen);
+       g_string_erase(in->buf, 0, taken);
 
        return ret;
 }
@@ -2181,11 +2018,14 @@ static int end(struct sr_input *in)
                ret = SR_OK;
 
        /* Flush most recently queued sample data when EOF is seen. */
-       count = inc->data_after_timestamp ? 1 : 0;
-       add_samples(in, count, TRUE);
+       if (inc->got_header && ret == SR_OK) {
+               count = inc->data_after_timestamp ? 1 : 0;
+               add_samples(in, count, TRUE);
+       }
 
        /* Optionally suggest downsampling after all input data was seen. */
-       (void)ts_stats_post(inc, !inc->data_after_timestamp);
+       if (inc->got_header)
+               (void)ts_stats_post(inc, !inc->data_after_timestamp);
 
        /* Must send DF_END when DF_HEADER was sent before. */
        if (inc->started)
@@ -2216,7 +2056,6 @@ static void cleanup(struct sr_input *in)
        inc->scope_prefix = NULL;
        g_slist_free_full(inc->ignored_signals, g_free);
        inc->ignored_signals = NULL;
-       free_text_split(inc, NULL);
 }
 
 static int reset(struct sr_input *in)
index a6369f4d4bd60778035e3d4597a22c45334bb774..0675ec8f4088216d8cdf572ce168f60b5f25c0ad 100644 (file)
@@ -51,7 +51,7 @@ struct context {
        int num_channels;
        int unitsize;
        gboolean found_data;
-       gboolean create_channels;
+       GSList *prev_sr_channels;
 };
 
 static int parse_wav_header(GString *buf, struct context *inc)
@@ -151,15 +151,10 @@ static int format_match(GHashTable *metadata, unsigned int *confidence)
 
 static int init(struct sr_input *in, GHashTable *options)
 {
-       struct context *inc;
-
        (void)options;
 
        in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
        in->priv = g_malloc0(sizeof(struct context));
-       inc = in->priv;
-
-       inc->create_channels = TRUE;
 
        return SR_OK;
 }
@@ -306,6 +301,44 @@ static int process_buffer(struct sr_input *in)
        return SR_OK;
 }
 
+/*
+ * Check the channel list for consistency across file re-import. See
+ * the VCD input module for more details and motivation.
+ */
+
+static void keep_header_for_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       inc = in->priv;
+       g_slist_free_full(inc->prev_sr_channels, sr_channel_free_cb);
+       inc->prev_sr_channels = in->sdi->channels;
+       in->sdi->channels = NULL;
+}
+
+static int check_header_in_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       if (!in)
+               return FALSE;
+       inc = in->priv;
+       if (!inc)
+               return FALSE;
+       if (!inc->prev_sr_channels)
+               return TRUE;
+
+       if (sr_channel_lists_differ(inc->prev_sr_channels, in->sdi->channels)) {
+               sr_err("Channel list change not supported for file re-read.");
+               return FALSE;
+       }
+       g_slist_free_full(in->sdi->channels, sr_channel_free_cb);
+       in->sdi->channels = inc->prev_sr_channels;
+       inc->prev_sr_channels = NULL;
+
+       return TRUE;
+}
+
 static int receive(struct sr_input *in, GString *buf)
 {
        struct context *inc;
@@ -330,14 +363,12 @@ static int receive(struct sr_input *in, GString *buf)
                else if (ret != SR_OK)
                        return ret;
 
-               if (inc->create_channels) {
-                       for (int i = 0; i < inc->num_channels; i++) {
-                               snprintf(channelname, sizeof(channelname), "CH%d", i + 1);
-                               sr_channel_new(in->sdi, i, SR_CHANNEL_ANALOG, TRUE, channelname);
-                       }
+               for (int i = 0; i < inc->num_channels; i++) {
+                       snprintf(channelname, sizeof(channelname), "CH%d", i + 1);
+                       sr_channel_new(in->sdi, i, SR_CHANNEL_ANALOG, TRUE, channelname);
                }
-
-               inc->create_channels = FALSE;
+               if (!check_header_in_reread(in))
+                       return SR_ERR_DATA;
 
                /* sdi is ready, notify frontend. */
                in->sdi_ready = TRUE;
@@ -368,12 +399,18 @@ static int end(struct sr_input *in)
 
 static int reset(struct sr_input *in)
 {
-       memset(in->priv, 0, sizeof(struct context));
+       struct context *inc;
+
+       inc = in->priv;
+       memset(inc, 0, sizeof(*inc));
 
        /*
-        * We only want to create the sigrok channels once, so
-        * inc->create_channels won't be set to TRUE this time around.
+        * Create, and re-create channels for every iteration of file
+        * import. Other logic will enforce a consistent set of channels
+        * across re-import, or an appropriate error message when file
+        * properties should change.
         */
+       keep_header_for_reread(in);
 
        g_string_truncate(in->buf, 0);
 
index a2350e02ddf3aed2c48c89bcab2ac8eb8f59dfae..68a45f27766160ebc092d561e678a39eabbf0547 100644 (file)
@@ -172,6 +172,23 @@ static inline uint32_t read_u24le(const uint8_t *p)
        return u;
 }
 
+/**
+ * Read a 24 bits big endian unsigned integer out of memory.
+ * @param x a pointer to the input memory
+ * @return the corresponding unsigned integer
+ */
+static inline uint32_t read_u24be(const uint8_t *p)
+{
+       uint32_t u;
+
+       u = 0;
+       u <<= 8; u |= p[0];
+       u <<= 8; u |= p[1];
+       u <<= 8; u |= p[2];
+
+       return u;
+}
+
 /**
  * Read a 32 bits big endian unsigned integer out of memory.
  * @param x a pointer to the input memory
@@ -450,6 +467,19 @@ static inline void write_u16le(uint8_t *p, uint16_t x)
 }
 #define WL16(p, x) write_u16le((uint8_t *)(p), (uint16_t)(x))
 
+/**
+ * Write a 24 bits unsigned integer to memory stored as little endian.
+ * @param p a pointer to the output memory
+ * @param x the input unsigned integer
+ */
+static inline void write_u24le(uint8_t *p, uint32_t x)
+{
+       p[0] = x & 0xff; x >>= 8;
+       p[1] = x & 0xff; x >>= 8;
+       p[2] = x & 0xff; x >>= 8;
+}
+#define WL24(p, x) write_u24le((uint8_t *)(p), (uint32_t)(x))
+
 /**
  * Write a 32 bits unsigned integer to memory stored as big endian.
  * @param p a pointer to the output memory
@@ -478,6 +508,21 @@ static inline void write_u32le(uint8_t *p, uint32_t x)
 }
 #define WL32(p, x) write_u32le((uint8_t *)(p), (uint32_t)(x))
 
+/**
+ * Write a 40 bits unsigned integer to memory stored as little endian.
+ * @param p a pointer to the output memory
+ * @param x the input unsigned integer
+ */
+static inline void write_u40le(uint8_t *p, uint64_t x)
+{
+       p[0] = x & 0xff; x >>= 8;
+       p[1] = x & 0xff; x >>= 8;
+       p[2] = x & 0xff; x >>= 8;
+       p[3] = x & 0xff; x >>= 8;
+       p[4] = x & 0xff; x >>= 8;
+}
+#define WL40(p, x) write_u40le((uint8_t *)(p), (uint64_t)(x))
+
 /**
  * Write a 48 bits unsigned integer to memory stored as little endian.
  * @param p a pointer to the output memory
@@ -587,6 +632,30 @@ static inline uint8_t read_u8_inc(const uint8_t **p)
        return v;
 }
 
+/**
+ * Read unsigned 8bit integer, check length, increment read position.
+ * @param[in, out] p Pointer into byte stream.
+ * @param[in, out] l Remaining input payload length.
+ * @return Retrieved integer value, unsigned.
+ */
+static inline uint8_t read_u8_inc_len(const uint8_t **p, size_t *l)
+{
+       uint8_t v;
+
+       if (!p || !*p)
+               return 0;
+       if (l && *l < sizeof(v)) {
+               *l = 0;
+               return 0;
+       }
+       v = read_u8(*p);
+       *p += sizeof(v);
+       if (l)
+               *l -= sizeof(v);
+
+       return v;
+}
+
 /**
  * Read signed 8bit integer from raw memory, increment read position.
  * @param[in, out] p Pointer into byte stream.
@@ -638,6 +707,30 @@ static inline uint16_t read_u16le_inc(const uint8_t **p)
        return v;
 }
 
+/**
+ * Read unsigned 16bit integer (LE format), check length, increment position.
+ * @param[in, out] p Pointer into byte stream.
+ * @param[in, out] l Remaining input payload length.
+ * @return Retrieved integer value, unsigned.
+ */
+static inline uint16_t read_u16le_inc_len(const uint8_t **p, size_t *l)
+{
+       uint16_t v;
+
+       if (!p || !*p)
+               return 0;
+       if (l && *l < sizeof(v)) {
+               *l = 0;
+               return 0;
+       }
+       v = read_u16le(*p);
+       *p += sizeof(v);
+       if (l)
+               *l -= sizeof(v);
+
+       return v;
+}
+
 /**
  * Read signed 16bit integer from raw memory (big endian format), increment read position.
  * @param[in, out] p Pointer into byte stream.
@@ -898,6 +991,19 @@ static inline void write_u16le_inc(uint8_t **p, uint16_t x)
        *p += sizeof(x);
 }
 
+/**
+ * Write unsigned 24bit liggle endian integer to raw memory, increment write position.
+ * @param[in, out] p Pointer into byte stream.
+ * @param[in] x Value to write.
+ */
+static inline void write_u24le_inc(uint8_t **p, uint32_t x)
+{
+       if (!p || !*p)
+               return;
+       write_u24le(*p, x);
+       *p += 3 * sizeof(uint8_t);
+}
+
 /**
  * Write unsigned 32bit big endian integer to raw memory, increment write position.
  * @param[in, out] p Pointer into byte stream.
@@ -924,6 +1030,19 @@ static inline void write_u32le_inc(uint8_t **p, uint32_t x)
        *p += sizeof(x);
 }
 
+/**
+ * Write unsigned 40bit little endian integer to raw memory, increment write position.
+ * @param[in, out] p Pointer into byte stream.
+ * @param[in] x Value to write.
+ */
+static inline void write_u40le_inc(uint8_t **p, uint64_t x)
+{
+       if (!p || !*p)
+               return;
+       write_u40le(*p, x);
+       *p += 5 * sizeof(uint8_t);
+}
+
 /**
  * Write unsigned 48bit little endian integer to raw memory, increment write position.
  * @param[in, out] p Pointer into byte stream.
@@ -1469,6 +1588,13 @@ struct sr_usb_dev_inst {
 };
 #endif
 
+/** Raw TCP device instance. */
+struct sr_tcp_dev_inst {
+       char *host_addr;        /**!< IP address or host name */
+       char *tcp_port;         /**!< TCP port number/name */
+       int sock_fd;            /**!< TCP socket's file descriptor */
+};
+
 struct sr_serial_dev_inst;
 #ifdef HAVE_SERIAL_COMM
 struct ser_lib_functions;
@@ -1518,6 +1644,9 @@ struct sr_serial_dev_inst {
                SER_BT_CONN_BLE122,     /**!< BLE, BLE122 module, indications */
                SER_BT_CONN_NRF51,      /**!< BLE, Nordic nRF51, notifications */
                SER_BT_CONN_CC254x,     /**!< BLE, TI CC254x, notifications */
+               SER_BT_CONN_AC6328,     /**!< BLE, JL AC6328B, notifications */
+               SER_BT_CONN_DIALOG,     /**!< BLE, dialog DA14580, notifications */
+               SER_BT_CONN_NOTIFY,     /**!< BLE, generic notifications */
                SER_BT_CONN_MAX,        /**!< sentinel */
        } bt_conn_type;
        char *bt_addr_local;
@@ -1527,9 +1656,11 @@ struct sr_serial_dev_inst {
        uint16_t bt_notify_handle_write;
        uint16_t bt_notify_handle_cccd;
        uint16_t bt_notify_value_cccd;
+       uint16_t bt_ble_mtu;
        struct sr_bt_desc *bt_desc;
        GSList *bt_source_args;
 #endif
+       struct sr_tcp_dev_inst *tcp_dev;
 };
 #endif
 
@@ -1547,17 +1678,20 @@ struct drv_context {
 
 /*--- log.c -----------------------------------------------------------------*/
 
+/* Provide a macro for other source code locations to re-use. */
 #if defined(_WIN32) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4))
 /*
  * On MinGW, we need to specify the gnu_printf format flavor or GCC
  * will assume non-standard Microsoft printf syntax.
  */
-SR_PRIV int sr_log(int loglevel, const char *format, ...)
-               __attribute__((__format__ (__gnu_printf__, 2, 3)));
+#define ATTR_FMT_PRINTF(fmt_pos, arg_pos) \
+               __attribute__((__format__ (__gnu_printf__, fmt_pos, arg_pos)))
 #else
-SR_PRIV int sr_log(int loglevel, const char *format, ...) G_GNUC_PRINTF(2, 3);
+#define ATTR_FMT_PRINTF(fmt_pos, arg_pos)      G_GNUC_PRINTF(fmt_pos, arg_pos)
 #endif
 
+SR_PRIV int sr_log(int loglevel, const char *format, ...) ATTR_FMT_PRINTF(2, 3);
+
 /* Message logging helpers with subsystem-specific prefix string. */
 #define sr_spew(...)   sr_log(SR_LOG_SPEW, LOG_PREFIX ": " __VA_ARGS__)
 #define sr_dbg(...)    sr_log(SR_LOG_DBG,  LOG_PREFIX ": " __VA_ARGS__)
@@ -1591,6 +1725,11 @@ SR_PRIV struct sr_channel *sr_next_enabled_channel(const struct sr_dev_inst *sdi
 SR_PRIV gboolean sr_channels_differ(struct sr_channel *ch1, struct sr_channel *ch2);
 SR_PRIV gboolean sr_channel_lists_differ(GSList *l1, GSList *l2);
 
+SR_PRIV struct sr_channel_group *sr_channel_group_new(struct sr_dev_inst *sdi,
+       const char *name, void *priv);
+SR_PRIV void sr_channel_group_free(struct sr_channel_group *cg);
+SR_PRIV void sr_channel_group_free_cb(void *cg);
+
 /** Device instance data */
 struct sr_dev_inst {
        /** Device driver. */
@@ -1629,6 +1768,7 @@ SR_PRIV void sr_dev_inst_free(struct sr_dev_inst *sdi);
 SR_PRIV struct sr_usb_dev_inst *sr_usb_dev_inst_new(uint8_t bus,
                uint8_t address, struct libusb_device_handle *hdl);
 SR_PRIV void sr_usb_dev_inst_free(struct sr_usb_dev_inst *usb);
+SR_PRIV void sr_usb_dev_inst_free_cb(gpointer p); /* Glib wrapper. */
 #endif
 
 #ifdef HAVE_SERIAL_COMM
@@ -1983,6 +2123,8 @@ SR_PRIV int ser_name_is_hid(struct sr_serial_dev_inst *serial);
 extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_hid;
 SR_PRIV int ser_name_is_bt(struct sr_serial_dev_inst *serial);
 extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_bt;
+SR_PRIV int ser_name_is_tcpraw(struct sr_serial_dev_inst *serial);
+extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_tcpraw;
 
 #ifdef HAVE_LIBHIDAPI
 struct vid_pid_item {
@@ -2036,7 +2178,8 @@ SR_PRIV int sr_bt_config_addr_remote(struct sr_bt_desc *desc, const char *addr);
 SR_PRIV int sr_bt_config_rfcomm(struct sr_bt_desc *desc, size_t channel);
 SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc,
        uint16_t read_handle, uint16_t write_handle,
-       uint16_t cccd_handle, uint16_t cccd_value);
+       uint16_t cccd_handle, uint16_t cccd_value,
+       uint16_t ble_mtu);
 
 SR_PRIV int sr_bt_scan_le(struct sr_bt_desc *desc, int duration);
 SR_PRIV int sr_bt_scan_bt(struct sr_bt_desc *desc, int duration);
@@ -2066,6 +2209,8 @@ SR_PRIV int ezusb_upload_firmware(struct sr_context *ctx, libusb_device *dev,
 
 /*--- usb.c -----------------------------------------------------------------*/
 
+SR_PRIV int sr_usb_split_conn(const char *conn,
+       uint16_t *vid, uint16_t *pid, uint8_t *bus, uint8_t *addr);
 #ifdef HAVE_LIBUSB_1_0
 SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn);
 SR_PRIV int sr_usb_open(libusb_context *usb_ctx, struct sr_usb_dev_inst *usb);
@@ -2078,6 +2223,27 @@ SR_PRIV gboolean usb_match_manuf_prod(libusb_device *dev,
                const char *manufacturer, const char *product);
 #endif
 
+/*--- tcp.c -----------------------------------------------------------------*/
+
+SR_PRIV gboolean sr_fd_is_readable(int fd);
+
+SR_PRIV struct sr_tcp_dev_inst *sr_tcp_dev_inst_new(
+       const char *host_addr, const char *tcp_port);
+SR_PRIV void sr_tcp_dev_inst_free(struct sr_tcp_dev_inst *tcp);
+SR_PRIV int sr_tcp_get_port_path(struct sr_tcp_dev_inst *tcp,
+       const char *prefix, char separator, char *path, size_t path_len);
+SR_PRIV int sr_tcp_connect(struct sr_tcp_dev_inst *tcp);
+SR_PRIV int sr_tcp_disconnect(struct sr_tcp_dev_inst *tcp);
+SR_PRIV int sr_tcp_write_bytes(struct sr_tcp_dev_inst *tcp,
+       const uint8_t *data, size_t dlen);
+SR_PRIV int sr_tcp_read_bytes(struct sr_tcp_dev_inst *tcp,
+       uint8_t *data, size_t dlen, gboolean nonblocking);
+SR_PRIV int sr_tcp_source_add(struct sr_session *session,
+       struct sr_tcp_dev_inst *tcp, int events, int timeout,
+       sr_receive_data_callback cb, void *cb_data);
+SR_PRIV int sr_tcp_source_remove(struct sr_session *session,
+       struct sr_tcp_dev_inst *tcp);
+
 /*--- binary_helpers.c ------------------------------------------------------*/
 
 /** Binary value type */
@@ -2085,68 +2251,47 @@ enum binary_value_type {
        BVT_INVALID,
 
        BVT_UINT8,
-       BVT_BE_UINT8 = BVT_UINT8,
-       BVT_LE_UINT8 = BVT_UINT8,
 
        BVT_BE_UINT16,
+       BVT_BE_UINT24,
        BVT_BE_UINT32,
-       BVT_BE_UINT64,
-       BVT_BE_FLOAT,
 
        BVT_LE_UINT16,
+       BVT_LE_UINT24,
        BVT_LE_UINT32,
-       BVT_LE_UINT64,
-       BVT_LE_FLOAT,
 };
 
 /** Binary value specification */
 struct binary_value_spec {
-       /** Offset into binary blob */
-       size_t offset;
-       /** Data type to decode */
-       enum binary_value_type type;
-       /** Scale factor to get native units */
-       float scale;
-};
-
-/** Binary channel definition */
-struct binary_analog_channel {
-       /** Channel name */
-       const char *name;
-       /** Binary value in data stream */
-       struct binary_value_spec spec;
-       /** Significant digits */
-       int digits;
-       /** Measured quantity */
-       enum sr_mq mq;
-       /** Measured unit */
-       enum sr_unit unit;
+       size_t offset;                  /**!< Offset into binary image */
+       enum binary_value_type type;    /**!< Data type to decode */
 };
 
 /**
- * Read extract a value from a binary blob.
+ * Read extract a value from a binary data image, ensuring no out-of-bounds
+ * read happens.
+ *
+ * @param[out] out Pointer to output buffer (conversion result)
+ * @param[in] spec Binary value specification
+ * @param[in] data Pointer to binary input data
+ * @param[in] length Size of binary input data
  *
- * @param out Pointer to output buffer.
- * @param spec Binary value specification
- * @param data Pointer to binary blob
- * @param length Size of binary blob
  * @return SR_OK on success, SR_ERR_* error code on failure.
  */
-SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const void *data, size_t length);
+SR_PRIV int bv_get_value_len(float *out, const struct binary_value_spec *spec,
+       const uint8_t *data, size_t length);
 
 /**
- * Send an analog channel packet based on a binary analog channel
- * specification.
+ * Read extract a value from a binary data image, without bound check.
+ *
+ * @param[out] out Pointer to output buffer (conversion result)
+ * @param[in] spec Binary value specification
+ * @param[in] data Pointer to binary input data
  *
- * @param sdi Device instance
- * @param ch Sigrok channel
- * @param spec Channel specification
- * @param data Pointer to binary blob
- * @param length Size of binary blob
  * @return SR_OK on success, SR_ERR_* error code on failure.
  */
-SR_PRIV int bv_send_analog_channel(const struct sr_dev_inst *sdi, struct sr_channel *ch,
-                                  const struct binary_analog_channel *spec, const void *data, size_t length);
+SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec,
+       const uint8_t *data);
 
 /*--- crc.c -----------------------------------------------------------------*/
 
@@ -2709,8 +2854,10 @@ struct feed_queue_analog;
 SR_API struct feed_queue_logic *feed_queue_logic_alloc(
        const struct sr_dev_inst *sdi,
        size_t sample_count, size_t unit_size);
-SR_API int feed_queue_logic_submit(struct feed_queue_logic *q,
-       const uint8_t *data, size_t count);
+SR_API int feed_queue_logic_submit_one(struct feed_queue_logic *q,
+       const uint8_t *data, size_t repeat_count);
+SR_API int feed_queue_logic_submit_many(struct feed_queue_logic *q,
+       const uint8_t *data, size_t samples_count);
 SR_API int feed_queue_logic_flush(struct feed_queue_logic *q);
 SR_API int feed_queue_logic_send_trigger(struct feed_queue_logic *q);
 SR_API void feed_queue_logic_free(struct feed_queue_logic *q);
@@ -2718,8 +2865,12 @@ SR_API void feed_queue_logic_free(struct feed_queue_logic *q);
 SR_API struct feed_queue_analog *feed_queue_analog_alloc(
        const struct sr_dev_inst *sdi,
        size_t sample_count, int digits, struct sr_channel *ch);
-SR_API int feed_queue_analog_submit(struct feed_queue_analog *q,
-       float data, size_t count);
+SR_API int feed_queue_analog_mq_unit(struct feed_queue_analog *q,
+       enum sr_mq mq, enum sr_mqflag mq_flag, enum sr_unit unit);
+SR_API int feed_queue_analog_scale_offset(struct feed_queue_analog *q,
+       const struct sr_rational *scale, const struct sr_rational *offset);
+SR_API int feed_queue_analog_submit_one(struct feed_queue_analog *q,
+       float data, size_t repeat_count);
 SR_API int feed_queue_analog_flush(struct feed_queue_analog *q);
 SR_API void feed_queue_analog_free(struct feed_queue_analog *q);
 
index 701df645e76cf296dfe7c760368704668575a2d5..1b01afee3368df65c02e2c847e219c59d17c16b8 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -184,16 +184,24 @@ SR_API int sr_log_callback_get(sr_log_callback *cb, void **cb_data)
 
 static int sr_logv(void *cb_data, int loglevel, const char *format, va_list args)
 {
+       int ret;
        uint64_t elapsed_us, minutes;
        unsigned int rest_us, seconds, microseconds;
-       char *raw_output, *output;
-       int raw_len, raw_idx, idx, ret;
+       char *raw_output, *output, c;
+       ssize_t print_len;
+       size_t raw_len;
+       const char *raw_ptr;
+       char *out_ptr;
 
        /* This specific log callback doesn't need the void pointer data. */
        (void)cb_data;
 
        (void)loglevel;
 
+       /* Prefix with 'sr:'. Optionally prefix with timestamp. */
+       ret = fputs("sr: ", stderr);
+       if (ret < 0)
+               return SR_ERR;
        if (cur_loglevel >= LOGLEVEL_TIMESTAMP) {
                elapsed_us = g_get_monotonic_time() - sr_log_start_time;
 
@@ -202,30 +210,41 @@ static int sr_logv(void *cb_data, int loglevel, const char *format, va_list args
                seconds = rest_us / G_TIME_SPAN_SECOND;
                microseconds = rest_us % G_TIME_SPAN_SECOND;
 
-               ret = g_fprintf(stderr, "sr: [%.2" PRIu64 ":%.2u.%.6u] ",
+               ret = g_fprintf(stderr, "[%.2" PRIu64 ":%.2u.%.6u] ",
                                minutes, seconds, microseconds);
-       } else {
-               ret = fputs("sr: ", stderr);
+               if (ret < 0)
+                       return SR_ERR;
        }
 
-       if (ret < 0 || (raw_len = g_vasprintf(&raw_output, format, args)) < 0)
+       /* Print the caller's message into a local buffer. */
+       raw_output = NULL;
+       print_len = g_vasprintf(&raw_output, format, args);
+       if (print_len < 0) {
+               g_free(raw_output);
                return SR_ERR;
+       }
+       raw_len = (size_t)print_len;
 
-       output = g_malloc0(raw_len + 1);
-
-       /* Copy the string without any unwanted newlines. */
-       raw_idx = idx = 0;
-       while (raw_idx < raw_len) {
-               if (raw_output[raw_idx] != '\n') {
-                       output[idx] = raw_output[raw_idx];
-                       idx++;
-               }
-               raw_idx++;
+       /* Copy the string. Strip unwanted line breaks. */
+       output = g_malloc(raw_len + 1);
+       if (!output) {
+               g_free(raw_output);
+               return SR_ERR;
        }
+       out_ptr = output;
+       raw_ptr = raw_output;
+       while (*raw_ptr) {
+               c = *raw_ptr++;
+               if (c == '\r' || c == '\n')
+                       continue;
+               *out_ptr++ = c;
+       }
+       *out_ptr = '\0';
+       g_free(raw_output);
 
+       /* Print the trimmed output text. */
        g_fprintf(stderr, "%s\n", output);
        fflush(stderr);
-       g_free(raw_output);
        g_free(output);
 
        return SR_OK;
@@ -241,6 +260,10 @@ SR_PRIV int sr_log(int loglevel, const char *format, ...)
        if (loglevel > cur_loglevel)
                return SR_OK;
 
+       /* Silently succeed when no logging callback is registered. */
+       if (!sr_log_cb)
+               return SR_OK;
+
        va_start(args, format);
        ret = sr_log_cb(sr_log_cb_data, loglevel, format, args);
        va_end(args);
index 848595f9d3096b9570fcd0e5505e2d1026ea1250..c4801ef7f984bca96b432fda2eb37c55fd9da730 100644 (file)
@@ -252,11 +252,13 @@ static GString *gen_header(const struct sr_output *o,
        /* Some metadata */
        if (ctx->header && !ctx->did_header) {
                /* save_gnuplot knows how many lines we print. */
+               time_t secs;
+               secs = hdr->starttime.tv_sec;
                g_string_append_printf(header,
                        "%s CSV generated by %s %s\n%s from %s on %s",
                        ctx->comment, PACKAGE_NAME,
                        sr_package_version_string_get(), ctx->comment,
-                       ctx->title, ctime(&hdr->starttime.tv_sec));
+                       ctx->title, ctime(&secs));
 
                /* Columns / channels */
                channels = o->sdi ? o->sdi->channels : NULL;
index 28223c7c6eca6a72524ae31c83a7a8f92ffd4415..3be0bccb29f3cd316095ad48d3895d570e4f6381 100644 (file)
@@ -38,7 +38,7 @@ struct out_context {
        size_t analog_ch_count;
        gint *analog_index_map;
        struct logic_buff {
-               size_t unit_size;
+               size_t zip_unit_size;
                size_t alloc_size;
                uint8_t *samples;
                size_t fill_size;
@@ -204,14 +204,14 @@ static int zip_create(const struct sr_output *o)
         * during execution. This simplifies other locations.
         */
        alloc_size = CHUNK_SIZE;
-       outc->logic_buff.unit_size = logic_channels;
-       outc->logic_buff.unit_size += 8 - 1;
-       outc->logic_buff.unit_size /= 8;
+       outc->logic_buff.zip_unit_size = logic_channels;
+       outc->logic_buff.zip_unit_size += 8 - 1;
+       outc->logic_buff.zip_unit_size /= 8;
        outc->logic_buff.samples = g_try_malloc0(alloc_size);
        if (!outc->logic_buff.samples)
                return SR_ERR_MALLOC;
-       if (outc->logic_buff.unit_size)
-               alloc_size /= outc->logic_buff.unit_size;
+       if (outc->logic_buff.zip_unit_size)
+               alloc_size /= outc->logic_buff.zip_unit_size;
        outc->logic_buff.alloc_size = alloc_size;
        outc->logic_buff.fill_size = 0;
 
@@ -396,19 +396,61 @@ static int zip_append(const struct sr_output *o,
  * @returns SR_OK et al error codes.
  */
 static int zip_append_queue(const struct sr_output *o,
-       uint8_t *buf, size_t unitsize, size_t length, gboolean flush)
+       const uint8_t *buf, size_t feed_unitsize, size_t length,
+       gboolean flush)
 {
+       static gboolean sizes_seen;
+
        struct out_context *outc;
        struct logic_buff *buff;
-       size_t send_size, remain, copy_size;
-       uint8_t *wrptr, *rdptr;
+       size_t sample_copy_size, sample_skip_size, sample_pad_size;
+       size_t send_count, remain, copy_count;
+       const uint8_t *rdptr;
+       uint8_t *wrptr;
        int ret;
 
+       /*
+        * Check input parameters. Prepare to either grab data as is,
+        * or to adjust between differing input and output unit sizes.
+        * Diagnostics is rate limited for improved usability, assumes
+        * that session feeds are consistent across calls. Processing
+        * would cope with inconsistent calls though when required.
+        */
        outc = o->priv;
        buff = &outc->logic_buff;
-       if (length && unitsize != buff->unit_size) {
-               sr_warn("Unexpected unit size, discarding logic data.");
-               return SR_ERR_ARG;
+       if (length) {
+               if (!sizes_seen) {
+                       sr_info("output unit size %zu, feed unit size %zu.",
+                               buff->zip_unit_size, feed_unitsize);
+               }
+               if (feed_unitsize > buff->zip_unit_size) {
+                       if (!sizes_seen)
+                               sr_info("Large unit size, discarding excess logic data.");
+                       sample_copy_size = buff->zip_unit_size;
+                       sample_skip_size = feed_unitsize - buff->zip_unit_size;
+                       sample_pad_size = 0;
+               } else if (feed_unitsize < buff->zip_unit_size) {
+                       if (!sizes_seen)
+                               sr_info("Small unit size, padding logic data.");
+                       sample_copy_size = feed_unitsize;
+                       sample_skip_size = 0;
+                       sample_pad_size = buff->zip_unit_size - feed_unitsize;
+               } else {
+                       if (!sizes_seen)
+                               sr_dbg("Matching unit size, passing logic data as is.");
+                       sample_copy_size = buff->zip_unit_size;
+                       sample_skip_size = 0;
+                       sample_pad_size = 0;
+               }
+               if (sample_copy_size + sample_skip_size != feed_unitsize) {
+                       sr_err("Inconsistent input unit size. Implementation flaw?");
+                       return SR_ERR_BUG;
+               }
+               if (sample_copy_size + sample_pad_size != buff->zip_unit_size) {
+                       sr_err("Inconsistent output unit size. Implementation flaw?");
+                       return SR_ERR_BUG;
+               }
+               sizes_seen = TRUE;
        }
 
        /*
@@ -416,32 +458,39 @@ static int zip_append_queue(const struct sr_output *o,
         * Flush to the ZIP archive when the buffer space is exhausted.
         */
        rdptr = buf;
-       send_size = buff->unit_size ? length / buff->unit_size : 0;
-       while (send_size) {
+       send_count = feed_unitsize ? length / feed_unitsize : 0;
+       while (send_count) {
                remain = buff->alloc_size - buff->fill_size;
+               wrptr = &buff->samples[buff->fill_size * buff->zip_unit_size];
                if (remain) {
-                       wrptr = &buff->samples[buff->fill_size * buff->unit_size];
-                       copy_size = MIN(send_size, remain);
-                       send_size -= copy_size;
-                       buff->fill_size += copy_size;
-                       memcpy(wrptr, rdptr, copy_size * buff->unit_size);
-                       rdptr += copy_size * buff->unit_size;
-                       remain -= copy_size;
+                       copy_count = MIN(send_count, remain);
+                       if (sample_skip_size || sample_pad_size)
+                               copy_count = 1;
+                       send_count -= copy_count;
+                       buff->fill_size += copy_count;
+                       memcpy(wrptr, rdptr, copy_count * sample_copy_size);
+                       if (sample_pad_size) {
+                               wrptr += sample_copy_size;
+                               memset(wrptr, 0, sample_pad_size);
+                       }
+                       rdptr += copy_count * sample_copy_size;
+                       if (sample_skip_size)
+                               rdptr += sample_skip_size;
+                       remain -= copy_count;
                }
-               if (send_size && !remain) {
-                       ret = zip_append(o, buff->samples, buff->unit_size,
-                               buff->fill_size * buff->unit_size);
+               if (send_count && !remain) {
+                       ret = zip_append(o, buff->samples, buff->zip_unit_size,
+                               buff->fill_size * buff->zip_unit_size);
                        if (ret != SR_OK)
                                return ret;
                        buff->fill_size = 0;
-                       remain = buff->alloc_size - buff->fill_size;
                }
        }
 
        /* Flush to the ZIP archive if the caller wants us to. */
        if (flush && buff->fill_size) {
-               ret = zip_append(o, buff->samples, buff->unit_size,
-                       buff->fill_size * buff->unit_size);
+               ret = zip_append(o, buff->samples, buff->zip_unit_size,
+                       buff->fill_size * buff->zip_unit_size);
                if (ret != SR_OK)
                        return ret;
                buff->fill_size = 0;
@@ -664,8 +713,9 @@ static int receive(const struct sr_output *o, const struct sr_datafeed_packet *p
                        outc->zip_created = TRUE;
                }
                logic = packet->payload;
-               ret = zip_append_queue(o, logic->data,
-                       logic->unitsize, logic->length, FALSE);
+               ret = zip_append_queue(o,
+                       logic->data, logic->unitsize, logic->length,
+                       FALSE);
                if (ret != SR_OK)
                        return ret;
                break;
index a30b4b3f744ee0d154926038e6dfce67dcb029ba..3164b94313f5cf82b31ec023a15c6283252c3e89 100644 (file)
@@ -57,6 +57,19 @@ SR_API GSList *sr_resourcepaths_get(int res_type)
                env = g_getenv("SIGROK_FIRMWARE_DIR");
                if (env)
                        l = g_slist_append(l, g_strdup(env));
+
+               env = g_getenv("SIGROK_FIRMWARE_PATH");
+               if (env) {
+                       char **dir_list, **dir_iter, *dir_item;
+                       dir_list = g_strsplit(env, G_SEARCHPATH_SEPARATOR_S, 0);
+                       for (dir_iter = dir_list; *dir_iter; dir_iter++) {
+                               dir_item = *dir_iter;
+                               if (!dir_item || !*dir_item)
+                                       continue;
+                               l = g_slist_append(l, g_strdup(dir_item));
+                       }
+                       g_strfreev(dir_list);
+               }
        }
 
        l = g_slist_append(l, g_build_filename(g_get_user_data_dir(), subdir, NULL));
index 0e74b32901f10e21b4a657d2ddebd57dbd401957..8d24ceea97e29c88de244fae03600a8cf52b6507 100644 (file)
@@ -38,6 +38,7 @@ static const char *scpi_vendors[][2] = {
        { "Keysight Technologies", "Keysight" },
        { "PHILIPS", "Philips" },
        { "RIGOL TECHNOLOGIES", "Rigol" },
+       { "Siglent Technologies", "Siglent" },
 };
 
 /**
@@ -1037,7 +1038,29 @@ 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);
-       if ((ret != SR_OK) || (llen == 0)) {
+       /*
+        * 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) {
+               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;
index 067838adce5f04bc19e379b0a6e13412da2870f3..92d2c53196f98e010f7e3fc9b507ae313f69d050 100644 (file)
@@ -46,6 +46,7 @@ static const struct {
        { 0x0aad, 0x0117, "115200/8n1" },        /* R&S HMO series, previously branded as Hameg HMO */
        { 0x0aad, 0x0118, "115200/8n1" },        /* R&S HMO series, previously branded as Hameg HMO */
        { 0x0aad, 0x0119, "115200/8n1" },        /* R&S HMO series, previously branded as Hameg HMO */
+       { 0x0aad, 0x0135, "115200/8n1" },        /* R&S HMC series, previously branded as Hameg HMC */
        { 0x2184, 0x0030, "115200/8n1" },        /* GW-Instek GDM-8341 (VCP, SiLabs CP210x) */
        { 0x2184, 0x0058, "115200/8n1" },        /* GW-Instek GDM-9061 (USBCDC mode) */
 };
index 29e7a4394aa8a54c1c69871f5362151ea6ff66db..c85cf3eb93b0387ba3ecff420da9eb7af5052edc 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
-#ifdef _WIN32
-#define _WIN32_WINNT 0x0501
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#endif
-#include <glib.h>
-#include <string.h>
-#include <unistd.h>
-#ifndef _WIN32
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#endif
+#include "config.h"
+
 #include <errno.h>
+#include <glib.h>
 #include <libsigrok/libsigrok.h>
+#include <string.h>
+
 #include "libsigrok-internal.h"
 #include "scpi.h"
 
 #define LOG_PREFIX "scpi_tcp"
 
-#define LENGTH_BYTES 4
+#define LENGTH_BYTES sizeof(uint32_t)
 
 struct scpi_tcp {
-       char *address;
-       char *port;
-       int socket;
-       char length_buf[LENGTH_BYTES];
-       int length_bytes_read;
-       int response_length;
-       int response_bytes_read;
+       struct sr_tcp_dev_inst *tcp_dev;
+       uint8_t length_buf[LENGTH_BYTES];
+       size_t length_bytes_read;
+       size_t response_length;
+       size_t response_bytes_read;
 };
 
 static int scpi_tcp_dev_inst_new(void *priv, struct drv_context *drvc,
@@ -65,9 +53,9 @@ static int scpi_tcp_dev_inst_new(void *priv, struct drv_context *drvc,
                return SR_ERR;
        }
 
-       tcp->address = g_strdup(params[1]);
-       tcp->port = g_strdup(params[2]);
-       tcp->socket = -1;
+       tcp->tcp_dev = sr_tcp_dev_inst_new(params[1], params[2]);
+       if (!tcp->tcp_dev)
+               return SR_ERR;
 
        return SR_OK;
 }
@@ -75,42 +63,11 @@ static int scpi_tcp_dev_inst_new(void *priv, struct drv_context *drvc,
 static int scpi_tcp_open(struct sr_scpi_dev_inst *scpi)
 {
        struct scpi_tcp *tcp = scpi->priv;
-       struct addrinfo hints;
-       struct addrinfo *results, *res;
-       int err;
+       int ret;
 
-       memset(&hints, 0, sizeof(hints));
-       hints.ai_family = AF_UNSPEC;
-       hints.ai_socktype = SOCK_STREAM;
-       hints.ai_protocol = IPPROTO_TCP;
-
-       err = getaddrinfo(tcp->address, tcp->port, &hints, &results);
-
-       if (err) {
-               sr_err("Address lookup failed: %s:%s: %s", tcp->address, tcp->port,
-                       gai_strerror(err));
-               return SR_ERR;
-       }
-
-       for (res = results; res; res = res->ai_next) {
-               if ((tcp->socket = socket(res->ai_family, res->ai_socktype,
-                                               res->ai_protocol)) < 0)
-                       continue;
-               if (connect(tcp->socket, res->ai_addr, res->ai_addrlen) != 0) {
-                       close(tcp->socket);
-                       tcp->socket = -1;
-                       continue;
-               }
-               break;
-       }
-
-       freeaddrinfo(results);
-
-       if (tcp->socket < 0) {
-               sr_err("Failed to connect to %s:%s: %s", tcp->address, tcp->port,
-                               g_strerror(errno));
-               return SR_ERR;
-       }
+       ret = sr_tcp_connect(tcp->tcp_dev);
+       if (ret != SR_OK)
+               return ret;
 
        return SR_OK;
 }
@@ -119,10 +76,15 @@ static int scpi_tcp_connection_id(struct sr_scpi_dev_inst *scpi,
                char **connection_id)
 {
        struct scpi_tcp *tcp = scpi->priv;
+       char conn_text[128];
+       int ret;
 
-       *connection_id = g_strdup_printf("%s/%s:%s",
-               scpi->prefix, tcp->address, tcp->port);
+       ret = sr_tcp_get_port_path(tcp->tcp_dev, scpi->prefix, '/',
+               conn_text, sizeof(conn_text));
+       if (ret != SR_OK)
+               return ret;
 
+       *connection_id = g_strdup(conn_text);
        return SR_OK;
 }
 
@@ -131,33 +93,36 @@ static int scpi_tcp_source_add(struct sr_session *session, void *priv,
 {
        struct scpi_tcp *tcp = priv;
 
-       return sr_session_source_add(session, tcp->socket, events, timeout,
-                       cb, cb_data);
+       return sr_tcp_source_add(session, tcp->tcp_dev,
+               events, timeout, cb, cb_data);
 }
 
 static int scpi_tcp_source_remove(struct sr_session *session, void *priv)
 {
        struct scpi_tcp *tcp = priv;
 
-       return sr_session_source_remove(session, tcp->socket);
+       return sr_tcp_source_remove(session, tcp->tcp_dev);
 }
 
+/* Transmit text, usually a command. tcp-raw and tcp-rigol modes. */
 static int scpi_tcp_send(void *priv, const char *command)
 {
        struct scpi_tcp *tcp = priv;
-       int len, out;
-
-       len = strlen(command);
-       out = send(tcp->socket, command, len, 0);
-
-       if (out < 0) {
+       const uint8_t *wrptr;
+       size_t wrlen, written;
+       int ret;
+
+       wrptr = (const uint8_t *)command;
+       wrlen = strlen(command);
+       ret = sr_tcp_write_bytes(tcp->tcp_dev, wrptr, wrlen);
+       if (ret < 0) {
                sr_err("Send error: %s", g_strerror(errno));
                return SR_ERR;
        }
-
-       if (out < len) {
-               sr_dbg("Only sent %d/%d bytes of SCPI command: '%s'.", out,
-                      len, command);
+       written = (size_t)ret;
+       if (written < wrlen) {
+               sr_dbg("Only sent %zu/%zu bytes of SCPI command: '%s'.",
+                       written, wrlen, command);
        }
 
        sr_spew("Successfully sent SCPI command: '%s'.", command);
@@ -165,6 +130,7 @@ static int scpi_tcp_send(void *priv, const char *command)
        return SR_OK;
 }
 
+/* Start reception across multiple read calls. tcp-raw and tcp-rigol modes. */
 static int scpi_tcp_read_begin(void *priv)
 {
        struct scpi_tcp *tcp = priv;
@@ -175,90 +141,126 @@ static int scpi_tcp_read_begin(void *priv)
        return SR_OK;
 }
 
+/* Receive response data. tcp-raw mode. */
 static int scpi_tcp_raw_read_data(void *priv, char *buf, int maxlen)
 {
        struct scpi_tcp *tcp = priv;
-       int len;
-
-       len = recv(tcp->socket, buf, maxlen, 0);
-
-       if (len < 0) {
+       uint8_t *rdptr;
+       size_t rdlen, rcvd;
+       int ret;
+
+       /* Get another chunk of receive data. */
+       rdptr = (uint8_t *)buf;
+       rdlen = maxlen;
+       ret = sr_tcp_read_bytes(tcp->tcp_dev, rdptr, rdlen, FALSE);
+       if (ret < 0) {
                sr_err("Receive error: %s", g_strerror(errno));
                return SR_ERR;
        }
-
+       rcvd = (size_t)ret;
+
+       /*
+        * Raw data mode (in contrast to Rigol mode). Prepare to answer
+        * the "completed" condition while the payload's length is not
+        * known. Pretend that the length buffer had been received.
+        * Assume that short reads correspond to the end of a response,
+        * while full reads of the caller specified size suggest that
+        * more data can follow.
+        */
        tcp->length_bytes_read = LENGTH_BYTES;
-       tcp->response_length = len < maxlen ? len : maxlen + 1;
-       tcp->response_bytes_read = len;
+       tcp->response_length = rcvd < rdlen ? rcvd : rdlen + 1;
+       tcp->response_bytes_read = rcvd;
 
-       return len;
+       return rcvd;
 }
 
+/* Transmit data of given length. tcp-raw mode. */
 static int scpi_tcp_raw_write_data(void *priv, char *buf, int len)
 {
        struct scpi_tcp *tcp = priv;
-       int sentlen;
-
-       sentlen = send(tcp->socket, buf, len, 0);
-
-       if (sentlen < 0) {
+       const uint8_t *wrptr;
+       size_t wrlen, sent;
+       int ret;
+
+       wrptr = (const uint8_t *)buf;
+       wrlen = len;
+       ret = sr_tcp_write_bytes(tcp->tcp_dev, wrptr, wrlen);
+       if (ret < 0) {
                sr_err("Send error: %s.", g_strerror(errno));
                return SR_ERR;
        }
+       sent = (size_t)ret;
 
-       return sentlen;
+       return sent;
 }
 
+/* Receive response data. tcp-rigol mode. */
 static int scpi_tcp_rigol_read_data(void *priv, char *buf, int maxlen)
 {
        struct scpi_tcp *tcp = priv;
-       int len;
-
-       if (tcp->length_bytes_read < LENGTH_BYTES) {
-               len = recv(tcp->socket, tcp->length_buf + tcp->length_bytes_read,
-                               LENGTH_BYTES - tcp->length_bytes_read, 0);
-               if (len < 0) {
+       uint8_t *rdptr;
+       size_t rdlen, rcvd;
+       int ret;
+
+       /*
+        * Rigol mode, chunks are prefixed by a length spec.
+        * Get more length bytes when we haven't read them before.
+        * Return "zero length read" if length has yet to get received.
+        * Otherwise get chunk length from length bytes buffer.
+        */
+       if (tcp->length_bytes_read < sizeof(tcp->length_buf)) {
+               rdptr = &tcp->length_buf[tcp->length_bytes_read];
+               rdlen = sizeof(tcp->length_buf) - tcp->length_bytes_read;
+               ret = sr_tcp_read_bytes(tcp->tcp_dev, rdptr, rdlen, FALSE);
+               if (ret < 0) {
                        sr_err("Receive error: %s", g_strerror(errno));
                        return SR_ERR;
                }
-
-               tcp->length_bytes_read += len;
-
-               if (tcp->length_bytes_read < LENGTH_BYTES)
+               rcvd = (size_t)ret;
+               tcp->length_bytes_read += rcvd;
+               if (tcp->length_bytes_read < sizeof(tcp->length_buf))
                        return 0;
-               else
-                       tcp->response_length = RL32(tcp->length_buf);
+               tcp->response_length = read_u32le(tcp->length_buf);
        }
 
+       /* Received more chunk data than announced size? Fatal. */
        if (tcp->response_bytes_read >= tcp->response_length)
                return SR_ERR;
 
-       len = recv(tcp->socket, buf, maxlen, 0);
-
-       if (len < 0) {
+       /* Read another chunk of the receive data. */
+       rdptr = (uint8_t *)buf;
+       rdlen = maxlen;
+       ret = sr_tcp_read_bytes(tcp->tcp_dev, rdptr, rdlen, FALSE);
+       if (ret < 0) {
                sr_err("Receive error: %s", g_strerror(errno));
                return SR_ERR;
        }
+       rcvd = (size_t)ret;
+       tcp->response_bytes_read += rcvd;
 
-       tcp->response_bytes_read += len;
-
-       return len;
+       return rcvd;
 }
 
+/* Check reception completion. tcp-raw and tcp-rigol modes. */
 static int scpi_tcp_read_complete(void *priv)
 {
        struct scpi_tcp *tcp = priv;
+       gboolean have_length, have_response;
 
-       return (tcp->length_bytes_read == LENGTH_BYTES &&
-                       tcp->response_bytes_read >= tcp->response_length);
+       have_length = tcp->length_bytes_read == LENGTH_BYTES;
+       have_response = tcp->response_bytes_read >= tcp->response_length;
+
+       return have_length && have_response;
 }
 
 static int scpi_tcp_close(struct sr_scpi_dev_inst *scpi)
 {
        struct scpi_tcp *tcp = scpi->priv;
+       int ret;
 
-       if (close(tcp->socket) < 0)
-               return SR_ERR;
+       ret = sr_tcp_disconnect(tcp->tcp_dev);
+       if (ret != SR_OK)
+               return ret;
 
        return SR_OK;
 }
@@ -267,8 +269,7 @@ static void scpi_tcp_free(void *priv)
 {
        struct scpi_tcp *tcp = priv;
 
-       g_free(tcp->address);
-       g_free(tcp->port);
+       sr_tcp_dev_inst_free(tcp->tcp_dev);
 }
 
 SR_PRIV const struct sr_scpi_dev_inst scpi_tcp_raw_dev = {
index 353b7ae14899f12d49cac0a3761469488faa971d..d78dea20197d2f5a0ac52e3d44aaecb4c5a12e15 100644 (file)
@@ -99,6 +99,8 @@ SR_PRIV int serial_open(struct sr_serial_dev_inst *serial, int flags)
                serial->lib_funcs = ser_lib_funcs_hid;
        else if (ser_name_is_bt(serial))
                serial->lib_funcs = ser_lib_funcs_bt;
+       else if (ser_name_is_tcpraw(serial))
+               serial->lib_funcs = ser_lib_funcs_tcpraw;
        else
                serial->lib_funcs = ser_lib_funcs_libsp;
        if (!serial->lib_funcs)
index 9d7cfb2b2699fbedbddd8348ff6fa0a1a13f8eba..977f248b5eb4b62fc682a939fe7664cda4c63789 100644 (file)
 #define SER_BT_CONN_PREFIX     "bt"
 #define SER_BT_CHUNK_SIZE      1200
 
+#define SER_BT_PARAM_PREFIX_CHANNEL    "channel="
+#define SER_BT_PARAM_PREFIX_HDL_RX     "handle_rx="
+#define SER_BT_PARAM_PREFIX_HDL_TX     "handle_tx="
+#define SER_BT_PARAM_PREFIX_HDL_CCCD   "handle_cccd="
+#define SER_BT_PARAM_PREFIX_VAL_CCCD   "value_cccd="
+#define SER_BT_PARAM_PREFIX_BLE_MTU    "mtu="
+
 /**
  * @file
  *
 
 /* {{{ support for serial-over-BT channels */
 
+/*
+ * This builtin database of known devices (keyed by their names as
+ * provided during BT/BLE scans) can help improve the presentation of
+ * scan results. Ideally users could take the output and pass it to
+ * subsequent program invocations, not having to "come up with" the
+ * conn= spec, or only having to touch it up minimally. GUI dialogs
+ * could present scan results such that users just need to pick an
+ * item to open a connection.
+ *
+ * The current implementation guesses connection types from device
+ * names, and optionally amends them with additional parameters if
+ * experience shows that individual devices need these extra specs.
+ *
+ * This database may have to move to a separate source file should
+ * its size grow to amounts that are considered inappropriate here
+ * in the serial transport's BT dispatcher. For now the item count
+ * is small.
+ */
+
 static const struct scan_supported_item {
        const char *name;
        enum ser_bt_conn_t type;
+       const char *add_params;
 } scan_supported_items[] = {
-       /* Guess connection types from device names (useful for scans). */
-       { "121GW", SER_BT_CONN_BLE122, },
-       { "Adafruit Bluefruit LE 8134", SER_BT_CONN_NRF51, },
-       { "HC-05", SER_BT_CONN_RFCOMM, },
-       { NULL, SER_BT_CONN_UNKNOWN, },
+       { "121GW", SER_BT_CONN_BLE122, NULL, },
+       { "Adafruit Bluefruit LE 8134", SER_BT_CONN_NRF51, NULL, },
+       { "DL24M_BLE", SER_BT_CONN_AC6328, NULL, },
+       { "DL24M_SPP", SER_BT_CONN_RFCOMM, "/channel=2", },
+       { "HC-05", SER_BT_CONN_RFCOMM, NULL, },
+       { "TC66C", SER_BT_CONN_DIALOG, "/mtu=200", },
+       { "UC96_BLE", SER_BT_CONN_AC6328, NULL, },
+       { "UC96_SPP", SER_BT_CONN_RFCOMM, "/channel=2", },
+       { "UM25C", SER_BT_CONN_RFCOMM, NULL, },
+       { NULL, SER_BT_CONN_UNKNOWN, NULL, },
 };
 
+static const struct scan_supported_item *scan_is_supported(const char *name)
+{
+       size_t idx;
+       const struct scan_supported_item *item;
+
+       for (idx = 0; idx < ARRAY_SIZE(scan_supported_items); idx++) {
+               item = &scan_supported_items[idx];
+               if (!item->name)
+                       break;
+               if (strcmp(name, item->name) != 0)
+                       continue;
+               return item;
+       }
+
+       return NULL;
+}
+
 static const char *ser_bt_conn_names[SER_BT_CONN_MAX] = {
        [SER_BT_CONN_UNKNOWN] = "<type>",
        [SER_BT_CONN_RFCOMM] = "rfcomm",
        [SER_BT_CONN_BLE122] = "ble122",
        [SER_BT_CONN_NRF51] = "nrf51",
        [SER_BT_CONN_CC254x] = "cc254x",
+       [SER_BT_CONN_AC6328] = "ac6328",
+       [SER_BT_CONN_DIALOG] = "dialog",
+       [SER_BT_CONN_NOTIFY] = "notify",
 };
 
 static enum ser_bt_conn_t lookup_conn_name(const char *name)
@@ -119,14 +171,16 @@ static const char *conn_name_text(enum ser_bt_conn_t type)
  * - The next field is the remote device's address, either separated
  *   by colons or dashes or spaces, or not separated at all.
  * - Other parameters (RFCOMM channel, notify handles and write values)
- *   get derived from the connection type. A future implementation may
- *   accept more fields, but the syntax is yet to get developed.
+ *   get derived from the connection type.
+ * - More fields after the remote address are options which override
+ *   builtin defaults (RFCOMM channels, BLE handles, etc).
  *
  * Supported formats resulting from these rules:
- *   bt/<conn>/<addr>
+ *   bt/<conn>/<addr>[/<param>]...
  *
  * Examples:
  *   bt/rfcomm/11-22-33-44-55-66
+ *   bt/rfcomm/11-22-33-44-55-66/channel=2
  *   bt/ble122/88:6b:12:34:56:78
  *   bt/cc254x/0123456789ab
  *
@@ -140,11 +194,16 @@ static int ser_bt_parse_conn_spec(
        enum ser_bt_conn_t *conn_type, const char **remote_addr,
        size_t *rfcomm_channel,
        uint16_t *read_hdl, uint16_t *write_hdl,
-       uint16_t *cccd_hdl, uint16_t *cccd_val)
+       uint16_t *cccd_hdl, uint16_t *cccd_val,
+       uint16_t *ble_mtu)
 {
+       char **fields, *field;
        enum ser_bt_conn_t type;
        const char *addr;
-       char **fields, *field;
+       int ret_parse, ret;
+       size_t fields_count, field_idx;
+       char *endp;
+       unsigned long parm_val;
 
        if (conn_type)
                *conn_type = SER_BT_CONN_UNKNOWN;
@@ -160,9 +219,8 @@ static int ser_bt_parse_conn_spec(
                *cccd_hdl = 0;
        if (cccd_val)
                *cccd_val = 0;
-
-       type = SER_BT_CONN_UNKNOWN;
-       addr = NULL;
+       if (ble_mtu)
+               *ble_mtu = 0;
 
        if (!serial || !spec || !spec[0])
                return SR_ERR_ARG;
@@ -240,14 +298,126 @@ static int ser_bt_parse_conn_spec(
                if (cccd_val)
                        *cccd_val = 0x0001;
                break;
+       case SER_BT_CONN_AC6328:
+               if (read_hdl)
+                       *read_hdl = 12;
+               if (write_hdl)
+                       *write_hdl = 15;
+               if (cccd_hdl)
+                       *cccd_hdl = 13;
+               if (cccd_val)
+                       *cccd_val = 0x0001;
+               break;
+       case SER_BT_CONN_DIALOG:
+               if (read_hdl)
+                       *read_hdl = 23;
+               if (write_hdl)
+                       *write_hdl = 18;
+               if (cccd_hdl)
+                       *cccd_hdl = 0;
+               if (cccd_val)
+                       *cccd_val = 0x0001;
+               if (ble_mtu)
+                       *ble_mtu = 400;
+               break;
+       case SER_BT_CONN_NOTIFY:
+               /* All other values must be provided externally. */
+               if (cccd_val)
+                       *cccd_val = 0x0001;
+               break;
        default:
                return SR_ERR_ARG;
        }
 
-       /* TODO Evaluate optionally trailing fields, override defaults? */
+       /*
+        * Preset a successful return value for the conn= parse call.
+        * Scan optional additional fields which specify more params.
+        * Update the defaults which were setup above. Pessimize the
+        * routine's return value in error paths.
+        */
+       ret_parse = SR_OK;
+       fields_count = g_strv_length(fields);
+       for (field_idx = 3; field_idx < fields_count; field_idx++) {
+               field = fields[field_idx];
+               if (!field || !*field)
+                       continue;
+               if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_CHANNEL)) {
+                       field += strlen(SER_BT_PARAM_PREFIX_CHANNEL);
+                       endp = NULL;
+                       ret = sr_atoul_base(field, &parm_val, &endp, 0);
+                       if (ret != SR_OK || !endp || *endp != '\0') {
+                               ret_parse = SR_ERR_ARG;
+                               break;
+                       }
+                       if (rfcomm_channel)
+                               *rfcomm_channel = parm_val;
+                       continue;
+               }
+               if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_RX)) {
+                       field += strlen(SER_BT_PARAM_PREFIX_HDL_RX);
+                       endp = NULL;
+                       ret = sr_atoul_base(field, &parm_val, &endp, 0);
+                       if (ret != SR_OK || !endp || *endp != '\0') {
+                               ret_parse = SR_ERR_ARG;
+                               break;
+                       }
+                       if (read_hdl)
+                               *read_hdl = parm_val;
+                       continue;
+               }
+               if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_TX)) {
+                       field += strlen(SER_BT_PARAM_PREFIX_HDL_TX);
+                       endp = NULL;
+                       ret = sr_atoul_base(field, &parm_val, &endp, 0);
+                       if (ret != SR_OK || !endp || *endp != '\0') {
+                               ret_parse = SR_ERR_ARG;
+                               break;
+                       }
+                       if (write_hdl)
+                               *write_hdl = parm_val;
+                       continue;
+               }
+               if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_CCCD)) {
+                       field += strlen(SER_BT_PARAM_PREFIX_HDL_CCCD);
+                       endp = NULL;
+                       ret = sr_atoul_base(field, &parm_val, &endp, 0);
+                       if (ret != SR_OK || !endp || *endp != '\0') {
+                               ret_parse = SR_ERR_ARG;
+                               break;
+                       }
+                       if (cccd_hdl)
+                               *cccd_hdl = parm_val;
+                       continue;
+               }
+               if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_VAL_CCCD)) {
+                       field += strlen(SER_BT_PARAM_PREFIX_VAL_CCCD);
+                       endp = NULL;
+                       ret = sr_atoul_base(field, &parm_val, &endp, 0);
+                       if (ret != SR_OK || !endp || *endp != '\0') {
+                               ret_parse = SR_ERR_ARG;
+                               break;
+                       }
+                       if (cccd_val)
+                               *cccd_val = parm_val;
+                       continue;
+               }
+               if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_BLE_MTU)) {
+                       field += strlen(SER_BT_PARAM_PREFIX_BLE_MTU);
+                       endp = NULL;
+                       ret = sr_atoul_base(field, &parm_val, &endp, 0);
+                       if (ret != SR_OK || !endp || *endp != '\0') {
+                               ret_parse = SR_ERR_ARG;
+                               break;
+                       }
+                       if (ble_mtu)
+                               *ble_mtu = parm_val;
+                       continue;
+               }
+               return SR_ERR_DATA;
+       }
 
        g_strfreev(fields);
-       return SR_OK;
+       return ret_parse;
 }
 
 static void ser_bt_mask_databits(struct sr_serial_dev_inst *serial,
@@ -274,6 +444,11 @@ static int ser_bt_data_cb(void *cb_data, uint8_t *data, size_t dlen)
        if (!serial)
                return -1;
 
+       if (!data && dlen)
+               return -1;
+       if (!data || !dlen)
+               return 0;
+
        ser_bt_mask_databits(serial, data, dlen);
        sr_ser_queue_rx_data(serial, data, dlen);
 
@@ -312,6 +487,7 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags)
        const char *remote_addr;
        size_t rfcomm_channel;
        uint16_t read_hdl, write_hdl, cccd_hdl, cccd_val;
+       uint16_t ble_mtu;
        int rc;
        struct sr_bt_desc *desc;
 
@@ -322,7 +498,8 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags)
                        &conn_type, &remote_addr,
                        &rfcomm_channel,
                        &read_hdl, &write_hdl,
-                       &cccd_hdl, &cccd_val);
+                       &cccd_hdl, &cccd_val,
+                       &ble_mtu);
        if (rc != SR_OK)
                return SR_ERR_ARG;
 
@@ -350,14 +527,19 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags)
        case SER_BT_CONN_BLE122:
        case SER_BT_CONN_NRF51:
        case SER_BT_CONN_CC254x:
+       case SER_BT_CONN_AC6328:
+       case SER_BT_CONN_DIALOG:
+       case SER_BT_CONN_NOTIFY:
                rc = sr_bt_config_notify(desc,
-                       read_hdl, write_hdl, cccd_hdl, cccd_val);
+                       read_hdl, write_hdl, cccd_hdl, cccd_val,
+                       ble_mtu);
                if (rc < 0)
                        return SR_ERR;
                serial->bt_notify_handle_read = read_hdl;
                serial->bt_notify_handle_write = write_hdl;
                serial->bt_notify_handle_cccd = cccd_hdl;
                serial->bt_notify_value_cccd = cccd_val;
+               serial->bt_ble_mtu = ble_mtu;
                break;
        default:
                /* Unsupported type, or incomplete implementation. */
@@ -382,6 +564,9 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags)
        case SER_BT_CONN_BLE122:
        case SER_BT_CONN_NRF51:
        case SER_BT_CONN_CC254x:
+       case SER_BT_CONN_AC6328:
+       case SER_BT_CONN_DIALOG:
+       case SER_BT_CONN_NOTIFY:
                rc = sr_bt_connect_ble(desc);
                if (rc < 0)
                        return SR_ERR;
@@ -458,6 +643,9 @@ static int ser_bt_write(struct sr_serial_dev_inst *serial,
        case SER_BT_CONN_BLE122:
        case SER_BT_CONN_NRF51:
        case SER_BT_CONN_CC254x:
+       case SER_BT_CONN_AC6328:
+       case SER_BT_CONN_DIALOG:
+       case SER_BT_CONN_NOTIFY:
                /*
                 * Assume that when applications call the serial layer's
                 * write routine, then the BLE chip/module does support
@@ -498,7 +686,6 @@ static int ser_bt_read(struct sr_serial_dev_inst *serial,
         * where to stop reception.
         */
        deadline_us = 0;
-       now_us = 0;     /* Silence a (false) compiler warning. */
        if (timeout_ms) {
                now_us = g_get_monotonic_time();
                deadline_us = now_us + timeout_ms * 1000;
@@ -524,6 +711,9 @@ static int ser_bt_read(struct sr_serial_dev_inst *serial,
                case SER_BT_CONN_BLE122:
                case SER_BT_CONN_NRF51:
                case SER_BT_CONN_CC254x:
+               case SER_BT_CONN_AC6328:
+               case SER_BT_CONN_DIALOG:
+               case SER_BT_CONN_NOTIFY:
                        dlen = sr_ser_has_queued_data(serial);
                        rc = sr_bt_check_notify(serial->bt_desc);
                        if (rc < 0)
@@ -621,6 +811,9 @@ static int bt_source_cb(int fd, int revents, void *cb_data)
                case SER_BT_CONN_BLE122:
                case SER_BT_CONN_NRF51:
                case SER_BT_CONN_CC254x:
+               case SER_BT_CONN_AC6328:
+               case SER_BT_CONN_DIALOG:
+               case SER_BT_CONN_NOTIFY:
                        dlen = sr_ser_has_queued_data(serial);
                        rc = sr_bt_check_notify(serial->bt_desc);
                        if (rc < 0)
@@ -698,23 +891,6 @@ static int ser_bt_setup_source_remove(struct sr_session *session,
        return SR_OK;
 }
 
-static enum ser_bt_conn_t scan_is_supported(const char *name)
-{
-       size_t idx;
-       const struct scan_supported_item *item;
-
-       for (idx = 0; idx < ARRAY_SIZE(scan_supported_items); idx++) {
-               item = &scan_supported_items[idx];
-               if (!item->name)
-                       break;
-               if (strcmp(name, item->name) != 0)
-                       continue;
-               return item->type;
-       }
-
-       return SER_BT_CONN_UNKNOWN;
-}
-
 struct bt_scan_args_t {
        GSList *port_list;
        sr_ser_list_append_t append;
@@ -727,6 +903,7 @@ static void scan_cb(void *cb_args, const char *addr, const char *name)
        struct bt_scan_args_t *scan_args;
        GSList *l;
        char addr_text[20];
+       const struct scan_supported_item *item;
        enum ser_bt_conn_t type;
        char *port_name, *port_desc;
        char *addr_copy;
@@ -749,9 +926,11 @@ static void scan_cb(void *cb_args, const char *addr, const char *name)
        g_strcanon(addr_text, "0123456789abcdefABCDEF", '-');
 
        /* Create a port name, and a description. */
-       type = scan_is_supported(name);
-       port_name = g_strdup_printf("%s/%s/%s",
-               SER_BT_CONN_PREFIX, conn_name_text(type), addr_text);
+       item = scan_is_supported(name);
+       type = item ? item->type : SER_BT_CONN_UNKNOWN;
+       port_name = g_strdup_printf("%s/%s/%s%s",
+               SER_BT_CONN_PREFIX, conn_name_text(type), addr_text,
+               (item && item->add_params) ? item->add_params : "");
        port_desc = g_strdup_printf("%s (%s)", name, scan_args->bt_type);
 
        scan_args->port_list = scan_args->append(scan_args->port_list, port_name, port_desc);
@@ -765,7 +944,7 @@ static void scan_cb(void *cb_args, const char *addr, const char *name)
 
 static GSList *ser_bt_list(GSList *list, sr_ser_list_append_t append)
 {
-       static const int scan_duration = 2;
+       static const int scan_duration = 3;
 
        struct bt_scan_args_t scan_args;
        struct sr_bt_desc *desc;
index eccdf0f22ef5a0c1c5274d0931db98685aec3294..759a58c487499aeb5f82d18c5ef779166dfc7610 100644 (file)
@@ -181,9 +181,10 @@ static char *extract_hidapi_path(const char *copy)
 }
 
 /*
- * The HIDAPI specific list() callback, invoked by common serial.c code.
- * Enumerate all devices (no VID:PID is involved).
- * Invoke an 'append' callback with "path" and "name".
+ * Enumerate all devices (no VID:PID is involved). Invoke an 'append'
+ * callback with "path" and "name". Exclusively list connections that
+ * involve supported chip types, because mice and keyboards etc are not
+ * too useful to communicate to measurement equipment.
  */
 static GSList *ser_hid_hidapi_list(GSList *list, sr_ser_list_append_t append)
 {
@@ -197,14 +198,14 @@ static GSList *ser_hid_hidapi_list(GSList *list, sr_ser_list_append_t append)
        devs = hid_enumerate(0x0000, 0x0000);
        for (curdev = devs; curdev; curdev = curdev->next) {
                /*
-                * Determine the chip name from VID:PID (if it's one of
-                * the supported types with an ID known to us).
+                * Determine the chip name from VID:PID. Exlusively list
+                * supported connection types (known chips).
                 */
                vid = curdev->vendor_id;
                pid = curdev->product_id;
                chipname = ser_hid_chip_find_name_vid_pid(vid, pid);
                if (!chipname)
-                       chipname = "<chip>";
+                       continue;
 
                /*
                 * Prefix port names such that open() calls with this
@@ -250,19 +251,36 @@ static GSList *ser_hid_hidapi_list(GSList *list, sr_ser_list_append_t append)
 }
 
 /*
- * The HIDAPI specific find_usb() callback, invoked by common serial.c code.
- * Enumerate devices for the specified VID:PID pair.
- * Invoke an "append" callback with 'path' for the device.
+ * Enumerate devices for the specified VID:PID pair. Invoke an "append"
+ * callback with 'path' for found devices. Exclusively finds supported
+ * chip types, skips unknown VID:PID pairs (even if caller specified).
  */
 static GSList *ser_hid_hidapi_find_usb(GSList *list, sr_ser_find_append_t append,
                uint16_t vendor_id, uint16_t product_id)
 {
+       const char *caller_chip;
+       const char *dev_chip;
        struct hid_device_info *devs, *curdev;
        const char *name;
+       char *path;
+
+       caller_chip = ser_hid_chip_find_name_vid_pid(vendor_id, product_id);
 
        devs = hid_enumerate(vendor_id, product_id);
        for (curdev = devs; curdev; curdev = curdev->next) {
-               name = curdev->path;
+               dev_chip = caller_chip;
+               if (!dev_chip) {
+                       dev_chip = ser_hid_chip_find_name_vid_pid(
+                               curdev->vendor_id, curdev->product_id);
+               }
+               if (!dev_chip)
+                       continue;
+               path = get_hidapi_path_copy(curdev->path);
+               if (!path)
+                       continue;
+               name = g_strdup_printf("%s/%s/%s",
+                       SER_HID_CONN_PREFIX, dev_chip, path);
+               g_free(path);
                list = append(list, name);
        }
        hid_free_enumeration(devs);
@@ -1025,6 +1043,7 @@ static int ser_hid_chip_search(enum ser_hid_chip_t *chip_ref,
                        return SR_ERR_NA;
                have_chip = 1;
        }
+       (void)have_chip;
 
        if (chip_ref)
                *chip_ref = chip;
diff --git a/src/serial_tcpraw.c b/src/serial_tcpraw.c
new file mode 100644 (file)
index 0000000..119abbe
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libsigrok/libsigrok.h>
+#include <string.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "serial-tcpraw"
+
+#define SER_TCPRAW_CONN_PREFIX "tcp-raw"
+
+/**
+ * @file
+ *
+ * Serial port handling, raw TCP support.
+ */
+
+/**
+ * @defgroup grp_serial_tcpraw Serial port handling, raw TCP group
+ *
+ * Disguise raw byte sequences over TCP sockets as a serial transport.
+ *
+ * @{
+ */
+
+/* {{{ TCP specific helper routines */
+
+/**
+ * Parse conn= specs for serial over TCP communication.
+ *
+ * @param[in] serial The serial port that is about to get opened.
+ * @param[in] spec The caller provided conn= specification.
+ * @param[out] host_ref Pointer to host name or IP addr (text string).
+ * @param[out] port_ref Pointer to a TCP port (text string).
+ *
+ * @return 0 upon success, non-zero upon failure. Fills the *_ref output
+ * values.
+ *
+ * Summary of parsing rules as they are implemented:
+ * - The 'spec' MUST start with "tcp-raw" followed by a separator. The
+ *   prefix alone is not sufficient, host address and port number are
+ *   mandatory.
+ * - Host name follows. It's a DNS name or an IP address.
+ * - TCP port follows. Can be a number or a "service" name.
+ * - More than three fields are accepted, but currently don't take any
+ *   effect. It's yet to be seen whether "options" or "variants" are
+ *   needed or desired. For now any trailing fields are ignored. Cisco
+ *   style serial-over-TCP as seen in ser2net(1) comes to mind (which
+ *   includes configuration and control beyond data transmission). But
+ *   its spec is rather involved, and ser2net can already derive COM
+ *   port configurations from TCP port numbers, so it's not a blocker.
+ *   That variant probably should go under a different name anyway.
+ *
+ * Supported format resulting from these rules:
+ *   tcp-raw/<ipaddr>/<port>
+ */
+static int ser_tcpraw_parse_conn_spec(
+       struct sr_serial_dev_inst *serial, const char *spec,
+       char **host_ref, char **port_ref)
+{
+       char **fields;
+       size_t count;
+       gboolean valid;
+       char *host, *port;
+
+       if (host_ref)
+               *host_ref = NULL;
+       if (port_ref)
+               *port_ref = NULL;
+
+       host = NULL;
+       port = NULL;
+       if (!serial || !spec || !*spec)
+               return SR_ERR_ARG;
+
+       fields = g_strsplit(spec, "/", 0);
+       if (!fields)
+               return SR_ERR_ARG;
+       count = g_strv_length(fields);
+
+       valid = TRUE;
+       if (count < 3)
+               valid = FALSE;
+       if (valid && strcmp(fields[0], SER_TCPRAW_CONN_PREFIX) != 0)
+               valid = FALSE;
+       if (valid) {
+               host = fields[1];
+               if (!host || !*host)
+                       valid = FALSE;
+       }
+       if (valid) {
+               port = fields[2];
+               if (!port || !*port)
+                       valid = FALSE;
+       }
+       /* Silently ignore trailing fields. Could be future options. */
+       if (count > 3)
+               sr_warn("Ignoring excess parameters in %s.", spec);
+
+       if (valid) {
+               if (host_ref && host)
+                       *host_ref = g_strdup(host);
+               if (port_ref && port)
+                       *port_ref = g_strdup(port);
+       }
+       g_strfreev(fields);
+       return valid ? SR_OK : SR_ERR_ARG;
+}
+
+/* }}} */
+/* {{{ transport methods called by the common serial.c code */
+
+/* See if a serial port's name refers to a raw TCP connection. */
+SR_PRIV int ser_name_is_tcpraw(struct sr_serial_dev_inst *serial)
+{
+       char *p;
+
+       if (!serial)
+               return 0;
+       if (!serial->port || !*serial->port)
+               return 0;
+
+       p = serial->port;
+       if (!g_str_has_prefix(p, SER_TCPRAW_CONN_PREFIX))
+               return 0;
+       p += strlen(SER_TCPRAW_CONN_PREFIX);
+       if (*p != '/')
+               return 0;
+
+       return 1;
+}
+
+static int ser_tcpraw_open(struct sr_serial_dev_inst *serial, int flags)
+{
+       char *host, *port;
+       int ret;
+
+       (void)flags;
+
+       ret = ser_tcpraw_parse_conn_spec(serial, serial->port,
+                       &host, &port);
+       if (ret != SR_OK) {
+               g_free(host);
+               g_free(port);
+               return SR_ERR_ARG;
+       }
+       serial->tcp_dev = sr_tcp_dev_inst_new(host, port);
+       g_free(host);
+       g_free(port);
+       if (!serial->tcp_dev)
+               return SR_ERR_MALLOC;
+
+       /*
+        * Open the TCP socket. Only keep caller's parameters (and the
+        * resulting socket fd) when open completes successfully.
+        */
+       ret = sr_tcp_connect(serial->tcp_dev);
+       if (ret != SR_OK) {
+               sr_err("Failed to establish TCP connection.");
+               sr_tcp_dev_inst_free(serial->tcp_dev);
+               serial->tcp_dev = NULL;
+               return SR_ERR_IO;
+       }
+
+       return SR_OK;
+}
+
+static int ser_tcpraw_close(struct sr_serial_dev_inst *serial)
+{
+
+       if (!serial)
+               return SR_ERR_ARG;
+
+       if (!serial->tcp_dev)
+               return SR_OK;
+
+       (void)sr_tcp_disconnect(serial->tcp_dev);
+       return SR_OK;
+}
+
+static int ser_tcpraw_setup_source_add(struct sr_session *session,
+       struct sr_serial_dev_inst *serial, int events, int timeout,
+       sr_receive_data_callback cb, void *cb_data)
+{
+       if (!serial || !serial->tcp_dev)
+               return SR_ERR_ARG;
+       return sr_tcp_source_add(session, serial->tcp_dev,
+               events, timeout, cb, cb_data);
+}
+
+static int ser_tcpraw_setup_source_remove(struct sr_session *session,
+       struct sr_serial_dev_inst *serial)
+{
+       if (!serial || !serial->tcp_dev)
+               return SR_ERR_ARG;
+       (void)sr_tcp_source_remove(session, serial->tcp_dev);
+       return SR_OK;
+}
+
+static int ser_tcpraw_write(struct sr_serial_dev_inst *serial,
+       const void *buf, size_t count,
+       int nonblocking, unsigned int timeout_ms)
+{
+       size_t total, written;
+       ssize_t ret;
+
+       /* Non-blocking writes, and write timeouts, are not supported. */
+       (void)nonblocking;
+       (void)timeout_ms;
+
+       if (!serial || !serial->tcp_dev)
+               return SR_ERR_ARG;
+
+       total = 0;
+       while (count) {
+               ret = sr_tcp_write_bytes(serial->tcp_dev, buf, count);
+               if (ret < 0 && !total) {
+                       sr_err("Error sending TCP transmit data.");
+                       return total;
+               }
+               if (ret <= 0) {
+                       count += total;
+                       sr_warn("Short transmission of TCP data (%zu/%zu).",
+                               total, count);
+                       return total;
+               }
+               written = (size_t)ret;
+               buf += written;
+               count -= written;
+               total += written;
+       }
+
+       return total;
+}
+
+static int ser_tcpraw_read(struct sr_serial_dev_inst *serial,
+       void *buf, size_t count,
+       int nonblocking, unsigned int timeout_ms)
+{
+       guint64 deadline_us, now_us;
+       size_t total, chunk;
+       ssize_t ret;
+
+       if (!serial || !serial->tcp_dev)
+               return SR_ERR_ARG;
+       if (!count)
+               return 0;
+
+       /*
+        * Timeouts are only useful in blocking mode, non-blocking read
+        * will return as soon as an iteration sees no more data.
+        * Silence a (false) compiler warning, always assign to 'now_us'.
+        */
+       if (nonblocking)
+               timeout_ms = 0;
+       deadline_us = now_us = 0;
+       if (timeout_ms) {
+               now_us = g_get_monotonic_time();
+               deadline_us = now_us + timeout_ms * 1000;
+       }
+
+       /*
+        * Keep reading until the caller's requested length is reached,
+        * or fatal errors are seen, or specified timeouts have expired.
+        */
+       total = 0;
+       while (count) {
+               ret = sr_tcp_read_bytes(serial->tcp_dev,
+                       buf, count, nonblocking);
+               if (ret < 0 && !total) {
+                       sr_err("Failed to receive TCP data.");
+                       break;
+               }
+               if (ret < 0) {
+                       /* Short read, not worth warning about. */
+                       break;
+               }
+               if (ret == 0 && nonblocking)
+                       break;
+               if (ret == 0 && deadline_us) {
+                       now_us = g_get_monotonic_time();
+                       if (now_us >= deadline_us)
+                               break;
+                       g_usleep(10 * 1000);
+                       continue;
+               }
+               chunk = (size_t)ret;
+               buf += chunk;
+               count -= chunk;
+               total += chunk;
+       }
+
+       return total;
+}
+
+static struct ser_lib_functions serlib_tcpraw = {
+       .open = ser_tcpraw_open,
+       .close = ser_tcpraw_close,
+       .write = ser_tcpraw_write,
+       .read = ser_tcpraw_read,
+       .set_params = std_dummy_set_params,
+       .set_handshake = std_dummy_set_handshake,
+       .setup_source_add = ser_tcpraw_setup_source_add,
+       .setup_source_remove = ser_tcpraw_setup_source_remove,
+       .get_frame_format = NULL,
+};
+SR_PRIV struct ser_lib_functions *ser_lib_funcs_tcpraw = &serlib_tcpraw;
+
+/** @} */
index 81f269fa92dc26ee73b9ca90081dc27039d010d9..959d0005680c5d2ebad6abb1637ca28a9221f035 100644 (file)
--- a/src/std.c
+++ b/src/std.c
@@ -605,7 +605,8 @@ SR_PRIV GVariant *std_gvar_tuple_array(const uint64_t a[][2], unsigned int n)
                rational[1] = g_variant_new_uint64(a[i][1]);
 
                /* FIXME: Valgrind reports a memory leak here. */
-               g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational, 2));
+               g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational,
+                       ARRAY_SIZE(rational)));
        }
 
        return g_variant_builder_end(&gvb);
@@ -624,7 +625,8 @@ SR_PRIV GVariant *std_gvar_tuple_rational(const struct sr_rational *r, unsigned
                rational[1] = g_variant_new_uint64(r[i].q);
 
                /* FIXME: Valgrind reports a memory leak here. */
-               g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational, 2));
+               g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational,
+                       ARRAY_SIZE(rational)));
        }
 
        return g_variant_builder_end(&gvb);
@@ -687,17 +689,17 @@ SR_PRIV GVariant *std_gvar_min_max_step_thresholds(const double min, const doubl
 
        g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
 
-       for (d = min; d <= max; d += step) {
+       for (d = min; d <= max + step / 2.0; d += step) {
                /*
                 * We will never see exactly 0.0 because of the error we're
                 * accumulating, so catch the "zero" value and force it to be 0.
                 */
-               v = ((d > (-step / 2)) && (d < (step / 2))) ? 0 : d;
+               v = ((d > (-step / 2.0)) && (d < (step / 2.0))) ? 0 : d;
 
                range[0] = g_variant_new_double(v);
                range[1] = g_variant_new_double(v);
 
-               gvar = g_variant_new_tuple(range, 2);
+               gvar = g_variant_new_tuple(range, ARRAY_SIZE(range));
                g_variant_builder_add_value(&gvb, gvar);
        }
 
@@ -711,7 +713,7 @@ SR_PRIV GVariant *std_gvar_tuple_u64(uint64_t low, uint64_t high)
        range[0] = g_variant_new_uint64(low);
        range[1] = g_variant_new_uint64(high);
 
-       return g_variant_new_tuple(range, 2);
+       return g_variant_new_tuple(range, ARRAY_SIZE(range));
 }
 
 SR_PRIV GVariant *std_gvar_tuple_double(double low, double high)
@@ -721,7 +723,7 @@ SR_PRIV GVariant *std_gvar_tuple_double(double low, double high)
        range[0] = g_variant_new_double(low);
        range[1] = g_variant_new_double(high);
 
-       return g_variant_new_tuple(range, 2);
+       return g_variant_new_tuple(range, ARRAY_SIZE(range));
 }
 
 SR_PRIV GVariant *std_gvar_array_i32(const int32_t a[], unsigned int n)
@@ -770,7 +772,7 @@ SR_PRIV GVariant *std_gvar_thresholds(const double a[][2], unsigned int n)
        for (i = 0; i < n; i++) {
                range[0] = g_variant_new_double(a[i][0]);
                range[1] = g_variant_new_double(a[i][1]);
-               gvar = g_variant_new_tuple(range, 2);
+               gvar = g_variant_new_tuple(range, ARRAY_SIZE(range));
                g_variant_builder_add_value(&gvb, gvar);
        }
 
index c22c71757f30e42aa08b9e567eb11eeb9a802850..2136bd4936c2fde5bf303bbf491e938af5d4acb1 100644 (file)
@@ -174,7 +174,7 @@ SR_PRIV int sr_atoul_base(const char *str, unsigned long *ret, char **end, int b
        /* Add "0b" prefix support which strtol(3) may be missing. */
        while (str && isspace(*str))
                str++;
-       if (!base && strncmp(str, "0b", strlen("0b")) == 0) {
+       if ((!base || base == 2) && strncmp(str, "0b", strlen("0b")) == 0) {
                str += strlen("0b");
                base = 2;
        }
@@ -788,10 +788,17 @@ SR_PRIV GString *sr_hexdump_new(const uint8_t *data, const size_t len)
        GString *s;
        size_t i;
 
-       s = g_string_sized_new(3 * len);
+       i = 3 * len;
+       i += len / 8;
+       i += len / 16;
+       s = g_string_sized_new(i);
        for (i = 0; i < len; i++) {
                if (i)
                        g_string_append_c(s, ' ');
+               if (i && (i % 8) == 0)
+                       g_string_append_c(s, ' ');
+               if (i && (i % 16) == 0)
+                       g_string_append_c(s, ' ');
                g_string_append_printf(s, "%02x", data[i]);
        }
 
@@ -828,78 +835,149 @@ SR_PRIV void sr_hexdump_free(GString *s)
  */
 SR_API int sr_parse_rational(const char *str, struct sr_rational *ret)
 {
-       char *endptr = NULL;
+       const char *readptr;
+       char *endptr;
+       gboolean is_negative, empty_integral, empty_fractional, exp_negative;
        int64_t integral;
-       int64_t fractional = 0;
-       int64_t denominator = 1;
-       int32_t fractional_len = 0;
-       int32_t exponent = 0;
-       gboolean is_negative = FALSE;
-       gboolean no_integer, no_fractional;
-
-       while (isspace(*str))
-               str++;
+       int64_t fractional;
+       int64_t denominator;
+       uint32_t fractional_len;
+       int32_t exponent;
 
-       errno = 0;
-       integral = g_ascii_strtoll(str, &endptr, 10);
-
-       if (str == endptr && (str[0] == '-' || str[0] == '+') && str[1] == '.') {
-               endptr += 1;
-               no_integer = TRUE;
-       } else if (str == endptr && str[0] == '.') {
-               no_integer = TRUE;
-       } else if (errno) {
-               return SR_ERR;
-       } else {
-               no_integer = FALSE;
-       }
+       /*
+        * Implementor's note: This routine tries hard to avoid calling
+        * glib's or the platform's conversion routines with input that
+        * cannot get converted *at all* (see bug #1093). It also takes
+        * care to return with non-zero errno values for any failed
+        * conversion attempt. It's assumed that correctness and robustness
+        * are more important than performance, which is why code paths
+        * are not optimized at all. Maintainability took priority.
+        */
+
+       readptr = str;
+
+       /* Skip leading whitespace. */
+       while (isspace(*readptr))
+               readptr++;
 
-       if (integral < 0 || str[0] == '-')
+       /* Determine the sign, default to non-negative. */
+       is_negative = FALSE;
+       if (*readptr == '-') {
                is_negative = TRUE;
+               readptr++;
+       } else if (*readptr == '+') {
+               is_negative = FALSE;
+               readptr++;
+       }
 
+       /* Get the (optional) integral part. */
+       empty_integral = TRUE;
+       integral = 0;
+       endptr = (char *)readptr;
        errno = 0;
-       if (*endptr == '.') {
-               gboolean is_exp, is_eos;
-               const char *start = endptr + 1;
-               fractional = g_ascii_strtoll(start, &endptr, 10);
-               is_exp = *endptr == 'E' || *endptr == 'e';
-               is_eos = *endptr == '\0';
-               if (endptr == start && (is_exp || is_eos)) {
-                       fractional = 0;
-                       errno = 0;
-               }
+       if (isdigit(*readptr)) {
+               empty_integral = FALSE;
+               integral = g_ascii_strtoll(readptr, &endptr, 10);
                if (errno)
                        return SR_ERR;
-               no_fractional = endptr == start;
-               if (no_integer && no_fractional)
+               if (endptr == str) {
+                       errno = -EINVAL;
                        return SR_ERR;
-               fractional_len = endptr - start;
+               }
+               readptr = endptr;
        }
 
-       errno = 0;
-       if ((*endptr == 'E') || (*endptr == 'e')) {
-               exponent = g_ascii_strtoll(endptr + 1, &endptr, 10);
+       /* Get the optional fractional part. */
+       empty_fractional = TRUE;
+       fractional = 0;
+       fractional_len = 0;
+       if (*readptr == '.') {
+               readptr++;
+               endptr++;
+               errno = 0;
+               if (isdigit(*readptr)) {
+                       empty_fractional = FALSE;
+                       fractional = g_ascii_strtoll(readptr, &endptr, 10);
+                       if (errno)
+                               return SR_ERR;
+                       if (endptr == readptr) {
+                               errno = -EINVAL;
+                               return SR_ERR;
+                       }
+                       fractional_len = endptr - readptr;
+                       readptr = endptr;
+               }
+       }
+
+       /* At least one of integral or fractional is required. */
+       if (empty_integral && empty_fractional) {
+               errno = -EINVAL;
+               return SR_ERR;
+       }
+
+       /* Get the (optional) exponent. */
+       exponent = 0;
+       if ((*readptr == 'E') || (*readptr == 'e')) {
+               readptr++;
+               endptr++;
+               exp_negative = FALSE;
+               if (*readptr == '+') {
+                       exp_negative = FALSE;
+                       readptr++;
+                       endptr++;
+               } else if (*readptr == '-') {
+                       exp_negative = TRUE;
+                       readptr++;
+                       endptr++;
+               }
+               if (!isdigit(*readptr)) {
+                       errno = -EINVAL;
+                       return SR_ERR;
+               }
+               errno = 0;
+               exponent = g_ascii_strtoll(readptr, &endptr, 10);
                if (errno)
                        return SR_ERR;
+               if (endptr == readptr) {
+                       errno = -EINVAL;
+                       return SR_ERR;
+               }
+               readptr = endptr;
+               if (exp_negative)
+                       exponent = -exponent;
        }
 
-       if (*endptr != '\0')
+       /* Input must be exhausted. Unconverted remaining input is fatal. */
+       if (*endptr != '\0') {
+               errno = -EINVAL;
                return SR_ERR;
+       }
 
-       for (int i = 0; i < fractional_len; i++)
+       /*
+        * Apply the sign to the integral (and fractional) part(s).
+        * Adjust exponent (decimal position) such that the above integral
+        * and fractional parts both fit into the (new) integral part.
+        */
+       if (is_negative)
+               integral = -integral;
+       while (fractional_len-- > 0) {
                integral *= 10;
-       exponent -= fractional_len;
-
+               exponent--;
+       }
        if (!is_negative)
                integral += fractional;
        else
                integral -= fractional;
-
        while (exponent > 0) {
                integral *= 10;
                exponent--;
        }
 
+       /*
+        * When significant digits remain after the decimal, scale up the
+        * denominator such that we end up with two integer p/q numbers.
+        */
+       denominator = 1;
        while (exponent < 0) {
                denominator *= 10;
                exponent++;
@@ -1249,4 +1327,549 @@ SR_API int sr_parse_voltage(const char *voltstr, uint64_t *p, uint64_t *q)
        return SR_OK;
 }
 
+/**
+ * Append another text item to a NULL terminated string vector.
+ *
+ * @param[in] table The previous string vector.
+ * @param[in,out] sz The previous and the resulting vector size
+ *       (item count).
+ * @param[in] text The text string to append to the vector.
+ *       Can be #NULL.
+ *
+ * @returns The new vector, its location can differ from 'table'.
+ *       Or #NULL in case of failure.
+ *
+ * This implementation happens to work for the first invocation when
+ * 'table' is #NULL and 'sz' is 0, as well as subsequent append calls.
+ * The 'text' can be #NULL or can be a non-empty string. When 'sz' is
+ * not provided, then the 'table' must be a NULL terminated vector,
+ * so that the routine can auto-determine the vector's current length.
+ *
+ * This routine re-allocates the vector as needed. Callers must not
+ * rely on the memory address to remain the same across calls.
+ */
+static char **append_probe_name(char **table, size_t *sz, const char *text)
+{
+       size_t curr_size, alloc_size;
+       char **new_table;
+
+       /* Get the table's previous size (item count). */
+       if (sz)
+               curr_size = *sz;
+       else if (table)
+               curr_size = g_strv_length(table);
+       else
+               curr_size = 0;
+
+       /* Extend storage to hold one more item, and the termination. */
+       alloc_size = curr_size + (text ? 1 : 0) + 1;
+       alloc_size *= sizeof(table[0]);
+       new_table = g_realloc(table, alloc_size);
+       if (!new_table) {
+               g_strfreev(table);
+               if (sz)
+                       *sz = 0;
+               return NULL;
+       }
+
+       /* Append the item, NULL terminate. */
+       if (text) {
+               new_table[curr_size] = g_strdup(text);
+               if (!new_table[curr_size]) {
+                       g_strfreev(new_table);
+                       if (sz)
+                               *sz = 0;
+                       return NULL;
+               }
+               curr_size++;
+       }
+       if (sz)
+               *sz = curr_size;
+       new_table[curr_size] = NULL;
+
+       return new_table;
+}
+
+static char **append_probe_names(char **table, size_t *sz,
+       const char **names)
+{
+       if (!names)
+               return table;
+
+       while (names[0]) {
+               table = append_probe_name(table, sz, names[0]);
+               names++;
+       }
+       return table;
+}
+
+static const struct {
+       const char *name;
+       const char **expands;
+} probe_name_aliases[] = {
+       {
+               "ac97", (const char *[]){
+                       "sync", "clk",
+                       "out", "in", "rst",
+                       NULL,
+               },
+       },
+       {
+               "i2c", (const char *[]){
+                       "scl", "sda", NULL,
+               },
+       },
+       {
+               "jtag", (const char *[]){
+                       "tdi", "tdo", "tck", "tms", NULL,
+               },
+       },
+       {
+               "jtag-opt", (const char *[]){
+                       "tdi", "tdo", "tck", "tms",
+                       "trst", "srst", "rtck", NULL,
+               },
+       },
+       {
+               "ieee488", (const char *[]){
+                       "dio1", "dio2", "dio3", "dio4",
+                       "dio5", "dio6", "dio7", "dio8",
+                       "eoi", "dav", "nrfd", "ndac",
+                       "ifc", "srq", "atn", "ren", NULL,
+               },
+       },
+       {
+               "lpc", (const char *[]){
+                       "lframe", "lclk",
+                       "lad0", "lad1", "lad2", "lad3",
+                       NULL,
+               },
+       },
+       {
+               "lpc-opt", (const char *[]){
+                       "lframe", "lclk",
+                       "lad0", "lad1", "lad2", "lad3",
+                       "lreset", "ldrq", "serirq", "clkrun",
+                       "lpme", "lpcpd", "lsmi",
+                       NULL,
+               },
+       },
+       {
+               "mcs48", (const char *[]){
+                       "ale", "psen",
+                       "d0", "d1", "d2", "d3",
+                       "d4", "d5", "d6", "d7",
+                       "a8", "a9", "a10", "a11",
+                       "a12", "a13",
+                       NULL,
+               },
+       },
+       {
+               "microwire", (const char *[]){
+                       "cs", "sk", "si", "so", NULL,
+               },
+       },
+       {
+               "sdcard_sd", (const char *[]){
+                       "cmd", "clk",
+                       "dat0", "dat1", "dat2", "dat3",
+                       NULL,
+               },
+       },
+       {
+               "seven_segment", (const char *[]){
+                       "a", "b", "c", "d", "e", "f", "g",
+                       "dp", NULL,
+               },
+       },
+       {
+               "spi", (const char *[]){
+                       "clk", "miso", "mosi", "cs", NULL,
+               },
+       },
+       {
+               "swd", (const char *[]){
+                       "swclk", "swdio", NULL,
+               },
+       },
+       {
+               "uart", (const char *[]){
+                       "rx", "tx", NULL,
+               },
+       },
+       {
+               "usb", (const char *[]){
+                       "dp", "dm", NULL,
+               },
+       },
+       {
+               "z80", (const char *[]){
+                       "d0", "d1", "d2", "d3",
+                       "d4", "d5", "d6", "d7",
+                       "m1", "rd", "wr",
+                       "mreq", "iorq",
+                       "a0", "a1", "a2", "a3",
+                       "a4", "a5", "a6", "a7",
+                       "a8", "a9", "a10", "a11",
+                       "a12", "a13", "a14", "a15",
+                       NULL,
+               },
+       },
+};
+
+/* Case insensitive lookup of an alias name. */
+static const char **lookup_probe_alias(const char *name)
+{
+       size_t idx;
+
+       for (idx = 0; idx < ARRAY_SIZE(probe_name_aliases); idx++) {
+               if (g_ascii_strcasecmp(probe_name_aliases[idx].name, name) != 0)
+                       continue;
+               return probe_name_aliases[idx].expands;
+       }
+       return NULL;
+}
+
+/**
+ * Parse a probe names specification, allocate a string vector.
+ *
+ * @param[in] spec The input spec, list of probes or aliases.
+ * @param[in] dflt_names The default probe names, a string array.
+ * @param[in] dflt_count The default probe names count. Either must
+ *        match the unterminated array size, or can be 0 when the
+ *        default names are NULL terminated.
+ * @param[in] max_count Optional resulting vector size limit.
+ * @param[out] ret_count Optional result vector size (return value).
+ *
+ * @returns A string vector with resulting probe names. Or #NULL
+ *        in case of failure.
+ *
+ * The input spec is a comma separated list of probe names. Items can
+ * be aliases which expand to a corresponding set of signal names.
+ * The resulting names list optionally gets padded from the caller's
+ * builtin probe names, an empty input spec yields the original names
+ * as provided by the caller. Padding is omitted when the spec starts
+ * with '-', which may result in a device with fewer channels being
+ * created, enough to cover the user's spec, but none extra to maybe
+ * enable and use later on. An optional maximum length spec will trim
+ * the result set to that size. The resulting vector length optionally
+ * is returned to the caller, so that it need not re-get the length.
+ *
+ * Calling applications must release the allocated vector by means
+ * of @ref sr_free_probe_names().
+ *
+ * @since 0.6.0
+ */
+SR_API char **sr_parse_probe_names(const char *spec,
+       const char **dflt_names, size_t dflt_count,
+       size_t max_count, size_t *ret_count)
+{
+       char **result_names;
+       size_t result_count;
+       gboolean pad_from_dflt;
+       char **spec_names, *spec_name;
+       size_t spec_idx;
+       const char **alias_names;
+
+       if (!spec || !*spec)
+               spec = NULL;
+
+       /*
+        * Accept zero length spec for default input names. Determine
+        * the name table's length here. Cannot re-use g_strv_length()
+        * because of the 'const' decoration in application code.
+        */
+       if (!dflt_count) {
+               while (dflt_names && dflt_names[dflt_count])
+                       dflt_count++;
+       }
+       if (!dflt_count)
+               return NULL;
+
+       /*
+        * Start with an empty resulting names table. Will grow
+        * dynamically as more names get appended.
+        */
+       result_names = NULL;
+       result_count = 0;
+       pad_from_dflt = TRUE;
+
+       /*
+        * When an input spec exists, use its content. Lookup alias
+        * names, and append their corresponding signals. Or append
+        * the verbatim input name if it is not an alias. Recursion
+        * is not supported in this implementation.
+        *
+        * A leading '-' before the signal names list suppresses the
+        * padding of the resulting list from the device's default
+        * probe names.
+        */
+       spec_names = NULL;
+       if (spec && *spec == '-') {
+               spec++;
+               pad_from_dflt = FALSE;
+       }
+       if (spec && *spec)
+               spec_names = g_strsplit(spec, ",", 0);
+       for (spec_idx = 0; spec_names && spec_names[spec_idx]; spec_idx++) {
+               spec_name = spec_names[spec_idx];
+               if (!*spec_name)
+                       continue;
+               alias_names = lookup_probe_alias(spec_name);
+               if (alias_names) {
+                       result_names = append_probe_names(result_names,
+                               &result_count, alias_names);
+               } else {
+                       result_names = append_probe_name(result_names,
+                               &result_count, spec_name);
+               }
+       }
+       g_strfreev(spec_names);
+
+       /*
+        * By default pad the resulting names from the caller's
+        * probe names. Don't pad if the input spec started with
+        * '-', when the spec's exact length was requested.
+        */
+       if (pad_from_dflt) do {
+               if (max_count && result_count >= max_count)
+                       break;
+               if (result_count >= dflt_count)
+                       break;
+               result_names = append_probe_name(result_names, &result_count,
+                       dflt_names[result_count]);
+       } while (1);
+
+       /* Optionally trim the result to the caller's length limit. */
+       if (max_count) {
+               while (result_count > max_count) {
+                       --result_count;
+                       g_free(result_names[result_count]);
+                       result_names[result_count] = NULL;
+               }
+       }
+
+       if (ret_count)
+               *ret_count = result_count;
+
+       return result_names;
+}
+
+/**
+ * Release previously allocated probe names (string vector).
+ *
+ * @param[in] names The previously allocated string vector.
+ *
+ * @since 0.6.0
+ */
+SR_API void sr_free_probe_names(char **names)
+{
+       g_strfreev(names);
+}
+
+/**
+ * Trim leading and trailing whitespace off text.
+ *
+ * @param[in] s The input text.
+ *
+ * @return Start of trimmed input text.
+ *
+ * Manipulates the caller's input text in place.
+ *
+ * @since 0.6.0
+ */
+SR_API char *sr_text_trim_spaces(char *s)
+{
+       char *p;
+
+       if (!s || !*s)
+               return s;
+
+       p = s + strlen(s);
+       while (p > s && isspace((int)p[-1]))
+               *(--p) = '\0';
+       while (isspace((int)*s))
+               s++;
+
+       return s;
+}
+
+/**
+ * Check for another complete text line, trim, return consumed char count.
+ *
+ * @param[in] s The input text, current read position.
+ * @param[in] l The input text, remaining available characters.
+ * @param[out] next Position after the current text line.
+ * @param[out] taken Count of consumed chars in current text line.
+ *
+ * @return Start of trimmed and NUL terminated text line.
+ *   Or #NULL when no text line was found.
+ *
+ * Checks for the availability of another text line of input data.
+ * Manipulates the caller's input text in place.
+ *
+ * The end-of-line condition is the LF character ('\n'). Which covers
+ * LF-only as well as CR/LF input data. CR-only and LF/CR are considered
+ * unpopular and are not supported. LF/CR may appear to work at the
+ * caller's when leading whitespace gets trimmed (line boundaries will
+ * be incorrect, but content may get processed as expected). Support for
+ * all of the above combinations breaks the detection of empty lines (or
+ * becomes unmaintainably complex).
+ *
+ * The input buffer must be end-of-line terminated, lack of EOL results
+ * in failure to detect the text line. This is motivated by accumulating
+ * input in chunks, and the desire to not process incomplete lines before
+ * their reception has completed. Callers should enforce EOL if their
+ * source of input provides an EOF condition and is unreliable in terms
+ * of text line termination.
+ *
+ * When another text line is available, it gets NUL terminated and
+ * space gets trimmed of both ends. The start position of the trimmed
+ * text line is returned. Optionally the number of consumed characters
+ * is returned to the caller. Optionally 'next' points to after the
+ * returned text line, or #NULL when no other text is available in the
+ * input buffer.
+ *
+ * The 'taken' value is not preset by this routine, only gets updated.
+ * This is convenient for callers which expect to find multiple text
+ * lines in a received chunk, before finally discarding processed data
+ * from the input buffer (which can involve expensive memory move
+ * operations, and may be desirable to defer as much as possible).
+ *
+ * @since 0.6.0
+ */
+SR_API char *sr_text_next_line(char *s, size_t l, char **next, size_t *taken)
+{
+       char *p;
+
+       if (next)
+               *next = NULL;
+       if (!l)
+               l = strlen(s);
+
+       /* Immediate reject incomplete input data. */
+       if (!s || !*s || !l)
+               return NULL;
+
+       /* Search for the next line termination. NUL terminate. */
+       p = g_strstr_len(s, l, "\n");
+       if (!p)
+               return NULL;
+       *p++ = '\0';
+       if (taken)
+               *taken += p - s;
+       l -= p - s;
+       if (next)
+               *next = l ? p : NULL;
+
+       /* Trim NUL terminated text line at both ends. */
+       s = sr_text_trim_spaces(s);
+       return s;
+}
+
+/**
+ * Isolates another space separated word in a text line.
+ *
+ * @param[in] s The input text, current read position.
+ * @param[out] next The position after the current word.
+ *
+ * @return The start of the current word. Or #NULL if there is none.
+ *
+ * Advances over leading whitespace. Isolates (NUL terminates) the next
+ * whitespace separated word. Optionally returns the position after the
+ * current word. Manipulates the caller's input text in place.
+ *
+ * @since 0.6.0
+ */
+SR_API char *sr_text_next_word(char *s, char **next)
+{
+       char *word, *p;
+
+       word = s;
+       if (next)
+               *next = NULL;
+
+       /* Immediately reject incomplete input data. */
+       if (!word || !*word)
+               return NULL;
+
+       /* Advance over optional leading whitespace. */
+       while (isspace((int)*word))
+               word++;
+       if (!*word)
+               return NULL;
+
+       /*
+        * Advance until whitespace or end of text. Quick return when
+        * end of input is seen. Otherwise advance over whitespace and
+        * return the position of trailing text.
+        */
+       p = word;
+       while (*p && !isspace((int)*p))
+               p++;
+       if (!*p)
+               return word;
+       *p++ = '\0';
+       while (isspace((int)*p))
+               p++;
+       if (!*p)
+               return word;
+       if (next)
+               *next = p;
+       return word;
+}
+
+/**
+ * Get the number of necessary bits to hold a given value. Also gets
+ * the next power-of-two value at or above the caller provided value.
+ *
+ * @param[in] value The value that must get stored.
+ * @param[out] bits The required number of bits.
+ * @param[out] power The corresponding power-of-two.
+ *
+ * @return SR_OK upon success, SR_ERR* otherwise.
+ *
+ * TODO Move this routine to a more appropriate location, it is not
+ * strictly string related.
+ *
+ * @since 0.6.0
+ */
+SR_API int sr_next_power_of_two(size_t value, size_t *bits, size_t *power)
+{
+       size_t need_bits;
+       size_t check_mask;
+
+       if (bits)
+               *bits = 0;
+       if (power)
+               *power = 0;
+
+       /*
+        * Handle the special case of input value 0 (needs 1 bit
+        * and results in "power of two" value 1) here. It is not
+        * covered by the generic logic below.
+        */
+       if (!value) {
+               if (bits)
+                       *bits = 1;
+               if (power)
+                       *power = 1;
+               return SR_OK;
+       }
+
+       need_bits = 0;
+       check_mask = 0;
+       do {
+               need_bits++;
+               check_mask <<= 1;
+               check_mask |= 1UL << 0;
+       } while (value & ~check_mask);
+
+       if (bits)
+               *bits = need_bits;
+       if (power)
+               *power = ++check_mask;
+       return SR_OK;
+}
+
 /** @} */
index 1a9c4c58e123145d6ed2015e3eb2677d7084b053..41052d0f8f54ad6b43e74cad09c08f2b42782077 100644 (file)
  * Software limits helper functions
  */
 
-#include <config.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
+#include "config.h"
+
 #include <ctype.h>
 #include <libsigrok/libsigrok.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "sw_limits"
@@ -55,10 +57,11 @@ SR_PRIV void sr_sw_limits_init(struct sr_sw_limits *limits)
  * @param limits software limit instance
  * @param key config item key
  * @param data config item data
+ *
  * @return SR_ERR_NA if @p key is not a supported limit, SR_OK otherwise
  */
-SR_PRIV int sr_sw_limits_config_get(const struct sr_sw_limits *limits, uint32_t key,
-       GVariant **data)
+SR_PRIV int sr_sw_limits_config_get(const struct sr_sw_limits *limits,
+       uint32_t key, GVariant **data)
 {
        switch (key) {
        case SR_CONF_LIMIT_SAMPLES:
@@ -86,10 +89,11 @@ SR_PRIV int sr_sw_limits_config_get(const struct sr_sw_limits *limits, uint32_t
  * @param limits software limit instance
  * @param key config item key
  * @param data config item data
+ *
  * @return SR_ERR_NA if @p key is not a supported limit, SR_OK otherwise
  */
-SR_PRIV int sr_sw_limits_config_set(struct sr_sw_limits *limits, uint32_t key,
-       GVariant *data)
+SR_PRIV int sr_sw_limits_config_set(struct sr_sw_limits *limits,
+       uint32_t key, GVariant *data)
 {
        switch (key) {
        case SR_CONF_LIMIT_SAMPLES:
@@ -130,6 +134,7 @@ SR_PRIV void sr_sw_limits_acquisition_start(struct sr_sw_limits *limits)
  * processing has been done.
  *
  * @param limits software limits instance
+ *
  * @returns TRUE if any of the software limits has been reached and the driver
  *               should stop data acquisition, otherwise FALSE.
  */
@@ -184,6 +189,7 @@ SR_PRIV gboolean sr_sw_limits_check(struct sr_sw_limits *limits)
  * @param[out] samples remaining samples count until the limit is reached
  * @param[out] frames remaining frames count until the limit is reached
  * @param[out] msecs remaining milliseconds until the limit is reached
+ * @param[out] exceeded whether configured limits were reached before
  *
  * @return SR_ERR_* upon error, SR_OK otherwise
  */
diff --git a/src/tcp.c b/src/tcp.c
new file mode 100644 (file)
index 0000000..0870420
--- /dev/null
+++ b/src/tcp.c
@@ -0,0 +1,417 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+/* TODO
+ * Can we sort these include directives? Or do the platform specific
+ * headers depend on a specific order? Experience from VXI maintenance
+ * suggests that some systems can be picky and it's hard to notice ...
+ * For now the include statements follow the scpi_tcp.c template.
+ */
+#if defined _WIN32
+#define _WIN32_WINNT 0x0501
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+
+#include <errno.h>
+#include <glib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if !defined _WIN32
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif
+
+#if HAVE_POLL
+#include <poll.h>
+#elif HAVE_SELECT
+#include <sys/select.h>
+#endif
+
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+/*
+ * Workaround because Windows cannot simply use established identifiers.
+ * https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown
+ */
+#if !defined SHUT_RDWR && defined SD_BOTH
+#  define SHUT_RDWR SD_BOTH
+#endif
+
+#define LOG_PREFIX "tcp"
+
+/**
+ * Check whether a file descriptor is readable (without blocking).
+ *
+ * @param[in] fd The file descriptor to check for readability.
+ *
+ * @return TRUE when readable, FALSE when read would block or when
+ *   readability could not get determined.
+ *
+ * @since 6.0
+ *
+ * TODO Move to common code, applies to non-sockets as well.
+ */
+SR_PRIV gboolean sr_fd_is_readable(int fd)
+{
+#if HAVE_POLL
+       struct pollfd fds[1];
+       int ret;
+
+       memset(fds, 0, sizeof(fds));
+       fds[0].fd = fd;
+       fds[0].events = POLLIN;
+       ret = poll(fds, ARRAY_SIZE(fds), -1);
+       if (ret < 0)
+               return FALSE;
+       if (!ret)
+               return FALSE;
+       if (!(fds[0].revents & POLLIN))
+               return FALSE;
+
+       return TRUE;
+#elif HAVE_SELECT
+       fd_set rfds;
+       struct timeval tv;
+       int ret;
+
+       FD_ZERO(&rfds);
+       FD_SET(fd, &rfds);
+       memset(&tv, 0, sizeof(tv));
+       ret = select(fd + 1, &rfds, NULL, NULL, &tv);
+       if (ret < 0)
+               return FALSE;
+       if (!ret)
+               return FALSE;
+       if (!FD_ISSET(fd, rfds))
+               return FALSE;
+       return TRUE;
+#else
+       (void)fd;
+       return FALSE;
+#endif
+}
+
+/**
+ * Create a TCP communication instance.
+ *
+ * @param[in] host_addr The host name or IP address (a string).
+ * @param[in] tcp_port The TCP port number.
+ *
+ * @return A @ref sr_tcp_dev_inst structure on success. #NULL otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV struct sr_tcp_dev_inst *sr_tcp_dev_inst_new(
+       const char *host_addr, const char *tcp_port)
+{
+       char *host, *port;
+       struct sr_tcp_dev_inst *tcp;
+
+       host = NULL;
+       if (host_addr && *host_addr)
+               host = g_strdup(host_addr);
+       port = NULL;
+       if (tcp_port && *tcp_port)
+               port = g_strdup(tcp_port);
+
+       tcp = g_malloc0(sizeof(*tcp));
+       if (!tcp)
+               return NULL;
+       tcp->host_addr = host;
+       tcp->tcp_port = port;
+       tcp->sock_fd = -1;
+       return tcp;
+}
+
+/**
+ * Release a TCP communication instance.
+ *
+ * @param[in] host_addr The host name or IP address (a string).
+ * @param[in] tcp_port The TCP port number.
+ *
+ * @return A @ref sr_tcp_dev_inst structure on success. #NULL otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV void sr_tcp_dev_inst_free(struct sr_tcp_dev_inst *tcp)
+{
+
+       if (!tcp)
+               return;
+
+       (void)sr_tcp_disconnect(tcp);
+       g_free(tcp->host_addr);
+       g_free(tcp);
+}
+
+/**
+ * Construct display name for a TCP communication instance.
+ *
+ * @param[in] tcp The TCP communication instance to print the name of.
+ * @param[in] prefix An optional prefix text, or #NULL.
+ * @param[in] separator An optional separator character, or NUL.
+ * @param[out] path The caller provided buffer to fill in.
+ * @param[in] path_len The buffer's maximum length to fill in.
+ *
+ * @return SR_OK on success, SR_ERR_* otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV int sr_tcp_get_port_path(struct sr_tcp_dev_inst *tcp,
+       const char *prefix, char separator, char *path, size_t path_len)
+{
+       char sep_text[2];
+
+       /* Only construct connection name for full parameter sets. */
+       if (!tcp || !tcp->host_addr || !tcp->tcp_port)
+               return SR_ERR_ARG;
+
+       /* Normalize input. Apply defaults. */
+       if (!prefix)
+               prefix = "";
+       if (!*prefix && !separator)
+               separator = ':';
+
+       /* Turn everything into strings. Simplifies the printf() call. */
+       sep_text[0] = separator;
+       sep_text[1] = '\0';
+
+       /* Construct the resulting connection name. */
+       snprintf(path, path_len, "%s%s%s%s%s",
+               prefix, *prefix ? sep_text : "",
+               tcp->host_addr, sep_text, tcp->tcp_port);
+       return SR_OK;
+}
+
+/**
+ * Connect to a remote TCP communication peer.
+ *
+ * @param[in] tcp The TCP communication instance to connect.
+ *
+ * @return SR_OK on success, SR_ERR_* otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV int sr_tcp_connect(struct sr_tcp_dev_inst *tcp)
+{
+       struct addrinfo hints;
+       struct addrinfo *results, *r;
+       int ret;
+       int fd;
+
+       if (!tcp)
+               return SR_ERR_ARG;
+       if (!tcp->host_addr || !tcp->tcp_port)
+               return SR_ERR_ARG;
+
+       /* Lookup address information for the caller's spec. */
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = IPPROTO_TCP;
+       ret = getaddrinfo(tcp->host_addr, tcp->tcp_port, &hints, &results);
+       if (ret != 0) {
+               sr_err("Address lookup failed: %s:%s: %s.",
+                       tcp->host_addr, tcp->tcp_port, gai_strerror(ret));
+               return SR_ERR_DATA;
+       }
+
+       /* Try to connect using the resulting address details. */
+       fd = -1;
+       for (r = results; r; r = r->ai_next) {
+               fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+               if (fd < 0)
+                       continue;
+               ret = connect(fd, r->ai_addr, r->ai_addrlen);
+               if (ret != 0) {
+                       close(fd);
+                       fd = -1;
+                       continue;
+               }
+               break;
+       }
+       freeaddrinfo(results);
+       if (fd < 0) {
+               sr_err("Failed to connect to %s:%s: %s.",
+                       tcp->host_addr, tcp->tcp_port, g_strerror(errno));
+               return SR_ERR_IO;
+       }
+
+       tcp->sock_fd = fd;
+       return SR_OK;
+}
+
+/**
+ * Disconnect from a remote TCP communication peer.
+ *
+ * @param[in] tcp The TCP communication instance to disconnect.
+ *
+ * @return SR_OK on success, SR_ERR_* otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV int sr_tcp_disconnect(struct sr_tcp_dev_inst *tcp)
+{
+
+       if (!tcp)
+               return SR_ERR_ARG;
+
+       if (tcp->sock_fd < 0)
+               return SR_OK;
+
+       shutdown(tcp->sock_fd, SHUT_RDWR);
+       close(tcp->sock_fd);
+       tcp->sock_fd = -1;
+       return SR_OK;
+}
+
+/**
+ * Send transmit data to a TCP connection.
+ * Does a single operating system call, can return with short
+ * transmit byte counts. Will not continue after short writes,
+ * callers need to handle the condition.
+ *
+ * @param[in] tcp The TCP communication instance to send to.
+ * @param[in] data The data bytes to send.
+ * @param[in] dlen The number of bytes to send.
+ *
+ * @return Number of transmitted bytes on success, SR_ERR_* otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV int sr_tcp_write_bytes(struct sr_tcp_dev_inst *tcp,
+       const uint8_t *data, size_t dlen)
+{
+       ssize_t rc;
+       size_t written;
+
+       if (!tcp)
+               return SR_ERR_ARG;
+       if (!dlen)
+               return 0;
+       if (!data)
+               return SR_ERR_ARG;
+
+       if (tcp->sock_fd < 0)
+               return SR_ERR_IO;
+
+       rc = send(tcp->sock_fd, data, dlen, 0);
+       if (rc < 0)
+               return SR_ERR_IO;
+       written = (size_t)rc;
+       return written;
+}
+
+/**
+ * Fetch receive data from a TCP connection.
+ * Does a single operating system call, can return with short
+ * receive byte counts. Will not continue after short reads,
+ * callers need to handle the condition.
+ *
+ * @param[in] tcp The TCP communication instance to read from.
+ * @param[in] data Caller provided buffer for receive data.
+ * @param[in] dlen The maximum number of bytes to receive.
+ * @param[in] nonblocking Whether to block for receive data.
+ *
+ * @return Number of received bytes on success, SR_ERR_* otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV int sr_tcp_read_bytes(struct sr_tcp_dev_inst *tcp,
+       uint8_t *data, size_t dlen, gboolean nonblocking)
+{
+       ssize_t rc;
+       size_t got;
+
+       if (!tcp)
+               return SR_ERR_ARG;
+       if (!dlen)
+               return 0;
+       if (!data)
+               return SR_ERR_ARG;
+
+       if (tcp->sock_fd < 0)
+               return SR_ERR_IO;
+
+       if (nonblocking && !sr_fd_is_readable(tcp->sock_fd))
+               return 0;
+
+       rc = recv(tcp->sock_fd, data, dlen, 0);
+       if (rc < 0)
+               return SR_ERR_IO;
+       got = (size_t)rc;
+       return got;
+}
+
+/**
+ * Register receive callback for a TCP connection.
+ * The connection must have been established before. The callback
+ * gets invoked when receive data is available. Or when a timeout
+ * has expired.
+ *
+ * This is a simple wrapper around @ref sr_session_source_add().
+ *
+ * @param[in] session See @ref sr_session_source_add().
+ * @param[in] tcp The TCP communication instance to read from.
+ * @param[in] events See @ref sr_session_source_add().
+ * @param[in] timeout See @ref sr_session_source_add().
+ * @param[in] cb See @ref sr_session_source_add().
+ * @param[in] cb_data See @ref sr_session_source_add().
+ *
+ * @return SR_OK on success, SR_ERR* otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV int sr_tcp_source_add(struct sr_session *session,
+       struct sr_tcp_dev_inst *tcp, int events, int timeout,
+       sr_receive_data_callback cb, void *cb_data)
+{
+       if (!tcp || tcp->sock_fd < 0)
+               return SR_ERR_ARG;
+       return sr_session_source_add(session, tcp->sock_fd,
+               events, timeout, cb, cb_data);
+}
+
+/**
+ * Unregister receive callback for a TCP connection.
+ *
+ * This is a simple wrapper around @ref sr_session_source_remove().
+ *
+ * @param[in] session See @ref sr_session_source_remove().
+ * @param[in] tcp The TCP communication instance to unregister.
+ *
+ * @return SR_OK on success, SR_ERR* otherwise.
+ *
+ * @since 6.0
+ */
+SR_PRIV int sr_tcp_source_remove(struct sr_session *session,
+       struct sr_tcp_dev_inst *tcp)
+{
+       if (!tcp || tcp->sock_fd < 0)
+               return SR_ERR_ARG;
+       return sr_session_source_remove(session, tcp->sock_fd);
+}
index 6bd9e8182960cc330164350ccc4f37facd8a7aea..feafe9d2a2ae0aefbb4389638f9e7676a643ba9f 100644 (file)
--- a/src/usb.c
+++ b/src/usb.c
@@ -297,68 +297,116 @@ static GSource *usb_source_new(struct sr_session *session,
 }
 
 /**
- * Find USB devices according to a connection string.
+ * Extract VID:PID or bus.addr from a connection string.
  *
- * @param usb_ctx libusb context to use while scanning.
- * @param conn Connection string specifying the device(s) to match. This
- * can be of the form "<bus>.<address>", or "<vendorid>.<productid>".
+ * @param[in] conn Connection string.
+ * @param[out] vid Pointer to extracted vendor ID. Can be #NULL.
+ * @param[out] pid Pointer to extracted product ID. Can be #NULL.
+ * @param[out] bus Pointer to extracted bus number. Can be #NULL.
+ * @param[out] addr Pointer to extracted device number. Can be #NULL.
  *
- * @return A GSList of struct sr_usb_dev_inst, with bus and address fields
- * matching the device that matched the connection string. The GSList and
- * its contents must be freed by the caller.
+ * @return SR_OK when parsing succeeded, SR_ERR* otherwise.
+ *
+ * @private
+ *
+ * The routine fills in the result variables, and returns the scan success
+ * in the return code. Callers can specify #NULL for variable references
+ * if they are not interested in specific aspects of the USB address.
  */
-SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn)
+SR_PRIV int sr_usb_split_conn(const char *conn,
+       uint16_t *vid, uint16_t *pid, uint8_t *bus, uint8_t *addr)
 {
-       struct sr_usb_dev_inst *usb;
-       struct libusb_device **devlist;
-       struct libusb_device_descriptor des;
-       GSList *devices;
+       gboolean valid;
        GRegex *reg;
        GMatchInfo *match;
-       int vid, pid, bus, addr, b, a, ret, i;
        char *mstr;
+       uint32_t num;
+
+       if (vid) *vid = 0;
+       if (pid) *pid = 0;
+       if (bus) *bus = 0;
+       if (addr) *addr = 0;
 
-       vid = pid = bus = addr = 0;
+       valid = TRUE;
        reg = g_regex_new(CONN_USB_VIDPID, 0, 0, NULL);
        if (g_regex_match(reg, conn, 0, &match)) {
-               if ((mstr = g_match_info_fetch(match, 1)))
-                       vid = strtoul(mstr, NULL, 16);
+               /* Found a VID:PID style pattern. */
+               if ((mstr = g_match_info_fetch(match, 1))) {
+                       num = strtoul(mstr, NULL, 16);
+                       if (num > 0xffff)
+                               valid = FALSE;
+                       if (vid)
+                               *vid = num & 0xffff;
+               }
                g_free(mstr);
 
-               if ((mstr = g_match_info_fetch(match, 2)))
-                       pid = strtoul(mstr, NULL, 16);
+               if ((mstr = g_match_info_fetch(match, 2))) {
+                       num = strtoul(mstr, NULL, 16);
+                       if (num > 0xffff)
+                               valid = FALSE;
+                       if (pid)
+                               *pid = num & 0xffff;
+               }
                g_free(mstr);
-               /* Trying to find USB device via VID:PID. */
        } else {
                g_match_info_unref(match);
                g_regex_unref(reg);
                reg = g_regex_new(CONN_USB_BUSADDR, 0, 0, NULL);
                if (g_regex_match(reg, conn, 0, &match)) {
-                       if ((mstr = g_match_info_fetch(match, 1)))
-                               bus = strtoul(mstr, NULL, 10);
+                       /* Found a bus.address style pattern. */
+                       if ((mstr = g_match_info_fetch(match, 1))) {
+                               num = strtoul(mstr, NULL, 10);
+                               if (num > 255)
+                                       valid = FALSE;
+                               if (bus)
+                                       *bus = num & 0xff;
+                       }
                        g_free(mstr);
 
-                       if ((mstr = g_match_info_fetch(match, 2)))
-                               addr = strtoul(mstr, NULL, 10);
+                       if ((mstr = g_match_info_fetch(match, 2))) {
+                               num = strtoul(mstr, NULL, 10);
+                               if (num > 127)
+                                       valid = FALSE;
+                               if (addr)
+                                       *addr = num & 0x7f;
+                       }
                        g_free(mstr);
-                       /* Trying to find USB device via bus.address. */
                }
        }
        g_match_info_unref(match);
        g_regex_unref(reg);
 
-       if (vid + pid + bus + addr == 0) {
-               sr_err("Neither VID:PID nor bus.address was specified.");
-               return NULL;
-       }
+       return valid ? SR_OK : SR_ERR_ARG;
+}
 
-       if (bus > 255) {
-               sr_err("Invalid bus specified: %d.", bus);
+/**
+ * Find USB devices according to a connection string.
+ *
+ * @param usb_ctx libusb context to use while scanning.
+ * @param conn Connection string specifying the device(s) to match. This
+ * can be of the form "<bus>.<address>", or "<vendorid>.<productid>".
+ *
+ * @return A GSList of struct sr_usb_dev_inst, with bus and address fields
+ * matching the device that matched the connection string. The GSList and
+ * its contents must be freed by the caller.
+ */
+SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn)
+{
+       struct sr_usb_dev_inst *usb;
+       struct libusb_device **devlist;
+       struct libusb_device_descriptor des;
+       GSList *devices;
+       uint16_t vid, pid;
+       uint8_t bus, addr;
+       int b, a, ret, i;
+
+       ret = sr_usb_split_conn(conn, &vid, &pid, &bus, &addr);
+       if (ret != SR_OK) {
+               sr_err("Invalid input, or neither VID:PID nor bus.address specified.");
                return NULL;
        }
-
-       if (addr > 127) {
-               sr_err("Invalid address specified: %d.", addr);
+       if (!(vid && pid) && !(bus && addr)) {
+               sr_err("Could neither determine VID:PID nor bus.address numbers.");
                return NULL;
        }
 
@@ -372,12 +420,12 @@ SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn)
                        continue;
                }
 
-               if (vid + pid && (des.idVendor != vid || des.idProduct != pid))
+               if (vid && pid && (des.idVendor != vid || des.idProduct != pid))
                        continue;
 
                b = libusb_get_bus_number(devlist[i]);
                a = libusb_get_device_address(devlist[i]);
-               if (bus + addr && (b != bus || a != addr))
+               if (bus && addr && (b != bus || a != addr))
                        continue;
 
                sr_dbg("Found USB device (VID:PID = %04x:%04x, bus.address = "
index 402c7ca6093f36c9def1b1617cab190127e55819..912aedad30d91f5318251f9cf2cb14f8f472fea4 100644 (file)
@@ -18,6 +18,9 @@
  */
 
 #include <config.h>
+
+/* Request inclusion of the git version suffix in version.h. */
+#define WANT_LIBSIGROK_GIT_VERSION_H
 #include <libsigrok/libsigrok.h>
 
 /**
index 8490bf9fca2a14e90cb4e04eb4f2f72fc5a9ddb3..904f0cea2fa57ab20aa7bfe4ac56b9f133328944 100644 (file)
@@ -598,7 +598,7 @@ END_TEST
 
 START_TEST(test_mult_rational)
 {
-       const struct sr_rational r[][3] = {
+       static const struct sr_rational r[][3] = {
                /*   a    *    b    =    c   */
                { { 1, 1 }, { 1, 1 }, { 1, 1 }},
                { { 2, 1 }, { 3, 1 }, { 6, 1 }},
@@ -625,13 +625,15 @@ START_TEST(test_mult_rational)
                { { 10000*3, 4 }, { -80000*3, 1 }, { -200000000*9, 1 }},
        };
 
-       for (unsigned i = 0; i < ARRAY_SIZE(r); i++) {
-               struct sr_rational res;
+       size_t i;
+       struct sr_rational res;
+       int rc;
 
-               int rc = sr_rational_mult(&res, &r[i][0], &r[i][1]);
+       for (i = 0; i < ARRAY_SIZE(r); i++) {
+               rc = sr_rational_mult(&res, &r[i][0], &r[i][1]);
                fail_unless(rc == SR_OK);
                fail_unless(sr_rational_eq(&res, &r[i][2]) == 1,
-                       "sr_rational_mult() failed: [%d] %ld/%lu != %ld/%lu.",
+                       "sr_rational_mult() failed: [%zu] %" PRIi64 "/%" PRIu64 " != %" PRIi64 "/%" PRIu64 ".",
                        i, res.p, res.q, r[i][2].p, r[i][2].q);
        }
 }
@@ -639,7 +641,7 @@ END_TEST
 
 START_TEST(test_div_rational)
 {
-       const struct sr_rational r[][3] = {
+       static const struct sr_rational r[][3] = {
                /*   a    *    b    =    c   */
                { { 1, 1 }, { 1, 1 }, { 1, 1 }},
                { { 2, 1 }, { 1, 3 }, { 6, 1 }},
@@ -662,19 +664,20 @@ START_TEST(test_div_rational)
                { { 10000*3, 4 }, { -1, 80000*3 }, { -200000000*9, 1 }},
        };
 
-       for (unsigned i = 0; i < ARRAY_SIZE(r); i++) {
-               struct sr_rational res;
+       size_t i;
+       struct sr_rational res;
+       int rc;
 
-               int rc = sr_rational_div(&res, &r[i][0], &r[i][1]);
+       for (i = 0; i < ARRAY_SIZE(r); i++) {
+               rc = sr_rational_div(&res, &r[i][0], &r[i][1]);
                fail_unless(rc == SR_OK);
                fail_unless(sr_rational_eq(&res, &r[i][2]) == 1,
-                       "sr_rational_mult() failed: [%d] %ld/%lu != %ld/%lu.",
+                       "sr_rational_mult() failed: [%zu] %" PRIi64 "/%" PRIu64 " != %" PRIi64 "/%" PRIu64 ".",
                        i, res.p, res.q, r[i][2].p, r[i][2].q);
        }
 
        {
-               struct sr_rational res;
-               int rc = sr_rational_div(&res, &r[0][0], &((struct sr_rational){ 0, 5 }));
+               rc = sr_rational_div(&res, &r[0][0], &((struct sr_rational){ 0, 5 }));
 
                fail_unless(rc == SR_ERR_ARG);
        }
index 5ef0bab110397914249d85c29fa4e84bc8aa9c74..788c36728c37723581e3edc2a3825409458ce989 100644 (file)
@@ -114,6 +114,36 @@ START_TEST(test_endian_read)
 }
 END_TEST
 
+START_TEST(test_endian_read_inc_len)
+{
+       const uint8_t *p;
+       size_t l;
+
+       /* Position to the start of the input stream. */
+       p = &buff1234[0];
+       l = sizeof(buff1234);
+
+       /* Read several fields of known type and values. */
+       fail_unless(l == 8);
+       fail_unless(read_u8_inc_len(&p, &l) == 0x11);
+       fail_unless(l == 7);
+       fail_unless(read_u8_inc_len(&p, &l) == 0x22);
+       fail_unless(l == 6);
+       fail_unless(read_u16le_inc_len(&p, &l) == 0x4433);
+       fail_unless(l == 4);
+       fail_unless(read_u16le_inc_len(&p, &l) == 0x6655);
+       fail_unless(l == 2);
+       fail_unless(read_u16le_inc_len(&p, &l) == 0x8877);
+       fail_unless(l == 0);
+
+       /* Read beyond the end of the input stream. */
+       fail_unless(read_u8_inc_len(&p, &l) == 0x0);
+       fail_unless(l == 0);
+       fail_unless(read_u16le_inc_len(&p, &l) == 0x0);
+       fail_unless(l == 0);
+}
+END_TEST
+
 START_TEST(test_endian_read_inc)
 {
        const uint8_t *p;
@@ -228,6 +258,13 @@ START_TEST(test_endian_write_inc)
        l = p - &buff[0];
        fail_unless(l == 4 * 48 / 8 * sizeof(uint8_t));
        fail_unless(memcmp(&buff[0], &buff1234large[0], l) == 0);
+
+       p = &buff[0];
+       write_u24le_inc(&p, 0xfe030201);
+       write_u40le_inc(&p, 0xdcba0807060504ul);
+       l = p - &buff[0];
+       fail_unless(l == 24 / 8 + 40 / 8);
+       fail_unless(memcmp(&buff[0], &buff1234large[0], l) == 0);
 }
 END_TEST
 
@@ -242,6 +279,7 @@ Suite *suite_conv(void)
        tcase_add_test(tc, test_endian_macro);
        tcase_add_test(tc, test_endian_read);
        tcase_add_test(tc, test_endian_read_inc);
+       tcase_add_test(tc, test_endian_read_inc_len);
        tcase_add_test(tc, test_endian_write);
        tcase_add_test(tc, test_endian_write_inc);
        suite_add_tcase(s, tc);
index ec2233329b769c5eecc9597bdeb6d8e7564f4694..3e43dbdca2c2ba2a278c6e72b6306656da33be88 100644 (file)
@@ -121,7 +121,7 @@ static void test_rational(const char *input, struct sr_rational expected)
        fail_unless(ret == SR_OK, "Unexpected rc for '%s': %d, errno %d.",
                input, ret, errno);
        fail_unless((expected.p == rational.p) && (expected.q == rational.q),
-                   "Invalid result for '%s': %ld/%ld'.",
+                   "Invalid result for '%s': %" PRIi64 "/%" PRIu64 "'.",
                    input, rational.p, rational.q);
 }
 
@@ -430,6 +430,184 @@ START_TEST(test_exponent)
 }
 END_TEST
 
+START_TEST(test_text_line)
+{
+       /*
+        * Covers text line splitting as used in input modules. Accepts
+        * input with differing end-of-line conventions, accepts leading
+        * and trailing whitespace. Isolates "the core" of a text line.
+        * Supports repeated calls which accumulate what later needs to
+        * get discarded after input data got processed in pieces.
+        */
+#define EOL            "\n"
+
+#define TEXT_CORE_1    "Need to provide"
+#define TEXT_CORE_2    "an input text"
+#define TEXT_CORE_3    ""
+#define TEXT_CORE_4    "with empty lines and  funny  spacing perhaps?"
+
+#define TEXT_LINE_1    TEXT_CORE_1 " \n"
+#define TEXT_LINE_2    "  " TEXT_CORE_2 "\n"
+#define TEXT_LINE_3    TEXT_CORE_3 "\r\n"
+#define TEXT_LINE_4    TEXT_CORE_4 "\n"
+
+#define TEXT_INPUT     TEXT_LINE_1 TEXT_LINE_2 TEXT_LINE_3 TEXT_LINE_4
+
+       char *input_text, *read_pos, *next_pos, *line;
+       size_t input_len, taken;
+
+       input_text = g_strdup(TEXT_INPUT);
+       read_pos = input_text;
+       input_len = strlen(input_text);
+
+       /* Cover first line in tests. */
+       taken = 0;
+       line = sr_text_next_line(read_pos, input_len, &next_pos, &taken);
+       fail_unless(line, "Text line not found");
+       fail_unless(strcmp(line, TEXT_CORE_1) == 0, "Unexpected line content");
+       fail_unless(next_pos, "No next line found");
+       fail_unless(strncmp(next_pos, TEXT_LINE_2, strlen(TEXT_LINE_2)) == 0,
+               "Unexpected next line content");
+       fail_unless(taken == strlen(TEXT_LINE_1),
+               "Unexpected consumed count");
+       read_pos = next_pos;
+       input_len -= taken;
+       taken = 0;
+
+       /* Cover second line in tests. DO NOT void 'taken' yet. */
+       line = sr_text_next_line(read_pos, input_len, &next_pos, &taken);
+       fail_unless(line, "Text line not found");
+       fail_unless(strcmp(line, TEXT_CORE_2) == 0,
+               "Unexpected text line content");
+       fail_unless(next_pos, "No next line found");
+       fail_unless(strncmp(next_pos, TEXT_LINE_3, strlen(TEXT_LINE_3)) == 0,
+               "Unexpected next line content");
+       fail_unless(taken == strlen(TEXT_LINE_2),
+               "Unexpected consumed count");
+       input_len -= next_pos - read_pos;
+       read_pos = next_pos;
+
+       /* Cover third line in tests. Accumulates 'taken'. */
+       line = sr_text_next_line(read_pos, input_len, &next_pos, &taken);
+       fail_unless(line, "Text line not found");
+       fail_unless(strcmp(line, TEXT_CORE_3) == 0, "Unexpected line content");
+       fail_unless(next_pos, "No next line found");
+       fail_unless(strncmp(next_pos, TEXT_LINE_4, strlen(TEXT_LINE_4)) == 0,
+               "Unexpected next line content");
+       fail_unless(taken == strlen(TEXT_LINE_2) + strlen(TEXT_LINE_3),
+               "Unexpected consumed count (totalled)");
+       input_len -= next_pos - read_pos;
+       read_pos = next_pos;
+       taken = 0;
+
+       /* Cover last line in tests. */
+       line = sr_text_next_line(read_pos, input_len, &next_pos, &taken);
+       fail_unless(line, "Text line not found");
+       fail_unless(strcmp(line, TEXT_CORE_4) == 0,
+               "Unexpected text line content");
+       fail_unless(!next_pos, "Next line found, unexpected");
+       fail_unless(taken == strlen(TEXT_LINE_4),
+               "Unexpected consumed count");
+       input_len -= taken;
+       read_pos = next_pos;
+
+       /* All input must have been consumed. */
+       fail_unless(!read_pos);
+       fail_unless(!input_len);
+
+       g_free(input_text);
+}
+END_TEST
+
+/*
+ * TODO Ideally this table of test cases should reside within the
+ * test_text_word() routine. But compilation fails when it's put there
+ * (initializers are said to not be constant, cause is yet uncertain).
+ */
+static const struct {
+       const char *line;
+       const char **words;
+} word_cases[] = {
+       { "", (const char *[]){ NULL, }, },
+       { " ", (const char *[]){ NULL, }, },
+       { "one", (const char *[]){ "one", NULL, }, },
+       { "one ", (const char *[]){ "one", NULL, }, },
+       { " one ", (const char *[]){ "one", NULL, }, },
+       { " one two ", (const char *[]){ "one", "two", NULL, }, },
+       { "one  two three ",
+               (const char *[]){ "one", "two", "three", NULL, },
+       },
+};
+
+START_TEST(test_text_word)
+{
+       size_t case_idx, word_idx;
+       char *line;
+       const char **words, *want;
+       char *read_pos, *next_pos, *have;
+
+       for (case_idx = 0; case_idx < ARRAY_SIZE(word_cases); case_idx++) {
+               line = g_strdup(word_cases[case_idx].line);
+               words = word_cases[case_idx].words;
+               word_idx = 0;
+
+               read_pos = line;
+               while (read_pos) {
+                       want = words[word_idx];
+                       have = sr_text_next_word(read_pos, &next_pos);
+                       if (!want) {
+                               fail_unless(!have, "word found, unexpected");
+                               fail_unless(!next_pos, "next found after end");
+                               break;
+                       }
+                       word_idx++;
+                       read_pos = next_pos;
+                       fail_unless(have, "word not found");
+                       fail_unless(strcmp(have, want) == 0,
+                               "unexpected word found");
+               }
+               fail_unless(!words[word_idx], "missed expected words");
+
+               g_free(line);
+       }
+}
+END_TEST
+
+static const struct power_case_t {
+       size_t value;
+       size_t want_bits;
+       size_t want_power;
+} power_cases[] = {
+       { 0, 1, 1, },
+       { 1, 1, 2, },
+       { 2, 2, 4, },
+       { 3, 2, 4, },
+       { 4, 3, 8, },
+       { 5, 3, 8, },
+       { 6, 3, 8, },
+       { 7, 3, 8, },
+       { 8, 4, 16, },
+       { 15, 4, 16, },
+       { 16, 5, 32, },
+       { 31, 5, 32, },
+};
+
+START_TEST(test_calc_power_of_two)
+{
+       size_t case_idx, bits, power;
+       const struct power_case_t *tcase;
+       int ret;
+
+       for (case_idx = 0; case_idx < ARRAY_SIZE(power_cases); case_idx++) {
+               tcase = &power_cases[case_idx];
+               ret = sr_next_power_of_two(tcase->value, &bits, &power);
+               fail_unless(ret == SR_OK, "bits count not found");
+               fail_unless(bits == tcase->want_bits, "bits count differs");
+               fail_unless(power == tcase->want_power, "power differs");
+       }
+}
+END_TEST
+
 Suite *suite_strutil(void)
 {
        Suite *s;
@@ -452,5 +630,14 @@ Suite *suite_strutil(void)
        tcase_add_test(tc, test_exponent);
        suite_add_tcase(s, tc);
 
+       tc = tcase_create("text");
+       tcase_add_test(tc, test_text_line);
+       tcase_add_test(tc, test_text_word);
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("calc");
+       tcase_add_test(tc, test_calc_power_of_two);
+       suite_add_tcase(s, tc);
+
        return s;
 }
index fa3a63cbd383a5c00201a33b4e0b92f47a872852..2b0faff3496d36183b9ad218611f11f90d9e3c89 100644 (file)
@@ -70,27 +70,31 @@ END_TEST
  *
  * The upper limit assumes:
  * - The major, minor, and micro parts won't contain more than two
- *   digits each (this is an arbitrary choice).
+ *   digits each (this is an arbitrary choice). The three numbers
+ *   are separated by a period character.
  * - An optional "-git-<hash>" suffix might follow. While git(1)
  *   defaults to 7 hex digits for abbreviated hashes, projects of
  *   larger scale might recommend to use more digits to avoid
  *   potential ambiguity (e.g. Linux recommends core.abbrev=12).
  *   Again, this is an arbitrary choice.
+ * - An optional "-dirty" suffix might follow.
  */
 START_TEST(test_version_strings)
 {
        const char *str;
        const size_t len_min = 5;
-       const size_t len_max = 2 + 1 + 2 + 1 + 2 + 5 + 12;
+       const size_t len_max = 2 + 1 + 2 + 1 + 2 + 5 + 12 + 6;
 
        str = sr_package_version_string_get();
        fail_unless(str != NULL);
        fail_unless(strlen(str) >= len_min);
-       fail_unless(strlen(str) <= len_max);
+       fail_unless(strlen(str) <= len_max,
+               "Max len exceeded, max %zu, text %s", len_max, str);
        str = sr_lib_version_string_get();
        fail_unless(str != NULL);
        fail_unless(strlen(str) >= len_min);
-       fail_unless(strlen(str) <= len_max);
+       fail_unless(strlen(str) <= len_max,
+               "Max len exceeded, max %zu, text %s", len_max, str);
 }
 END_TEST