/*.kdev4
/Makefile.am.user
+# clangd language server cruft
+.cache/
+/compile_commands.json
+
# Configure/build cruft
*.[ao]
*.l[ao]
/config.*
/doxy/
/include/libsigrok/version.h
+/include/libsigrok/git-version.h
/libsigrok-*.tar.*
/libsigrok.pc
/libtool
- 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).
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.
# 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@
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 += \
src/input/chronovu_la8.c \
src/input/csv.c \
src/input/logicport.c \
+ src/input/protocoldata.c \
src/input/raw_analog.c \
src/input/saleae.c \
src/input/trace32_ad.c \
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/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 \
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 \
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 \
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 \
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 \
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 \
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
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
BUILD_EXTRA =
INSTALL_EXTRA =
UNINSTALL_EXTRA =
-CLEAN_EXTRA =
libsigrok-uninstall:
-rmdir $(DESTDIR)$(includedir)/libsigrok
- 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:
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
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.
}
#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;
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);
}
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;
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) :
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);
}
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);
/* 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();
/** 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 */
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
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);
$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);
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);
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);
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);
}
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);
}
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);
}
# 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 ##
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])
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])
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])
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])
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.
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.])])
])
#######################
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
# 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"
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"
# 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"
# 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"
SR_T_DOUBLE_RANGE,
SR_T_INT32,
SR_T_MQ,
+ SR_T_UINT32,
/* Update sr_variant_type_get() (hwdriver.c) upon changes! */
};
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;
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;
};
*/
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 -------------------------------------------------*/
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 -------------------------------------------------------------*/
#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).
# ===========================================================================
-# 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], [],
[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,
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
_AX_CXX_COMPILE_STDCXX_testbody_new_in_11
)
-
dnl Test body for checking C++14 support
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
#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"
struct Base
{
+ virtual ~Base() {}
virtual void f() {}
};
struct Derived : public Base
{
+ virtual ~Derived() override {}
virtual void f() override {}
};
#error "This is not a C++ compiler"
-#elif __cplusplus < 201402L
+#elif __cplusplus < 201402L && !defined _MSC_VER
#error "This is not a C++14 compiler"
}
- namespace test_digit_seperators
+ namespace test_digit_separators
{
constexpr auto ten_million = 100'000'000;
#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
+
+]])
[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)
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;
}
#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;
break
switch (spec->type) {
- VALUE_TYPE(BVT_UINT8, R8, sizeof(uint8_t));
+ VALUE_TYPE(BVT_UINT8, read_u8, sizeof(uint8_t));
- VALUE_TYPE(BVT_BE_UINT16, RB16, sizeof(uint16_t));
- VALUE_TYPE(BVT_BE_UINT32, RB32, sizeof(uint32_t));
- VALUE_TYPE(BVT_BE_UINT64, RB64, sizeof(uint64_t));
- VALUE_TYPE(BVT_BE_FLOAT, RBFL, sizeof(float));
+ VALUE_TYPE(BVT_BE_UINT16, read_u16be, sizeof(uint16_t));
+ VALUE_TYPE(BVT_BE_UINT24, read_u24be, 3 * sizeof(uint8_t));
+ VALUE_TYPE(BVT_BE_UINT32, read_u32be, sizeof(uint32_t));
- VALUE_TYPE(BVT_LE_UINT16, RL16, sizeof(uint16_t));
- VALUE_TYPE(BVT_LE_UINT32, RL32, sizeof(uint32_t));
- VALUE_TYPE(BVT_LE_UINT64, RL64, sizeof(uint64_t));
- VALUE_TYPE(BVT_LE_FLOAT, RLFL, sizeof(float));
+ VALUE_TYPE(BVT_LE_UINT16, read_u16le, sizeof(uint16_t));
+ VALUE_TYPE(BVT_LE_UINT24, read_u24le, 3 * sizeof(uint8_t));
+ VALUE_TYPE(BVT_LE_UINT32, read_u32le, sizeof(uint32_t));
default:
return SR_ERR_ARG;
#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;
}
#include <ctype.h>
#include <errno.h>
#include <glib.h>
+#include <inttypes.h>
#include <poll.h>
#include <stdarg.h>
#include <stdio.h>
#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>
uint16_t write_handle;
uint16_t cccd_handle;
uint16_t cccd_value;
+ uint16_t ble_mtu;
/* Internal state. */
int devid;
int fd;
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,
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)
desc->write_handle = write_handle;
desc->cccd_handle = cccd_handle;
desc->cccd_value = cccd_value;
+ desc->ble_mtu = ble_mtu;
return 0;
}
{
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;
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;
}
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)
*
* 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,
*
* 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
*
* 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>
{
int factor;
- (void)exponent;
-
/* Factors */
factor = 0;
if (info->is_pico)
if (info->is_mega)
factor += 6;
*floatval *= powf(10, factor);
+ *exponent += factor;
/* Measurement modes */
if (info->is_volt) {
struct sr_datafeed_analog *analog, void *info)
{
gboolean is_overload, is_bad_jack;
- int exponent;
int digits;
struct meterman_info mi;
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;
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;
}
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);
}
{ "^\"(\\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
};
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;
* 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;
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
#include <config.h>
#include "protocol.h"
+#define SCAN_EXPECTED_VENDOR 0x0403
+
static const uint32_t scanopts[] = {
SR_CONF_CONN,
};
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;
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);
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
static const char *scan_conn[] = {
/* 287/289 */
"115200/8n1",
- /* 187/189 */
+ /* 87/89/187/189 */
"9600/8n1",
/* Scopemeter 190 series */
"1200/8n1",
};
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 },
};
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,
#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;
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;
if ((e = strstr(tokens[1], "Out of range"))) {
is_oor = TRUE;
fvalue = -1;
+ digits = 0;
while (*e && *e != '.')
e++;
} else {
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);
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;
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
sr_err("Invalid float '%s'.", tokens[0]);
return;
}
+ digits = count_digits(tokens[0]);
}
devc = sdi->priv;
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;
struct sr_serial_dev_inst *serial;
int num_tokens, n, i;
char cmd[16], **tokens;
+ int ret;
devc = sdi->priv;
serial = sdi->conn;
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);
/* Supported models */
enum {
- FLUKE_187 = 1,
+ FLUKE_87 = 1,
+ FLUKE_89,
+ FLUKE_187,
FLUKE_189,
- FLUKE_287,
FLUKE_190,
+ FLUKE_287,
FLUKE_289,
};
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
"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",
"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",
.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),
+
+ .num_ydivs = 8,
+
.scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
},
};
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",
static const char *trigger_sources[] = {
"CH1", "CH2", "EXT",
- /* TODO: forced */
};
static const char *trigger_slopes[] = {
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;
*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:
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:
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;
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;
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;
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;
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) {
{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"},
"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",
};
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;
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;
{
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;
{
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;
{
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;
{
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;
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;
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;
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);
}
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;
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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, &);
+ 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;
+}
--- /dev/null
+/*
+ * 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
/* 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;
- g_free(sdi->vendor);
- g_free(sdi->model);
- g_free(sdi->version);
- g_free(sdi->serial_num);
- g_free(sdi->connection_id);
- sr_usb_dev_inst_free(sdi->conn);
- kingst_la2016_free_devc(sdi->priv);
+
+ 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). */
struct drv_context *drvc;
struct sr_context *ctx;
struct dev_context *devc;
- size_t unitsize;
+ size_t unitsize, xfersize, repsize, seqsize;
double voltage;
int ret;
devc = sdi->priv;
if (!devc->feed_queue) {
- if (devc->model->channel_count == 32)
+ /*
+ * 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);
- else if (devc->model->channel_count == 16)
+ repsize = sizeof(uint8_t);
+ seqsize = 2 * sizeof(uint8_t);
+ xfersize = 32;
+ } else if (devc->model->channel_count == 16) {
unitsize = sizeof(uint16_t);
- else
+ 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->packets_per_chunk = TRANSFER_PACKET_LENGTH;
- devc->packets_per_chunk--;
- devc->packets_per_chunk /= unitsize + sizeof(uint8_t);
+ devc->transfer_size = xfersize;
+ devc->sequence_size = seqsize;
+ devc->packets_per_chunk = xfersize;
+ devc->packets_per_chunk -= seqsize;
+ devc->packets_per_chunk /= unitsize + repsize;
}
sr_sw_limits_acquisition_start(&devc->sw_limits);
{ 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), },
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 *= TRANSFER_PACKET_LENGTH;
+ 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;
}
/*
- * A chunk (received via USB) contains a number of transfers (USB length
- * divided by 16) which contain a number of packets (5 per transfer) which
- * contain a number of samples (8bit repeat count per 16bit sample data).
+ * 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;
+ size_t num_xfers, num_pkts, num_seqs;
const uint8_t *rp;
uint32_t sample_value;
size_t repetitions;
/* Process the received chunk of capture data. */
sample_value = 0;
rp = data_buffer;
- num_xfers = data_length / TRANSFER_PACKET_LENGTH;
+ num_xfers = data_length / devc->transfer_size;
while (num_xfers--) {
num_pkts = devc->packets_per_chunk;
while (num_pkts--) {
- /* TODO Verify 32channel layout. */
if (devc->model->channel_count == 32)
sample_value = read_u32le_inc(&rp);
else if (devc->model->channel_count == 16)
devc->total_samples += repetitions;
write_u32le(sample_buff, sample_value);
- feed_queue_logic_submit(devc->feed_queue,
+ feed_queue_logic_submit_one(devc->feed_queue,
sample_buff, repetitions);
sr_sw_limits_update_samples_read(&devc->sw_limits,
repetitions);
}
}
}
- (void)read_u8_inc(&rp); /* Skip sequence number. */
+ /* Skip the sequence number bytes. */
+ num_seqs = devc->sequence_size;
+ while (num_seqs--)
+ (void)read_u8_inc(&rp);
}
/*
* 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.
- * - The memory content can be seen as a sequence of memory cells.
- * - Each cell contains samples that correspond to the same channel.
- * The next cell contains samples for the next channel, etc.
- * - Only enabled channels occupy memory cells. Disabled channels are
- * not part of the capture data memory layout.
- * - The LSB bit position in a cell is the sample which was taken first
- * for this channel. Upper bit positions were taken later.
+ *
+ * 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 memory layout of 32 channel models is yet to get
- * determined.
+ * 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)
/* TODO Add soft trigger support when in stream mode? */
- /*
- * TODO Are memory cells always as wide as the channel count?
- * Are they always 16bits wide? Verify for 32 channel devices.
- */
- bit_count = devc->model->channel_count;
- if (bit_count == 32) {
- data_length /= sizeof(uint32_t);
- } else if (bit_count == 16) {
- data_length /= sizeof(uint16_t);
- } else {
- /*
- * Unhandled case. Acquisition should not start.
- * The statement silences the compiler.
- */
- return;
- }
+ /* 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. */
- if (bit_count == 32)
- sample_value = read_u32le_inc(&rp);
- else if (bit_count == 16)
- sample_value = read_u16le_inc(&rp);
+ 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++) {
sample_value = stream->sample_data[bit_idx];
write_u32le(sample_buff, sample_value);
- feed_queue_logic_submit(devc->feed_queue, sample_buff, 1);
+ 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;
#define LA2016_THR_VOLTAGE_MIN 0.40
#define LA2016_THR_VOLTAGE_MAX 4.00
-/* Properties related to the layout of capture data downloads. */
-#define TRANSFER_PACKET_LENGTH 16
#define LA2016_NUM_SAMPLES_MAX (UINT64_C(10 * 1000 * 1000 * 1000))
/* Maximum device capabilities. May differ between models. */
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;
{"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
};
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:";
const char *conn, *serialcomm;
const char *force_detect;
struct sr_serial_dev_inst *serial;
- size_t i;
char reply[50];
int ret;
const struct korad_kaxxxxp_model *model;
serno += strlen(serno_prefix);
}
- model = NULL;
- for (i = 0; models[i].name; i++) {
- if (!model_matches(&models[i], reply))
- continue;
- model = &models[i];
- break;
- }
+ model = model_lookup(reply);
if (!model && force_detect) {
- sr_warn("Found unknown model ID '%s', trying '%s' spec.",
+ sr_warn("Could not find model ID '%s', trying '%s'.",
reply, force_detect);
- for (i = 0; models[i].name; i++) {
- if (!model_matches(&models[i], force_detect))
- continue;
+ model = model_lookup(force_detect);
+ if (model)
sr_info("Found replacement, using it instead.");
- model = &models[i];
- break;
- }
}
if (!model) {
- sr_err("Found unknown model ID '%s', aborting.", reply);
+ sr_err("Unsupported model ID '%s', aborting.", reply);
return NULL;
}
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(model->vendor);
sdi->model = g_strdup(model->name);
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 = model;
- devc->req_sent_at = 0;
+ devc->next_req_time = 0;
devc->cc_mode_1_changed = FALSE;
devc->cc_mode_2_changed = FALSE;
devc->output_enabled_changed = FALSE;
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,
#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;
{
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);
}
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];
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);
}
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;
KORAD_QUIRK_ID_NO_VENDOR = 1UL << 1,
KORAD_QUIRK_ID_TRAILING = 1UL << 2,
KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3,
- KORAD_QUIRK_ALL = (1UL << 4) - 1,
+ KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4,
+ KORAD_QUIRK_ALL = (1UL << 5) - 1,
};
/* Information on single model */
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. */
};
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
static const uint32_t scanopts[] = {
SR_CONF_CONN,
+ SR_CONF_PROBE_NAMES,
};
static const uint32_t drvopts[] = {
{
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;
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;
}
}
sdi->conn = usb;
sdi->connection_id = g_strdup(conn);
- /* Create the logic channels group. */
- cg = sr_channel_group_new(sdi, "Logic", NULL);
- 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.
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);
};
struct dev_context {
+ char **channel_names;
enum pickit_state state;
const uint64_t *samplerates;
size_t num_samplerates;
* 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;
}
}
- 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);
}
}
- 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;
}
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
* 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"
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;
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;
}
struct dev_context *devc;
struct rdtech_dps_state state;
int ret;
- const char *cc_text;
+ const char *cc_text, *range_text;
(void)cg;
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;
}
{
struct dev_context *devc;
struct rdtech_dps_state state;
+ const char *range_str;
+ const struct rdtech_dps_range *range;
+ size_t i;
(void)cg;
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;
}
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;
}
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);
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);
* 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 */
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 */
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.
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",
/* 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,
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;
(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:
/*
*/
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;
/* 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;
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);
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;
}
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;
}
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;
}
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;
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);
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);
#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>
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;
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;
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. */
STATE_VOLTAGE = 1 << 10,
STATE_CURRENT = 1 << 11,
STATE_POWER = 1 << 12,
+ STATE_RANGE = 1 << 13,
} mask;
gboolean lock;
gboolean output_enabled, regulation_cc;
float voltage_target, current_limit;
float ovp_threshold, ocp_threshold;
float voltage, current, power;
+ size_t range;
};
enum rdtech_dps_state_context {
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);
*/
#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"
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) {
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);
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);
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;
}
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 = {
.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,
*/
#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
#define OFF_SERIAL 12
-static const uint8_t AES_KEY[] = {
+static const uint8_t aes_key[] = {
0x58, 0x21, 0xfa, 0x56, 0x01, 0xb2, 0xf0, 0x26,
0x87, 0xff, 0x12, 0x04, 0x62, 0x2a, 0x4f, 0xb0,
0x86, 0xf4, 0x02, 0x60, 0x81, 0x6f, 0x9a, 0x0b,
0xa7, 0xf1, 0x06, 0x61, 0x9a, 0xb8, 0x72, 0x88,
};
-static const struct binary_analog_channel rdtech_tc_channels[] = {
- { "V", { 0 + 48, BVT_LE_UINT32, 1e-4, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "I", { 0 + 52, BVT_LE_UINT32, 1e-5, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE },
- { "D+", { 64 + 32, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "D-", { 64 + 36, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "E0", { 64 + 12, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
- { "E1", { 64 + 20, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
- ALL_ZERO,
+static const struct rdtech_tc_channel_desc rdtech_tc_channels[] = {
+ { "V", { 0 + 48, BVT_LE_UINT32, }, { 100, 1e6, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "I", { 0 + 52, BVT_LE_UINT32, }, { 10, 1e6, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+ { "D+", { 64 + 32, BVT_LE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "D-", { 64 + 36, BVT_LE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "E0", { 64 + 12, BVT_LE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
+ { "E1", { 64 + 20, BVT_LE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
};
-static int check_pac_crc(uint8_t *data)
+static gboolean check_pac_crc(uint8_t *data)
{
- uint16_t crc;
- uint32_t crc_field;
-
- crc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_DATA_LEN);
- crc_field = RL32(data + PAC_DATA_LEN);
+ uint16_t crc_calc;
+ uint32_t crc_recv;
- if (crc != crc_field) {
+ crc_calc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_CRC_POS);
+ crc_recv = read_u32le(&data[PAC_CRC_POS]);
+ if (crc_calc != crc_recv) {
sr_spew("CRC error. Calculated: %0x" PRIx16 ", expected: %0x" PRIx32,
- crc, crc_field);
- return 0;
- } else {
- return 1;
+ crc_calc, crc_recv);
+ return FALSE;
}
+
+ return TRUE;
}
-static int process_poll_pkt(struct dev_context *devc, uint8_t *dst)
+static int process_poll_pkt(struct dev_context *devc, uint8_t *dst)
{
struct aes256_ctx ctx;
+ gboolean ok;
- aes256_set_decrypt_key(&ctx, AES_KEY);
+ aes256_set_decrypt_key(&ctx, aes_key);
aes256_decrypt(&ctx, TC_POLL_LEN, dst, devc->buf);
- if (RL32(dst + OFF_PAC1) != MAGIC_PAC1 ||
- RL32(dst + OFF_PAC2) != MAGIC_PAC2 ||
- RL32(dst + OFF_PAC3) != MAGIC_PAC3) {
- sr_err("Invalid poll packet magic values!");
- return SR_ERR;
+ ok = TRUE;
+ ok &= read_u32be(&dst[OFF_PAC1]) == MAGIC_PAC1;
+ ok &= read_u32be(&dst[OFF_PAC2]) == MAGIC_PAC2;
+ ok &= read_u32be(&dst[OFF_PAC3]) == MAGIC_PAC3;
+ if (!ok) {
+ sr_err("Invalid poll response packet (magic values).");
+ return SR_ERR_DATA;
}
- if (!check_pac_crc(dst + OFF_PAC1) ||
- !check_pac_crc(dst + OFF_PAC2) ||
- !check_pac_crc(dst + OFF_PAC3)) {
- sr_err("Invalid poll checksum!");
- return SR_ERR;
+ ok &= check_pac_crc(&dst[OFF_PAC1]);
+ ok &= check_pac_crc(&dst[OFF_PAC2]);
+ ok &= check_pac_crc(&dst[OFF_PAC3]);
+ if (!ok) {
+ sr_err("Invalid poll response packet (checksum).");
+ return SR_ERR_DATA;
+ }
+
+ if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+ static const size_t chunk_max = 32;
+
+ const uint8_t *rdptr;
+ size_t rdlen, chunk_addr, chunk_len;
+ GString *txt;
+
+ sr_spew("check passed on decrypted receive data");
+ rdptr = dst;
+ rdlen = TC_POLL_LEN;
+ chunk_addr = 0;
+ while (rdlen) {
+ chunk_len = rdlen;
+ if (chunk_len > chunk_max)
+ chunk_len = chunk_max;
+ txt = sr_hexdump_new(rdptr, chunk_len);
+ sr_spew("%04zx %s", chunk_addr, txt->str);
+ sr_hexdump_free(txt);
+ chunk_addr += chunk_len;
+ rdptr += chunk_len;
+ rdlen -= chunk_len;
+ }
}
return SR_OK;
}
-SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc)
+SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc)
{
+ static const char *poll_cmd_cdc = "getva";
+ static const char *poll_cmd_ble = "bgetva\r\n";
+
int len;
uint8_t poll_pkt[TC_POLL_LEN];
- if (serial_write_blocking(serial, &POLL_CMD, sizeof(POLL_CMD) - 1,
- SERIAL_WRITE_TIMEOUT_MS) < 0) {
- sr_err("Unable to send probe request.");
+ /* Construct the request text. Which differs across transports. */
+ devc->is_bluetooth = ser_name_is_bt(serial);
+ snprintf(devc->req_text, sizeof(devc->req_text), "%s",
+ devc->is_bluetooth ? poll_cmd_ble : poll_cmd_cdc);
+ sr_dbg("is bluetooth %d -> poll request '%s'.",
+ devc->is_bluetooth, devc->req_text);
+
+ /* Transmit the request. */
+ len = serial_write_blocking(serial,
+ devc->req_text, strlen(devc->req_text), WRITE_TO_MS);
+ if (len < 0) {
+ sr_err("Failed to send probe request.");
return SR_ERR;
}
- len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, TC_TIMEOUT_MS);
+ /* Receive a response. */
+ len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, PROBE_TO_MS);
if (len != TC_POLL_LEN) {
sr_err("Failed to read probe response.");
return SR_ERR;
}
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)
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;
}
#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;
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
* 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"
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)
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);
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;
}
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 = {
.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,
*/
#include <config.h>
-#include <stdlib.h>
-#include <math.h>
-#include <string.h>
+
#include <glib.h>
#include <libsigrok/libsigrok.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
#include "libsigrok-internal.h"
#include "protocol.h"
-#define SERIAL_WRITE_TIMEOUT_MS 1
+/* Read/write timeouts, poll request intervals. */
+#define PROBE_TO_MS 1000
+#define WRITE_TO_MS 1
+#define POLL_PERIOD_MS 100
-#define UM_POLL_LEN 130
-#define UM_POLL_PERIOD_MS 100
-#define UM_TIMEOUT_MS 1000
+/* Expected receive data size for poll responses. */
+#define POLL_RECV_LEN 130
+/* Command code to request another poll response. */
#define UM_CMD_POLL 0xf0
-static const struct binary_analog_channel rdtech_default_channels[] = {
- { "V", { 2, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "I", { 4, BVT_BE_UINT16, 0.001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
- { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+static const struct rdtech_um_channel_desc default_channels[] = {
+ { "V", { 2, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "I", { 4, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+ { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
/* Threshold-based recording (mWh) */
- { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
- ALL_ZERO,
+ { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
};
-static const struct binary_analog_channel rdtech_um25c_channels[] = {
- { "V", { 2, BVT_BE_UINT16, 0.001, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "I", { 4, BVT_BE_UINT16, 0.0001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
- { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
- { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+static const struct rdtech_um_channel_desc um25c_channels[] = {
+ { "V", { 2, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "I", { 4, BVT_BE_UINT16, }, { 100, 1e6, }, 4, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+ { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+ { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
/* Threshold-based recording (mWh) */
- { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
- ALL_ZERO,
+ { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
};
-static int poll_csum_fff1(char buf[], int len)
+static gboolean csum_ok_fff1(const uint8_t *buf, size_t len)
{
- if (len != UM_POLL_LEN)
- return 0;
- else
- return RB16(&buf[len - 2]) == 0xFFF1;
+ uint16_t csum_recv;
+
+ if (len != POLL_RECV_LEN)
+ return FALSE;
+
+ csum_recv = read_u16be(&buf[len - sizeof(uint16_t)]);
+ if (csum_recv != 0xfff1)
+ return FALSE;
+
+ return TRUE;
}
-static int poll_csum_um34c(char buf[], int len)
+static gboolean csum_ok_um34c(const uint8_t *buf, size_t len)
{
static const int positions[] = {
1, 3, 7, 9, 15, 17, 19, 23, 31, 39, 41, 45, 49, 53,
55, 57, 59, 63, 67, 69, 73, 79, 83, 89, 97, 99, 109,
111, 113, 119, 121, 127,
};
- unsigned int i;
- uint8_t csum = 0;
- if (len != UM_POLL_LEN)
- return 0;
+ size_t i;
+ uint8_t csum_calc, csum_recv;
+
+ if (len != POLL_RECV_LEN)
+ return FALSE;
+ csum_calc = 0;
for (i = 0; i < ARRAY_SIZE(positions); i++)
- csum ^= buf[positions[i]];
+ csum_calc ^= buf[positions[i]];
+ csum_recv = read_u8(&buf[len - sizeof(uint8_t)]);
+ if (csum_recv != csum_calc)
+ return FALSE;
- return csum == (uint8_t)buf[len - 1];
+ return TRUE;
}
static const struct rdtech_um_profile um_profiles[] = {
- { "UM24C", RDTECH_UM24C, rdtech_default_channels, &poll_csum_fff1, },
- { "UM25C", RDTECH_UM25C, rdtech_um25c_channels, &poll_csum_fff1, },
- { "UM34C", RDTECH_UM34C, rdtech_default_channels, &poll_csum_um34c, },
+ { "UM24C", RDTECH_UM24C, ARRAY_AND_SIZE(default_channels), csum_ok_fff1, },
+ { "UM25C", RDTECH_UM25C, ARRAY_AND_SIZE(um25c_channels), csum_ok_fff1, },
+ { "UM34C", RDTECH_UM34C, ARRAY_AND_SIZE(default_channels), csum_ok_um34c, },
};
static const struct rdtech_um_profile *find_profile(uint16_t id)
{
- unsigned int i;
+ size_t i;
+ const struct rdtech_um_profile *profile;
+
for (i = 0; i < ARRAY_SIZE(um_profiles); i++) {
- if (um_profiles[i].model_id == id)
- return &um_profiles[i];
+ profile = &um_profiles[i];
+ if (profile->model_id == id)
+ return profile;
}
return NULL;
}
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)
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;
}
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
{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},
{ 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",
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),
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)
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. */
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,
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;
}
#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[] = {
};
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 },
};
/*
};
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[] = {
};
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[] = {
};
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[] = {
};
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[] = {
};
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[] = {
};
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[] = {
};
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[] = {
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,
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,
};
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[] = {
{ 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
};
};
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[] = {
.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),
.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),
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 */
double frequency[5];
double ovp[5];
double ocp[5];
+ double ocp_delay[5];
};
struct channel_group_spec {
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. */
};
static const uint32_t devopts_cg_digital[] = {
+ /* EMPTY */
};
enum {
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)
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;
/* 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);
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;
#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);
#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)
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;
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);
#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);
"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",
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:
{
if (ch_idx >= inc->analog_channels)
return;
- if (!value)
- return;
inc->analog_sample_buffer[ch_idx * inc->analog_datafeed_buf_size] = value;
}
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;
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++;
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;
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);
*/
/** @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,
};
--- /dev/null
+/*
+ * 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,
+};
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_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)
/* 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);
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;
} 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;
/*
* 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)
{
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";
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))
*/
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;
/*
* was emitted by libsigrok's VCD output module.
*/
sr_spew("$scope, got: \"%s\"", contents);
- parts = g_strsplit_set(contents, " \r\n\t", 0);
- remove_empty_parts(parts);
- length = g_strv_length(parts);
- if (length != 2) {
- sr_err("Unsupported 'scope' syntax: %s", contents);
- g_strfreev(parts);
+ type_pos = sr_text_next_word(contents, &contents);
+ if (!type_pos) {
+ sr_err("Cannot parse 'scope' directive");
+ return SR_ERR_DATA;
+ }
+ name_pos = sr_text_next_word(contents, &contents);
+ if (!name_pos || contents) {
+ sr_err("Cannot parse 'scope' directive");
return SR_ERR_DATA;
}
- name_pos = parts[1];
+
if (strcmp(name_pos, PACKAGE_NAME) == 0) {
sr_info("Skipping scope with application's package name: %s",
name_pos);
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;
*/
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;
* 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;
id, ref, idx ? idx : "", type);
inc->ignored_signals = g_slist_append(inc->ignored_signals,
g_strdup(id));
- g_strfreev(parts);
return SR_OK;
} else {
sr_err("Unsupported signal type: '%s'", type);
- g_strfreev(parts);
return SR_ERR_DATA;
}
}
if (!size) {
sr_warn("Unsupported signal size: '%s'", size_txt);
- g_strfreev(parts);
return SR_ERR_DATA;
}
if (inc->conv_bits.max_bits < size)
ref, idx ? idx : "", inc->options.maxchannels);
inc->ignored_signals = g_slist_append(inc->ignored_signals,
g_strdup(id));
- g_strfreev(parts);
return SR_OK;
}
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;
}
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);
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);
}
}
/* Parse one text line of the data section. */
-static int parse_textline(const struct sr_input *in, char *lines)
+static int parse_textline(const struct sr_input *in, char *line)
{
struct context *inc;
int ret;
- char **words;
- size_t word_count, word_idx;
- char *curr_word, *next_word, curr_first;
+ char *curr_word, curr_first;
gboolean is_timestamp, is_section;
gboolean is_real, is_multibit, is_singlebit, is_string;
uint64_t timestamp;
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
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;
* 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.");
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;
const char *str_value;
str_value = &curr_word[1];
- identifier = next_word;
- word_idx++;
+ identifier = sr_text_next_word(line, &line);
if (!vcd_string_valid(str_value)) {
sr_err("Invalid string data: %s", str_value);
ret = SR_ERR_DATA;
ret = SR_ERR_DATA;
break;
}
- free_text_split(inc, words);
return ret;
}
uint64_t samplerate;
GVariant *gvar;
int ret;
- char *rdptr, *endptr, *trimptr;
- size_t rdlen;
+ char *rdptr, *line;
+ size_t taken, rdlen;
inc = in->priv;
/* 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;
}
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)
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
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.
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.
};
#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;
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;
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
/*--- 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__)
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 {
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);
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 */
BVT_INVALID,
BVT_UINT8,
- BVT_BE_UINT8 = BVT_UINT8,
- BVT_LE_UINT8 = BVT_UINT8,
BVT_BE_UINT16,
+ BVT_BE_UINT24,
BVT_BE_UINT32,
- BVT_BE_UINT64,
- BVT_BE_FLOAT,
BVT_LE_UINT16,
+ BVT_LE_UINT24,
BVT_LE_UINT32,
- BVT_LE_UINT64,
- BVT_LE_FLOAT,
};
/** Binary value specification */
struct binary_value_spec {
- /** Offset into binary blob */
- size_t offset;
- /** Data type to decode */
- enum binary_value_type type;
- /** Scale factor to get native units */
- float scale;
-};
-
-/** Binary channel definition */
-struct binary_analog_channel {
- /** Channel name */
- const char *name;
- /** Binary value in data stream */
- struct binary_value_spec spec;
- /** Significant digits */
- int digits;
- /** Measured quantity */
- enum sr_mq mq;
- /** Measured unit */
- enum sr_unit unit;
+ size_t offset; /**!< Offset into binary image */
+ enum binary_value_type type; /**!< Data type to decode */
};
/**
- * Read extract a value from a binary blob.
+ * Read extract a value from a binary data image, ensuring no out-of-bounds
+ * read happens.
+ *
+ * @param[out] out Pointer to output buffer (conversion result)
+ * @param[in] spec Binary value specification
+ * @param[in] data Pointer to binary input data
+ * @param[in] length Size of binary input data
*
- * @param out Pointer to output buffer.
- * @param spec Binary value specification
- * @param data Pointer to binary blob
- * @param length Size of binary blob
* @return SR_OK on success, SR_ERR_* error code on failure.
*/
-SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const void *data, size_t length);
+SR_PRIV int bv_get_value_len(float *out, const struct binary_value_spec *spec,
+ const uint8_t *data, size_t length);
/**
- * Send an analog channel packet based on a binary analog channel
- * specification.
+ * Read extract a value from a binary data image, without bound check.
+ *
+ * @param[out] out Pointer to output buffer (conversion result)
+ * @param[in] spec Binary value specification
+ * @param[in] data Pointer to binary input data
*
- * @param sdi Device instance
- * @param ch Sigrok channel
- * @param spec Channel specification
- * @param data Pointer to binary blob
- * @param length Size of binary blob
* @return SR_OK on success, SR_ERR_* error code on failure.
*/
-SR_PRIV int bv_send_analog_channel(const struct sr_dev_inst *sdi, struct sr_channel *ch,
- const struct binary_analog_channel *spec, const void *data, size_t length);
+SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec,
+ const uint8_t *data);
/*--- crc.c -----------------------------------------------------------------*/
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);
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;
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;
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);
/* 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;
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;
* 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;
* @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;
}
/*
* 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;
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;
{ "Keysight Technologies", "Keysight" },
{ "PHILIPS", "Philips" },
{ "RIGOL TECHNOLOGIES", "Rigol" },
+ { "Siglent Technologies", "Siglent" },
};
/**
{ 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) */
};
* 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,
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;
}
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;
}
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;
}
{
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);
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;
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;
}
{
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 = {
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)
#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)
* - 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
*
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;
+ 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;
*cccd_hdl = 0;
if (cccd_val)
*cccd_val = 0;
+ if (ble_mtu)
+ *ble_mtu = 0;
if (!serial || !spec || !spec[0])
return SR_ERR_ARG;
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,
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);
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;
&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;
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. */
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;
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
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)
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)
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;
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;
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);
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;
--- /dev/null
+/*
+ * 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;
+
+/** @} */
/* 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;
}
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]);
}
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;
+}
+
/** @} */
* 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"
* @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:
* @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:
* 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.
*/
* @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
*/
--- /dev/null
+/*
+ * 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);
+}
*/
#include <config.h>
+
+/* Request inclusion of the git version suffix in version.h. */
+#define WANT_LIBSIGROK_GIT_VERSION_H
#include <libsigrok/libsigrok.h>
/**
}
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;
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);
}
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;
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;
}
*
* 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