]> 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.

160 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/backend.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.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/chronovu-la/protocol.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/ftdi-la/api.c
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/ikalogic-scanaplus/api.c
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/uni-t-ut181a/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/session.c
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 94cbbe02bebbbd874b3a59e9b5514f98355be2b7..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,13 +89,17 @@ 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/stf.c \
        src/input/trace32_ad.c \
        src/input/vcd.c \
        src/input/wav.c \
        src/input/null.c
+if HAVE_INPUT_STF
+libsigrok_la_SOURCES += \
+       src/input/stf.c
+endif
 
 # Output modules
 libsigrok_la_SOURCES += \
@@ -142,7 +152,11 @@ 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 += \
+       src/serial.c
 endif
 if NEED_USB
 libsigrok_la_SOURCES += \
@@ -250,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 \
@@ -315,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 \
@@ -351,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 \
@@ -411,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 \
@@ -435,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 \
@@ -543,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 \
@@ -710,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
 
@@ -752,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
@@ -783,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 d1f965667bc828ffd60d64fbbb0d07129d0fa49f..8686a262889351fae0e088883be6b8157bcdd4c1 100644 (file)
--- a/README
+++ b/README
@@ -37,6 +37,7 @@ Requirements for the C library:
  - libtool (only needed when building from git)
  - pkg-config >= 0.22
  - libglib >= 2.32.0
+ - zlib (optional, used for CRC32 calculation in STF input)
  - libzip >= 0.10
  - libtirpc (optional, used by VXI, fallback when glibc >= 2.26)
  - libserialport >= 0.1.1 (optional, used by some drivers)
@@ -48,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)
@@ -62,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 6d139aa9af0b88b31f7e8f028ed6a12f054abe84..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
 ----------------------------------------------------
 
@@ -500,7 +539,7 @@ a protocol description and use of the protocol depends on the firmware's
 availability at the user's site. Which then would allow to capture to
 DRAM at high rates without the communication bottleneck, before the data
 gets communicated to the PC after the acquisition has completed. Compare
-the native sigrok supoprt for Asix Sigma.
+the native sigrok support for Asix Sigma.
 
 
 ChronoVu LA8/LA16 USB VID/PIDs
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 982f54300b40625041db9504f43dd97e3d27e6bd..5c30a8165f383484c07c0eab6210d085b7a376a7 100644 (file)
@@ -96,6 +96,18 @@ SR_PKGLIBS_RUBY=
 SR_EXTRA_LIBS=
 SR_EXTRA_CXX_LIBS=
 
+SR_ARG_OPT_PKG([zlib], [ZLIB], , [zlib])
+AM_CONDITIONAL([HAVE_ZLIB], [test "x$sr_have_zlib" = xyes])
+AM_COND_IF([HAVE_ZLIB], [
+       SR_APPEND([sr_deps_avail], [crc32 zlib])
+       SR_PREPEND([SR_EXTRA_LIBS], [-lz])
+])
+
+AM_CONDITIONAL([HAVE_INPUT_STF], [test "x$sr_have_zlib" = xyes])
+AM_COND_IF([HAVE_INPUT_STF], [
+       AC_DEFINE([HAVE_INPUT_STF], [1], [Is the STF input module supported?])
+])
+
 SR_ARG_OPT_PKG([libserialport], [LIBSERIALPORT], ,
        [libserialport >= 0.1.1])
 
@@ -213,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  ##
@@ -283,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])
@@ -293,15 +320,17 @@ 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], [serial_comm])
+SR_DRIVER([Hameg HMO], [hameg-hmo])
 SR_DRIVER([Hantek 4032L], [hantek-4032l], [libusb])
 SR_DRIVER([Hantek 6xxx], [hantek-6xxx], [libusb])
 SR_DRIVER([Hantek DSO], [hantek-dso], [libusb])
@@ -309,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])
@@ -331,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])
@@ -389,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.
@@ -426,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.])])
 ])
 
 #######################
@@ -604,6 +673,7 @@ AM_COND_IF([BINDINGS_CXX], [
 ])
 
 # Check for specific libusb features, now that we know the CFLAGS.
+# Also check for version dependent libftdi features.
 AC_LANG([C])
 sr_save_cflags=$CFLAGS
 sr_save_libs=$LIBS
@@ -613,17 +683,18 @@ AC_CHECK_TYPES([libusb_os_handle],
        [sr_have_libusb_os_handle=yes], [sr_have_libusb_os_handle=no],
        [[#include <libusb.h>]])
 AC_CHECK_FUNCS([zip_discard])
+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`
 
 AC_DEFINE_UNQUOTED([CONF_LIBZIP_VERSION], ["$sr_libzip_version"],
        [Build-time version of libzip.])
+AC_DEFINE_UNQUOTED([CONF_ZLIB_VERSION], ["$sr_zlib_version"],
+       [Build-time version of zlib.])
 AC_DEFINE_UNQUOTED([CONF_HOST], ["$host"],
        [The canonical host libsigrok will run on.])
 
@@ -643,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 79340822fb38f8e9cc817297e7b5db6f948fa282..82ad42f7ee6b7b2e442a78a7af93c06433dd4c3f 100644 (file)
@@ -137,6 +137,10 @@ SR_API GSList *sr_buildinfo_libs_get(void)
                glib_binary_age, glib_interface_age));
        l = g_slist_append(l, m);
 
+       m = g_slist_append(NULL, g_strdup("zlib"));
+       m = g_slist_append(m, g_strdup_printf("%s", CONF_ZLIB_VERSION));
+       l = g_slist_append(l, m);
+
        m = g_slist_append(NULL, g_strdup("libzip"));
        m = g_slist_append(m, g_strdup_printf("%s", CONF_LIBZIP_VERSION));
        l = g_slist_append(l, m);
index 9108f5bb7678983e04aad9c753696db4fbbbee0d..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, 1);
+       VALUE_TYPE(BVT_UINT8, read_u8, sizeof(uint8_t));
 
-               VALUE_TYPE(BVT_BE_UINT16, RB16, 2);
-               VALUE_TYPE(BVT_BE_UINT32, RB32, 4);
-               VALUE_TYPE(BVT_BE_UINT64, RB64, 8);
-               VALUE_TYPE(BVT_BE_FLOAT, RBFL, 4);
+       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, 2);
-               VALUE_TYPE(BVT_LE_UINT32, RL32, 4);
-               VALUE_TYPE(BVT_LE_UINT64, RL64, 8);
-               VALUE_TYPE(BVT_LE_FLOAT, RLFL, 4);
+       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 df59693db0e62841368ed10637db209ac853c02a..142316514c7ceab0ebda7fc8a157fa66cc495302 100644 (file)
@@ -625,7 +625,7 @@ static int sigma_fpga_init_bitbang_once(struct dev_context *devc)
        if (ret != SR_OK)
                return ret;
        g_usleep(10 * 1000);
-       ftdi_usb_purge_buffers(&devc->ftdi.ctx);
+       PURGE_FTDI_BOTH(&devc->ftdi.ctx);
 
        /*
         * Wait until the FPGA asserts INIT_B. Check in a maximum number
@@ -889,7 +889,7 @@ static int upload_firmware(struct sr_context *ctx, struct dev_context *devc,
                        ftdi_get_error_string(&devc->ftdi.ctx));
                return SR_ERR;
        }
-       ftdi_usb_purge_buffers(&devc->ftdi.ctx);
+       PURGE_FTDI_BOTH(&devc->ftdi.ctx);
        while (sigma_read_raw(devc, &pins, sizeof(pins)) > 0)
                ;
 
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 694bd54ac84fb91f0f864cefa67d9d31eb2a641c..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);
 
@@ -243,7 +254,7 @@ static int dev_open(struct sr_dev_inst *sdi)
                goto err_ftdi_free;
        }
 
-       if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0) {
+       if ((ret = PURGE_FTDI_BOTH(devc->ftdic)) < 0) {
                sr_err("Failed to purge FTDI buffers (%d): %s.",
                       ret, ftdi_get_error_string(devc->ftdic));
                goto err_ftdi_free;
index da772f5b1d8f5ffcdb1d3ab46227dad39dc2922d..ae8f427a7e9af002e6069382493be45375e47728 100644 (file)
@@ -202,7 +202,7 @@ static int close_usb_reset_sequencer(struct dev_context *devc)
                sr_dbg("Purging buffers, resetting+closing FTDI device.");
 
                /* Log errors, but ignore them (i.e., don't abort). */
-               if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0)
+               if ((ret = PURGE_FTDI_BOTH(devc->ftdic)) < 0)
                        sr_err("Failed to purge FTDI buffers (%d): %s.",
                               ret, ftdi_get_error_string(devc->ftdic));
                if ((ret = ftdi_usb_reset(devc->ftdic)) < 0)
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 8bb67121cc6c71051b279b200ffc6a6d95e2372d..2e7e59f49324c4b63310e4fb6c03ced6f229ed29 100644 (file)
@@ -325,7 +325,7 @@ static int dev_open(struct sr_dev_inst *sdi)
                goto err_ftdi_free;
        }
 
-       ret = ftdi_usb_purge_buffers(devc->ftdic);
+       ret = PURGE_FTDI_BOTH(devc->ftdic);
        if (ret < 0) {
                sr_err("Failed to purge FTDI RX/TX buffers (%d): %s.",
                       ret, ftdi_get_error_string(devc->ftdic));
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 f29e5c39ae327c24b37a4ade4047cecc4410bc98..fb3b137f3fc36f7ad620295494607cd0185aa0e5 100644 (file)
@@ -141,7 +141,7 @@ static int dev_open(struct sr_dev_inst *sdi)
                return SR_ERR;
        }
 
-       if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0) {
+       if ((ret = PURGE_FTDI_BOTH(devc->ftdic)) < 0) {
                sr_err("Failed to purge FTDI RX/TX buffers (%d): %s.",
                       ret, ftdi_get_error_string(devc->ftdic));
                goto err_dev_open_close_ftdic;
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 1b1b425a0204169440429807430067f4ac58064a..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];
+
+       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);
 
-#define MAX_NUM_LOGIC_THRESHOLD_ENTRIES ARRAY_SIZE(logic_threshold)
+       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;
+       g_slist_free_full(conn_devices, sr_usb_dev_inst_free_cb);
 
-       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);
-
-               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,35 +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->priv;
+       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 (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 (!sdi)
+                       return SR_ERR_ARG;
+               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;
        }
@@ -539,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);
 
@@ -791,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 f0ef680527abc48d925f9dae6b9bbcb1adeb5add..8f6f97f7d716e5aac585a72f49cf70a667517b6a 100644 (file)
@@ -90,7 +90,7 @@ SR_PRIV int p_ols_open(struct dev_context *devc)
                return SR_ERR;
        }
 
-       if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0) {
+       if ((ret = PURGE_FTDI_BOTH(devc->ftdic)) < 0) {
                sr_err("Failed to purge FTDI RX/TX buffers (%d): %s.",
                       ret, ftdi_get_error_string(devc->ftdic));
                goto err_open_close_ftdic;
@@ -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 456681ea49d6e7766db9b972dcd380f70a89a61d..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 },
-       { NULL, },
+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 25bad0dfc95ec7356fb5993aa5beb6301cfb38eb..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 },
-       { NULL, },
+       { "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 },
-       { NULL, },
+       { "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 95de8f6a0d7b30339a7fd6756ba3d4bfe556d0f6..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++) {
@@ -883,6 +877,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi)
        GSList *l;
        char *cmd;
        int protocol;
+       int ret;
 
        scpi = sdi->conn;
        devc = sdi->priv;
@@ -1021,20 +1016,27 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 
        devc->channel_entry = devc->enabled_channels;
 
-       if (devc->data_source == DATA_SOURCE_LIVE)
-               devc->sample_rate = analog_frame_size(sdi) / 
+       if (devc->data_source == DATA_SOURCE_LIVE) {
+               devc->sample_rate = analog_frame_size(sdi) /
                        (devc->timebase * devc->model->series->num_horizontal_divs);
-       else {
+       else {
                float xinc;
-               if (devc->model->series->protocol >= PROTOCOL_V3 && 
-                               sr_scpi_get_float(sdi->conn, "WAV:XINC?", &xinc) != SR_OK) {
-                       sr_err("Couldn't get sampling rate");
+               if (devc->model->series->protocol < PROTOCOL_V3) {
+                       sr_err("Cannot get samplerate (below V3).");
+                       return SR_ERR;
+               }
+               ret = sr_scpi_get_float(sdi->conn, "WAV:XINC?", &xinc);
+               if (ret != SR_OK) {
+                       sr_err("Cannot get samplerate (WAV:XINC? failed).");
+                       return SR_ERR;
+               }
+               if (!xinc) {
+                       sr_err("Cannot get samplerate (zero XINC value).");
                        return SR_ERR;
                }
                devc->sample_rate = 1. / xinc;
        }
 
-
        if (rigol_ds_capture_start(sdi) != SR_OK)
                return SR_ERR;
 
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 de64f2f273f1319f6fbeb207ed433014b70e1525..53fdf18cf9d59300e688641325b3a6d1a4116488 100644 (file)
@@ -203,7 +203,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        serial = sr_serial_dev_inst_new(conn, serialcomm);
        ret = serial_open(serial, SERIAL_RDWR);
        snprintf(conn_id, sizeof(conn_id), "%s", serial->port);
-       serial_flush(serial);
        /*
         * We cannot identify the device at this point in time.
         * Successful open shall suffice for now. More activity
@@ -532,6 +531,10 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi)
                devc->info.rec_data.samples_total = devc->wait_state.data_value;
                devc->info.rec_data.samples_curr = 0;
                ret = ut181a_send_cmd_get_rec_samples(serial, rec_idx, 0);
+       } else {
+               sr_err("Unhandled data source %d, programming error?",
+                       (int)devc->data_source);
+               ret = SR_ERR_BUG;
        }
        if (ret < 0)
                return ret;
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 bd5c16c7f621947f55cd43dd8f641acb3874edab..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;
@@ -95,6 +124,21 @@ SR_API int feed_queue_logic_flush(struct feed_queue_logic *q)
        return SR_OK;
 }
 
+SR_API int feed_queue_logic_send_trigger(struct feed_queue_logic *q)
+{
+       int ret;
+
+       ret = feed_queue_logic_flush(q);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = std_session_send_df_trigger(q->sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
 SR_API void feed_queue_logic_free(struct feed_queue_logic *q)
 {
 
@@ -147,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 8708cc637a886beb5829354d9b69fffdd1356611..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,
 };
 
@@ -171,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 cbf93be4aa9f1a612ad997ed85cd32e040a7ffdb..ddef5de6ee8bc2b72ef92f054329f194485e4c18 100644 (file)
@@ -623,17 +623,16 @@ 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;
-               feed_queue_logic_flush(inc->submit.feed);
                sr_dbg("Trigger: sending DF packet, at %" PRIu64 ".",
                        inc->submit.submit_count);
-               std_session_send_df_trigger(in->sdi);
+               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 4e0310bac67e4e55f7e56f6a124812d13dbfb3aa..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.
@@ -304,7 +290,7 @@ static void ts_stats_check_early(struct ts_stats *stats)
                if (stats->total_ts_seen != cp->count)
                        continue;
                /* First occurance of that timestamp count. Check the value. */
-               sr_dbg("TS early chk: total %" PRIu64 ", min delta %zu / %zu.",
+               sr_dbg("TS early chk: total %zu, min delta %" PRIu64 " / %" PRIu64 ".",
                        cp->count, seen_delta, check_delta);
                if (check_delta < cp->delta)
                        return;
@@ -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 = parts[1];
+       name_pos = sr_text_next_word(contents, &contents);
+       if (!name_pos || contents) {
+               sr_err("Cannot parse 'scope' directive");
+               return SR_ERR_DATA;
+       }
+
        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,10 +679,9 @@ 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;
        enum sr_channeltype ch_type;
        size_t size, next_size;
        struct vcd_channel *vcd_ch;
@@ -829,34 +690,36 @@ 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;
        is_int = g_strcmp0(type, "integer") == 0;
+       is_str = g_strcmp0(type, "string") == 0;
 
        if (is_reg || is_wire) {
                ch_type = SR_CHANNEL_LOGIC;
        } else if (is_real || is_int) {
                ch_type = SR_CHANNEL_ANALOG;
+       } else if (is_str) {
+               sr_warn("Skipping id %s, name '%s%s', unsupported type '%s'.",
+                       id, ref, idx ? idx : "", type);
+               inc->ignored_signals = g_slist_append(inc->ignored_signals,
+                       g_strdup(id));
+               return SR_OK;
        } else {
-               sr_info("Unsupported signal type: '%s'", type);
-               g_strfreev(parts);
+               sr_err("Unsupported signal type: '%s'", type);
                return SR_ERR_DATA;
        }
 
@@ -882,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)
@@ -893,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;
        }
 
@@ -922,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;
 }
@@ -1065,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.",
@@ -1080,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);
        }
 }
 
@@ -1126,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;
 
@@ -1165,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;
 
@@ -1180,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;
@@ -1188,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;
                }
 
@@ -1225,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. */
@@ -1295,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);
@@ -1308,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);
        }
@@ -1510,15 +1367,103 @@ static uint8_t vcd_char_to_value(char bit_char, int *warn)
        return ~0;
 }
 
+/*
+ * Check the validity of a VCD string value. It's essential to reliably
+ * accept valid data which the community uses in the field, yet robustly
+ * reject invalid data for users' awareness. Since IEEE 1800-2017 would
+ * not discuss the representation of this data type, it's assumed to not
+ * be an official feature of the VCD file format. This implementation is
+ * an educated guess after inspection of other arbitrary implementations,
+ * not backed by any specification or public documentation.
+ *
+ * A quick summary of the implemented assumptions: Must be a sequence of
+ * ASCII printables. Must not contain whitespace. Might contain escape
+ * sequences: A backslash followed by a single character, like '\n' or
+ * '\\'. Or a backslash and the letter x followed by two hex digits,
+ * like '\x20'. Or a backslash followed by three octal digits, like
+ * '\007'. As an exception also accepts a single digit '\0' but only at
+ * the text end. The string value may be empty, but must not be NULL.
+ *
+ * This implementation assumes an ASCII based platform for simplicity
+ * and readability. Should be a given on sigrok supported platforms.
+ */
+static gboolean vcd_string_valid(const char *s)
+{
+       char c;
+
+       if (!s)
+               return FALSE;
+
+       while (*s) {
+               c = *s++;
+               /* Reject non-printable ASCII chars including DEL. */
+               if (c < ' ')
+                       return FALSE;
+               if (c > '~')
+                       return FALSE;
+               /* Deeper inspection of escape sequences. */
+               if (c == '\\') {
+                       c = *s++;
+                       switch (c) {
+                       case 'a': /* BEL, bell aka "alarm" */
+                       case 'b': /* BS, back space */
+                       case 't': /* TAB, tabulator */
+                       case 'n': /* NL, newline */
+                       case 'v': /* VT, vertical tabulator */
+                       case 'f': /* FF, form feed */
+                       case 'r': /* CR, carriage return */
+                       case '"': /* double quotes */
+                       case '\'': /* tick, single quote */
+                       case '?': /* question mark */
+                       case '\\': /* backslash */
+                               continue;
+                       case 'x': /* \xNN two hex digits */
+                               c = *s++;
+                               if (!g_ascii_isxdigit(c))
+                                       return FALSE;
+                               c = *s++;
+                               if (!g_ascii_isxdigit(c))
+                                       return FALSE;
+                               continue;
+                       case '0': /* \NNN three octal digits */
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                               /* Special case '\0' at end of text. */
+                               if (c == '0' && !*s)
+                                       return TRUE;
+                               /*
+                                * First digit was covered by the outer
+                                * switch(). Two more digits to check.
+                                */
+                               c = *s++;
+                               if (!g_ascii_isdigit(c) || c > '7')
+                                       return FALSE;
+                               c = *s++;
+                               if (!g_ascii_isdigit(c) || c > '7')
+                                       return FALSE;
+                               continue;
+                       default:
+                               return FALSE;
+                       }
+               }
+       }
+
+       return TRUE;
+}
+
 /* 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;
-       gboolean is_timestamp, is_section, is_real, is_multibit, is_singlebit;
+       char *curr_word, curr_first;
+       gboolean is_timestamp, is_section;
+       gboolean is_real, is_multibit, is_singlebit, is_string;
        uint64_t timestamp;
        char *identifier, *endptr;
        size_t count;
@@ -1526,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
@@ -1697,6 +1645,7 @@ static int parse_textline(const struct sr_input *in, char *lines)
                 * timestamp.
                 *
                 * Supported input data formats are:
+                * - S<value> <sep> <id> (value not used, VCD type 'string').
                 * - R<value> <sep> <id> (analog channel, VCD type 'real').
                 * - B<value> <sep> <id> (analog channel, VCD type 'integer').
                 * - B<value> <sep> <id> (logic channels, VCD bit vectors).
@@ -1725,13 +1674,13 @@ static int parse_textline(const struct sr_input *in, char *lines)
                is_singlebit |= curr_first == 'l' || curr_first == 'h';
                is_singlebit |= curr_first == 'x' || curr_first == 'z';
                is_singlebit |= curr_first == 'u' || curr_first == '-';
+               is_string = curr_first == 's';
                if (is_real) {
                        char *real_text;
                        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;
@@ -1765,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.");
@@ -1851,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;
@@ -1872,13 +1818,37 @@ static int parse_textline(const struct sr_input *in, char *lines)
                        process_bits(inc, identifier, inc->conv_bits.value, 1);
                        continue;
                }
+               if (is_string) {
+                       const char *str_value;
+
+                       str_value = &curr_word[1];
+                       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;
+                               break;
+                       }
+                       if (!identifier || !*identifier) {
+                               sr_err("String value without identifier.");
+                               ret = SR_ERR_DATA;
+                               break;
+                       }
+                       sr_spew("Got string data, id '%s', value \"%s\".",
+                               identifier, str_value);
+                       if (!is_ignored(inc, identifier)) {
+                               sr_err("String value for identifier '%s'.",
+                                       identifier);
+                               ret = SR_ERR_DATA;
+                               break;
+                       }
+                       continue;
+               }
 
                /* Design choice: Consider unsupported input fatal. */
                sr_err("Unknown token '%s'.", curr_word);
                ret = SR_ERR_DATA;
                break;
        }
-       free_text_split(inc, words);
 
        return ret;
 }
@@ -1889,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);
@@ -1918,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;
 }
@@ -2054,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)
@@ -2089,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 7a5b717d6d4c7482534a185fbb30ab67e48e2cb4..68a45f27766160ebc092d561e678a39eabbf0547 100644 (file)
@@ -32,6 +32,9 @@
 #ifdef HAVE_LIBUSB_1_0
 #include <libusb.h>
 #endif
+#ifdef HAVE_LIBFTDI
+#include <ftdi.h>
+#endif
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -169,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
@@ -447,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
@@ -475,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
@@ -584,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.
@@ -635,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.
@@ -895,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.
@@ -921,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.
@@ -981,6 +1103,21 @@ static inline void write_dblle_inc(uint8_t **p, double x)
        libusb_handle_events_timeout(ctx, tv)
 #endif
 
+/*
+ * Convenience for FTDI library version dependency.
+ * - Version 1.5 introduced ftdi_tciflush(), ftdi_tcoflush(), and
+ *   ftdi_tcioflush() all within the same commit, and deprecated
+ *   ftdi_usb_purge_buffers() which suffered from inverse semantics.
+ *   The API is drop-in compatible (arguments count and data types are
+ *   identical). The libsigrok source code always flushes RX and TX at
+ *   the same time, never individually.
+ */
+#if defined HAVE_FTDI_TCIOFLUSH && HAVE_FTDI_TCIOFLUSH
+#  define PURGE_FTDI_BOTH ftdi_tcioflush
+#else
+#  define PURGE_FTDI_BOTH ftdi_usb_purge_buffers
+#endif
+
 /* Static definitions of structs ending with an all-zero entry are a
  * problem when compiling with -Wmissing-field-initializers: GCC
  * suppresses the warning only with { 0 }, clang wants { } */
@@ -1451,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;
@@ -1500,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;
@@ -1509,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
 
@@ -1529,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__)
@@ -1573,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. */
@@ -1611,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
@@ -1916,8 +2074,6 @@ SR_PRIV int serial_stream_detect(struct sr_serial_dev_inst *serial,
                size_t packet_size, packet_valid_callback is_valid,
                packet_valid_len_callback is_valid_len, size_t *return_size,
                uint64_t timeout_ms);
-SR_PRIV int sr_serial_extract_options(GSList *options, const char **serial_device,
-                                     const char **serial_options);
 SR_PRIV int serial_source_add(struct sr_session *session,
                struct sr_serial_dev_inst *serial, int events, int timeout,
                sr_receive_data_callback cb, void *cb_data);
@@ -1967,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 {
@@ -1996,6 +2154,9 @@ SR_PRIV const char *ser_hid_chip_find_name_vid_pid(uint16_t vid, uint16_t pid);
 #endif
 #endif
 
+SR_PRIV int sr_serial_extract_options(GSList *options,
+       const char **serial_device, const char **serial_options);
+
 /*--- bt/ API ---------------------------------------------------------------*/
 
 #ifdef HAVE_BLUETOOTH
@@ -2017,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);
@@ -2047,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);
@@ -2059,73 +2223,75 @@ 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 */
 enum binary_value_type {
-       BVT_UINT8 = 0,
-       BVT_BE_UINT8 = BVT_UINT8,
-       BVT_LE_UINT8 = BVT_UINT8,
+       BVT_INVALID,
+
+       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 -----------------------------------------------------------------*/
 
@@ -2688,16 +2854,23 @@ 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);
 
 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 97bd3159b094f673e0db8070d87eadb960e1cab8..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)
@@ -925,6 +927,8 @@ SR_PRIV int serial_stream_detect(struct sr_serial_dev_inst *serial,
        return SR_ERR;
 }
 
+#endif
+
 /**
  * Extract the serial device and options from the options linked list.
  *
@@ -977,6 +981,8 @@ SR_PRIV int sr_serial_extract_options(GSList *options,
        return SR_OK;
 }
 
+#ifdef HAVE_SERIAL_COMM
+
 /** @private */
 SR_PRIV int serial_source_add(struct sr_session *session,
        struct sr_serial_dev_inst *serial, int events, int timeout,
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 d1a19c56e9967a7187d3dc8fdfca01da25848ab1..8d3f66966a4f156c632d185d4f788274b15e58be 100644 (file)
@@ -1482,9 +1482,16 @@ SR_PRIV int sr_session_source_destroyed(struct sr_session *session,
 
 static void copy_src(struct sr_config *src, struct sr_datafeed_meta *meta_copy)
 {
+       struct sr_config *item;
+
+#if GLIB_CHECK_VERSION(2, 67, 3)
+       item = g_memdup2(src, sizeof(*src));
+#else
+       item = g_memdup(src, sizeof(*src));
+#endif
+
        g_variant_ref(src->data);
-       meta_copy->config = g_slist_append(meta_copy->config,
-                                          g_memdup(src, sizeof(struct sr_config)));
+       meta_copy->config = g_slist_append(meta_copy->config, item);
 }
 
 SR_API int sr_packet_copy(const struct sr_datafeed_packet *packet,
@@ -1496,6 +1503,9 @@ SR_API int sr_packet_copy(const struct sr_datafeed_packet *packet,
        struct sr_datafeed_logic *logic_copy;
        const struct sr_datafeed_analog *analog;
        struct sr_datafeed_analog *analog_copy;
+       struct sr_analog_encoding *encoding_copy;
+       struct sr_analog_meaning *meaning_copy;
+       struct sr_analog_spec *spec_copy;
        uint8_t *payload;
 
        *copy = g_malloc0(sizeof(struct sr_datafeed_packet));
@@ -1540,14 +1550,20 @@ SR_API int sr_packet_copy(const struct sr_datafeed_packet *packet,
                memcpy(analog_copy->data, analog->data,
                                analog->encoding->unitsize * analog->num_samples);
                analog_copy->num_samples = analog->num_samples;
-               analog_copy->encoding = g_memdup(analog->encoding,
-                               sizeof(struct sr_analog_encoding));
-               analog_copy->meaning = g_memdup(analog->meaning,
-                               sizeof(struct sr_analog_meaning));
+#if GLIB_CHECK_VERSION(2, 67, 3)
+               encoding_copy = g_memdup2(analog->encoding, sizeof(*analog->encoding));
+               meaning_copy = g_memdup2(analog->meaning, sizeof(*analog->meaning));
+               spec_copy = g_memdup2(analog->spec, sizeof(*analog->spec));
+#else
+               encoding_copy = g_memdup(analog->encoding, sizeof(*analog->encoding));
+               meaning_copy = g_memdup(analog->meaning, sizeof(*analog->meaning));
+               spec_copy = g_memdup(analog->spec, sizeof(*analog->spec));
+#endif
+               analog_copy->encoding = encoding_copy;
+               analog_copy->meaning = meaning_copy;
                analog_copy->meaning->channels = g_slist_copy(
                                analog->meaning->channels);
-               analog_copy->spec = g_memdup(analog->spec,
-                               sizeof(struct sr_analog_spec));
+               analog_copy->spec = spec_copy;
                (*copy)->payload = analog_copy;
                break;
        default:
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