From: Gerhard Sittig Date: Mon, 1 Jan 2024 13:37:50 +0000 (+0100) Subject: output/csv: use intermediate time_t var, silence compiler warning X-Git-Url: https://sigrok.org/gitweb/?p=libsigrok.git;a=commitdiff_plain;h=HEAD;hp=d32120c4c30650c30720d04eaf88dcf7b76a1e9f output/csv: use intermediate time_t var, silence compiler warning There are platforms where timeval and time_t disagree on the width of the data type of the field which holds seconds. Passing a pointer to an unexpected type results in warnings (and probably unreliable execution). Assign the value which is gotten from a timeval to an intermediate time_t variable, so that the ctime() invocation becomes portable. --- diff --git a/.gitignore b/.gitignore index e943562f..235d51c2 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,10 @@ /*.kdev4 /Makefile.am.user +# clangd language server cruft +.cache/ +/compile_commands.json + # Configure/build cruft *.[ao] *.l[ao] @@ -26,6 +30,7 @@ /config.* /doxy/ /include/libsigrok/version.h +/include/libsigrok/git-version.h /libsigrok-*.tar.* /libsigrok.pc /libtool diff --git a/HACKING b/HACKING index 9d71c55c..2d9abe95 100644 --- a/HACKING +++ b/HACKING @@ -79,6 +79,9 @@ Random notes - Generally avoid assigning values to variables at declaration time, especially so for complex and/or run-time dependent values. + - Separate assignments from control flow. Example: Avoid the pattern + if (var = func()) {...} as it complicates review and maintenance. + - Consistently use g_*malloc() / g_*malloc0(). Do not use standard malloc()/calloc() if it can be avoided (sometimes other libs such as libftdi can return malloc()'d memory, for example). @@ -95,6 +98,9 @@ Random notes Do use g_try_malloc() or g_try_malloc0() for large (>= 1MB) allocations and check the return value. + - Endianness conversion: Prefer the common helpers that are provided in + libsigrok-internal.h, such as read_u16be() etc. + - You should never print any messages (neither to stdout nor stderr nor elsewhere) "manually" via e.g. printf() or g_log() or similar functions. Only sr_err()/sr_warn()/sr_info()/sr_dbg()/sr_spew() should be used. diff --git a/Makefile.am b/Makefile.am index 280cf64d..62aca8ac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,6 +25,8 @@ GNUMAKEFLAGS = --no-print-directory # distutils/setuptools cause trouble on distcheck. Disable for now. DISTCHECK_CONFIGURE_FLAGS = --disable-python +CLEAN_EXTRA = + FIRMWARE_DIR = $(datadir)/sigrok-firmware local_includes = -Iinclude -I$(srcdir)/include -I$(srcdir)/src -I. @RPC_CFLAGS@ @@ -69,11 +71,15 @@ libsigrok_la_SOURCES = \ src/version.c \ src/error.c \ src/std.c \ - src/sw_limits.c + src/sw_limits.c \ + src/tcp.c # Support code, shared among input and driver modules libsigrok_la_SOURCES += \ - src/minilzo/minilzo.c + src/minilzo/minilzo.c \ + src/minilzo/minilzo.h \ + src/minilzo/lzoconf.h \ + src/minilzo/lzodefs.h # Input modules libsigrok_la_SOURCES += \ @@ -83,6 +89,7 @@ libsigrok_la_SOURCES += \ src/input/chronovu_la8.c \ src/input/csv.c \ src/input/logicport.c \ + src/input/protocoldata.c \ src/input/raw_analog.c \ src/input/saleae.c \ src/input/trace32_ad.c \ @@ -145,6 +152,7 @@ libsigrok_la_SOURCES += \ src/serial_hid_cp2110.c \ src/serial_hid_victor.c \ src/serial_libsp.c \ + src/serial_tcpraw.c \ src/scpi/scpi_serial.c else libsigrok_la_SOURCES += \ @@ -256,6 +264,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/asix-sigma/protocol.c \ src/hardware/asix-sigma/api.c endif +if HW_ATORCH +src_libdrivers_la_SOURCES += \ + src/hardware/atorch/protocol.h \ + src/hardware/atorch/protocol.c \ + src/hardware/atorch/api.c +endif if HW_ATTEN_PPS3XXX src_libdrivers_la_SOURCES += \ src/hardware/atten-pps3xxx/protocol.h \ @@ -321,6 +335,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/demo/protocol.c \ src/hardware/demo/api.c endif +if HW_DEVANTECH_ETH008 +src_libdrivers_la_SOURCES += \ + src/hardware/devantech-eth008/protocol.h \ + src/hardware/devantech-eth008/protocol.c \ + src/hardware/devantech-eth008/api.c +endif if HW_DREAMSOURCELAB_DSLOGIC src_libdrivers_la_SOURCES += \ src/hardware/dreamsourcelab-dslogic/protocol.h \ @@ -357,6 +377,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/gmc-mh-1x-2x/protocol.c \ src/hardware/gmc-mh-1x-2x/api.c endif +if HW_GREATFET +src_libdrivers_la_SOURCES += \ + src/hardware/greatfet/protocol.h \ + src/hardware/greatfet/protocol.c \ + src/hardware/greatfet/api.c +endif if HW_GWINSTEK_GDS_800 src_libdrivers_la_SOURCES += \ src/hardware/gwinstek-gds-800/protocol.h \ @@ -417,6 +443,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/hung-chang-dso-2100/protocol.c \ src/hardware/hung-chang-dso-2100/api.c endif +if HW_ICSTATION_USBRELAY +src_libdrivers_la_SOURCES += \ + src/hardware/icstation-usbrelay/protocol.h \ + src/hardware/icstation-usbrelay/protocol.c \ + src/hardware/icstation-usbrelay/api.c +endif if HW_IKALOGIC_SCANALOGIC2 src_libdrivers_la_SOURCES += \ src/hardware/ikalogic-scanalogic2/protocol.h \ @@ -441,6 +473,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/itech-it8500/protocol.c \ src/hardware/itech-it8500/api.c endif +if HW_JUNTEK_JDS6600 +src_libdrivers_la_SOURCES += \ + src/hardware/juntek-jds6600/protocol.h \ + src/hardware/juntek-jds6600/protocol.c \ + src/hardware/juntek-jds6600/api.c +endif if HW_KECHENG_KC_330B src_libdrivers_la_SOURCES += \ src/hardware/kecheng-kc-330b/protocol.h \ @@ -549,6 +587,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/pipistrello-ols/protocol.c \ src/hardware/pipistrello-ols/api.c endif +if HW_RASPBERRYPI_PICO +src_libdrivers_la_SOURCES += \ + src/hardware/raspberrypi-pico/protocol.h \ + src/hardware/raspberrypi-pico/protocol.c \ + src/hardware/raspberrypi-pico/api.c +endif if HW_RDTECH_DPS src_libdrivers_la_SOURCES += \ src/hardware/rdtech-dps/protocol.h \ @@ -716,6 +760,35 @@ nodist_library_include_HEADERS = \ include/libsigrok/version.h noinst_HEADERS = src/libsigrok-internal.h +$(builddir)/src/version.lo: $(builddir)/include/libsigrok/git-version.h + +# Create the git-version.h file even for non-versioned source trees, +# to reduce complexity in the library code. Re-create the header file +# when branches change, when revisions change, or upon re-configuration. +# Use the verbatim tagged version number when applicable, or append the +# "-git-[-dirty]" suffix for non-tagged source trees. +if VCS_IS_GIT + +$(builddir)/include/libsigrok/git-version.h: Makefile $(VERSION_GITVERSION_DEPS) + $(AM_V_GEN) \ + HASH=`git -C "$(srcdir)" describe --match "@VERSION_TAG_MATCH@" --always --dirty` && \ + HASH=`echo "$${HASH}" | sed 's/@VERSION_TAG_MATCH@-//'` && \ + SUFFIX=`git -C "$(srcdir)" describe --match "@VERSION_TAG_MATCH@" --exact-match > /dev/null 2> /dev/null || echo "-$${HASH}"` && \ + echo "#undef SR_PACKAGE_VERSION_STRING_SUFFIX" > $@ && \ + echo "#define SR_PACKAGE_VERSION_STRING_SUFFIX \"$${SUFFIX}\"" >> $@ + +else + +$(builddir)/include/libsigrok/git-version.h: + $(AM_V_GEN)echo '#define SR_PACKAGE_VERSION_STRING_SUFFIX ""' > $@ + +endif + +version-clean: + rm -f $(builddir)/include/libsigrok/git-version.h + +CLEAN_EXTRA += version-clean + pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libsigrok.pc @@ -758,7 +831,11 @@ EXTRA_DIST = \ contrib/vnd.sigrok.session.xml \ contrib/60-libsigrok.rules \ contrib/61-libsigrok-plugdev.rules \ - contrib/61-libsigrok-uaccess.rules + contrib/61-libsigrok-uaccess.rules \ + src/minilzo/COPYING \ + src/minilzo/Makefile \ + src/minilzo/README.LZO \ + src/minilzo/testmini.c if HAVE_CHECK TESTS = tests/main @@ -789,7 +866,6 @@ tests_main_LDADD = libsigrok.la $(SR_EXTRA_LIBS) $(TESTS_LIBS) BUILD_EXTRA = INSTALL_EXTRA = UNINSTALL_EXTRA = -CLEAN_EXTRA = libsigrok-uninstall: -rmdir $(DESTDIR)$(includedir)/libsigrok diff --git a/README b/README index ecdc2fd9..8686a262 100644 --- a/README +++ b/README @@ -64,7 +64,7 @@ Requirements for the C++ bindings: - doxygen (required for building the bindings, not only for C++ API docs!) - graphviz (optional, only needed for the C++ API docs) - Python (2 or 3) executable (development files are not needed) - - glibmm-2.4 (>= 2.32.0) + - glibmm-2.4 (>= 2.32.0) or glibmm-2.68 (>= 2.68.0) Requirements for the Python bindings: diff --git a/README.devices b/README.devices index 95726786..7563a57c 100644 --- a/README.devices +++ b/README.devices @@ -201,7 +201,7 @@ Formal syntax for serial communication: path may contain slashes path and serno are "greedy" (span to the end of the spec) - Bluetooth Classic and Bluetooth Low Energy (BLE): - conn=bt// + conn=bt//[/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 @@ -209,6 +209,8 @@ Formal syntax for serial communication: from a string that separates fields by colon, e.g. in the "--driver :conn=" 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. diff --git a/bindings/cxx/ConfigKey_methods.cpp b/bindings/cxx/ConfigKey_methods.cpp index 7f8c201e..d899ddcc 100644 --- a/bindings/cxx/ConfigKey_methods.cpp +++ b/bindings/cxx/ConfigKey_methods.cpp @@ -70,6 +70,50 @@ static inline double stod( const std::string& str ) } #endif +#ifndef HAVE_STOUL + +/* Fallback implementation of stoul. */ + +#include +#include +#include +#include + +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::min() || + ret > std::numeric_limits::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::max()) + throw std::out_of_range("stou32"); + return ret; +} + Glib::VariantBase ConfigKey::parse_string(std::string value, enum sr_datatype dt) { GVariant *variant; @@ -109,6 +153,13 @@ Glib::VariantBase ConfigKey::parse_string(std::string value, enum sr_datatype dt throw Error(SR_ERR_ARG); } break; + case SR_T_UINT32: + try { + variant = g_variant_new_uint32(stou32(value)); + } catch (invalid_argument&) { + throw Error(SR_ERR_ARG); + } + break; default: throw Error(SR_ERR_BUG); } diff --git a/bindings/cxx/classes.cpp b/bindings/cxx/classes.cpp index f9f79273..d5477847 100644 --- a/bindings/cxx/classes.cpp +++ b/bindings/cxx/classes.cpp @@ -287,12 +287,12 @@ shared_ptr Context::create_user_device( default_delete{}}; } -shared_ptr Context::create_header_packet(Glib::TimeVal start_time) +shared_ptr Context::create_header_packet(Glib::DateTime start_time) { auto header = g_new(struct sr_datafeed_header, 1); header->feed_version = 1; - header->starttime.tv_sec = start_time.tv_sec; - header->starttime.tv_usec = start_time.tv_usec; + header->starttime.tv_sec = start_time.to_unix(); + header->starttime.tv_usec = start_time.get_microsecond(); auto packet = g_new(struct sr_datafeed_packet, 1); packet->type = SR_DF_HEADER; packet->payload = header; @@ -1154,11 +1154,10 @@ int Header::feed_version() const return _structure->feed_version; } -Glib::TimeVal Header::start_time() const +Glib::DateTime Header::start_time() const { - return Glib::TimeVal( - _structure->starttime.tv_sec, - _structure->starttime.tv_usec); + Glib::DateTime time = Glib::DateTime::create_now_utc(_structure->starttime.tv_sec); + return time.add_seconds(_structure->starttime.tv_usec / 1.0e6); } Meta::Meta(const struct sr_datafeed_meta *structure) : @@ -1566,6 +1565,8 @@ Glib::VariantBase Option::parse_string(string value) dt = SR_T_FLOAT; } else if (g_variant_is_of_type(tmpl, G_VARIANT_TYPE_INT32)) { dt = SR_T_INT32; + } else if (g_variant_is_of_type(tmpl, G_VARIANT_TYPE_UINT32)) { + dt = SR_T_UINT32; } else { throw Error(SR_ERR_BUG); } diff --git a/bindings/cxx/include/libsigrokcxx/libsigrokcxx.hpp b/bindings/cxx/include/libsigrokcxx/libsigrokcxx.hpp index 97e54e17..3bebdd23 100644 --- a/bindings/cxx/include/libsigrokcxx/libsigrokcxx.hpp +++ b/bindings/cxx/include/libsigrokcxx/libsigrokcxx.hpp @@ -274,7 +274,7 @@ public: std::shared_ptr create_user_device( std::string vendor, std::string model, std::string version); /** Create a header packet. */ - std::shared_ptr create_header_packet(Glib::TimeVal start_time); + std::shared_ptr create_header_packet(Glib::DateTime start_time); /** Create a meta packet. */ std::shared_ptr create_meta_packet( std::map config); @@ -711,7 +711,7 @@ public: /* Feed version number. */ int feed_version() const; /* Start time of this session. */ - Glib::TimeVal start_time() const; + Glib::DateTime start_time() const; private: explicit Header(const struct sr_datafeed_header *structure); ~Header(); @@ -791,9 +791,22 @@ public: /** Samples are stored in big-endian order. */ bool is_bigendian() const; /** - * Number of significant digits after the decimal point if positive, - * or number of non-significant digits before the decimal point if negative - * (refers to the value we actually read on the wire). + * Number of significant digits after the decimal point, if positive. + * When negative, exponent with reversed polarity that is necessary to + * express the value with all digits without a decimal point. + * Refers to the value we actually read on the wire. + * + * Examples: + * + * | On the wire | Exponential notation | Exp. not. (normalized) | digits | + * |-------------|----------------------|------------------------|--------| + * | 12.34 MOhm | 1.234 * 10^7 Ohm | 1234 * 10^4 Ohm | -4 | + * | 1.2345 MOhm | 1.2345 * 10^6 Ohm | 12345 * 10^2 Ohm | -2 | + * | 123.4 kOhm | 1.234 * 10^5 Ohm | 1234 * 10^2 Ohm | -2 | + * | 1234 Ohm | 1.234 * 10^3 Ohm | 1234 * 10^0 Ohm | 0 | + * | 12.34 Ohm | 1.234 * 10^1 Ohm | 1234 * 10^-2 Ohm | 2 | + * | 0.0123 Ohm | 1.23 * 10^-2 Ohm | 123 * 10^-4 Ohm | 4 | + * | 1.234 pF | 1.234 * 10^-12 F | 1234 * 10^-15 F | 15 | */ int digits() const; /** TBD */ diff --git a/bindings/cxx/libsigrokcxx.pc.in b/bindings/cxx/libsigrokcxx.pc.in index 10a92f2d..7d2723fc 100644 --- a/bindings/cxx/libsigrokcxx.pc.in +++ b/bindings/cxx/libsigrokcxx.pc.in @@ -6,7 +6,7 @@ includedir=@includedir@ Name: libsigrokcxx Description: C++ bindings for libsigrok URL: http://www.sigrok.org -Requires: libsigrok glibmm-2.4 +Requires: libsigrok @SR_GLIBMM_REQUIRES@ Version: @SR_PACKAGE_VERSION@ Libs: -L${libdir} -lsigrokcxx Libs.private: -lm diff --git a/bindings/python/sigrok/core/classes.i b/bindings/python/sigrok/core/classes.i index eb557d03..d22db1c1 100644 --- a/bindings/python/sigrok/core/classes.i +++ b/bindings/python/sigrok/core/classes.i @@ -134,7 +134,7 @@ typedef guint pyg_flags_type; auto arglist = Py_BuildValue("(OO)", log_obj, string_obj); - auto result = PyEval_CallObject($input, arglist); + auto result = PyObject_CallObject($input, arglist); Py_XDECREF(arglist); Py_XDECREF(log_obj); @@ -177,7 +177,7 @@ typedef guint pyg_flags_type; $1 = [=] () { const auto gstate = PyGILState_Ensure(); - const auto result = PyEval_CallObject($input, nullptr); + const auto result = PyObject_CallObject($input, nullptr); const bool completed = !PyErr_Occurred(); const bool valid_result = (completed && result == Py_None); @@ -221,7 +221,7 @@ typedef guint pyg_flags_type; auto arglist = Py_BuildValue("(OO)", device_obj, packet_obj); - auto result = PyEval_CallObject($input, arglist); + auto result = PyObject_CallObject($input, arglist); Py_XDECREF(arglist); Py_XDECREF(device_obj); @@ -340,6 +340,8 @@ Glib::VariantBase python_to_variant_by_key(PyObject *input, const sigrok::Config return Glib::Variant::create(PyFloat_AsDouble(input)); else if (type == SR_T_INT32 && PyInt_Check(input)) return Glib::Variant::create(PyInt_AsLong(input)); + else if (type == SR_T_UINT32 && PyInt_Check(input)) + return Glib::Variant::create(PyInt_AsLong(input)); else if ((type == SR_T_RATIONAL_VOLT) && PyTuple_Check(input) && (PyTuple_Size(input) == 2)) { PyObject *numObj = PyTuple_GetItem(input, 0); PyObject *denomObj = PyTuple_GetItem(input, 1); @@ -369,6 +371,8 @@ Glib::VariantBase python_to_variant_by_option(PyObject *input, return Glib::Variant::create(PyFloat_AsDouble(input)); else if (type == G_VARIANT_TYPE_INT32 && PyInt_Check(input)) return Glib::Variant::create(PyInt_AsLong(input)); + else if (type == G_VARIANT_TYPE_UINT32 && PyInt_Check(input)) + return Glib::Variant::create(PyInt_AsLong(input)); else throw sigrok::Error(SR_ERR_ARG); } diff --git a/bindings/ruby/classes.i b/bindings/ruby/classes.i index 13496a86..fbd7c36c 100644 --- a/bindings/ruby/classes.i +++ b/bindings/ruby/classes.i @@ -236,6 +236,8 @@ Glib::VariantBase ruby_to_variant_by_key(VALUE input, const sigrok::ConfigKey *k return Glib::Variant::create(RFLOAT_VALUE(input)); else if (type == SR_T_INT32 && RB_TYPE_P(input, T_FIXNUM)) return Glib::Variant::create(NUM2INT(input)); + else if (type == SR_T_UINT32 && RB_TYPE_P(input, T_FIXNUM)) + return Glib::Variant::create(NUM2UINT(input)); else throw sigrok::Error(SR_ERR_ARG); } @@ -261,6 +263,8 @@ Glib::VariantBase ruby_to_variant_by_option(VALUE input, std::shared_ptr::create(RFLOAT_VALUE(input)); else if (variant.is_of_type(Glib::VARIANT_TYPE_INT32) && RB_TYPE_P(input, T_FIXNUM)) return Glib::Variant::create(NUM2INT(input)); + else if (variant.is_of_type(Glib::VARIANT_TYPE_UINT32) && RB_TYPE_P(input, T_FIXNUM)) + return Glib::Variant::create(NUM2UINT(input)); else throw sigrok::Error(SR_ERR_ARG); } diff --git a/configure.ac b/configure.ac index 5c480818..5c30a816 100644 --- a/configure.ac +++ b/configure.ac @@ -225,6 +225,20 @@ AM_CONDITIONAL([NEED_RPC], [test "x$sr_cv_have_rpc" = xyes]) # Check for compiler support of 128 bit integers AC_CHECK_TYPES([__int128_t, __uint128_t], [], [], []) +AC_CACHE_CHECK([for poll], [sr_cv_have_poll], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[(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 ]], + [[(void) select(0, 0, 0, 0, 0);]])], + [sr_cv_have_select=yes], [sr_cv_have_select=no])]) +AS_IF([test "x$sr_cv_have_select" = xyes], + [AC_DEFINE([HAVE_SELECT], [1], + [Specifies whether we have the select(2) function.])]) ####################### ## miniLZO related ## @@ -295,6 +309,7 @@ SR_DRIVER([Appa 55II], [appa-55ii], [serial_comm]) SR_DRIVER([Arachnid Labs Re:load Pro], [arachnid-labs-re-load-pro], [serial_comm]) SR_DRIVER([ASIX SIGMA/SIGMA2], [asix-sigma], [libftdi]) SR_DRIVER([ASIX OMEGA RTM CLI], [asix-omega-rtm-cli]) +SR_DRIVER([Atorch], [atorch], [serial_comm]) SR_DRIVER([Atten PPS3xxx], [atten-pps3xxx], [serial_comm]) SR_DRIVER([BayLibre ACME], [baylibre-acme], [sys_timerfd_h]) SR_DRIVER([BeagleLogic], [beaglelogic], [sys_mman_h sys_ioctl_h]) @@ -305,12 +320,14 @@ SR_DRIVER([Colead SLM], [colead-slm], [serial_comm]) SR_DRIVER([Conrad DIGI 35 CPU], [conrad-digi-35-cpu], [serial_comm]) SR_DRIVER([dcttech usbrelay], [dcttech-usbrelay], [libhidapi]) SR_DRIVER([demo], [demo]) +SR_DRIVER([Devantech ETH008], [devantech-eth008], [serial_comm]) SR_DRIVER([DreamSourceLab DSLogic], [dreamsourcelab-dslogic], [libusb]) SR_DRIVER([Fluke 45], [fluke-45]) SR_DRIVER([Fluke DMM], [fluke-dmm], [serial_comm]) SR_DRIVER([FTDI LA], [ftdi-la], [libusb libftdi]) SR_DRIVER([fx2lafw], [fx2lafw], [libusb]) SR_DRIVER([GMC MH 1x/2x], [gmc-mh-1x-2x], [serial_comm]) +SR_DRIVER([Great Scott Gadgets GreatFET One], [greatfet], [libusb]) SR_DRIVER([GW Instek GDS-800], [gwinstek-gds-800], [serial_comm]) SR_DRIVER([GW Instek GPD], [gwinstek-gpd], [serial_comm]) SR_DRIVER([Hameg HMO], [hameg-hmo]) @@ -321,10 +338,12 @@ SR_DRIVER([HP 3457A], [hp-3457a]) SR_DRIVER([HP 3478A], [hp-3478a], [libgpib]) SR_DRIVER([hp-59306a], [hp-59306a]) SR_DRIVER([Hung-Chang DSO-2100], [hung-chang-dso-2100], [libieee1284]) +SR_DRIVER([ICStation USBRelay], [icstation-usbrelay], [serial_comm]) SR_DRIVER([Ikalogic Scanalogic-2], [ikalogic-scanalogic2], [libusb]) SR_DRIVER([Ikalogic Scanaplus], [ikalogic-scanaplus], [libftdi]) SR_DRIVER([IPDBG LA], [ipdbg-la]) SR_DRIVER([ITECH IT8500], [itech-it8500], [serial_comm]) +SR_DRIVER([JUNTEK JDS6600], [juntek-jds6600], [serial_comm]) SR_DRIVER([Kecheng KC-330B], [kecheng-kc-330b], [libusb]) SR_DRIVER([KERN scale], [kern-scale], [serial_comm]) SR_DRIVER([Kingst LA2016], [kingst-la2016], [libusb]) @@ -343,6 +362,7 @@ SR_DRIVER([Norma DMM], [norma-dmm], [serial_comm]) SR_DRIVER([OpenBench Logic Sniffer], [openbench-logic-sniffer], [serial_comm]) SR_DRIVER([PCE PCE-322A], [pce-322a], [serial_comm]) SR_DRIVER([Pipistrello-OLS], [pipistrello-ols], [libftdi]) +SR_DRIVER([RaspberryPI PICO], [raspberrypi-pico], [serial_comm]) SR_DRIVER([RDTech DPSxxxx/DPHxxxx], [rdtech-dps], [serial_comm]) SR_DRIVER([RDTech UMXX], [rdtech-um], [serial_comm]) SR_DRIVER([RDTech TCXX], [rdtech-tc], [serial_comm libnettle]) @@ -401,14 +421,41 @@ AC_ARG_ENABLE([java], sr_cxx_missing= -# Check if the C++ compiler supports the C++11 standard. -AX_CXX_COMPILE_STDCXX([11], [noext], [optional]) -AS_IF([test "x$HAVE_CXX11" != x1], +# Check if the C++ compiler supports at least the C++11 standard. +# Get the highest of the available C++17/C++14/C++11 standards. +# This transparently amends CXXFLAGS to pick the detected standard. +HAVE_MODERN_CXX= +AS_IF([test "x$HAVE_MODERN_CXX" = x], + [AX_CXX_COMPILE_STDCXX([17], [noext], [optional])]) +AS_IF([test "x$HAVE_CXX17" = x1], + [HAVE_MODERN_CXX=yes]) +AS_IF([test "x$HAVE_MODERN_CXX" = x], + [AX_CXX_COMPILE_STDCXX([14], [noext], [optional])]) +AS_IF([test "x$HAVE_CXX14" = x1], + [HAVE_MODERN_CXX=yes]) +AS_IF([test "x$HAVE_MODERN_CXX" = x], + [AX_CXX_COMPILE_STDCXX([11], [noext], [optional])]) +AS_IF([test "x$HAVE_CXX11" = x1], + [HAVE_MODERN_CXX=yes]) +AS_IF([test "x$HAVE_MODERN_CXX" = x], [SR_APPEND([sr_cxx_missing], [', '], ['C++11'])]) # The C++ bindings need glibmm. -SR_PKG_CHECK([glibmm], [SR_PKGLIBS_CXX], [glibmm-2.4 >= 2.32.0]) +# Prefer glibmm-2.4 for backwards compatibility. +# Accept glibmm-2.68 when glibmm-2.4 is not available. +sr_have_glibmm=no AS_IF([test "x$sr_have_glibmm" != xyes], + [SR_PKG_CHECK([glibmm24], [SR_PKGLIBS_CXX], [glibmm-2.4 >= 2.32.0])]) +AS_IF([test "x$sr_have_glibmm24" = xyes], + [SR_APPEND([SR_GLIBMM_REQUIRES], ['glibmm-2.4 >= 2.32.0']) + sr_have_glibmm=yes]) +AS_IF([test "x$sr_have_glibmm" != xyes], + [SR_PKG_CHECK([glibmm268], [SR_PKGLIBS_CXX], [glibmm-2.68 >= 2.68.0])]) +AS_IF([test "x$sr_have_glibmm268" = xyes], + [SR_APPEND([SR_GLIBMM_REQUIRES], ['glibmm-2.68 >= 2.68.0']) + sr_have_glibmm=yes]) +AS_IF([test "x$sr_have_glibmm" = xyes], + [AC_SUBST(SR_GLIBMM_REQUIRES)], [SR_APPEND([sr_cxx_missing], [', '], [glibmm])]) # The C++ bindings use Doxygen to parse libsigrok symbols. @@ -438,6 +485,16 @@ AM_COND_IF([BINDINGS_CXX], [ AS_IF([test "x$sr_cv_have_stoi_stod" = xyes], [AC_DEFINE([HAVE_STOI_STOD], [1], [Specifies whether we have the stoi and stod functions.])]) + # In theory std::stoul() should have identical availability + # as std::stoi() and std::stod() have. All of them are C++11. + # But we play it safe here, and check support individually. + AC_CACHE_CHECK([for stoul], [sr_cv_have_stoul], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[(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.])]) ]) ####################### @@ -657,6 +714,7 @@ cat >&AS_MESSAGE_FD <<_EOF libsigrok configuration summary: - Package version................. $SR_PACKAGE_VERSION + - Version string suffix .......... $SR_PACKAGE_VERSION_STRING_SUFFIX - Library ABI version............. $SR_LIB_VERSION - Prefix.......................... $prefix - Building on..................... $build diff --git a/contrib/60-libsigrok.rules b/contrib/60-libsigrok.rules index 569bac7c..b6ac3ba0 100644 --- a/contrib/60-libsigrok.rules +++ b/contrib/60-libsigrok.rules @@ -113,6 +113,10 @@ ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0020", ENV{ID_SIGROK}="1" # DreamSourceLab DSLogic Basic ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0021", ENV{ID_SIGROK}="1" +# Great Scott Gadgets +# GreatFET One +ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="60e6", ENV{ID_SIGROK}="1" + # GW-Instek GDM-9061 (USBTMC mode) ATTRS{idVendor}=="2184", ATTRS{idProduct}=="0059", ENV{ID_SIGROK}="1" @@ -250,6 +254,9 @@ ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0117", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0118", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0119", ENV{ID_SIGROK}="1" +# Rohde&Schwarz HMC series power supply (previously branded Hameg) VCP/USBTMC mode +ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0135", ENV{ID_SIGROK}="1" + # Sainsmart DDS120 / Rocktech BM102, renumerates as 1d50:608e "sigrok fx2lafw", Serial: Sainsmart DDS120 ATTRS{idVendor}=="8102", ATTRS{idProduct}=="8102", ENV{ID_SIGROK}="1" @@ -265,13 +272,22 @@ ATTRS{idVendor}=="0925", ATTRS{idProduct}=="3881", ENV{ID_SIGROK}="1" # Saleae Logic16 ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1001", ENV{ID_SIGROK}="1" +ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1003", ENV{ID_SIGROK}="1" + +# Saleae Logic 8, currently unsupported by libsigrok +#ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1004", ENV{ID_SIGROK}="1" + +# Saleae Logic Pro 8 +ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1005", ENV{ID_SIGROK}="1" + # Saleae Logic Pro 16 ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1006", ENV{ID_SIGROK}="1" # Siglent USBTMC devices. # f4ec:ee3a: E.g. SDS1052DL+ scope -# f4ec:ee38: E.g. SDS1104X-E scope +# f4ec:ee38: E.g. SDS1104X-E scope or SDM3055 Multimeter # f4ed:ee3a: E.g. SDS1202X-E scope or SDG1010 waveform generator +ATTRS{idVendor}=="f4ec", ATTRS{idProduct}=="ee38", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="f4ec", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="f4ed", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1" @@ -326,6 +342,9 @@ ATTRS{idVendor}=="04fc", ATTRS{idProduct}=="0201", ENV{ID_SIGROK}="1" # Victor 86C ATTRS{idVendor}=="1244", ATTRS{idProduct}=="d237", ENV{ID_SIGROK}="1" +# Voltcraft DSO2020, renumerates as 1d50:608e "sigrok fx2lafw", Serial: Hantek 6022BE +ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="2020", ENV{ID_SIGROK}="1" + # YiXingDianZi MDSO ATTRS{idVendor}=="d4a2", ATTRS{idProduct}=="5660", ENV{ID_SIGROK}="1" diff --git a/include/libsigrok/libsigrok.h b/include/libsigrok/libsigrok.h index 42614fd0..6c5dea08 100644 --- a/include/libsigrok/libsigrok.h +++ b/include/libsigrok/libsigrok.h @@ -153,6 +153,7 @@ enum sr_datatype { SR_T_DOUBLE_RANGE, SR_T_INT32, SR_T_MQ, + SR_T_UINT32, /* Update sr_variant_type_get() (hwdriver.c) upon changes! */ }; @@ -528,9 +529,22 @@ struct sr_analog_encoding { gboolean is_float; gboolean is_bigendian; /** - * Number of significant digits after the decimal point if positive, - * or number of non-significant digits before the decimal point if - * negative (refers to the value we actually read on the wire). + * Number of significant digits after the decimal point, if positive. + * When negative, exponent with reversed polarity that is necessary to + * express the value with all digits without a decimal point. + * Refers to the value we actually read on the wire. + * + * Examples: + * + * | Disp. value | Exp. notation | Exp. not. normalized | digits | + * |-------------|---------------------|----------------------|--------| + * | 12.34 MOhm | 1.234 * 10^7 Ohm | 1234 * 10^4 Ohm | -4 | + * | 1.2345 MOhm | 1.2345 * 10^6 Ohm | 12345 * 10^2 Ohm | -2 | + * | 123.4 kOhm | 1.234 * 10^5 Ohm | 1234 * 10^2 Ohm | -2 | + * | 1234 Ohm | 1.234 * 10^3 Ohm | 1234 * 10^0 Ohm | 0 | + * | 12.34 Ohm | 1.234 * 10^1 Ohm | 1234 * 10^-2 Ohm | 2 | + * | 0.0123 Ohm | 1.23 * 10^-2 Ohm | 123 * 10^-4 Ohm | 4 | + * | 1.234 pF | 1.234 * 10^-12 F | 1234 * 10^-15 F | 15 | */ int8_t digits; gboolean is_digits_decimal; @@ -547,10 +561,22 @@ struct sr_analog_meaning { struct sr_analog_spec { /** - * Number of significant digits after the decimal point if positive, - * or number of non-significant digits before the decimal point if - * negative (refers to vendor specifications/datasheet or actual - * device display). + * Number of significant digits after the decimal point, if positive. + * When negative, exponent with reversed polarity that is necessary to + * express the value with all digits without a decimal point. + * Refers to vendor specifications/datasheet or actual device display. + * + * Examples: + * + * | On the wire | Exp. notation | Exp. not. normalized | spec_digits | + * |-------------|---------------------|----------------------|-------------| + * | 12.34 MOhm | 1.234 * 10^7 Ohm | 1234 * 10^4 Ohm | -4 | + * | 1.2345 MOhm | 1.2345 * 10^6 Ohm | 12345 * 10^2 Ohm | -2 | + * | 123.4 kOhm | 1.234 * 10^5 Ohm | 1234 * 10^2 Ohm | -2 | + * | 1234 Ohm | 1.234 * 10^3 Ohm | 1234 * 10^0 Ohm | 0 | + * | 12.34 Ohm | 1.234 * 10^1 Ohm | 1234 * 10^-2 Ohm | 2 | + * | 0.0123 Ohm | 1.23 * 10^-2 Ohm | 123 * 10^-4 Ohm | 4 | + * | 1.234 pF | 1.234 * 10^-12 F | 1234 * 10^-15 F | 15 | */ int8_t spec_digits; }; @@ -1103,6 +1129,14 @@ enum sr_configkey { */ SR_CONF_RESISTANCE_TARGET, + /** + * Over-current protection (OCP) delay + * @arg type: double (time) + * @arg get: get current delay + * @arg set: set new delay + */ + SR_CONF_OVER_CURRENT_PROTECTION_DELAY, + /* Update sr_key_info_config[] (hwdriver.c) upon changes! */ /*--- Special stuff -------------------------------------------------*/ diff --git a/include/libsigrok/proto.h b/include/libsigrok/proto.h index 46a0bdc2..2c3b2559 100644 --- a/include/libsigrok/proto.h +++ b/include/libsigrok/proto.h @@ -264,6 +264,11 @@ SR_API int sr_snprintf_ascii(char *buf, size_t buf_size, SR_API int sr_vsnprintf_ascii(char *buf, size_t buf_size, const char *format, va_list args); SR_API int sr_parse_rational(const char *str, struct sr_rational *ret); +SR_API char *sr_text_trim_spaces(char *s); +SR_API char *sr_text_next_line(char *s, size_t l, char **next, size_t *taken); +SR_API char *sr_text_next_word(char *s, char **next); + +SR_API int sr_next_power_of_two(size_t value, size_t *bits, size_t *power); /*--- version.c -------------------------------------------------------------*/ diff --git a/include/libsigrok/version.h.in b/include/libsigrok/version.h.in index 1c977bd8..385097e8 100644 --- a/include/libsigrok/version.h.in +++ b/include/libsigrok/version.h.in @@ -20,6 +20,21 @@ #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 +#else +# undef SR_PACKAGE_VERSION_STRING_SUFFIX +# define SR_PACKAGE_VERSION_STRING_SUFFIX "" +#endif + /** * @file * @@ -46,7 +61,10 @@ #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). diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 index 2c18e49c..a3d964c6 100644 --- a/m4/ax_cxx_compile_stdcxx.m4 +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -1,5 +1,5 @@ # =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS @@ -10,13 +10,13 @@ # # 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 @@ -33,21 +33,26 @@ # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# Copyright (c) 2021 Jörn Heusipp # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 4 +#serial 15 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl - m4_if([$1], [11], [], - [$1], [14], [], - [$1], [17], [m4_fatal([support for C++17 not yet implemented in AX_CXX_COMPILE_STDCXX])], + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [$1], [20], [ax_cxx_compile_alternatives="20"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], @@ -59,18 +64,21 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no - AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, - ax_cv_cxx_compile_cxx$1, - [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], - [ax_cv_cxx_compile_cxx$1=yes], - [ax_cv_cxx_compile_cxx$1=no])]) - if test x$ax_cv_cxx_compile_cxx$1 = xyes; then - ac_success=yes - fi + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then - for switch in -std=gnu++$1 -std=gnu++0x; do + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, @@ -96,22 +104,27 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" - for switch in -std=c++$1 -std=c++0x +std=c++$1 "-h std=c++$1"; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, - $cachevar, - [ac_save_CXX="$CXX" - CXX="$CXX $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXX="$ac_save_CXX"]) - if eval test x\$$cachevar = xyes; then - CXX="$CXX $switch" - if test -n "$CXXCPP" ; then - CXXCPP="$CXXCPP $switch" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break fi - ac_success=yes + done + if test x$ac_success = xyes; then break fi done @@ -140,7 +153,6 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 ) - dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], @@ -148,6 +160,23 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) +dnl Test body for checking C++17 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 +) + dnl Tests for new features in C++11 @@ -160,7 +189,11 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201103L +// MSVC always sets __cplusplus to 199711L in older versions; newer versions +// only set it correctly if /Zc:__cplusplus is specified as well as a +// /std:c++NN switch: +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +#elif __cplusplus < 201103L && !defined _MSC_VER #error "This is not a C++11 compiler" @@ -185,11 +218,13 @@ namespace cxx11 struct Base { + virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { + virtual ~Derived() override {} virtual void f() override {} }; @@ -449,7 +484,7 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201402L +#elif __cplusplus < 201402L && !defined _MSC_VER #error "This is not a C++14 compiler" @@ -518,7 +553,7 @@ namespace cxx14 } - namespace test_digit_seperators + namespace test_digit_separators { constexpr auto ten_million = 100'000'000; @@ -560,3 +595,415 @@ namespace cxx14 #endif // __cplusplus >= 201402L ]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L && !defined _MSC_VER + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + 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, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template 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 + 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 + 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 + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + 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 + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template 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 + +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 + +]]) diff --git a/m4/sigrok.m4 b/m4/sigrok.m4 index 2ca2444d..5dc29609 100644 --- a/m4/sigrok.m4 +++ b/m4/sigrok.m4 @@ -52,34 +52,48 @@ m4_define([_SR_PKG_VERSION_SET], [dnl m4_assert([$# >= 6])[]dnl $1=$4 +dnl Check if we can get version control details. Re-configure when +dnl branches change (when HEAD starts pointing somewhere else). +dnl Track individual revisions at compile time, and only as a local +dnl dependency of just a part of the library build. In other words: +dnl Don't re-configure and re-build everything just because a commit +dnl happened). Checks for tagged sources also happen at compile time. sr_git_deps= -# Check if we can get revision information from git. sr_head=`git -C "$srcdir" rev-parse --verify --short HEAD 2>&AS_MESSAGE_LOG_FD` - -AS_IF([test "$?" = 0 && test "x$sr_head" != x], [dnl - test ! -f "$srcdir/.git/HEAD" \ - || sr_git_deps="$sr_git_deps \$(top_srcdir)/.git/HEAD" - +AS_IF([test "$?" = 0 && test -n "$sr_head"], [dnl + test ! -f "$srcdir/.git/HEAD" || \ + sr_git_deps="$sr_git_deps ${ac_abs_confdir}/.git/HEAD" sr_head_name=`git -C "$srcdir" rev-parse --symbolic-full-name HEAD 2>&AS_MESSAGE_LOG_FD` - AS_IF([test "$?" = 0 && test -f "$srcdir/.git/$sr_head_name"], - [sr_git_deps="$sr_git_deps \$(top_srcdir)/.git/$sr_head_name"]) - - # Append the revision hash unless we are exactly on a tagged release. - git -C "$srcdir" describe --match "$3$4" \ - --exact-match >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD \ - || $1="[$]$1-git-$sr_head" + AS_IF([test "$?" = 0 && test -f "$srcdir/.git/$sr_head_name"], [dnl + sr_head_file="${ac_abs_confdir}/.git/$sr_head_name" + AC_SUBST(VERSION_HEAD_FILE, [$sr_head_file]) + ]) + sr_hash=`git -C "$srcdir" describe --match "$3$4" --always --dirty` + sr_hash=`echo "$sr_hash" | sed 's/$3$4-//'` && \ + $1_STRING_SUFFIX=`git -C "$srcdir" describe --match "$3$4" --exact-match > /dev/null 2> /dev/null || echo "-$sr_hash"` + AS_IF([test -n "$1_STRING_SUFFIX"], [$1="[$]$1-git"]) ]) -# Use $(wildcard) so that things do not break if for whatever -# reason these files do not exist anymore at make time. +AM_CONDITIONAL([VCS_IS_GIT], [test -n "$sr_git_deps"]) +# Use $(wildcard) so that things do not break if for whatever reason +# these files do not exist anymore at make time. AS_IF([test -n "$sr_git_deps"], - [SR_APPEND([CONFIG_STATUS_DEPENDENCIES], ["\$(wildcard$sr_git_deps)"])]) + [SR_APPEND([CONFIG_STATUS_DEPENDENCIES], ["\$(wildcard $sr_git_deps)"])]) AC_SUBST([CONFIG_STATUS_DEPENDENCIES])[]dnl +AS_IF([test -n "$sr_git_deps$sr_head_file"], + [SR_APPEND([VERSION_GITVERSION_DEPS], ["\$(wildcard $sr_git_deps $sr_head_file)"])]) +AC_SUBST(VERSION_GITVERSION_DEPS)[]dnl +AC_SUBST(VERSION_SOURCE_DIR, [${ac_abs_confdir}]) +AC_SUBST(VERSION_TAG_PREFIX, [$3])[]dnl +AC_SUBST(VERSION_TAG_NUMBER, [$4])[]dnl +AC_SUBST(VERSION_TAG_MATCH, [$3$4])[]dnl +dnl End of git version control details gathering. +dnl AC_SUBST([$1])[]dnl dnl AC_DEFINE([$1_MAJOR], [$5], [Major version number of $2.])[]dnl AC_DEFINE([$1_MINOR], [$6], [Minor version number of $2.])[]dnl m4_ifval([$7], [AC_DEFINE([$1_MICRO], [$7], [Micro version number of $2.])])[]dnl -AC_DEFINE_UNQUOTED([$1_STRING], ["[$]$1"], [Version of $2.])[]dnl +AC_DEFINE_UNQUOTED([$1_STRING_PREFIX], ["[$]$1"], [Version of $2.])[]dnl ]) ## SR_PKG_VERSION_SET(var-prefix, version-triple) diff --git a/src/analog.c b/src/analog.c index f5cc6285..76ea659c 100644 --- a/src/analog.c +++ b/src/analog.c @@ -492,8 +492,7 @@ SR_API int sr_analog_unit_to_string(const struct sr_datafeed_analog *analog, if (analog->meaning->mqflags & mq_strings[i].value) g_string_append(buf, mq_strings[i].str); - *result = buf->str; - g_string_free(buf, FALSE); + *result = g_string_free(buf, FALSE); return SR_OK; } diff --git a/src/binary_helpers.c b/src/binary_helpers.c index 86c4c583..14196110 100644 --- a/src/binary_helpers.c +++ b/src/binary_helpers.c @@ -22,7 +22,8 @@ #include #include "libsigrok-internal.h" -SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const void *data, size_t length) +SR_PRIV int bv_get_value_len(float *out, const struct binary_value_spec *spec, + const uint8_t *data, size_t length) { float value; @@ -37,17 +38,15 @@ SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const break switch (spec->type) { - VALUE_TYPE(BVT_UINT8, R8, sizeof(uint8_t)); + VALUE_TYPE(BVT_UINT8, read_u8, sizeof(uint8_t)); - VALUE_TYPE(BVT_BE_UINT16, RB16, sizeof(uint16_t)); - VALUE_TYPE(BVT_BE_UINT32, RB32, sizeof(uint32_t)); - VALUE_TYPE(BVT_BE_UINT64, RB64, sizeof(uint64_t)); - VALUE_TYPE(BVT_BE_FLOAT, RBFL, sizeof(float)); + VALUE_TYPE(BVT_BE_UINT16, read_u16be, sizeof(uint16_t)); + VALUE_TYPE(BVT_BE_UINT24, read_u24be, 3 * sizeof(uint8_t)); + VALUE_TYPE(BVT_BE_UINT32, read_u32be, sizeof(uint32_t)); - VALUE_TYPE(BVT_LE_UINT16, RL16, sizeof(uint16_t)); - VALUE_TYPE(BVT_LE_UINT32, RL32, sizeof(uint32_t)); - VALUE_TYPE(BVT_LE_UINT64, RL64, sizeof(uint64_t)); - VALUE_TYPE(BVT_LE_FLOAT, RLFL, sizeof(float)); + VALUE_TYPE(BVT_LE_UINT16, read_u16le, sizeof(uint16_t)); + VALUE_TYPE(BVT_LE_UINT24, read_u24le, 3 * sizeof(uint8_t)); + VALUE_TYPE(BVT_LE_UINT32, read_u32le, sizeof(uint32_t)); default: return SR_ERR_ARG; @@ -55,51 +54,46 @@ SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const #undef VALUE_TYPE - *out = value * spec->scale; + if (out) + *out = value; return SR_OK; } -SR_PRIV int bv_send_analog_channel(const struct sr_dev_inst *sdi, struct sr_channel *ch, - const struct binary_analog_channel *bac, const void *data, size_t length) +SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, + const uint8_t *data) { - int err; - struct sr_analog_encoding encoding; - struct sr_analog_meaning meaning; - struct sr_analog_spec spec; - struct sr_datafeed_analog analog; - struct sr_datafeed_packet packet = { - .type = SR_DF_ANALOG, - .payload = &analog, - }; float value; + const uint8_t *ptr; - err = bv_get_value(&value, &bac->spec, data, length); - if (err != SR_OK) - goto err_out; + ptr = &data[spec->offset]; - err = sr_analog_init(&analog, &encoding, &meaning, &spec, bac->digits); - if (err != SR_OK) - goto err_out; - - meaning.mq = bac->mq; - meaning.unit = bac->unit; - meaning.mqflags = 0; - meaning.channels = g_slist_append(NULL, ch); - - spec.spec_digits = bac->digits; - - analog.data = &value; - analog.num_samples = 1; - - err = sr_session_send(sdi, &packet); - if (err != SR_OK) - goto err_free; + switch (spec->type) { + case BVT_UINT8: + value = read_u8(ptr); + break; + case BVT_BE_UINT16: + value = read_u16be(ptr); + break; + case BVT_BE_UINT24: + value = read_u24be(ptr); + break; + case BVT_BE_UINT32: + value = read_u32be(ptr); + break; + case BVT_LE_UINT16: + value = read_u16le(ptr); + break; + case BVT_LE_UINT24: + value = read_u24le(ptr); + break; + case BVT_LE_UINT32: + value = read_u32le(ptr); + break; + default: + return SR_ERR_ARG; + } + if (out) + *out = value; return SR_OK; - -err_free: - g_slist_free(meaning.channels); - -err_out: - return err; } diff --git a/src/bt/bt_bluez.c b/src/bt/bt_bluez.c index d0a3b7c8..dc3a2636 100644 --- a/src/bt/bt_bluez.c +++ b/src/bt/bt_bluez.c @@ -74,6 +74,7 @@ #include #include #include +#include #include #include #include @@ -96,9 +97,6 @@ #define CONNECT_RFCOMM_TRIES 3 #define CONNECT_RFCOMM_RETRY_MS 100 -/* Silence warning about (currently) unused routine. */ -#define WITH_WRITE_TYPE_HANDLE 0 - /* {{{ compat decls */ /* * The availability of conversion helpers in @@ -239,6 +237,7 @@ struct sr_bt_desc { uint16_t write_handle; uint16_t cccd_handle; uint16_t cccd_value; + uint16_t ble_mtu; /* Internal state. */ int devid; int fd; @@ -249,10 +248,8 @@ static int sr_bt_desc_open(struct sr_bt_desc *desc, int *id_ref); static void sr_bt_desc_close(struct sr_bt_desc *desc); static int sr_bt_check_socket_usable(struct sr_bt_desc *desc); static ssize_t sr_bt_write_type(struct sr_bt_desc *desc, uint8_t type); -#if WITH_WRITE_TYPE_HANDLE static ssize_t sr_bt_write_type_handle(struct sr_bt_desc *desc, uint8_t type, uint16_t handle); -#endif static ssize_t sr_bt_write_type_handle_bytes(struct sr_bt_desc *desc, uint8_t type, uint16_t handle, const uint8_t *data, size_t len); static ssize_t sr_bt_char_write_req(struct sr_bt_desc *desc, @@ -365,7 +362,8 @@ SR_PRIV int sr_bt_config_rfcomm(struct sr_bt_desc *desc, size_t channel) SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc, uint16_t read_handle, uint16_t write_handle, - uint16_t cccd_handle, uint16_t cccd_value) + uint16_t cccd_handle, uint16_t cccd_value, + uint16_t ble_mtu) { if (!desc) @@ -375,6 +373,7 @@ SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc, desc->write_handle = write_handle; desc->cccd_handle = cccd_handle; desc->cccd_value = cccd_value; + desc->ble_mtu = ble_mtu; return 0; } @@ -902,10 +901,15 @@ SR_PRIV int sr_bt_check_notify(struct sr_bt_desc *desc) { uint8_t buf[1024]; ssize_t rdlen; + const uint8_t *bufptr; + size_t buflen; uint8_t packet_type; uint16_t packet_handle; uint8_t *packet_data; size_t packet_dlen; + const char *type_text; + int ret; + uint16_t mtu; if (!desc) return -1; @@ -913,57 +917,113 @@ SR_PRIV int sr_bt_check_notify(struct sr_bt_desc *desc) if (sr_bt_check_socket_usable(desc) < 0) return -2; - /* Get another message from the Bluetooth socket. */ + /* + * Get another message from the Bluetooth socket. + * + * TODO Can we assume that every "message" comes in a separate + * read(2) call, or can data combine at the caller's? Need we + * loop over the received content until all was consumed? + */ rdlen = sr_bt_read(desc, buf, sizeof(buf)); - if (rdlen < 0) + if (rdlen < 0) { + sr_dbg("check notifiy, read error, %zd", rdlen); return -2; - if (!rdlen) + } + if (!rdlen) { + if (0) sr_spew("check notifiy, empty read"); return 0; + } + bufptr = &buf[0]; + buflen = (size_t)rdlen; + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + GString *txt; + txt = sr_hexdump_new(bufptr, buflen); + sr_spew("check notifiy, read succes, length %zd, data: %s", + rdlen, txt->str); + sr_hexdump_free(txt); + } - /* Get header fields and references to the payload data. */ - packet_type = 0x00; + /* + * Get header fields and references to the payload data. Notice + * that the first 16bit item after the packet type often is the + * handle, but need not always be. That is why the read position + * is kept, so that individual packet type handlers can either + * read _their_ layout strictly sequentially, or can conveniently + * access what a common preparation step has provided to them. + */ packet_handle = 0x0000; packet_data = NULL; packet_dlen = 0; - if (rdlen >= 1) - packet_type = buf[0]; - if (rdlen >= 3) { - packet_handle = bt_get_le16(&buf[1]); - packet_data = &buf[3]; - packet_dlen = rdlen - 3; + packet_type = read_u8_inc_len(&bufptr, &buflen); + if (buflen >= sizeof(uint16_t)) { + packet_handle = read_u16le(bufptr); + packet_data = (void *)&bufptr[sizeof(uint16_t)]; + packet_dlen = buflen - sizeof(uint16_t); + if (!packet_dlen) + packet_data = NULL; } + if (0) sr_spew("check notifiy, prep, hdl %" PRIu16 ", data %p len %zu", + packet_handle, packet_data, packet_dlen); /* Dispatch according to the message type. */ switch (packet_type) { + case BLE_ATT_EXCHANGE_MTU_REQ: + type_text = "MTU exchange request"; + if (buflen < sizeof(uint16_t)) { + sr_dbg("%s, invalid (size)", type_text); + break; + } + mtu = read_u16le_inc_len(&bufptr, &buflen); + sr_dbg("%s, peripheral value %" PRIu16, type_text, mtu); + if (desc->ble_mtu) { + mtu = desc->ble_mtu; + sr_dbg("%s, central value %" PRIu16, type_text, mtu); + sr_bt_write_type_handle(desc, + BLE_ATT_EXCHANGE_MTU_RESP, mtu); + break; + } + sr_warn("Unhandled BLE %s.", type_text); + break; case BLE_ATT_ERROR_RESP: - sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "error response"); + type_text = "error response"; + if (!buflen) { + sr_dbg("%s, no payload", type_text); + break; + } /* EMPTY */ + sr_dbg("%s, not handled here", type_text); break; case BLE_ATT_WRITE_RESP: - sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "write response"); - /* EMPTY */ + type_text = "write response"; + sr_dbg("%s, note taken", type_text); break; case BLE_ATT_HANDLE_INDICATION: - sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "handle indication"); + type_text = "handle indication"; + sr_dbg("%s, data len %zu", type_text, packet_dlen); sr_bt_write_type(desc, BLE_ATT_HANDLE_CONFIRMATION); + sr_spew("%s, confirmation sent", type_text); if (packet_handle != desc->read_handle) return -4; - if (!packet_data) - return -4; if (!desc->data_cb) return 0; - return desc->data_cb(desc->data_cb_data, packet_data, packet_dlen); + ret = desc->data_cb(desc->data_cb_data, + packet_data, packet_dlen); + sr_spew("%s, data cb ret %d", type_text, ret); + return ret; case BLE_ATT_HANDLE_NOTIFICATION: - sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "handle notification"); + type_text = "handle notification"; + sr_dbg("%s, data len %zu", type_text, packet_dlen); if (packet_handle != desc->read_handle) return -4; - if (!packet_data) - return -4; if (!desc->data_cb) return 0; - return desc->data_cb(desc->data_cb_data, packet_data, packet_dlen); + ret = desc->data_cb(desc->data_cb_data, + packet_data, packet_dlen); + sr_spew("%s, data cb ret %d", type_text, ret); + return ret; default: - sr_spew("unsupported type 0x%02x", packet_type); + sr_dbg("unhandled type 0x%02x, len %zu", + packet_type, buflen); return -3; } @@ -1013,13 +1073,11 @@ static ssize_t sr_bt_write_type(struct sr_bt_desc *desc, uint8_t type) return 0; } -#if WITH_WRITE_TYPE_HANDLE static ssize_t sr_bt_write_type_handle(struct sr_bt_desc *desc, uint8_t type, uint16_t handle) { return sr_bt_write_type_handle_bytes(desc, type, handle, NULL, 0); } -#endif static ssize_t sr_bt_write_type_handle_bytes(struct sr_bt_desc *desc, uint8_t type, uint16_t handle, const uint8_t *data, size_t len) diff --git a/src/dmm/bm52x.c b/src/dmm/bm52x.c index 3a4a62b3..e5379f12 100644 --- a/src/dmm/bm52x.c +++ b/src/dmm/bm52x.c @@ -23,9 +23,10 @@ * * 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, diff --git a/src/dmm/bm85x.c b/src/dmm/bm85x.c index f6528885..ebab59fc 100644 --- a/src/dmm/bm85x.c +++ b/src/dmm/bm85x.c @@ -24,9 +24,10 @@ * * 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 diff --git a/src/dmm/bm86x.c b/src/dmm/bm86x.c index 442e5cca..c160a88c 100644 --- a/src/dmm/bm86x.c +++ b/src/dmm/bm86x.c @@ -23,9 +23,10 @@ * * 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 diff --git a/src/dmm/metex14.c b/src/dmm/metex14.c index d93052a8..1c751bd5 100644 --- a/src/dmm/metex14.c +++ b/src/dmm/metex14.c @@ -195,8 +195,6 @@ static void handle_flags(struct sr_datafeed_analog *analog, float *floatval, { int factor; - (void)exponent; - /* Factors */ factor = 0; if (info->is_pico) @@ -212,6 +210,7 @@ static void handle_flags(struct sr_datafeed_analog *analog, float *floatval, if (info->is_mega) factor += 6; *floatval *= powf(10, factor); + *exponent += factor; /* Measurement modes */ if (info->is_volt) { diff --git a/src/dmm/mm38xr.c b/src/dmm/mm38xr.c index 1e4ecd5a..8f51adda 100644 --- a/src/dmm/mm38xr.c +++ b/src/dmm/mm38xr.c @@ -341,7 +341,6 @@ SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval, struct sr_datafeed_analog *analog, void *info) { gboolean is_overload, is_bad_jack; - int exponent; int digits; struct meterman_info mi; @@ -350,6 +349,8 @@ SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval, if (meterman_38xr_decode(buf, &mi) != SR_OK) return SR_ERR; + digits = 0; + if (mi.meas_mode != MEAS_MODE_CONTINUITY) { is_overload = mi.reading == METERMAN_DIGITS_OVERLOAD; is_bad_jack = mi.reading == METERMAN_DIGITS_BAD_INPUT_JACK; @@ -440,18 +441,17 @@ SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval, if (mi.rflag_h == 0x0a || mi.peakstatus == 0x0b) analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE; if (mi.meas_mode != MEAS_MODE_CONTINUITY) { - digits = decimal_digits[mi.meas_mode][mi.rangecode]; - exponent = units_exponents[mi.meas_mode][mi.rangecode]; + digits = units_exponents[mi.meas_mode][mi.rangecode] - + decimal_digits[mi.meas_mode][mi.rangecode]; *floatval = mi.reading; if (meterman_38xr_is_negative(&mi)) { *floatval *= -1.0f; } - *floatval *= powf(10, -digits); - *floatval *= powf(10, exponent); + *floatval *= powf(10, digits); } - analog->encoding->digits = 4; - analog->spec->spec_digits = 4; + analog->encoding->digits = -digits; + analog->spec->spec_digits = -digits; return SR_OK; } diff --git a/src/drivers.c b/src/drivers.c index bf3f0b04..6dc2c55c 100644 --- a/src/drivers.c +++ b/src/drivers.c @@ -52,6 +52,5 @@ SR_API void sr_drivers_init(struct sr_context *ctx) drivers < sr_driver_list__stop; drivers++) g_array_append_val(array, *drivers); #endif - ctx->driver_list = (struct sr_dev_driver **)array->data; - g_array_free(array, FALSE); + ctx->driver_list = (struct sr_dev_driver **)g_array_free(array, FALSE); } diff --git a/src/hardware/agilent-dmm/protocol.c b/src/hardware/agilent-dmm/protocol.c index 82ef93cb..f004ec4b 100644 --- a/src/hardware/agilent-dmm/protocol.c +++ b/src/hardware/agilent-dmm/protocol.c @@ -1011,11 +1011,12 @@ SR_PRIV const struct agdmm_recv agdmm_recvs_u124x[] = { { "^\"(\\d\\d.{18}\\d)\"$", recv_stat_u124x }, { "^\\*([0-9])$", recv_switch }, { "^([-+][0-9]\\.[0-9]{8}E[-+][0-9]{2})$", recv_fetc }, - { "^\"(VOLT|CURR|RES|CAP|FREQ) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, - { "^\"(VOLT:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, - { "^\"(CURR:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, - { "^\"(CPER:[40]-20mA) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, + { "^\"(VOLT|CURR|RES|CAP|FREQ) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, + { "^\"(VOLT:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, + { "^\"(CURR:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, + { "^\"(CPER:[40]-20mA) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6,8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x }, { "^\"(T[0-9]:[A-Z]+) ([A-Z]+)\"$", recv_conf_u124x_5x }, + { "^\"(TEMP:[A-Z]+) ([A-Z]+)\"$", recv_conf_u124x_5x }, { "^\"(DIOD)\"$", recv_conf_u124x_5x }, ALL_ZERO }; diff --git a/src/hardware/asix-omega-rtm-cli/protocol.c b/src/hardware/asix-omega-rtm-cli/protocol.c index e72036bf..d7434951 100644 --- a/src/hardware/asix-omega-rtm-cli/protocol.c +++ b/src/hardware/asix-omega-rtm-cli/protocol.c @@ -215,7 +215,7 @@ static int omega_rtm_cli_process_rawdata(const struct sr_dev_inst *sdi) devc->samples.remain_count -= count; } if (count) { - ret = feed_queue_logic_submit(devc->samples.queue, + ret = feed_queue_logic_submit_one(devc->samples.queue, devc->samples.last_sample, count); if (ret != SR_OK) break; @@ -229,13 +229,13 @@ static int omega_rtm_cli_process_rawdata(const struct sr_dev_inst *sdi) * hand because future chunks might repeat it. */ write_u16le(devc->samples.last_sample, sample1); - ret = feed_queue_logic_submit(devc->samples.queue, + ret = feed_queue_logic_submit_one(devc->samples.queue, devc->samples.last_sample, 1); if (ret != SR_OK) break; write_u16le(devc->samples.last_sample, sample2); - ret = feed_queue_logic_submit(devc->samples.queue, + ret = feed_queue_logic_submit_one(devc->samples.queue, devc->samples.last_sample, 1); if (ret != SR_OK) break; diff --git a/src/hardware/atorch/api.c b/src/hardware/atorch/api.c new file mode 100644 index 00000000..69fe4e37 --- /dev/null +++ b/src/hardware/atorch/api.c @@ -0,0 +1,227 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Mathieu Pilato + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "protocol.h" + +static struct sr_dev_driver atorch_driver_info; + +static const uint32_t scanopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, +}; + +static const uint32_t drvopts[] = { + SR_CONF_ENERGYMETER, + SR_CONF_POWERMETER, + SR_CONF_ELECTRONIC_LOAD, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET, +}; + +static int create_channels_feed_queues(struct sr_dev_inst *sdi, + struct dev_context *devc) +{ + size_t i; + struct sr_channel *sr_ch; + const struct atorch_channel_desc *at_ch; + struct feed_queue_analog *feed; + const struct atorch_device_profile *p; + + p = devc->profile; + devc->feeds = g_malloc0(p->channel_count * sizeof(devc->feeds[0])); + for (i = 0; i < p->channel_count; i++) { + at_ch = &p->channels[i]; + sr_ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, at_ch->name); + feed = feed_queue_analog_alloc(sdi, 1, at_ch->digits, sr_ch); + feed_queue_analog_mq_unit(feed, at_ch->mq, at_ch->flags, at_ch->unit); + feed_queue_analog_scale_offset(feed, &at_ch->scale, NULL); + devc->feeds[i] = feed; + } + + return SR_OK; +} + +static GSList *atorch_scan(struct sr_dev_driver *di, + const char *conn, const char *serialcomm) +{ + struct sr_serial_dev_inst *serial; + GSList *devices; + struct dev_context *devc; + struct sr_dev_inst *sdi; + + serial = sr_serial_dev_inst_new(conn, serialcomm); + if (serial_open(serial, SERIAL_RDWR) != SR_OK) + goto err_out; + + devc = g_malloc0(sizeof(*devc)); + + if (atorch_probe(serial, devc) != SR_OK) { + sr_err("Failed to find a supported Atorch device."); + goto err_out_serial; + } + + sr_sw_limits_init(&devc->limits); + + sdi = g_malloc0(sizeof(*sdi)); + sdi->priv = devc; + sdi->status = SR_ST_INACTIVE; + sdi->vendor = g_strdup("Atorch"); + sdi->model = g_strdup(devc->profile->device_name); + sdi->version = NULL; + sdi->inst_type = SR_INST_SERIAL; + sdi->conn = serial; + + create_channels_feed_queues(sdi, devc); + + serial_close(serial); + + devices = g_slist_append(NULL, sdi); + return std_scan_complete(di, devices); + +err_out_serial: + g_free(devc); + serial_close(serial); +err_out: + sr_serial_dev_inst_free(serial); + + return NULL; +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + const char *serial_device, *serial_options; + + serial_device = NULL; + serial_options = "9600/8n1"; + + (void)sr_serial_extract_options(options, &serial_device, &serial_options); + if (!serial_device || !*serial_device) + return NULL; + + return atorch_scan(di, serial_device, serial_options); +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + (void)cg; + + if (!sdi || !data) + return SR_ERR_ARG; + + devc = sdi->priv; + + switch (key) { + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_FRAMES: + case SR_CONF_LIMIT_MSEC: + return sr_sw_limits_config_get(&devc->limits, key, data); + default: + return SR_ERR_NA; + } +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + (void)data; + (void)cg; + + devc = sdi->priv; + + switch (key) { + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_FRAMES: + case SR_CONF_LIMIT_MSEC: + return sr_sw_limits_config_set(&devc->limits, key, data); + default: + return SR_ERR_NA; + } +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + + serial = sdi->conn; + devc = sdi->priv; + + sr_sw_limits_acquisition_start(&devc->limits); + std_session_send_df_header(sdi); + + serial_source_add(sdi->session, serial, G_IO_IN, 100, + atorch_receive_data_callback, (void *)sdi); + + return SR_OK; +} + +static void clear_helper(struct dev_context *devc) +{ + size_t idx; + + if (!devc) + return; + + if (devc->feeds && devc->profile) { + for (idx = 0; idx < devc->profile->channel_count; idx++) + feed_queue_analog_free(devc->feeds[idx]); + g_free(devc->feeds); + } +} + +static int dev_clear(const struct sr_dev_driver *driver) +{ + return std_dev_clear_with_callback(driver, (std_dev_clear_callback)clear_helper); +} + +static struct sr_dev_driver atorch_driver_info = { + .name = "atorch", + .longname = "atorch meters and loads", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = std_serial_dev_open, + .dev_close = std_serial_dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = std_serial_dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(atorch_driver_info); diff --git a/src/hardware/atorch/protocol.c b/src/hardware/atorch/protocol.c new file mode 100644 index 00000000..595e619b --- /dev/null +++ b/src/hardware/atorch/protocol.c @@ -0,0 +1,246 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Mathieu Pilato + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "protocol.h" + +/* Duration of scan. */ +#define ATORCH_PROBE_TIMEOUT_MS 10000 + +/* + * Message layout: + * 2 magic header bytes + * 1 message type byte + * N payload bytes, determined by message type + */ + +/* Position of message type byte in a message. */ +#define HEADER_MSGTYPE_IDX 2 +#define PAYLOAD_START_IDX 3 + +/* Length of each message type. */ +#define MSGLEN_REPORT (4 + 32) +#define MSGLEN_REPLY (4 + 4) +#define MSGLEN_COMMAND (4 + 6) + +/* Minimal length of a valid message. */ +#define MSGLEN_MIN 4 + +static const uint8_t header_magic[] = { + 0xff, 0x55, +}; + +static const struct atorch_channel_desc atorch_dc_power_meter_channels[] = { + { "V", { 4, BVT_BE_UINT24, }, { 100, 1e3, }, 1, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "I", { 7, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, }, + { "C", { 10, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, }, + { "E", { 13, BVT_BE_UINT32, }, { 10, 1, }, -2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, }, + { "T", { 24, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, }, +}; + +static const struct atorch_channel_desc atorch_usb_power_meter_channels[] = { + { "V", { 4, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "I", { 7, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, }, + { "C", { 10, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, }, + { "E", { 13, BVT_BE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, }, + { "D-", { 17, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "D+", { 19, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "T", { 21, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, }, +}; + +static const struct atorch_device_profile atorch_profiles[] = { + { 0x02, "DC Meter", ARRAY_AND_SIZE(atorch_dc_power_meter_channels), }, + { 0x03, "USB Meter", ARRAY_AND_SIZE(atorch_usb_power_meter_channels), }, +}; + +static size_t get_length_for_msg_type(uint8_t msg_type) +{ + switch (msg_type) { + case MSG_REPORT: + return MSGLEN_REPORT; + case MSG_REPLY: + return MSGLEN_REPLY; + case MSG_COMMAND: + return MSGLEN_COMMAND; + default: + return 0; + } +} + +static void log_atorch_msg(const uint8_t *buf, size_t len) +{ + GString *text; + + if (sr_log_loglevel_get() < SR_LOG_DBG) + return; + + text = sr_hexdump_new(buf, len); + sr_dbg("Atorch msg: %s", text->str); + sr_hexdump_free(text); +} + +static const uint8_t *locate_next_valid_msg(struct dev_context *devc) +{ + uint8_t *valid_msg_ptr; + size_t valid_msg_len; + uint8_t *msg_ptr; + + /* Enough byte to make a message? */ + while (devc->rd_idx + MSGLEN_MIN <= devc->wr_idx) { + /* Look for header magic. */ + msg_ptr = devc->buf + devc->rd_idx; + if (memcmp(msg_ptr, header_magic, sizeof(header_magic)) != 0) { + devc->rd_idx += 1; + continue; + } + + /* Determine msg type and length. */ + valid_msg_len = get_length_for_msg_type(msg_ptr[HEADER_MSGTYPE_IDX]); + if (!valid_msg_len) { + devc->rd_idx += 2; + continue; + } + + /* Do we have the complete message? */ + if (devc->rd_idx + valid_msg_len <= devc->wr_idx) { + valid_msg_ptr = msg_ptr; + devc->rd_idx += valid_msg_len; + log_atorch_msg(valid_msg_ptr, valid_msg_len); + return valid_msg_ptr; + } + + return NULL; + } + return NULL; +} + +static const uint8_t *receive_msg(struct sr_serial_dev_inst *serial, + struct dev_context *devc) +{ + size_t len; + const uint8_t *valid_msg_ptr; + + while (1) { + /* Remove bytes already processed. */ + if (devc->rd_idx > 0) { + len = devc->wr_idx - devc->rd_idx; + memmove(devc->buf, devc->buf + devc->rd_idx, len); + devc->wr_idx -= devc->rd_idx; + devc->rd_idx = 0; + } + + /* Read more bytes to process. */ + len = ATORCH_BUFSIZE - devc->wr_idx; + len = serial_read_nonblocking(serial, devc->buf + devc->wr_idx, len); + if (len <= 0) + return NULL; + devc->wr_idx += len; + + /* Locate next start of message. */ + valid_msg_ptr = locate_next_valid_msg(devc); + if (valid_msg_ptr) + return valid_msg_ptr; + } +} + +static const struct atorch_device_profile *find_profile_for_device_type(uint8_t dev_type) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(atorch_profiles); i++) { + if (atorch_profiles[i].device_type == dev_type) + return &atorch_profiles[i]; + } + return NULL; +} + +static void parse_report_msg(struct sr_dev_inst *sdi, const uint8_t *report_ptr) +{ + struct dev_context *devc; + float val; + size_t i; + + devc = sdi->priv; + + std_session_send_df_frame_begin(sdi); + + for (i = 0; i < devc->profile->channel_count; i++) { + bv_get_value(&val, &devc->profile->channels[i].spec, report_ptr); + feed_queue_analog_submit_one(devc->feeds[i], val, 1); + } + + std_session_send_df_frame_end(sdi); + + sr_sw_limits_update_frames_read(&devc->limits, 1); + if (sr_sw_limits_check(&devc->limits)) + sr_dev_acquisition_stop(sdi); +} + +SR_PRIV int atorch_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc) +{ + int64_t deadline_us; + const struct atorch_device_profile *p; + const uint8_t *msg_ptr; + + devc->wr_idx = 0; + devc->rd_idx = 0; + + deadline_us = g_get_monotonic_time(); + deadline_us += ATORCH_PROBE_TIMEOUT_MS * 1000; + while (g_get_monotonic_time() <= deadline_us) { + msg_ptr = receive_msg(serial, devc); + if (msg_ptr && msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT) { + p = find_profile_for_device_type(msg_ptr[PAYLOAD_START_IDX]); + if (p) { + devc->profile = p; + return SR_OK; + } + sr_err("Unrecognized device type (0x%.4" PRIx8 ").", + devc->buf[PAYLOAD_START_IDX]); + return SR_ERR; + } + g_usleep(100 * 1000); + } + return SR_ERR; +} + +SR_PRIV int atorch_receive_data_callback(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + const uint8_t *msg_ptr; + + (void)fd; + + sdi = cb_data; + devc = sdi->priv; + + if (!sdi || !devc) + return TRUE; + + if (revents & G_IO_IN) { + while ((msg_ptr = receive_msg(sdi->conn, devc))) { + if (msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT) + parse_report_msg(sdi, msg_ptr); + } + } + + return TRUE; +} diff --git a/src/hardware/atorch/protocol.h b/src/hardware/atorch/protocol.h new file mode 100644 index 00000000..05cbe839 --- /dev/null +++ b/src/hardware/atorch/protocol.h @@ -0,0 +1,68 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Mathieu Pilato + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_ATORCH_PROTOCOL_H +#define LIBSIGROK_HARDWARE_ATORCH_PROTOCOL_H + +#include +#include +#include + +#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 diff --git a/src/hardware/chronovu-la/api.c b/src/hardware/chronovu-la/api.c index d35a4f53..d5ca278b 100644 --- a/src/hardware/chronovu-la/api.c +++ b/src/hardware/chronovu-la/api.c @@ -20,6 +20,8 @@ #include #include "protocol.h" +#define SCAN_EXPECTED_VENDOR 0x0403 + static const uint32_t scanopts[] = { SR_CONF_CONN, }; @@ -123,52 +125,62 @@ err_free_devc: static GSList *scan(struct sr_dev_driver *di, GSList *options) { - int i, ret, model; struct drv_context *drvc; - GSList *devices, *conn_devices, *l; + GSList *devices; + const char *conn; + int ret; + GSList *conn_devices, *l; + size_t i; struct sr_usb_dev_inst *usb; - struct sr_config *src; + uint8_t bus, addr; struct libusb_device_descriptor des; libusb_device **devlist; struct libusb_device_handle *hdl; - const char *conn; char product[64], serial_num[64], connection_id[64]; + int model; drvc = di->context; + devices = NULL; conn = NULL; - for (l = options; l; l = l->next) { - src = l->data; - switch (src->key) { - case SR_CONF_CONN: - conn = g_variant_get_string(src->data, NULL); - break; - } - } + (void)sr_serial_extract_options(options, &conn, NULL); + conn_devices = NULL; if (conn) conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn); - else - conn_devices = NULL; - devices = NULL; libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); - for (i = 0; devlist[i]; i++) { + bus = libusb_get_bus_number(devlist[i]); + addr = libusb_get_device_address(devlist[i]); if (conn) { + /* Check if the connection matches the user spec. */ for (l = conn_devices; l; l = l->next) { usb = l->data; - if (usb->bus == libusb_get_bus_number(devlist[i]) - && usb->address == libusb_get_device_address(devlist[i])) + if (usb->bus == bus && usb->address == addr) break; } if (!l) - /* This device matched none of the ones that - * matched the conn specification. */ continue; } libusb_get_device_descriptor(devlist[i], &des); + /* + * In theory we'd accept any USB device with a matching + * product string. In practice the enumeration takes a + * shortcut and only inspects devices when their USB VID + * matches the expectation. This avoids access to flaky + * devices which are unrelated to measurement purposes + * yet cause trouble when accessed including segfaults, + * while libusb won't transparently handle their flaws. + * + * See https://sigrok.org/bugzilla/show_bug.cgi?id=1115 + * and https://github.com/sigrokproject/libsigrok/pull/166 + * for a discussion. + */ + if (des.idVendor != SCAN_EXPECTED_VENDOR) + continue; + if ((ret = libusb_open(devlist[i], &hdl)) < 0) continue; @@ -214,7 +226,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sr_dbg("Failed to add device: %d.", ret); } } - libusb_free_device_list(devlist, 1); g_slist_free_full(conn_devices, (GDestroyNotify)sr_usb_dev_inst_free); diff --git a/src/hardware/devantech-eth008/api.c b/src/hardware/devantech-eth008/api.c new file mode 100644 index 00000000..9bace4ea --- /dev/null +++ b/src/hardware/devantech-eth008/api.c @@ -0,0 +1,389 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "protocol.h" + +#define VENDOR_TEXT "Devantech" + +static const uint32_t scanopts[] = { + SR_CONF_CONN, +}; + +static const uint32_t drvopts[] = { + SR_CONF_MULTIPLEXER, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONN | SR_CONF_GET, + SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */ +}; + +static const uint32_t devopts_cg_do[] = { + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg_di[] = { + SR_CONF_ENABLED | SR_CONF_GET, +}; + +static const uint32_t devopts_cg_ai[] = { + SR_CONF_VOLTAGE | SR_CONF_GET, +}; + +/* List of supported devices. Sorted by model ID. */ +static const struct devantech_eth008_model models[] = { + { 18, "ETH002", 2, 0, 0, 0, 1, 0, 0, }, + { 19, "ETH008", 8, 0, 0, 0, 1, 0, 0, }, + { 20, "ETH484", 16, 8, 4, 0, 2, 2, 0x00f0, }, + { 21, "ETH8020", 20, 8, 8, 0, 3, 4, 0, }, + { 22, "WIFI484", 16, 8, 4, 0, 2, 2, 0x00f0, }, + { 24, "WIFI8020", 20, 8, 8, 0, 3, 4, 0, }, + { 26, "WIFI002", 2, 0, 0, 0, 1, 0, 0, }, + { 28, "WIFI008", 8, 0, 0, 0, 1, 0, 0, }, + { 52, "ETH1610", 10, 16, 16, 0, 2, 2, 0, }, +}; + +static const struct devantech_eth008_model *find_model(uint8_t code) +{ + size_t idx; + const struct devantech_eth008_model *check; + + for (idx = 0; idx < ARRAY_SIZE(models); idx++) { + check = &models[idx]; + if (check->code != code) + continue; + return check; + } + + return NULL; +} + +static struct sr_dev_driver devantech_eth008_driver_info; + +static struct sr_dev_inst *probe_device_conn(const char *conn) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_serial_dev_inst *ser; + uint8_t code, hwver, fwver; + const struct devantech_eth008_model *model; + gboolean has_serno_cmd; + char snr_txt[16]; + struct channel_group_context *cgc; + size_t ch_idx, nr, do_idx, di_idx, ai_idx; + struct sr_channel_group *cg; + char cg_name[24]; + int ret; + + sdi = g_malloc0(sizeof(*sdi)); + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; + ser = sr_serial_dev_inst_new(conn, NULL); + sdi->conn = ser; + if (!ser) + goto probe_fail; + ret = serial_open(ser, 0); + if (ret != SR_OK) + goto probe_fail; + + ret = devantech_eth008_get_model(ser, &code, &hwver, &fwver); + if (ret != SR_OK) + goto probe_fail; + model = find_model(code); + if (!model) { + sr_err("Unknown model ID 0x%02x (HW %u, FW %u).", + code, hwver, fwver); + goto probe_fail; + } + devc->model_code = code; + devc->hardware_version = hwver; + devc->firmware_version = fwver; + devc->model = model; + sdi->vendor = g_strdup(VENDOR_TEXT); + sdi->model = g_strdup(model->name); + sdi->version = g_strdup_printf("HW%u FW%u", hwver, fwver); + sdi->connection_id = g_strdup(conn); + sdi->driver = &devantech_eth008_driver_info; + sdi->inst_type = SR_INST_SERIAL; + + has_serno_cmd = TRUE; + if (model->min_serno_fw && fwver < model->min_serno_fw) + has_serno_cmd = FALSE; + if (has_serno_cmd) { + snr_txt[0] = '\0'; + ret = devantech_eth008_get_serno(ser, + snr_txt, sizeof(snr_txt)); + if (ret != SR_OK) + goto probe_fail; + sdi->serial_num = g_strdup(snr_txt); + } + + ch_idx = 0; + devc->mask_do = (1UL << devc->model->ch_count_do) - 1; + devc->mask_do &= ~devc->model->mask_do_missing; + for (do_idx = 0; do_idx < devc->model->ch_count_do; do_idx++) { + nr = do_idx + 1; + if (devc->model->mask_do_missing & (1UL << do_idx)) + continue; + snprintf(cg_name, sizeof(cg_name), "DO%zu", nr); + cgc = g_malloc0(sizeof(*cgc)); + cg = sr_channel_group_new(sdi, cg_name, cgc); + cgc->index = do_idx; + cgc->number = nr; + cgc->ch_type = DV_CH_DIGITAL_OUTPUT; + (void)cg; + ch_idx++; + } + for (di_idx = 0; di_idx < devc->model->ch_count_di; di_idx++) { + nr = di_idx + 1; + snprintf(cg_name, sizeof(cg_name), "DI%zu", nr); + cgc = g_malloc0(sizeof(*cgc)); + cg = sr_channel_group_new(sdi, cg_name, cgc); + cgc->index = di_idx; + cgc->number = nr; + cgc->ch_type = DV_CH_DIGITAL_INPUT; + (void)cg; + ch_idx++; + } + for (ai_idx = 0; ai_idx < devc->model->ch_count_ai; ai_idx++) { + nr = ai_idx + 1; + snprintf(cg_name, sizeof(cg_name), "AI%zu", nr); + cgc = g_malloc0(sizeof(*cgc)); + cg = sr_channel_group_new(sdi, cg_name, cgc); + cgc->index = ai_idx; + cgc->number = nr; + cgc->ch_type = DV_CH_ANALOG_INPUT; + (void)cg; + ch_idx++; + } + if (1) { + /* Create an analog channel for the supply voltage. */ + snprintf(cg_name, sizeof(cg_name), "Vsupply"); + cgc = g_malloc0(sizeof(*cgc)); + cg = sr_channel_group_new(sdi, cg_name, cgc); + cgc->index = 0; + cgc->number = 0; + cgc->ch_type = DV_CH_SUPPLY_VOLTAGE; + (void)cg; + ch_idx++; + } + + return sdi; + +probe_fail: + if (ser) { + serial_close(ser); + sr_serial_dev_inst_free(ser); + } + if (devc) { + g_free(devc); + } + if (sdi) { + sdi->priv = NULL; + sr_dev_inst_free(sdi); + sdi = NULL; + } + return sdi; +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + struct drv_context *drvc; + const char *conn; + GSList *devices; + struct sr_dev_inst *sdi; + + drvc = di->context; + drvc->instances = NULL; + + /* A conn= spec is required for the TCP attached device. */ + conn = NULL; + (void)sr_serial_extract_options(options, &conn, NULL); + if (!conn || !*conn) + return NULL; + + devices = NULL; + sdi = probe_device_conn(conn); + if (sdi) + devices = g_slist_append(devices, sdi); + + return std_scan_complete(di, devices); +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct channel_group_context *cgc; + gboolean on; + uint16_t vin; + double vsupply; + int ret; + + if (!cg) { + switch (key) { + case SR_CONF_CONN: + if (!sdi->connection_id) + return SR_ERR_NA; + *data = g_variant_new_string(sdi->connection_id); + return SR_OK; + default: + return SR_ERR_NA; + } + } + + cgc = cg->priv; + if (!cgc) + return SR_ERR_NA; + switch (key) { + case SR_CONF_ENABLED: + if (cgc->ch_type == DV_CH_DIGITAL_OUTPUT) { + ret = devantech_eth008_query_do(sdi, cg, &on); + if (ret != SR_OK) + return ret; + *data = g_variant_new_boolean(on); + return SR_OK; + } + if (cgc->ch_type == DV_CH_DIGITAL_INPUT) { + ret = devantech_eth008_query_di(sdi, cg, &on); + if (ret != SR_OK) + return ret; + *data = g_variant_new_boolean(on); + return SR_OK; + } + return SR_ERR_NA; + case SR_CONF_VOLTAGE: + if (cgc->ch_type == DV_CH_ANALOG_INPUT) { + ret = devantech_eth008_query_ai(sdi, cg, &vin); + if (ret != SR_OK) + return ret; + *data = g_variant_new_uint32(vin); + return SR_OK; + } + if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) { + ret = devantech_eth008_query_supply(sdi, cg, &vin); + if (ret != SR_OK) + return ret; + vsupply = vin; + vsupply /= 1000.; + *data = g_variant_new_double(vsupply); + return SR_OK; + } + return SR_ERR_NA; + default: + return SR_ERR_NA; + } +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct channel_group_context *cgc; + gboolean on; + + if (!cg) { + switch (key) { + case SR_CONF_ENABLED: + /* Enable/disable all channels at the same time. */ + on = g_variant_get_boolean(data); + return devantech_eth008_setup_do(sdi, cg, on); + default: + return SR_ERR_NA; + } + } + + cgc = cg->priv; + if (!cgc) + return SR_ERR_NA; + switch (key) { + case SR_CONF_ENABLED: + if (cgc->ch_type != DV_CH_DIGITAL_OUTPUT) + return SR_ERR_NA; + on = g_variant_get_boolean(data); + return devantech_eth008_setup_do(sdi, cg, on); + default: + return SR_ERR_NA; + } +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct channel_group_context *cgc; + + if (!cg) { + switch (key) { + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); + default: + return SR_ERR_NA; + } + } + + cgc = cg->priv; + if (!cgc) + return SR_ERR_NA; + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + if (cgc->ch_type == DV_CH_DIGITAL_OUTPUT) { + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_do)); + return SR_OK; + } + if (cgc->ch_type == DV_CH_DIGITAL_INPUT) { + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_di)); + return SR_OK; + } + if (cgc->ch_type == DV_CH_ANALOG_INPUT) { + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_ai)); + return SR_OK; + } + if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) { + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_ai)); + return SR_OK; + } + return SR_ERR_NA; + default: + return SR_ERR_NA; + } +} + +static struct sr_dev_driver devantech_eth008_driver_info = { + .name = "devantech-eth008", + .longname = "Devantech ETH008", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = std_dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = std_serial_dev_open, + .dev_close = std_serial_dev_close, + .dev_acquisition_start = std_dummy_dev_acquisition_start, + .dev_acquisition_stop = std_dummy_dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(devantech_eth008_driver_info); diff --git a/src/hardware/devantech-eth008/protocol.c b/src/hardware/devantech-eth008/protocol.c new file mode 100644 index 00000000..afbbea97 --- /dev/null +++ b/src/hardware/devantech-eth008/protocol.c @@ -0,0 +1,611 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * 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 . + */ + +/* + * 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 + +#include "protocol.h" + +#define READ_TIMEOUT_MS 20 + +enum cmd_code { + CMD_GET_MODULE_INFO = 0x10, + CMD_DIGITAL_ACTIVE = 0x20, + CMD_DIGITAL_INACTIVE = 0x21, + CMD_DIGITAL_SET_OUTPUTS = 0x23, + CMD_DIGITAL_GET_OUTPUTS = 0x24, + CMD_DIGITAL_GET_INPUTS = 0x25, + CMD_DIGITAL_ACTIVE_1MS = 0x26, + CMD_DIGITAL_INACTIVE_1MS = 0x27, + CMD_ANALOG_GET_INPUT = 0x32, + CMD_ANALOG_GET_INPUT_12BIT = 0x33, + CMD_ANALOG_GET_ALL_VOLTAGES = 0x34, + CMD_ASCII_TEXT_COMMAND = 0x3a, + CMD_GET_SERIAL_NUMBER = 0x77, + CMD_GET_SUPPLY_VOLTS = 0x78, + CMD_PASSWORD_ENTRY = 0x79, + CMD_GET_UNLOCK_TIME = 0x7a, + CMD_IMMEDIATE_LOGOUT = 0x7b, +}; + +/* + * Transmit a request to the relay card. Checks that all bytes get sent, + * short writes are considered fatal. + */ +static int send_request(struct sr_serial_dev_inst *ser, + const uint8_t *data, size_t dlen) +{ + int ret; + size_t written; + + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + GString *txt = sr_hexdump_new(data, dlen); + sr_spew("TX --> %s.", txt->str); + sr_hexdump_free(txt); + } + ret = serial_write_blocking(ser, data, dlen, 0); + if (ret < 0) + return ret; + written = (size_t)ret; + if (written != dlen) + return SR_ERR_DATA; + return SR_OK; +} + +/* + * Receive a response from the relay card. Assumes fixed size payload, + * considers short reads fatal. + */ +static int recv_response(struct sr_serial_dev_inst *ser, + uint8_t *data, size_t dlen) +{ + int ret; + size_t got; + + ret = serial_read_blocking(ser, data, dlen, READ_TIMEOUT_MS); + if (ret < 0) + return ret; + got = (size_t)ret; + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + GString *txt = sr_hexdump_new(data, got); + sr_spew("<-- RX %s.", txt->str); + sr_hexdump_free(txt); + } + if (got != dlen) + return SR_ERR_DATA; + return SR_OK; +} + +/* Send a request then receive a response. Convenience routine. */ +static int send_then_recv(struct sr_serial_dev_inst *serial, + const uint8_t *tx_data, size_t tx_length, + uint8_t *rx_data, size_t rx_length) +{ + int ret; + + if (tx_data && tx_length) { + ret = send_request(serial, tx_data, tx_length); + if (ret != SR_OK) + return ret; + } + + if (rx_data && rx_length) { + ret = recv_response(serial, rx_data, rx_length); + if (ret != SR_OK) + return ret; + } + + return SR_OK; +} + +/* Identify the relay card, gather version information details. */ +SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial, + uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version) +{ + uint8_t req[1], *wrptr; + uint8_t rsp[3], v8; + const uint8_t *rdptr; + int ret; + + if (model_code) + *model_code = 0; + if (hw_version) + *hw_version = 0; + if (fw_version) + *fw_version = 0; + + wrptr = req; + write_u8_inc(&wrptr, CMD_GET_MODULE_INFO); + ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp)); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + v8 = read_u8_inc(&rdptr); + if (model_code) + *model_code = v8; + v8 = read_u8_inc(&rdptr); + if (hw_version) + *hw_version = v8; + v8 = read_u8_inc(&rdptr); + if (fw_version) + *fw_version = v8; + + return SR_OK; +} + +/* Get the relay card's serial number (its MAC address). */ +SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial, + char *text_buffer, size_t text_length) +{ + uint8_t req[1], *wrptr; + uint8_t rsp[6], b; + const uint8_t *rdptr, *endptr; + size_t written; + int ret; + + if (text_buffer && !text_length) + return SR_ERR_ARG; + if (text_buffer) + memset(text_buffer, 0, text_length); + + wrptr = req; + write_u8_inc(&wrptr, CMD_GET_SERIAL_NUMBER); + ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp)); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + endptr = rsp + sizeof(rsp); + while (rdptr < endptr && text_buffer && text_length >= 3) { + b = read_u8_inc(&rdptr); + written = snprintf(text_buffer, text_length, "%02x", b); + text_buffer += written; + text_length -= written; + } + + return SR_OK; +} + +/* Update an internal cache from the relay card's current state. */ +SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi) +{ + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + size_t rx_size; + uint8_t req[1], *wrptr; + uint8_t rsp[4]; + const uint8_t *rdptr; + uint32_t have; + int ret; + + serial = sdi->conn; + if (!serial) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* Get the state of digital outputs when the model supports them. */ + if (devc->model->ch_count_do) { + rx_size = devc->model->width_do; + if (rx_size > sizeof(rsp)) + return SR_ERR_NA; + + wrptr = req; + write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS); + ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + switch (rx_size) { + case 1: + have = read_u8_inc(&rdptr); + break; + case 2: + have = read_u16le_inc(&rdptr); + break; + case 3: + have = read_u24le_inc(&rdptr); + break; + default: + return SR_ERR_NA; + } + have &= devc->mask_do; + devc->curr_do = have; + } + + /* + * Get the state of digital inputs when the model supports them. + * (Sending unsupported requests to unaware firmware versions + * yields no response. That's why requests must be conditional.) + * + * Caching the state of analog inputs is condidered undesirable. + * Firmware does conversion at the very moment when the request + * is received to get a voltage reading. + */ + if (devc->model->ch_count_di) { + rx_size = devc->model->width_di; + if (rx_size > sizeof(rsp)) + return SR_ERR_NA; + + wrptr = req; + write_u8_inc(&wrptr, CMD_DIGITAL_GET_INPUTS); + ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + switch (rx_size) { + case 2: + have = read_u16be_inc(&rdptr); + break; + case 4: + have = read_u32be_inc(&rdptr); + break; + default: + return SR_ERR_NA; + } + have &= (1UL << devc->model->ch_count_di) - 1; + devc->curr_di = have; + } + + return SR_OK; +} + +/* Query the state of an individual relay channel. */ +SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, gboolean *on) +{ + struct dev_context *devc; + struct channel_group_context *cgc; + uint32_t have; + int ret; + + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* Unconditionally update the internal cache. */ + ret = devantech_eth008_cache_state(sdi); + if (ret != SR_OK) + return ret; + + /* + * Only reject unexpected requests after the update. Get the + * individual channel's state from the cache of all channels. + */ + if (!cg) + return SR_ERR_ARG; + cgc = cg->priv; + if (!cgc) + return SR_ERR_BUG; + if (cgc->index >= devc->model->ch_count_do) + return SR_ERR_ARG; + have = devc->curr_do; + have >>= cgc->index; + have &= 1 << 0; + if (on) + *on = have ? TRUE : FALSE; + + return SR_OK; +} + +/* + * Manipulate the state of an individual relay channel (when cg is given). + * Or set/clear all channels at the same time (when cg is NULL). + */ +SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, gboolean on) +{ + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + size_t width_do; + struct channel_group_context *cgc; + size_t number; + uint32_t reg; + uint8_t req[4], *wrptr, cmd; + uint8_t rsp[1], v8; + const uint8_t *rdptr; + int ret; + + serial = sdi->conn; + if (!serial) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + cgc = cg ? cg->priv : NULL; + if (cgc && cgc->index >= devc->model->ch_count_do) + return SR_ERR_ARG; + + width_do = devc->model->width_do; + if (1 + width_do > sizeof(req)) + return SR_ERR_NA; + + wrptr = req; + if (cgc) { + /* Manipulate an individual channel. */ + cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE; + number = cgc->number; + write_u8_inc(&wrptr, cmd); + write_u8_inc(&wrptr, number & 0xff); + write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */ + } else { + /* Manipulate all channels at the same time. */ + reg = on ? devc->mask_do : 0; + write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS); + switch (width_do) { + case 1: + write_u8_inc(&wrptr, reg & 0xff); + break; + case 2: + write_u16le_inc(&wrptr, reg & 0xffff); + break; + case 3: + write_u24le_inc(&wrptr, reg & 0xffffff); + break; + default: + return SR_ERR_NA; + } + } + ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp)); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + v8 = read_u8_inc(&rdptr); + if (v8 != 0) + return SR_ERR_DATA; + + return SR_OK; +} + +SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, gboolean *on) +{ + struct dev_context *devc; + struct channel_group_context *cgc; + uint32_t have; + int ret; + + /* Unconditionally update the internal cache. */ + ret = devantech_eth008_cache_state(sdi); + if (ret != SR_OK) + return ret; + + /* + * Only reject unexpected requests after the update. Get the + * individual channel's state from the cache of all channels. + */ + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (!cg) + return SR_ERR_ARG; + cgc = cg->priv; + if (!cgc) + return SR_ERR_BUG; + if (cgc->index >= devc->model->ch_count_di) + return SR_ERR_ARG; + have = devc->curr_di; + have >>= cgc->index; + have &= 1 << 0; + if (on) + *on = have ? TRUE : FALSE; + + return SR_OK; +} + +SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, uint16_t *adc_value) +{ + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + struct channel_group_context *cgc; + uint8_t req[2], *wrptr; + uint8_t rsp[2]; + const uint8_t *rdptr; + uint32_t have; + int ret; + + serial = sdi->conn; + if (!serial) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (!cg) + return SR_ERR_ARG; + cgc = cg->priv; + if (!cgc) + return SR_ERR_ARG; + if (cgc->index >= devc->model->ch_count_ai) + return SR_ERR_ARG; + + wrptr = req; + write_u8_inc(&wrptr, CMD_ANALOG_GET_INPUT); + write_u8_inc(&wrptr, cgc->number & 0xff); + ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp)); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + /* + * The interpretation of analog readings differs across models. + * All firmware versions provide an ADC result in BE format in + * a 16bit response. Some models provide 10 significant digits, + * others provide 12 bits. Full scale can either be 3V3 or 5V0. + * Some devices are 5V tolerant but won't read more than 3V3 + * values (and clip above that full scale value). Some firmware + * versions support request 0x33 in addition to 0x32. + * + * This is why this implementation provides the result to the + * caller as a unit-less value. It is also what the firmware's + * web interface does. + */ + have = read_u16be_inc(&rdptr); + if (adc_value) + *adc_value = have; + + return SR_OK; +} + +SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, uint16_t *millivolts) +{ + struct sr_serial_dev_inst *serial; + uint8_t req[1], *wrptr; + uint8_t rsp[1]; + const uint8_t *rdptr; + uint16_t have; + int ret; + + (void)cg; + + serial = sdi->conn; + if (!serial) + return SR_ERR_ARG; + + wrptr = req; + write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS); + ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp)); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */ + have = read_u8_inc(&rdptr); + have *= 100; + if (millivolts) + *millivolts = have; + + return SR_OK; +} diff --git a/src/hardware/devantech-eth008/protocol.h b/src/hardware/devantech-eth008/protocol.h new file mode 100644 index 00000000..7a687c71 --- /dev/null +++ b/src/hardware/devantech-eth008/protocol.h @@ -0,0 +1,83 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_DEVANTECH_ETH008_PROTOCOL_H +#define LIBSIGROK_HARDWARE_DEVANTECH_ETH008_PROTOCOL_H + +#include +#include +#include + +#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 diff --git a/src/hardware/fluke-dmm/api.c b/src/hardware/fluke-dmm/api.c index f533f1c5..b28e9be4 100644 --- a/src/hardware/fluke-dmm/api.c +++ b/src/hardware/fluke-dmm/api.c @@ -45,7 +45,7 @@ static const uint32_t devopts[] = { static const char *scan_conn[] = { /* 287/289 */ "115200/8n1", - /* 187/189 */ + /* 87/89/187/189 */ "9600/8n1", /* Scopemeter 190 series */ "1200/8n1", @@ -53,10 +53,12 @@ static const char *scan_conn[] = { }; static const struct flukedmm_profile supported_flukedmm[] = { + { FLUKE_87, "87", 100, 1000 }, + { FLUKE_89, "89", 100, 1000 }, { FLUKE_187, "187", 100, 1000 }, { FLUKE_189, "189", 100, 1000 }, - { FLUKE_287, "287", 100, 1000 }, { FLUKE_190, "199B", 1000, 3500 }, + { FLUKE_287, "287", 100, 1000 }, { FLUKE_289, "289", 100, 1000 }, }; @@ -226,7 +228,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) static struct sr_dev_driver flukedmm_driver_info = { .name = "fluke-dmm", - .longname = "Fluke 18x/28x series DMMs", + .longname = "Fluke 8x/18x/28x series DMMs", .api_version = 1, .init = std_init, .cleanup = std_cleanup, diff --git a/src/hardware/fluke-dmm/protocol.c b/src/hardware/fluke-dmm/protocol.c index fc2f11dd..d8837f66 100644 --- a/src/hardware/fluke-dmm/protocol.c +++ b/src/hardware/fluke-dmm/protocol.c @@ -26,6 +26,28 @@ #include "libsigrok-internal.h" #include "protocol.h" +static int count_digits(const char *str) +{ + int digits; + + if (*str == '-' || *str == '+') + str++; + + while (*str >= '0' && *str <= '9') + str++; + + digits = 0; + if (*str == '.') { + str++; + while (*str >= '0' && *str <= '9') { + str++; + digits++; + } + } + + return digits; +} + static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens) { struct dev_context *devc; @@ -37,6 +59,11 @@ static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens) float fvalue; char *e, *u; gboolean is_oor; + int digits; + int exponent; + enum sr_mq mq; + enum sr_unit unit; + enum sr_mqflag mqflags; devc = sdi->priv; @@ -46,6 +73,7 @@ static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens) if ((e = strstr(tokens[1], "Out of range"))) { is_oor = TRUE; fvalue = -1; + digits = 0; while (*e && *e != '.') e++; } else { @@ -56,101 +84,111 @@ static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens) while (*e && *e != ' ') e++; *e++ = '\0'; - if (sr_atof_ascii(tokens[1], &fvalue) != SR_OK || fvalue == 0.0) { + if (sr_atof_ascii(tokens[1], &fvalue) != SR_OK) { /* Happens all the time, when switching modes. */ - sr_dbg("Invalid float."); + sr_dbg("Invalid float: '%s'", tokens[1]); return; } + digits = count_digits(tokens[1]); } while (*e && *e == ' ') e++; - /* TODO: Use proper 'digits' value for this device (and its modes). */ - sr_analog_init(&analog, &encoding, &meaning, &spec, 2); - analog.data = &fvalue; - analog.meaning->channels = sdi->channels; - analog.num_samples = 1; if (is_oor) fvalue = NAN; - analog.meaning->mq = 0; + mq = 0; + unit = 0; + exponent = 0; + mqflags = 0; if ((u = strstr(e, "V DC")) || (u = strstr(e, "V AC"))) { - analog.meaning->mq = SR_MQ_VOLTAGE; - analog.meaning->unit = SR_UNIT_VOLT; + mq = SR_MQ_VOLTAGE; + unit = SR_UNIT_VOLT; if (!is_oor && e[0] == 'm') - fvalue /= 1000; + exponent = -3; /* This catches "V AC", "V DC" and "V AC+DC". */ if (strstr(u, "AC")) - analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; + mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; if (strstr(u, "DC")) - analog.meaning->mqflags |= SR_MQFLAG_DC; + mqflags |= SR_MQFLAG_DC; } else if ((u = strstr(e, "dBV")) || (u = strstr(e, "dBm"))) { - analog.meaning->mq = SR_MQ_VOLTAGE; + mq = SR_MQ_VOLTAGE; if (u[2] == 'm') - analog.meaning->unit = SR_UNIT_DECIBEL_MW; + unit = SR_UNIT_DECIBEL_MW; else - analog.meaning->unit = SR_UNIT_DECIBEL_VOLT; - analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; + unit = SR_UNIT_DECIBEL_VOLT; + mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; } else if ((u = strstr(e, "Ohms"))) { - analog.meaning->mq = SR_MQ_RESISTANCE; - analog.meaning->unit = SR_UNIT_OHM; + mq = SR_MQ_RESISTANCE; + unit = SR_UNIT_OHM; if (is_oor) fvalue = INFINITY; else if (e[0] == 'k') - fvalue *= 1000; + exponent = 3; else if (e[0] == 'M') - fvalue *= 1000000; + exponent = 6; } else if (!strcmp(e, "nS")) { - analog.meaning->mq = SR_MQ_CONDUCTANCE; - analog.meaning->unit = SR_UNIT_SIEMENS; - *((float *)analog.data) /= 1e+9; + mq = SR_MQ_CONDUCTANCE; + unit = SR_UNIT_SIEMENS; + exponent = -9; } else if ((u = strstr(e, "Farads"))) { - analog.meaning->mq = SR_MQ_CAPACITANCE; - analog.meaning->unit = SR_UNIT_FARAD; + mq = SR_MQ_CAPACITANCE; + unit = SR_UNIT_FARAD; if (!is_oor) { if (e[0] == 'm') - fvalue /= 1e+3; + exponent = -3; else if (e[0] == 'u') - fvalue /= 1e+6; + exponent = -6; else if (e[0] == 'n') - fvalue /= 1e+9; + exponent = -9; } } else if ((u = strstr(e, "Deg C")) || (u = strstr(e, "Deg F"))) { - analog.meaning->mq = SR_MQ_TEMPERATURE; + mq = SR_MQ_TEMPERATURE; if (u[4] == 'C') - analog.meaning->unit = SR_UNIT_CELSIUS; + unit = SR_UNIT_CELSIUS; else - analog.meaning->unit = SR_UNIT_FAHRENHEIT; + unit = SR_UNIT_FAHRENHEIT; } else if ((u = strstr(e, "A AC")) || (u = strstr(e, "A DC"))) { - analog.meaning->mq = SR_MQ_CURRENT; - analog.meaning->unit = SR_UNIT_AMPERE; + mq = SR_MQ_CURRENT; + unit = SR_UNIT_AMPERE; /* This catches "A AC", "A DC" and "A AC+DC". */ if (strstr(u, "AC")) - analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; + mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; if (strstr(u, "DC")) - analog.meaning->mqflags |= SR_MQFLAG_DC; + mqflags |= SR_MQFLAG_DC; if (!is_oor) { if (e[0] == 'm') - fvalue /= 1e+3; + exponent = -3; else if (e[0] == 'u') - fvalue /= 1e+6; + exponent = -6; } } else if ((u = strstr(e, "Hz"))) { - analog.meaning->mq = SR_MQ_FREQUENCY; - analog.meaning->unit = SR_UNIT_HERTZ; + mq = SR_MQ_FREQUENCY; + unit = SR_UNIT_HERTZ; if (e[0] == 'k') - fvalue *= 1e+3; + exponent = 3; } else if (!strcmp(e, "%")) { - analog.meaning->mq = SR_MQ_DUTY_CYCLE; - analog.meaning->unit = SR_UNIT_PERCENTAGE; + mq = SR_MQ_DUTY_CYCLE; + unit = SR_UNIT_PERCENTAGE; } else if ((u = strstr(e, "ms"))) { - analog.meaning->mq = SR_MQ_PULSE_WIDTH; - analog.meaning->unit = SR_UNIT_SECOND; - fvalue /= 1e+3; + mq = SR_MQ_PULSE_WIDTH; + unit = SR_UNIT_SECOND; + exponent = -3; } - if (analog.meaning->mq != 0) { + if (mq != 0) { /* Got a measurement. */ + digits -= exponent; + fvalue *= pow(10.0f, exponent); + + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); + analog.data = &fvalue; + analog.num_samples = 1; + analog.meaning->unit = unit; + analog.meaning->mq = mq; + analog.meaning->mqflags = mqflags; + analog.meaning->channels = sdi->channels; + packet.type = SR_DF_ANALOG; packet.payload = &analog; sr_session_send(sdi, &packet); @@ -167,19 +205,40 @@ static void handle_qm_28x(const struct sr_dev_inst *sdi, char **tokens) struct sr_analog_meaning meaning; struct sr_analog_spec spec; float fvalue; + int digits; + int exponent; + char *e; devc = sdi->priv; if (!tokens[1]) return; - if (sr_atof_ascii(tokens[0], &fvalue) != SR_OK || fvalue == 0.0) { - sr_err("Invalid float '%s'.", tokens[0]); + /* Split measurement into mantissa / exponent */ + e = tokens[0]; + while (*e) { + if (*e == 'e' || *e == 'E') { + *e = '\0'; + e++; + break; + } + e++; + } + + if (sr_atof_ascii(tokens[0], &fvalue) != SR_OK) { + sr_err("Invalid mantissa '%s'.", tokens[0]); + return; + } + + if (sr_atoi(e, &exponent) != SR_OK) { + sr_err("Invalid exponent '%s'.", e); return; } - /* TODO: Use proper 'digits' value for this device (and its modes). */ - sr_analog_init(&analog, &encoding, &meaning, &spec, 2); + digits = count_digits(tokens[0]) - exponent; + fvalue *= pow(10.0f, exponent); + + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); analog.data = &fvalue; analog.meaning->channels = sdi->channels; analog.num_samples = 1; @@ -378,7 +437,9 @@ static void handle_qm_19x_data(const struct sr_dev_inst *sdi, char **tokens) struct sr_analog_meaning meaning; struct sr_analog_spec spec; float fvalue; + int digits; + digits = 2; if (!strcmp(tokens[0], "9.9E+37")) { /* An invalid measurement shows up on the display as "OL", but * comes through like this. Since comparing 38-digit floats @@ -389,6 +450,7 @@ static void handle_qm_19x_data(const struct sr_dev_inst *sdi, char **tokens) sr_err("Invalid float '%s'.", tokens[0]); return; } + digits = count_digits(tokens[0]); } devc = sdi->priv; @@ -405,8 +467,7 @@ static void handle_qm_19x_data(const struct sr_dev_inst *sdi, char **tokens) fvalue = 1.0; } - /* TODO: Use proper 'digits' value for this device (and its modes). */ - sr_analog_init(&analog, &encoding, &meaning, &spec, 2); + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); analog.meaning->channels = sdi->channels; analog.num_samples = 1; analog.data = &fvalue; @@ -426,6 +487,7 @@ static void handle_line(const struct sr_dev_inst *sdi) struct sr_serial_dev_inst *serial; int num_tokens, n, i; char cmd[16], **tokens; + int ret; devc = sdi->priv; serial = sdi->conn; @@ -443,34 +505,48 @@ static void handle_line(const struct sr_dev_inst *sdi) tokens = g_strsplit(devc->buf, ",", 0); if (tokens[0]) { - if (devc->profile->model == FLUKE_187 || devc->profile->model == FLUKE_189) { + switch (devc->profile->model) { + case FLUKE_87: + case FLUKE_89: + case FLUKE_187: + case FLUKE_189: devc->expect_response = FALSE; handle_qm_18x(sdi, tokens); - } else if (devc->profile->model == FLUKE_287 || devc->profile->model == FLUKE_289) { + break; + case FLUKE_190: devc->expect_response = FALSE; - handle_qm_28x(sdi, tokens); - } else if (devc->profile->model == FLUKE_190) { - devc->expect_response = FALSE; - for (num_tokens = 0; tokens[num_tokens]; num_tokens++); - if (num_tokens >= 7) { - /* Response to QM: this is a comma-separated list of - * fields with metadata about the measurement. This - * format can return multiple sets of metadata, - * split into sets of 7 tokens each. */ - devc->meas_type = 0; - for (i = 0; i < num_tokens; i += 7) - handle_qm_19x_meta(sdi, tokens + i); - if (devc->meas_type) { - /* Slip the request in now, before the main - * timer loop asks for metadata again. */ - n = sprintf(cmd, "QM %d\r", devc->meas_type); - if (serial_write_blocking(serial, cmd, n, SERIAL_WRITE_TIMEOUT_MS) < 0) - sr_err("Unable to send QM (measurement)."); - } - } else { + num_tokens = g_strv_length(tokens); + if (num_tokens < 7) { /* Response to QM 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); diff --git a/src/hardware/fluke-dmm/protocol.h b/src/hardware/fluke-dmm/protocol.h index 36ece3c9..4eab15ac 100644 --- a/src/hardware/fluke-dmm/protocol.h +++ b/src/hardware/fluke-dmm/protocol.h @@ -29,10 +29,12 @@ /* Supported models */ enum { - FLUKE_187 = 1, + FLUKE_87 = 1, + FLUKE_89, + FLUKE_187, FLUKE_189, - FLUKE_287, FLUKE_190, + FLUKE_287, FLUKE_289, }; diff --git a/src/hardware/greatfet/api.c b/src/hardware/greatfet/api.c new file mode 100644 index 00000000..db05c3d8 --- /dev/null +++ b/src/hardware/greatfet/api.c @@ -0,0 +1,568 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2019 Katherine J. Temkin + * Copyright (C) 2019 Mikaela Szekely + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include "protocol.h" + +#define DEFAULT_CONN "1d50.60e6" +#define CONTROL_INTERFACE 0 +#define SAMPLES_INTERFACE 1 + +#define VENDOR_TEXT "Great Scott Gadgets" +#define MODEL_TEXT "GreatFET" + +#define BUFFER_SIZE (4 * 1024 * 1024) + +#define DEFAULT_SAMPLERATE SR_KHZ(34000) +#define BANDWIDTH_THRESHOLD (SR_MHZ(42) * 8) + +static const uint32_t scanopts[] = { + SR_CONF_CONN, + SR_CONF_PROBE_NAMES, +}; + +static const uint32_t drvopts[] = { + SR_CONF_LOGIC_ANALYZER, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS | SR_CONF_GET, + SR_CONF_CONN | SR_CONF_GET, + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg[] = { + /* EMPTY */ +}; + +static const char *channel_names[] = { + "SGPIO0", "SGPIO1", "SGPIO2", "SGPIO3", + "SGPIO4", "SGPIO5", "SGPIO6", "SGPIO7", + "SGPIO8", "SGPIO9", "SGPIO10", "SGPIO11", + "SGPIO12", "SGPIO13", "SGPIO14", "SGPIO15", +}; + +/* + * The seemingly odd samplerates result from the 204MHz base clock and + * a 12bit integer divider. Theoretical minimum could be 50kHz but we + * don't bother to provide so low a selection item here. + * + * When users specify different samplerates, device firmware will pick + * the minimum rate which satisfies the user's request. + */ +static const uint64_t samplerates[] = { + SR_KHZ(1000), /* 1.0MHz */ + SR_KHZ(2000), /* 2.0MHz */ + SR_KHZ(4000), /* 4.0MHz */ + SR_KHZ(8500), /* 8.5MHz */ + SR_KHZ(10200), /* 10.2MHz */ + SR_KHZ(12000), /* 12.0MHz */ + SR_KHZ(17000), /* 17.0MHz */ + SR_KHZ(20400), /* 20.4MHz, the maximum for 16 channels */ + SR_KHZ(25500), /* 25.5MHz */ + SR_KHZ(34000), /* 34.0MHz */ + SR_KHZ(40800), /* 40.8MHz, the maximum for 8 channels */ + SR_KHZ(51000), /* 51.0MHz */ + SR_KHZ(68000), /* 68.0MHz, the maximum for 4 channels */ + SR_KHZ(102000), /* 102.0MHz, the maximum for 2 channels */ + SR_KHZ(204000), /* 204.0MHz, the maximum for 1 channel */ +}; + +static void greatfet_free_devc(struct dev_context *devc) +{ + + if (!devc) + return; + + if (devc->sdi) + devc->sdi->priv = NULL; + + g_string_free(devc->usb_comm_buffer, TRUE); + g_free(devc->firmware_version); + g_free(devc->serial_number); + sr_free_probe_names(devc->channel_names); + feed_queue_logic_free(devc->acquisition.feed_queue); + g_free(devc->transfers.transfers); + g_free(devc->transfers.transfer_buffer); + /* + * USB transfers should not have been allocated when we get here + * during device probe/scan, or during shutdown after acquisition + * has terminated. + */ + + g_free(devc); +} + +static void greatfet_free_sdi(struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb; + struct dev_context *devc; + + if (!sdi) + return; + + usb = sdi->conn; + sdi->conn = NULL; + if (usb && usb->devhdl) + sr_usb_close(usb); + sr_usb_dev_inst_free(usb); + + devc = sdi->priv; + sdi->priv = NULL; + greatfet_free_devc(devc); + + sr_dev_inst_free(sdi); +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + struct drv_context *drvc; + struct sr_context *ctx; + GSList *devices; + const char *conn, *probe_names; + const char *want_snr; + struct sr_config *src; + GSList *conn_devices, *l; + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_usb_dev_inst *usb; + gboolean skip_device; + struct libusb_device *dev; + struct libusb_device_descriptor des; + char *match; + char serno_txt[64], conn_id[64]; + int ret; + size_t ch_off, ch_max, ch_idx; + gboolean enabled; + struct sr_channel *ch; + struct sr_channel_group *cg; + + drvc = di->context; + ctx = drvc->sr_ctx; + + devices = NULL; + + /* Accept user specs for conn= and probe names. */ + conn = DEFAULT_CONN; + probe_names = NULL; + for (l = options; l; l = l->next) { + src = l->data; + switch (src->key) { + case SR_CONF_CONN: + conn = g_variant_get_string(src->data, NULL); + break; + case SR_CONF_PROBE_NAMES: + probe_names = g_variant_get_string(src->data, NULL); + break; + } + } + + /* + * By default search for all devices with the expected VID/PID. + * Accept external specs in either "bus.addr" or "vid.pid" form. + * As an alternative accept "sn=..." specs and keep using the + * default VID/PID in that case. This should result in maximum + * usability while still using a maximum amount of common code. + */ + want_snr = NULL; + if (g_str_has_prefix(conn, "sn=")) { + want_snr = conn + strlen("sn="); + conn = DEFAULT_CONN; + sr_info("Searching default %s and serial number %s.", + conn, want_snr); + } + conn_devices = sr_usb_find(ctx->libusb_ctx, conn); + if (!conn_devices) + return devices; + + /* + * Iterate over all devices that have the matching VID/PID. + * Skip those which we cannot open. Skip those which don't + * match additional serial number conditions. Allocate the + * structs for found devices "early", to re-use common code + * for communication to the firmware. Release these structs + * when identification fails or the device does not match. + * + * Notice that the scan for devices uses the USB string for + * the serial number, and does a weak check (partial match). + * This allows users to either use lsusb(8) or gf(1) output + * as well as match lazily when only part of the serial nr is + * known and becomes unique. Matching against serial nr and + * finding multiple devices is as acceptable, just might be a + * rare use case. Failure in this stage is silent, there are + * legal reasons why we cannot access a device during scan. + * + * Once a device was found usable, we get its serial number + * and version details by means of firmware communication. + * To verify that the firmware is operational and that the + * protocol works to a minimum degree. And to present data + * in --scan output which matches the vendor's gf(1) utility. + * This version detail is _not_ checked against conn= specs + * because users may specify the longer text string with + * more leading digits from lsusb(8) output. That test would + * fail when executed against the shorter firmware output. + */ + for (l = conn_devices; l; l = l->next) { + usb = l->data; + + ret = sr_usb_open(ctx->libusb_ctx, usb); + if (ret != SR_OK) + continue; + + skip_device = FALSE; + if (want_snr) do { + dev = libusb_get_device(usb->devhdl); + ret = libusb_get_device_descriptor(dev, &des); + if (ret != 0 || !des.iSerialNumber) { + skip_device = TRUE; + break; + } + ret = libusb_get_string_descriptor_ascii(usb->devhdl, + des.iSerialNumber, + (uint8_t *)serno_txt, sizeof(serno_txt)); + if (ret < 0) { + skip_device = TRUE; + break; + } + match = strstr(serno_txt, want_snr); + skip_device = !match; + sr_dbg("got serno %s, checking %s, match %d", + serno_txt, want_snr, !!match); + } while (0); + if (skip_device) { + sr_usb_close(usb); + continue; + } + + sdi = g_malloc0(sizeof(*sdi)); + sdi->conn = usb; + sdi->inst_type = SR_INST_USB; + sdi->status = SR_ST_INACTIVE; + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; + devc->sdi = sdi; + devc->usb_comm_buffer = NULL; + + /* + * Get the serial number by way of device communication. + * Get the firmware version. Failure is fatal. + */ + ret = greatfet_get_serial_number(sdi); + if (ret != SR_OK || !devc->serial_number) { + sr_err("Cannot get serial number."); + greatfet_free_sdi(sdi); + continue; + } + ret = greatfet_get_version_number(sdi); + if (ret != SR_OK || !devc->firmware_version) { + sr_err("Cannot get firmware version."); + greatfet_free_sdi(sdi); + continue; + } + + /* Continue filling in sdi and devc. */ + snprintf(conn_id, sizeof(conn_id), "%u.%u", + usb->bus, usb->address); + sdi->connection_id = g_strdup(conn_id); + sr_usb_close(usb); + + sdi->vendor = g_strdup(VENDOR_TEXT); + sdi->model = g_strdup(MODEL_TEXT); + sdi->version = g_strdup(devc->firmware_version); + sdi->serial_num = g_strdup(devc->serial_number); + + /* Create the "Logic" channel group. */ + ch_off = 0; + ch_max = ARRAY_SIZE(channel_names); + devc->channel_names = sr_parse_probe_names(probe_names, + channel_names, ch_max, ch_max, &ch_max); + devc->channel_count = ch_max; + cg = sr_channel_group_new(sdi, "Logic", NULL); + for (ch_idx = 0; ch_idx < ch_max; ch_idx++) { + enabled = ch_idx < 8; + ch = sr_channel_new(sdi, ch_off, + SR_CHANNEL_LOGIC, enabled, + devc->channel_names[ch_idx]); + ch_off++; + cg->channels = g_slist_append(cg->channels, ch); + } + devc->feed_unit_size = (ch_max + 8 - 1) / 8; + + sr_sw_limits_init(&devc->sw_limits); + devc->samplerate = DEFAULT_SAMPLERATE; + devc->acquisition.bandwidth_threshold = BANDWIDTH_THRESHOLD; + devc->acquisition.control_interface = CONTROL_INTERFACE; + devc->acquisition.samples_interface = SAMPLES_INTERFACE; + devc->acquisition.acquisition_state = ACQ_IDLE; + + devices = g_slist_append(devices, sdi); + } + g_slist_free(conn_devices); + + return std_scan_complete(di, devices); +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + struct sr_dev_driver *di; + struct drv_context *drvc; + struct sr_context *ctx; + struct sr_usb_dev_inst *usb; + + di = sdi->driver; + drvc = di->context; + ctx = drvc->sr_ctx; + usb = sdi->conn; + + return sr_usb_open(ctx->libusb_ctx, usb); +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb; + struct dev_context *devc; + struct dev_acquisition_t *acq; + + if (!sdi) + return SR_ERR_ARG; + usb = sdi->conn; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + acq = &devc->acquisition; + + greatfet_release_resources(sdi); + + if (!usb->devhdl) + return SR_ERR_BUG; + + sr_info("Closing device on %s interface %d.", + sdi->connection_id, acq->control_interface); + if (acq->control_interface_claimed) { + libusb_release_interface(usb->devhdl, acq->control_interface); + acq->control_interface_claimed = FALSE; + } + sr_usb_close(usb); + + return SR_OK; +} + +static void clear_helper(struct dev_context *devc) +{ + greatfet_free_devc(devc); +} + +static int dev_clear(const struct sr_dev_driver *driver) +{ + return std_dev_clear_with_callback(driver, + (std_dev_clear_callback)clear_helper); +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + + /* Handle requests for the "Logic" channel group. */ + if (cg) { + switch (key) { + default: + return SR_ERR_NA; + } + } + + /* Handle global options for the device. */ + switch (key) { + case SR_CONF_CONN: + if (!sdi->connection_id) + return SR_ERR_NA; + *data = g_variant_new_string(sdi->connection_id); + return SR_OK; + case SR_CONF_CONTINUOUS: + *data = g_variant_new_boolean(TRUE); + return SR_OK; + case SR_CONF_SAMPLERATE: + if (!devc) + return SR_ERR_NA; + *data = g_variant_new_uint64(devc->samplerate); + return SR_OK; + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_MSEC: + if (!devc) + return SR_ERR_NA; + return sr_sw_limits_config_get(&devc->sw_limits, key, data); + default: + return SR_ERR_NA; + } +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + devc = sdi->priv; + + /* Handle requests for the "Logic" channel group. */ + if (cg) { + switch (key) { + default: + return SR_ERR_NA; + } + } + + /* Handle global options for the device. */ + switch (key) { + case SR_CONF_SAMPLERATE: + if (!devc) + return SR_ERR_NA; + devc->samplerate = g_variant_get_uint64(data); + return SR_OK; + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_MSEC: + if (!devc) + return SR_ERR_NA; + return sr_sw_limits_config_set(&devc->sw_limits, key, data); + default: + return SR_ERR_NA; + } +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + + /* Handle requests for the "Logic" channel group. */ + if (cg) { + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + if (ARRAY_SIZE(devopts_cg) == 0) + return SR_ERR_NA; + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + ARRAY_AND_SIZE(devopts_cg), + sizeof(devopts_cg[0])); + return SR_OK; + default: + return SR_ERR_NA; + } + } + + /* Handle global options for the device. */ + switch (key) { + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); + case SR_CONF_SAMPLERATE: + *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates)); + return SR_OK; + default: + return SR_ERR_NA; + } +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct sr_dev_driver *di; + struct drv_context *drvc; + struct sr_context *ctx; + struct dev_context *devc; + struct dev_acquisition_t *acq; + int ret; + + if (!sdi || !sdi->driver || !sdi->priv) + return SR_ERR_ARG; + di = sdi->driver; + drvc = di->context; + ctx = drvc->sr_ctx; + devc = sdi->priv; + acq = &devc->acquisition; + + acq->acquisition_state = ACQ_PREPARE; + + ret = greatfet_setup_acquisition(sdi); + if (ret != SR_OK) + return ret; + + if (!acq->feed_queue) { + acq->feed_queue = feed_queue_logic_alloc(sdi, + BUFFER_SIZE, devc->feed_unit_size); + if (!acq->feed_queue) { + sr_err("Cannot allocate session feed buffer."); + return SR_ERR_MALLOC; + } + } + + sr_sw_limits_acquisition_start(&devc->sw_limits); + + ret = greatfet_start_acquisition(sdi); + acq->start_req_sent = ret == SR_OK; + if (ret != SR_OK) { + greatfet_abort_acquisition(sdi); + feed_queue_logic_free(acq->feed_queue); + acq->feed_queue = NULL; + return ret; + } + acq->acquisition_state = ACQ_RECEIVE; + + usb_source_add(sdi->session, ctx, 50, + greatfet_receive_data, (void *)sdi); + + ret = std_session_send_df_header(sdi); + acq->frame_begin_sent = ret == SR_OK; + (void)sr_session_send_meta(sdi, SR_CONF_SAMPLERATE, + g_variant_new_uint64(acq->capture_samplerate)); + + return SR_OK; +} + +static int dev_acquisition_stop(struct sr_dev_inst *sdi) +{ + greatfet_abort_acquisition(sdi); + return SR_OK; +} + +static struct sr_dev_driver greatfet_driver_info = { + .name = "greatfet", + .longname = "Great Scott Gadgets GreatFET One", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = dev_open, + .dev_close = dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(greatfet_driver_info); diff --git a/src/hardware/greatfet/protocol.c b/src/hardware/greatfet/protocol.c new file mode 100644 index 00000000..4b1d1d96 --- /dev/null +++ b/src/hardware/greatfet/protocol.c @@ -0,0 +1,1424 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2019 Katherine J. Temkin + * Copyright (C) 2019 Mikaela Szekely + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "protocol.h" + +/* + * Communicate to GreatFET firmware, especially its Logic Analyzer mode. + * + * Firmware communication is done by two means: Control transfers to + * EP0 for command execution. Bulk transfer from EP1 for sample data. + * The sample data endpoint number is also provided by firmware in + * responses to LA configuration requests. + * + * Control transfers have a fixed layout: 2x u32 class and verb numbers, + * and u8[] payload data up to 512 bytes length. Payload layout depends + * on commands and the verb's parameters. Binary data is represented in + * LE format (firmware executes on Cortex-M). Strings are limited to a + * maximum of 128 bytes. + * + * The set of commands used by this sigrok driver is minimal: + * - Get the GreatFET's firmware version and serial number. + * - String queries, a core verb, individual verb codes for the + * version and for the serial number. + * - Configure Logic Analyzer mode, start and stop captures. + * - Configure takes a u32 samplerate and u8 channel count. Yields + * u32 samplerate, u32 buffer size, u8 endpoint number. + * - Start takes a u32 samplerate (does it? depending on firmware + * version?). Empty/no response. + * - Stop has empty/no request and response payloads. + * + * Firmware implementation details, observed during sigrok driver + * creation. + * - Serial number "strings" in responses may carry binary data and + * not a text presentation of the serial number. It's uncertain + * whether that is by design or an oversight. This sigrok driver + * copes when it happens. (Remainder from another request which + * provided the part number as well?) + * - The GreatFET firmware is designed for exploration by host apps. + * The embedded classes, their methods, their in/out parameters, + * including builtin help texts, can get enumerated. This driver + * does not use this discovery approach, assumes a given protocol. + * - The NXP LPC4330 chip has 16 SGPIO pins. It's assumed that the + * GreatFET firmware currently does not support more than 8 logic + * channels due to constraints on bitbang machinery synchronization + * which is under construction (IIUC, it's about pin banks that + * run independently). When firmware versions get identified which + * transparently (from the host's perspective) support more than + * 8 channels, this host driver may need a little adjustment. + * - The device can sample and stream 8 channels to the host at a + * continuous rate of 40.8MHz. Higher rates are possible assuming + * that fewer pins get sampled. The firmware then provides sample + * memory where data taken at several sample points reside in the + * same byte of sample memory. It helps that power-of-two bitness + * is applied, IOW that there are either 1, 2, 4, or 8 bits per + * sample point. Even when say 3 or 5 channels are enabled. The + * device firmware may assume that a "dense" list of channels gets + * enabled, the sigrok driver supports when some disabled channels + * preceed other enabled channels. The device is then asked to get + * as many channels as are needed to cover all enabled channels, + * including potentially disabled channels before them. + * - The LA configure request returns a samplerate that is supported + * by the hardware/firmware combination and will be used during + * acquisition. This returned rate is at least as high as the + * requested samplerate. But might exceed the USB bandwidth which + * the firmware is capable to sustain. Users may not expect that + * since numbers add up differently from their perspective. In the + * example of 3 enabled channels and a requested 72MHz samplerate, + * the firmware will derive that it needs to sample 4 channels at + * a 102MHz rate. Which exceeds its capabilities while users may + * not be aware of these constraints. This sigrok driver attempts + * to detect the condition, and not start an acquisition. And also + * emits diagnostics (at info level which is silent by default). + * It's assumed that users increase verbosity when diagnosing + * issues they may experience. + */ + +/* + * Assign a symbolic name to endpoint 0 which is used for USB control + * transfers. Although those "or 0" phrases don't take effect from the + * compiler's perspective, they hopefully increase readability of the + * USB related incantations. + * + * Endpoint 1 for sample data reception is not declared here. Its value + * is taken from logic analyzer configure response. Which remains more + * portable across firmware versions and supported device models. + */ +#define CONTROL_ENDPOINT 0 + +/* Header fields for USB control requests. */ +#define LIBGREAT_REQUEST_NUMBER 0x65 +#define LIBGREAT_VALUE_EXECUTE 0 +#define LIBGREAT_FLAG_SKIP_RSP (1UL << 0) + +/* Classes and their verbs for core and logic analyzer. */ +#define GREATFET_CLASS_CORE 0x000 +#define CORE_VERB_READ_VERSION 0x1 +#define CORE_VERB_READ_SERIAL 0x3 + +#define GREATFET_CLASS_LA 0x10d +#define LA_VERB_CONFIGURE 0x0 +#define LA_VERB_FIRST_PIN 0x1 +#define LA_VERB_ALT_PIN_MAP 0x2 +#define LA_VERB_START_CAPTURE 0x3 +#define LA_VERB_STOP_CAPTURE 0x4 + +/* Maximum text string and binary payload sizes for control requests. */ +#define CORE_MAX_STRING_LENGTH 128 +#define LOGIC_MAX_PAYLOAD_DATA 512 + +/* USB communication parameters, pool dimensions. */ +#define LOGIC_DEFAULT_TIMEOUT 1000 +#define TRANSFER_POOL_SIZE 16 +#define TRANSFER_BUFFER_SIZE (256 * 1024) + +static int greatfet_process_receive_data(const struct sr_dev_inst *sdi, + const uint8_t *data, size_t dlen); +static int greatfet_cancel_transfers(const struct sr_dev_inst *sdi); + +/* Communicate a GreatFET request to EP0, and get its response. */ +static int greatfet_ctrl_out_in(const struct sr_dev_inst *sdi, + const uint8_t *tx_data, size_t tx_size, + uint8_t *rx_data, size_t rx_size, unsigned int timeout_ms) +{ + struct sr_usb_dev_inst *usb; + uint16_t flags; + int ret; + size_t sent, rcvd; + + usb = sdi->conn; + if (!usb) + return SR_ERR_ARG; + + /* Caller can request to skip transmission of a response. */ + flags = 0; + if (!rx_size) + flags |= LIBGREAT_FLAG_SKIP_RSP; + + /* Send USB Control OUT request. */ + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + GString *dump = sr_hexdump_new(tx_data, tx_size); + sr_spew("USB out data: %s", dump->str); + sr_hexdump_free(dump); + } + ret = libusb_control_transfer(usb->devhdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_ENDPOINT | + LIBUSB_ENDPOINT_OUT | CONTROL_ENDPOINT, + LIBGREAT_REQUEST_NUMBER, LIBGREAT_VALUE_EXECUTE, + flags, (void *)tx_data, tx_size, timeout_ms); + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + const char *msg; + msg = ret < 0 ? libusb_error_name(ret) : "-"; + sr_spew("USB out, rc %d, %s", ret, msg); + } + if (ret < 0) { + /* Rate limit error messages. Skip "please retry" kinds. */ + if (ret != LIBUSB_ERROR_BUSY) { + sr_err("USB out transfer failed: %s (%d)", + libusb_error_name(ret), ret); + } + return SR_ERR_IO; + } + sent = (size_t)ret; + if (sent != tx_size) { + sr_err("Short USB write: want %zu, got %zu: %s.", + tx_size, sent, libusb_error_name(ret)); + return SR_ERR_IO; + } + + /* Get the USB Control IN response. */ + if (!rx_size) + return SR_OK; + ret = libusb_control_transfer(usb->devhdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_ENDPOINT | + LIBUSB_ENDPOINT_IN | CONTROL_ENDPOINT, + LIBGREAT_REQUEST_NUMBER, LIBGREAT_VALUE_EXECUTE, + 0, rx_data, rx_size, timeout_ms); + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + const char *msg; + msg = ret < 0 ? libusb_error_name(ret) : "-"; + sr_spew("USB in, rc %d, %s", ret, msg); + } + if (ret < 0) { + sr_err("USB in transfer failed: %s (%d)", + libusb_error_name(ret), ret); + return SR_ERR_IO; + } + rcvd = (size_t)ret; + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + GString *dump = sr_hexdump_new(rx_data, rcvd); + sr_spew("USB in data: %s", dump->str); + sr_hexdump_free(dump); + } + /* Short read, including zero length, is not fatal. */ + + return rcvd; +} + +/* + * Use a string buffer in devc for USB transfers. This simplifies + * resource management in error paths. + */ +static int greatfet_prep_usb_buffer(const struct sr_dev_inst *sdi, + uint8_t **tx_buff, size_t *tx_size, uint8_t **rx_buff, size_t *rx_size) +{ + struct dev_context *devc; + size_t want_len; + GString *s; + + if (tx_buff) + *tx_buff = NULL; + if (tx_size) + *tx_size = 0; + if (rx_buff) + *rx_buff = NULL; + if (rx_size) + *rx_size = 0; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* + * Allocate the string buffer unless previously done. + * Ensure sufficient allocated space for request/response use. + * Assume that glib GString is suitable to hold uint8_t[] data. + */ + if (!devc->usb_comm_buffer) { + want_len = 2 * sizeof(uint32_t) + LOGIC_MAX_PAYLOAD_DATA; + devc->usb_comm_buffer = g_string_sized_new(want_len); + if (!devc->usb_comm_buffer) + return SR_ERR_MALLOC; + } + + /* Pass buffer start and size to the caller if requested. */ + s = devc->usb_comm_buffer; + if (tx_buff) + *tx_buff = (uint8_t *)s->str; + if (tx_size) + *tx_size = s->allocated_len; + if (rx_buff) + *rx_buff = (uint8_t *)s->str; + if (rx_size) + *rx_size = s->allocated_len; + + return SR_OK; +} + +/* Retrieve a string by executing a core service. */ +static int greatfet_get_string(const struct sr_dev_inst *sdi, + uint32_t verb, char **value) +{ + uint8_t *req, *rsp; + size_t rsp_size; + uint8_t *wrptr; + size_t wrlen, rcvd; + const char *text; + int ret; + + if (value) + *value = NULL; + if (!sdi) + return SR_ERR_ARG; + ret = greatfet_prep_usb_buffer(sdi, &req, NULL, &rsp, &rsp_size); + if (ret != SR_OK) + return ret; + + wrptr = req; + write_u32le_inc(&wrptr, GREATFET_CLASS_CORE); + write_u32le_inc(&wrptr, verb); + wrlen = wrptr - req; + ret = greatfet_ctrl_out_in(sdi, req, wrlen, + rsp, rsp_size, LOGIC_DEFAULT_TIMEOUT); + if (ret < 0) { + sr_err("Cannot get core string."); + return ret; + } + rcvd = (size_t)ret; + + rsp[rcvd] = '\0'; + text = (const char *)rsp; + sr_dbg("got string, verb %u, text (%zu) %s", verb, rcvd, text); + if (value && *text) { + *value = g_strndup(text, rcvd); + } else if (value) { + /* + * g_strndup(3) does _not_ copy 'n' bytes. Instead it + * truncates the result at the first NUL character seen. + * That's why we need extra logic to pass binary data + * to callers, to not violate API layers and confuse + * USB readers with firmware implementation details + * (that may be version dependent). + * The very condition to determine whether text or some + * binary data was received is a simple check for NUL + * in the first position, implemented above. This is + * GoodEnough(TM) to handle the firmware version case. + */ + *value = g_malloc0(rcvd + 1); + memcpy(*value, text, rcvd); + } + + return rcvd; +} + +SR_PRIV int greatfet_get_serial_number(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + char *text; + int ret; + const uint8_t *rdptr; + size_t rdlen; + GString *snr; + uint32_t chunk; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + ret = greatfet_get_string(sdi, CORE_VERB_READ_SERIAL, &text); + if (ret < 0) + return ret; + if (!text) + return SR_ERR_DATA; + + /* + * The simple case, we got a text string. The 2019 K.Temkin + * implementation took the received string as is. So there + * are firmware versions which provide this presentation. + */ + if (*text) { + devc->serial_number = text; + return SR_OK; + } + + /* + * The complex case. The received "string" looks binary. Local + * setups with v2018.12.1 and v2021.2.1 firmware versions yield + * response data that does not look like a text string. Instead + * it looks like four u32 fields which carry a binary value and + * leading padding. Try that interpreation as well. Construct a + * twenty character text presentation from that binary content. + * + * Implementation detail: Is the "leader" the part number which + * a different firmware request may yield? Are there other verbs + * which reliably yield the serial number in text format? + */ + rdptr = (const uint8_t *)text; + rdlen = (size_t)ret; + sr_dbg("trying to read serial nr \"text\" as binary"); + if (rdlen != 4 * sizeof(uint32_t)) { + g_free(text); + return SR_ERR_DATA; + } + snr = g_string_sized_new(20 + 1); + chunk = read_u32le_inc(&rdptr); + if (chunk) { + g_free(text); + return SR_ERR_DATA; + } + chunk = read_u32le_inc(&rdptr); + if (chunk) { + g_free(text); + return SR_ERR_DATA; + } + g_string_append_printf(snr, "%04" PRIx32, chunk); + chunk = read_u32le_inc(&rdptr); + g_string_append_printf(snr, "%08" PRIx32, chunk); + chunk = read_u32le_inc(&rdptr); + g_string_append_printf(snr, "%08" PRIx32, chunk); + sr_dbg("got serial number text %s", snr->str); + g_free(text); + text = g_string_free(snr, FALSE); + devc->serial_number = text; + return SR_OK; +} + +SR_PRIV int greatfet_get_version_number(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + char *text; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + ret = greatfet_get_string(sdi, CORE_VERB_READ_VERSION, &text); + if (ret < SR_OK) + return ret; + + devc->firmware_version = text; + return SR_OK; +} + +/* + * Transmit a parameter-less request that wants no response. Or a + * request with just a few bytes worth of parameter values, still + * not expecting a response. + */ +static int greatfet_trivial_request(const struct sr_dev_inst *sdi, + uint32_t cls, uint32_t verb, const uint8_t *tx_data, size_t tx_dlen) +{ + struct dev_context *devc; + uint8_t *req; + uint8_t *wrptr; + size_t wrlen; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + ret = greatfet_prep_usb_buffer(sdi, &req, NULL, NULL, NULL); + if (ret != SR_OK) + return ret; + + wrptr = req; + write_u32le_inc(&wrptr, cls); + write_u32le_inc(&wrptr, verb); + while (tx_dlen--) + write_u8_inc(&wrptr, *tx_data++); + wrlen = wrptr - req; + return greatfet_ctrl_out_in(sdi, req, wrlen, + NULL, 0, LOGIC_DEFAULT_TIMEOUT); +} + +/* + * Transmit a "configure logic analyzer" request. Gets the resulting + * samplerate (which can differ from requested values) and endpoint + * (which is very useful for compatibility across devices/versions). + * Also gets the device firmware's buffer size, which is only used + * for information, while the host assumes a fixed larger buffer size + * for its own purposes. + */ +static int greatfet_logic_config(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_acquisition_t *acq; + struct sr_usb_dev_inst *usb; + uint8_t *req, *rsp; + size_t rsp_size; + uint8_t *wrptr; + size_t wrlen, rcvd, want_len; + const uint8_t *rdptr; + uint64_t rate, bw; + size_t bufsize; + uint8_t ep; + char *print_bw; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + usb = sdi->conn; + if (!devc || !usb) + return SR_ERR_ARG; + acq = &devc->acquisition; + + ret = greatfet_prep_usb_buffer(sdi, &req, NULL, &rsp, &rsp_size); + if (ret != SR_OK) + return ret; + + /* + * Optionally request to capture the upper pin bank. The device + * can sample from pins starting at number 8. We use the feature + * transparently when the first 8 channels are disabled. + * + * Values different from 0 or 8 are not used here. The details + * of the SGPIO hardware implementation degrade performance in + * this case. Its use is not desirable for users. + */ + sr_dbg("about to config first pin, upper %d", acq->use_upper_pins); + wrptr = req; + write_u32le_inc(&wrptr, GREATFET_CLASS_LA); + write_u32le_inc(&wrptr, LA_VERB_FIRST_PIN); + write_u8_inc(&wrptr, acq->use_upper_pins ? 8 : 0); + wrlen = wrptr - req; + ret = greatfet_ctrl_out_in(sdi, req, wrlen, + NULL, 0, LOGIC_DEFAULT_TIMEOUT); + if (ret < 0) { + sr_err("Cannot configure first capture pin."); + return ret; + } + + /* Disable alt pin mapping, just for good measure. */ + sr_dbg("about to config alt pin mapping"); + wrptr = req; + write_u32le_inc(&wrptr, GREATFET_CLASS_LA); + write_u32le_inc(&wrptr, LA_VERB_ALT_PIN_MAP); + write_u8_inc(&wrptr, 0); + wrlen = wrptr - req; + ret = greatfet_ctrl_out_in(sdi, req, wrlen, + NULL, 0, LOGIC_DEFAULT_TIMEOUT); + if (ret < 0) { + sr_err("Cannot configure alt pin mapping."); + return ret; + } + + /* + * Prepare to get a specific amount of receive data. The logic + * analyzer configure response is strictly binary, in contrast + * to variable length string responses elsewhere. + */ + want_len = 2 * sizeof(uint32_t) + sizeof(uint8_t); + if (rsp_size < want_len) + return SR_ERR_BUG; + rsp_size = want_len; + + sr_dbg("about to config LA, rate %" PRIu64 ", chans %zu", + devc->samplerate, acq->capture_channels); + wrptr = req; + write_u32le_inc(&wrptr, GREATFET_CLASS_LA); + write_u32le_inc(&wrptr, LA_VERB_CONFIGURE); + write_u32le_inc(&wrptr, devc->samplerate); + write_u8_inc(&wrptr, acq->capture_channels); + wrlen = wrptr - req; + ret = greatfet_ctrl_out_in(sdi, req, wrlen, + rsp, rsp_size, LOGIC_DEFAULT_TIMEOUT); + if (ret < 0) { + sr_err("Cannot configure logic analyzer mode."); + return ret; + } + rcvd = (size_t)ret; + if (rcvd != want_len) { + sr_warn("Unexpected LA configuration response length."); + return SR_ERR_DATA; + } + + rdptr = rsp; + rate = read_u32le_inc(&rdptr); + bufsize = read_u32le_inc(&rdptr); + ep = read_u8_inc(&rdptr); + sr_dbg("LA configured, rate %" PRIu64 ", buf %zu, ep %" PRIu8, + rate, bufsize, ep); + if (rate != devc->samplerate) { + sr_info("Configuration feedback, want rate %" PRIu64 ", got rate %." PRIu64, + devc->samplerate, rate); + devc->samplerate = rate; + } + acq->capture_samplerate = rate; + acq->firmware_bufsize = bufsize; + acq->samples_endpoint = ep; + + /* + * The firmware does not reject requests that would exceed + * its capabilities. Yet the device becomes unaccessible when + * START is sent in that situation. (Observed with v2021.2.1 + * firmware.) + * + * Assume a maximum USB bandwidth that we don't want to exceed. + * It's protecting the GreatFET's firmware. It's not a statement + * on the host's capability of keeping up with the GreatFET's + * firmware capabilities. :) + */ + print_bw = sr_samplerate_string(acq->capture_samplerate); + sr_info("Capture configuration: %zu channels, samplerate %s.", + acq->capture_channels, print_bw); + g_free(print_bw); + bw = acq->capture_samplerate * 8 / acq->points_per_byte; + if (!acq->use_upper_pins) + bw *= acq->wire_unit_size; + print_bw = sr_si_string_u64(bw, "bps"); + sr_info("Resulting USB bandwidth: %s.", print_bw); + g_free(print_bw); + if (acq->bandwidth_threshold && bw > acq->bandwidth_threshold) { + sr_err("Configuration exceeds bandwidth limit. Aborting."); + return SR_ERR_SAMPLERATE; + } + + return SR_OK; +} + +/* Transmit "start logic capture" request. */ +static int greatfet_logic_start(const struct sr_dev_inst *sdi) +{ + int ret; + + ret = greatfet_trivial_request(sdi, + GREATFET_CLASS_LA, LA_VERB_START_CAPTURE, NULL, 0); + sr_dbg("LA start, USB out, rc %d", ret); + if (ret != SR_OK) + sr_err("Cannot start logic analyzer capture."); + + return ret; +} + +/* Transmit "stop logic capture" request. */ +static int greatfet_logic_stop(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_acquisition_t *acq; + int ret; + + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + acq = &devc->acquisition; + + /* Only send STOP when START was sent before. */ + if (!acq->start_req_sent) + return SR_OK; + + ret = greatfet_trivial_request(sdi, + GREATFET_CLASS_LA, LA_VERB_STOP_CAPTURE, NULL, 0); + sr_dbg("LA stop, USB out, rc %d", ret); + if (ret == SR_OK) + acq->start_req_sent = FALSE; + else + sr_warn("Cannot stop logic analyzer capture in the device."); + + return ret; +} + +/* + * Determine how many channels the device firmware needs to sample. + * So that resulting capture data will cover all those logic channels + * which currently are enabled on the sigrok side. We (have to) accept + * when the sequence of enabled channels "has gaps" in them. Disabling + * channels in the middle of the pin groups is a user's choice that we + * need to obey. The count of enabled channels is not good enough for + * the purpose of acquisition, it must be "a maximum index" or a total + * to-get-sampled count. + */ +static int greatfet_calc_capture_chans(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_acquisition_t *acq; + GSList *l; + struct sr_channel *ch; + int last_used_idx; + uint16_t pin_map; + size_t logic_ch_count, en_ch_count, fw_ch_count; + gboolean have_upper, have_lower, use_upper_pins; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + acq = &devc->acquisition; + + last_used_idx = -1; + logic_ch_count = 0; + pin_map = 0; + for (l = sdi->channels; l; l = l->next) { + ch = l->data; + if (ch->type != SR_CHANNEL_LOGIC) + continue; + logic_ch_count++; + if (!ch->enabled) + continue; + if (last_used_idx < ch->index) + last_used_idx = ch->index; + pin_map |= 1UL << ch->index; + } + en_ch_count = last_used_idx + 1; + sr_dbg("channel count, logic %zu, highest enabled idx %d -> count %zu", + logic_ch_count, last_used_idx, en_ch_count); + if (!en_ch_count) + return SR_ERR_ARG; + have_upper = pin_map & 0xff00; + have_lower = pin_map & 0x00ff; + use_upper_pins = have_upper && !have_lower; + if (use_upper_pins) { + sr_dbg("ch mask 0x%04x -> using upper pins", pin_map); + last_used_idx -= 8; + en_ch_count -= 8; + } + if (have_upper && !use_upper_pins) + sr_warn("Multi-bank capture, check firmware support!"); + + acq->capture_channels = en_ch_count; + acq->use_upper_pins = use_upper_pins; + ret = sr_next_power_of_two(last_used_idx, NULL, &fw_ch_count); + if (ret != SR_OK) + return ret; + if (!fw_ch_count) + return SR_ERR_ARG; + if (fw_ch_count > 8) { + acq->wire_unit_size = sizeof(uint16_t); + acq->points_per_byte = 1; + } else { + acq->wire_unit_size = sizeof(uint8_t); + acq->points_per_byte = 8 / fw_ch_count; + } + acq->channel_shift = fw_ch_count % 8; + sr_dbg("unit %zu, dense %d -> shift %zu, points %zu", + acq->wire_unit_size, !!acq->channel_shift, + acq->channel_shift, acq->points_per_byte); + + return SR_OK; +} + +/* + * This is an opportunity to adapt the host's USB transfer size to + * the value which the device firmware has provided in the LA config + * response. + * + * We let the opportunity pass. Always use a fixed value for the host + * configuration. BULK transfers will adopt, which reduces the number + * of transfer completion events for the host. + * + * Notice that transfer size adjustment is _not_ a means to get user + * feedback earlier at low samplerates. This may be done in other + * drivers but does not take effect here. Because a buffer is used to + * submit sample values to the session. When in doubt, the feed queue + * needs flushing. + * + * TODO Consider whether sample data needs flushing when sample rates + * are low and buffers are deep. Ideally use common feed queue support + * if that becomes available in the future. Translate low samplerates + * (and channel counts) to the amount of samples after which the queue + * should get flushed. + * + * This implementation assumes that samplerates start at 1MHz, and + * flushing is not necessary. + */ +static int greatfet_calc_submit_size(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_transfers_t *dxfer; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + dxfer = &devc->transfers; + + dxfer->capture_bufsize = dxfer->transfer_bufsize; + return SR_OK; +} + +/* + * This routine is local to protocol.c and does mere data manipulation + * and a single attempt at sending "logic analyzer stop" to the device. + * This routine gets invoked from USB transfer completion callbacks as + * well as periodic timer or data availability callbacks. It is essential + * to not spend extended periods of time here. + */ +static void greatfet_abort_acquisition_quick(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_acquisition_t *acq; + + if (!sdi) + return; + devc = sdi->priv; + if (!devc) + return; + acq = &devc->acquisition; + + if (acq->acquisition_state == ACQ_RECEIVE) + acq->acquisition_state = ACQ_SHUTDOWN; + + (void)greatfet_logic_stop(sdi); + greatfet_cancel_transfers(sdi); + + if (acq->feed_queue) + feed_queue_logic_flush(acq->feed_queue); +} + +/* Allocate USB transfers and associated receive buffers. */ +static int greatfet_allocate_transfers(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_transfers_t *dxfer; + size_t alloc_size, idx; + struct libusb_transfer *xfer; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + dxfer = &devc->transfers; + + dxfer->transfer_bufsize = TRANSFER_BUFFER_SIZE; + dxfer->transfers_count = TRANSFER_POOL_SIZE; + + alloc_size = dxfer->transfers_count * dxfer->transfer_bufsize; + dxfer->transfer_buffer = g_malloc0(alloc_size); + if (!dxfer->transfer_buffer) + return SR_ERR_MALLOC; + + alloc_size = dxfer->transfers_count; + alloc_size *= sizeof(dxfer->transfers[0]); + dxfer->transfers = g_malloc0(alloc_size); + if (!dxfer->transfers) + return SR_ERR_MALLOC; + + for (idx = 0; idx < dxfer->transfers_count; idx++) { + xfer = libusb_alloc_transfer(0); + if (!xfer) + return SR_ERR_MALLOC; + dxfer->transfers[idx] = xfer; + } + + return SR_OK; +} + +/* Submit USB transfers for reception, registers the data callback. */ +static int greatfet_prepare_transfers(const struct sr_dev_inst *sdi, + libusb_transfer_cb_fn callback) +{ + struct dev_context *devc; + struct dev_acquisition_t *acq; + struct dev_transfers_t *dxfer; + struct sr_usb_dev_inst *conn; + uint8_t ep; + size_t submit_length; + size_t off, idx; + struct libusb_transfer *xfer; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + conn = sdi->conn; + if (!devc || !conn) + return SR_ERR_ARG; + acq = &devc->acquisition; + dxfer = &devc->transfers; + + ep = acq->samples_endpoint; + ret = greatfet_calc_submit_size(sdi); + if (ret != SR_OK) + return ret; + submit_length = dxfer->capture_bufsize; + if (submit_length > dxfer->transfer_bufsize) + submit_length = dxfer->transfer_bufsize; + sr_dbg("prep xfer, ep %u (%u), len %zu", + ep, ep & ~LIBUSB_ENDPOINT_IN, submit_length); + + dxfer->active_transfers = 0; + off = 0; + for (idx = 0; idx < dxfer->transfers_count; idx++) { + xfer = dxfer->transfers[idx]; + libusb_fill_bulk_transfer(xfer, conn->devhdl, ep, + &dxfer->transfer_buffer[off], submit_length, + callback, (void *)sdi, 0); + if (!xfer->buffer) + return SR_ERR_MALLOC; + ret = libusb_submit_transfer(xfer); + if (ret != 0) { + sr_spew("submit bulk xfer failed, idx %zu, %d: %s", + idx, ret, libusb_error_name(ret)); + return SR_ERR_IO; + } + dxfer->active_transfers++; + off += submit_length; + } + + return SR_OK; +} + +/* + * Initiate the termination of an acquisition. Cancel all USB transfers. + * Their completion will drive further progress including resource + * release. + */ +static int greatfet_cancel_transfers(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_transfers_t *dxfer; + size_t idx; + struct libusb_transfer *xfer; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + dxfer = &devc->transfers; + if (!dxfer->transfers) + return SR_OK; + + for (idx = 0; idx < dxfer->transfers_count; idx++) { + xfer = dxfer->transfers[idx]; + if (!xfer) + continue; + (void)libusb_cancel_transfer(xfer); + /* + * Cancelled transfers will cause acquisitions to abort + * in their callback. Keep the "active" count as is. + */ + } + + return SR_OK; +} + +/* + * Free an individual transfer during its callback's execution. + * Releasing the last USB transfer also happens to drive more of + * the shutdown path. + */ +static void greatfet_free_transfer(const struct sr_dev_inst *sdi, + struct libusb_transfer *xfer) +{ + struct drv_context *drvc; + struct sr_usb_dev_inst *usb; + struct dev_context *devc; + struct dev_acquisition_t *acq; + struct dev_transfers_t *dxfer; + size_t idx; + + if (!sdi || !sdi->driver) + return; + drvc = sdi->driver->context; + usb = sdi->conn; + devc = sdi->priv; + if (!drvc || !usb || !devc) + return; + acq = &devc->acquisition; + dxfer = &devc->transfers; + + /* Void the transfer in the driver's list of transfers. */ + for (idx = 0; idx < dxfer->transfers_count; idx++) { + if (xfer != dxfer->transfers[idx]) + continue; + dxfer->transfers[idx] = NULL; + dxfer->active_transfers--; + break; + } + + /* Release the transfer from libusb use. */ + libusb_free_transfer(xfer); + + /* Done here when more transfers are still pending. */ + if (!dxfer->active_transfers) + return; + + /* + * The last USB transfer has been freed after completion. + * Post process the previous acquisition's execution. + */ + (void)greatfet_stop_acquisition(sdi); + if (acq->frame_begin_sent) { + std_session_send_df_end(sdi); + acq->frame_begin_sent = FALSE; + } + usb_source_remove(sdi->session, drvc->sr_ctx); + if (acq->samples_interface_claimed) { + libusb_release_interface(usb->devhdl, acq->samples_interface); + acq->samples_interface_claimed = FALSE; + } + feed_queue_logic_free(acq->feed_queue); + acq->feed_queue = NULL; + acq->acquisition_state = ACQ_IDLE; +} + +/* + * Callback for the completion of previously submitted USB transfers. + * Processes received sample memory content. Initiates termination of + * the current acquisition in case of failed processing or failed + * communication to the acquisition device. Also initiates termination + * when previously configured acquisition limits were reached. + */ +static void LIBUSB_CALL xfer_complete_cb(struct libusb_transfer *xfer) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct dev_acquisition_t *acq; + const uint8_t *data; + size_t dlen; + gboolean was_completed, was_cancelled; + gboolean has_timedout, device_gone, is_stalled; + int level; + gboolean shall_abort; + int ret; + + sdi = xfer ? xfer->user_data : NULL; + devc = sdi ? sdi->priv : NULL; + if (!sdi || !devc) { + /* ShouldNotHappen(TM) */ + sr_warn("Completion of unregistered USB transfer."); + libusb_free_transfer(xfer); + return; + } + acq = &devc->acquisition; + + /* + * Outside of an acquisition? Or in its shutdown path? + * Just release the USB transfer, don't process its data. + */ + if (acq->acquisition_state != ACQ_RECEIVE) { + greatfet_free_transfer(sdi, xfer); + return; + } + + /* + * Avoid the unfortunate libusb identifiers and data types. + * Simplify USB transfer status checks for later code paths. + * Optionally log the USB transfers' completion. + */ + data = xfer->buffer; + dlen = xfer->actual_length; + was_completed = xfer->status == LIBUSB_TRANSFER_COMPLETED; + has_timedout = xfer->status == LIBUSB_TRANSFER_TIMED_OUT; + was_cancelled = xfer->status == LIBUSB_TRANSFER_CANCELLED; + device_gone = xfer->status == LIBUSB_TRANSFER_NO_DEVICE; + is_stalled = xfer->status == LIBUSB_TRANSFER_STALL; + level = sr_log_loglevel_get(); + if (level >= SR_LOG_SPEW) { + sr_spew("USB transfer, status %s, byte count %zu.", + libusb_error_name(xfer->status), dlen); + } else if (level >= SR_LOG_DBG && !was_completed) { + sr_dbg("USB transfer, status %s, byte count %zu.", + libusb_error_name(xfer->status), dlen); + } + + /* + * Timed out transfers may contain a little data. Warn but accept. + * Typical case will be completed transfers. Cancelled transfers + * are seen in shutdown paths, their data need not get processed. + * Terminate acquisition in case of communication or processing + * failure, or when limits were reached. + */ + shall_abort = FALSE; + if (has_timedout) + sr_warn("USB transfer timed out. Using available data."); + if (was_completed || has_timedout) { + ret = greatfet_process_receive_data(sdi, data, dlen); + if (ret != SR_OK) { + sr_err("Error processing sample data. Aborting."); + shall_abort = TRUE; + } + if (acq->acquisition_state != ACQ_RECEIVE) { + sr_dbg("Sample data processing ends acquisition."); + feed_queue_logic_flush(acq->feed_queue); + shall_abort = TRUE; + } + } else if (device_gone) { + sr_err("Device gone during USB transfer. Aborting."); + shall_abort = TRUE; + } else if (was_cancelled) { + sr_dbg("Cancelled USB transfer. Terminating acquisition."); + shall_abort = TRUE; + } else if (is_stalled) { + sr_err("Device firmware is stalled on USB transfer. Aborting."); + shall_abort = TRUE; + } else { + sr_err("USB transfer failed (%s). Aborting.", + libusb_error_name(xfer->status)); + shall_abort = TRUE; + } + + /* + * Resubmit the USB transfer for continued reception of sample + * data. Or release the transfer when acquisition terminates + * after errors were seen, or limits were reached, or the end + * was requested in other regular ways. + * + * In the case of error or other terminating conditions cancel + * the currently executing acquisition, end all USB transfers. + */ + if (!shall_abort) { + ret = libusb_submit_transfer(xfer); + if (ret < 0) { + sr_err("Cannot resubmit USB transfer. Aborting."); + shall_abort = TRUE; + } + } + if (shall_abort) { + greatfet_free_transfer(sdi, xfer); + greatfet_abort_acquisition_quick(sdi); + } +} + +/* The public protocol.c API to start/stop acquisitions. */ + +SR_PRIV int greatfet_setup_acquisition(const struct sr_dev_inst *sdi) +{ + int ret; + + ret = greatfet_allocate_transfers(sdi); + if (ret != SR_OK) + return ret; + + ret = greatfet_calc_capture_chans(sdi); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +SR_PRIV int greatfet_start_acquisition(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_acquisition_t *acq; + struct sr_usb_dev_inst *usb; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + usb = sdi->conn; + if (!devc || !usb) + return SR_ERR_ARG; + acq = &devc->acquisition; + + /* + * Configure the logic analyzer. Claim the USB interface. This + * part of the sequence is not time critical. + */ + ret = greatfet_logic_config(sdi); + if (ret != SR_OK) + return ret; + + ret = libusb_claim_interface(usb->devhdl, acq->samples_interface); + acq->samples_interface_claimed = ret == 0; + + /* + * Ideally we could submit USB transfers before sending the + * logic analyzer start request. Experience suggests that this + * results in libusb IO errors. That's why we need to accept the + * window of blindness between sending the LA start request and + * initiating USB data reception. + */ + ret = greatfet_logic_start(sdi); + if (ret != SR_OK) + return ret; + + ret = greatfet_prepare_transfers(sdi, xfer_complete_cb); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +/* + * The public acquisition abort routine, invoked by api.c logic. Could + * optionally spend more time than the _quick() routine. + */ +SR_PRIV void greatfet_abort_acquisition(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!sdi) + return; + devc = sdi->priv; + if (!devc) + return; + + (void)greatfet_logic_stop(sdi); + greatfet_abort_acquisition_quick(sdi); +} + +SR_PRIV int greatfet_stop_acquisition(const struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb; + int ret; + + if (!sdi) + return SR_ERR_ARG; + usb = sdi->conn; + if (!usb) + return SR_ERR_ARG; + + ret = greatfet_logic_stop(sdi); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +SR_PRIV void greatfet_release_resources(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct dev_transfers_t *dxfer; + + if (!sdi) + return; + devc = sdi->priv; + if (!devc) + return; + dxfer = &devc->transfers; + + /* + * Is there something that needs to be done here? Transfers' + * cancellation gets initiated and then happens as they keep + * completing. The completion handler releases their libusb + * resources. The last release also unregisters the periodic + * glib main loop callback. + * + * Can something be done here? The receive buffer still is + * allocated. As is the feed queue. Can we synchronize to the + * last release of the USB resources? Need we keep invoking + * the receive callback until the USB transfers pool has been + * released? Need we wait for the active transfers counter to + * drop to zero, is more checking involved? + */ + if (dxfer->active_transfers) + sr_warn("Got active USB transfers in release code path."); +} + +/* + * Process received sample date. There are two essential modes: + * - The straight forward case. The device provides 16 bits per sample + * point. Forward raw received data as is to the sigrok session. The + * device's endianess matches the session's LE expectation. And the + * data matches the device's announced total channel count. + * - The compact presentation where a smaller number of channels is + * active, and their data spans only part of a byte per sample point. + * Multiple samples' data is sharing bytes, and bytes will carry data + * that was taken at different times. This requires some untangling + * before forwarding sample data to the sigrok session which is of + * the expected width (unit size) and carries one sample per item. + * - The cases where one sample point's data occupies full bytes, but + * the firmware only communicates one byte per sample point, are seen + * as a special case of the above bit packing. The "complex case" + * logic covers the "bytes extension" as well. + * + * Implementation details: + * - Samples taken first are found in the least significant bits of a + * byte. Samples taken next are found in upper bits of the byte. For + * example a byte containing 4x 2bit sample data is seen as 33221100. + * - Depending on the number of enabled channels there could be up to + * eight samples in one byte of sample memory. This implementation + * tries to accumulate one input byte's content, but not more. To + * simplify the implementation. Performance can get tuned later as + * the need gets identified. Sampling at 204MHz results in some 3% + * CPU load with Pulseview on the local workstation. + * - Samples for 16 channels transparently are handled by the simple + * 8 channel case above. All logic data of an individual samplepoint + * occupies full bytes, endianess of sample data as provided by the + * device firmware and the sigrok session are the same. No conversion + * is required. + */ +static int greatfet_process_receive_data(const struct sr_dev_inst *sdi, + const uint8_t *data, size_t dlen) +{ + static int diag_shown; + + struct dev_context *devc; + struct dev_acquisition_t *acq; + struct feed_queue_logic *q; + uint64_t samples_remain; + gboolean exceeded; + size_t samples_rcvd; + uint8_t raw_mask, raw_data; + size_t points_per_byte, points_count; + uint16_t wr_data; + uint8_t accum[8 * sizeof(wr_data)]; + const uint8_t *rdptr; + uint8_t *wrptr; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + acq = &devc->acquisition; + q = acq->feed_queue; + + /* + * Check whether acquisition limits apply, and whether they + * were reached or exceeded before. Constrain the submission + * of more sample values to what's still within the limits of + * the current acquisition. + */ + ret = sr_sw_limits_get_remain(&devc->sw_limits, + &samples_remain, NULL, NULL, &exceeded); + if (ret != SR_OK) + return ret; + if (exceeded) + return SR_OK; + + /* + * Check for the simple case first. Where the firmware provides + * sample data for all logic channels supported by the device. + * Pass sample memory as received from the device in verbatim + * form to the session feed. + * + * This happens to work because sample data received from the + * device and logic data in sigrok sessions both are in little + * endian format. + */ + if (acq->wire_unit_size == devc->feed_unit_size) { + samples_rcvd = dlen / acq->wire_unit_size; + if (samples_remain && samples_rcvd > samples_remain) + samples_rcvd = samples_remain; + ret = feed_queue_logic_submit_many(q, data, samples_rcvd); + if (ret != SR_OK) + return ret; + sr_sw_limits_update_samples_read(&devc->sw_limits, samples_rcvd); + return SR_OK; + } + if (sizeof(wr_data) != devc->feed_unit_size) { + sr_err("Unhandled unit size mismatch. Flawed implementation?"); + return SR_ERR_BUG; + } + + /* + * Handle the complex cases where one byte carries values that + * were taken at multiple sample points, or where the firmware + * does not communicate all pin banks to the host (upper pins + * or lower pins only on the wire). + * + * This involves manipulation between reception and forwarding. + * It helps that the firmware provides sample data in units of + * power-of-two bit counts per sample point. This eliminates + * fragments which could span several transfers. + * + * Notice that "upper pins" and "multiple samples per byte" can + * happen in combination. The implementation transparently deals + * with upper pin use where bytes carry exactly one value. + */ + if (acq->channel_shift) { + raw_mask = (1UL << acq->channel_shift) - 1; + points_per_byte = 8 / acq->channel_shift; + } else { + raw_mask = (1UL << 8) - 1; + points_per_byte = 1; + } + if (!diag_shown++) { + sr_dbg("sample mem: ch count %zu, ch shift %zu, mask 0x%x, points %zu, upper %d", + acq->capture_channels, acq->channel_shift, + raw_mask, points_per_byte, acq->use_upper_pins); + } + samples_rcvd = dlen * points_per_byte; + if (samples_remain && samples_rcvd > samples_remain) { + samples_rcvd = samples_remain; + dlen = samples_rcvd; + dlen += points_per_byte - 1; + dlen /= points_per_byte; + } + rdptr = data; + while (dlen--) { + raw_data = read_u8_inc(&rdptr); + wrptr = accum; + points_count = points_per_byte; + while (points_count--) { + wr_data = raw_data & raw_mask; + if (acq->use_upper_pins) + wr_data <<= 8; + write_u16le_inc(&wrptr, wr_data); + raw_data >>= acq->channel_shift; + } + points_count = points_per_byte; + ret = feed_queue_logic_submit_many(q, accum, points_count); + if (ret != SR_OK) + return ret; + sr_sw_limits_update_samples_read(&devc->sw_limits, points_count); + } + return SR_OK; +} + +/* Receive callback, invoked when data is available, or periodically. */ +SR_PRIV int greatfet_receive_data(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct drv_context *drvc; + libusb_context *ctx; + struct timeval tv; + + (void)fd; + (void)revents; + + sdi = cb_data; + if (!sdi || !sdi->priv || !sdi->driver) + return TRUE; + devc = sdi->priv; + if (!devc) + return TRUE; + drvc = sdi->driver->context; + if (!drvc || !drvc->sr_ctx) + return TRUE; + ctx = drvc->sr_ctx->libusb_ctx; + + /* + * Handle those USB transfers which have completed so far + * in a regular fashion. These carry desired sample values. + */ + tv.tv_sec = tv.tv_usec = 0; + libusb_handle_events_timeout(ctx, &tv); + + /* + * End the current acquisition when limites were reached. + * Process USB transfers again here before returning, because + * acquisition termination will unregister the receive callback, + * and cancel previously submitted transfers. Reap those here. + */ + if (sr_sw_limits_check(&devc->sw_limits)) { + greatfet_abort_acquisition_quick(sdi); + tv.tv_sec = tv.tv_usec = 0; + libusb_handle_events_timeout(ctx, &tv); + } + + return TRUE; +} diff --git a/src/hardware/greatfet/protocol.h b/src/hardware/greatfet/protocol.h new file mode 100644 index 00000000..fe344a75 --- /dev/null +++ b/src/hardware/greatfet/protocol.h @@ -0,0 +1,88 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2019 Katherine J. Temkin + * Copyright (C) 2019 Mikaela Szekely + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_GREATFET_PROTOCOL_H +#define LIBSIGROK_HARDWARE_GREATFET_PROTOCOL_H + +#include +#include +#include + +#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 diff --git a/src/hardware/hameg-hmo/protocol.c b/src/hardware/hameg-hmo/protocol.c index c155d338..f355e2c2 100644 --- a/src/hardware/hameg-hmo/protocol.c +++ b/src/hardware/hameg-hmo/protocol.c @@ -161,6 +161,11 @@ static const char *coupling_options_rtm300x[] = { "GND", }; +static const char *coupling_options_rth100x[] = { + "ACL", // AC with 1 MOhm termination + "DCL", // DC with 1 MOhm termination +}; + static const char *scope_trigger_slopes[] = { "POS", "NEG", @@ -209,6 +214,18 @@ static const char *an2_dig16_sbus_trigger_sources[] = { "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15", }; +/* RTH1002 */ +static const char *an2_dig8_isol_trigger_sources[] = { + "CH1", "CH2", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", +}; + +/* RTH1004 */ +static const char *an4_dig8_isol_trigger_sources[] = { + "CH1", "CH2", "CH3", "CH4", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", +}; + /* HMO Compact4 */ static const char *an4_dig8_trigger_sources[] = { "CH1", "CH2", "CH3", "CH4", @@ -754,6 +771,86 @@ static struct scope_config scope_models[] = { .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, }, }; diff --git a/src/hardware/hantek-6xxx/api.c b/src/hardware/hantek-6xxx/api.c index 41a77795..f9194b66 100644 --- a/src/hardware/hantek-6xxx/api.c +++ b/src/hardware/hantek-6xxx/api.c @@ -86,6 +86,12 @@ static const struct hantek_6xxx_profile dev_profiles[] = { ARRAY_AND_SIZE(dc_coupling), FALSE, ARRAY_AND_SIZE(vdivs), }, + { + 0x04b4, 0x2020, 0x1d50, 0x608e, 0x0001, + "Voltcraft", "DSO2020", "fx2lafw-hantek-6022be.fw", + ARRAY_AND_SIZE(dc_coupling), FALSE, + ARRAY_AND_SIZE(vdivs), + }, { 0x8102, 0x8102, 0x1d50, 0x608e, 0x0002, "Sainsmart", "DDS120", "fx2lafw-sainsmart-dds120.fw", diff --git a/src/hardware/hantek-dso/api.c b/src/hardware/hantek-dso/api.c index f0cbb615..bfad5f71 100644 --- a/src/hardware/hantek-dso/api.c +++ b/src/hardware/hantek-dso/api.c @@ -169,7 +169,6 @@ static const uint64_t vdivs[][2] = { static const char *trigger_sources[] = { "CH1", "CH2", "EXT", - /* TODO: forced */ }; static const char *trigger_slopes[] = { @@ -219,7 +218,7 @@ static struct sr_dev_inst *dso_dev_new(const struct dso_profile *prof) devc->voffset_trigger = DEFAULT_VERT_TRIGGERPOS; devc->framesize = DEFAULT_FRAMESIZE; devc->triggerslope = SLOPE_POSITIVE; - devc->triggersource = g_strdup(DEFAULT_TRIGGER_SOURCE); + devc->triggersource = NULL; devc->capture_ratio = DEFAULT_CAPTURE_RATIO; sdi->priv = devc; @@ -466,6 +465,8 @@ static int config_get(uint32_t key, GVariant **data, *data = g_variant_new_uint64(devc->framesize); break; case SR_CONF_TRIGGER_SOURCE: + if (!devc->triggersource) + return SR_ERR_NA; *data = g_variant_new_string(devc->triggersource); break; case SR_CONF_TRIGGER_SLOPE: @@ -556,6 +557,7 @@ static int config_set(uint32_t key, GVariant *data, case SR_CONF_TRIGGER_SOURCE: if ((idx = std_str_idx(data, ARRAY_AND_SIZE(trigger_sources))) < 0) return SR_ERR_ARG; + g_free(devc->triggersource); devc->triggersource = g_strdup(trigger_sources[idx]); break; default: @@ -837,8 +839,10 @@ static int handle_event(int fd, int revents, void *cb_data) return TRUE; if (dso_enable_trigger(sdi) != SR_OK) return TRUE; -// if (dso_force_trigger(sdi) != SR_OK) -// return TRUE; + if (!devc->triggersource) { + if (dso_force_trigger(sdi) != SR_OK) + return TRUE; + } sr_dbg("Successfully requested next chunk."); devc->dev_state = CAPTURE; return TRUE; @@ -859,8 +863,10 @@ static int handle_event(int fd, int revents, void *cb_data) break; if (dso_enable_trigger(sdi) != SR_OK) break; -// if (dso_force_trigger(sdi) != SR_OK) -// break; + if (!devc->triggersource) { + if (dso_force_trigger(sdi) != SR_OK) + break; + } sr_dbg("Successfully requested next chunk."); } break; diff --git a/src/hardware/hantek-dso/protocol.c b/src/hardware/hantek-dso/protocol.c index d2101f2a..34a3935b 100644 --- a/src/hardware/hantek-dso/protocol.c +++ b/src/hardware/hantek-dso/protocol.c @@ -275,8 +275,10 @@ static int dso2250_set_trigger_samplerate(const struct sr_dev_inst *sdi) memset(cmdstring, 0, sizeof(cmdstring)); /* Command */ cmdstring[0] = CMD_2250_SET_TRIGGERSOURCE; - sr_dbg("Trigger source %s.", devc->triggersource); - if (!strcmp("CH2", devc->triggersource)) + sr_dbg("Trigger source %s.", devc->triggersource ? : ""); + if (!devc->triggersource) + tmp = 0; + else if (!strcmp("CH2", devc->triggersource)) tmp = 3; else if (!strcmp("CH1", devc->triggersource)) tmp = 2; @@ -421,8 +423,10 @@ SR_PRIV int dso_set_trigger_samplerate(const struct sr_dev_inst *sdi) cmdstring[0] = CMD_SET_TRIGGER_SAMPLERATE; /* Trigger source */ - sr_dbg("Trigger source %s.", devc->triggersource); - if (!strcmp("CH2", devc->triggersource)) + sr_dbg("Trigger source %s.", devc->triggersource ? : ""); + if (!devc->triggersource) + tmp = 2; + else if (!strcmp("CH2", devc->triggersource)) tmp = 0; else if (!strcmp("CH1", devc->triggersource)) tmp = 1; @@ -668,7 +672,7 @@ static int dso_set_relays(const struct sr_dev_inst *sdi) if (devc->coupling[1] != COUPLING_AC) relays[6] = ~relays[6]; - if (!strcmp(devc->triggersource, "EXT")) + if (devc->triggersource && strcmp(devc->triggersource, "EXT") == 0) relays[7] = ~relays[7]; if (sr_log_loglevel_get() >= SR_LOG_DBG) { diff --git a/src/hardware/hp-3478a/api.c b/src/hardware/hp-3478a/api.c index 1979c529..c21546f2 100644 --- a/src/hardware/hp-3478a/api.c +++ b/src/hardware/hp-3478a/api.c @@ -71,21 +71,21 @@ static const struct { {SR_MQ_VOLTAGE, SR_MQFLAG_AC, 2, "300V"}, /* -99 is a dummy exponent for auto ranging. */ {SR_MQ_CURRENT, SR_MQFLAG_DC, -99, "Auto"}, - {SR_MQ_CURRENT, SR_MQFLAG_DC, -1, "300mV"}, - {SR_MQ_CURRENT, SR_MQFLAG_DC, 0, "3V"}, + {SR_MQ_CURRENT, SR_MQFLAG_DC, -1, "300mA"}, + {SR_MQ_CURRENT, SR_MQFLAG_DC, 0, "3A"}, /* -99 is a dummy exponent for auto ranging. */ {SR_MQ_CURRENT, SR_MQFLAG_AC, -99, "Auto"}, - {SR_MQ_CURRENT, SR_MQFLAG_AC, -1, "300mV"}, - {SR_MQ_CURRENT, SR_MQFLAG_AC, 0, "3V"}, + {SR_MQ_CURRENT, SR_MQFLAG_AC, -1, "300mA"}, + {SR_MQ_CURRENT, SR_MQFLAG_AC, 0, "3A"}, /* -99 is a dummy exponent for auto ranging. */ {SR_MQ_RESISTANCE, 0, -99, "Auto"}, - {SR_MQ_RESISTANCE, 0, 1, "30"}, - {SR_MQ_RESISTANCE, 0, 2, "300"}, - {SR_MQ_RESISTANCE, 0, 3, "3k"}, - {SR_MQ_RESISTANCE, 0, 4, "30k"}, - {SR_MQ_RESISTANCE, 0, 5, "300k"}, - {SR_MQ_RESISTANCE, 0, 6, "3M"}, - {SR_MQ_RESISTANCE, 0, 7, "30M"}, + {SR_MQ_RESISTANCE, 0, 1, "30R"}, + {SR_MQ_RESISTANCE, 0, 2, "300R"}, + {SR_MQ_RESISTANCE, 0, 3, "3kR"}, + {SR_MQ_RESISTANCE, 0, 4, "30kR"}, + {SR_MQ_RESISTANCE, 0, 5, "300kR"}, + {SR_MQ_RESISTANCE, 0, 6, "3MR"}, + {SR_MQ_RESISTANCE, 0, 7, "30MR"}, /* -99 is a dummy exponent for auto ranging. */ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, -99, "Auto"}, {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 1, "30R"}, @@ -102,7 +102,7 @@ static const char *digits[] = { "3.5", "4.5", "5.5", }; -/** Mapping between devc->spec_digits and digits string. */ +/** Mapping between devc->digits and digits string. */ static const char *digits_map[] = { "", "", "", "", "3.5", "4.5", "5.5", }; @@ -227,7 +227,7 @@ static int config_get(uint32_t key, GVariant **data, ret = hp_3478a_get_status_bytes(sdi); if (ret != SR_OK) return ret; - *data = g_variant_new_string(digits_map[devc->spec_digits]); + *data = g_variant_new_string(digits_map[devc->digits]); break; default: return SR_ERR_NA; diff --git a/src/hardware/hp-3478a/protocol.c b/src/hardware/hp-3478a/protocol.c index ed8878be..6e3b2bc1 100644 --- a/src/hardware/hp-3478a/protocol.c +++ b/src/hardware/hp-3478a/protocol.c @@ -115,11 +115,11 @@ SR_PRIV int hp_3478a_set_digits(const struct sr_dev_inst *sdi, uint8_t digits) struct sr_scpi_dev_inst *scpi = sdi->conn; struct dev_context *devc = sdi->priv; - /* No need to send command if we're not changing the range. */ - if (devc->spec_digits == digits) + /* No need to send command if we're not changing the resolution. */ + if (devc->digits == digits) return SR_OK; - /* digits are based on devc->spec_digits, so we have to substract 1 */ + /* digits are the total number of digits, so we have to substract 1 */ ret = sr_scpi_send(scpi, "N%i", digits-1); if (ret != SR_OK) return ret; @@ -131,19 +131,19 @@ static int parse_range_vdc(struct dev_context *devc, uint8_t range_byte) { if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30MV) { devc->range_exp = -2; - devc->enc_digits = devc->spec_digits - 2; + devc->sr_digits = devc->digits + 1; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300MV) { devc->range_exp = -1; - devc->enc_digits = devc->spec_digits - 3; + devc->sr_digits = devc->digits; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_3V) { devc->range_exp = 0; - devc->enc_digits = devc->spec_digits - 1; + devc->sr_digits = devc->digits - 1; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30V) { devc->range_exp = 1; - devc->enc_digits = devc->spec_digits - 2; + devc->sr_digits = devc->digits - 2; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300V) { devc->range_exp = 2; - devc->enc_digits = devc->spec_digits - 3; + devc->sr_digits = devc->digits - 3; } else return SR_ERR_DATA; @@ -154,16 +154,16 @@ static int parse_range_vac(struct dev_context *devc, uint8_t range_byte) { if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300MV) { devc->range_exp = -1; - devc->enc_digits = devc->spec_digits - 3; + devc->sr_digits = devc->digits; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_3V) { devc->range_exp = 0; - devc->enc_digits = devc->spec_digits - 1; + devc->sr_digits = devc->digits - 1; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_30V) { devc->range_exp = 1; - devc->enc_digits = devc->spec_digits - 2; + devc->sr_digits = devc->digits - 2; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300V) { devc->range_exp = 2; - devc->enc_digits = devc->spec_digits - 3; + devc->sr_digits = devc->digits - 3; } else return SR_ERR_DATA; @@ -174,10 +174,10 @@ static int parse_range_a(struct dev_context *devc, uint8_t range_byte) { if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_300MA) { devc->range_exp = -1; - devc->enc_digits = devc->spec_digits - 3; + devc->sr_digits = devc->digits; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_3A) { devc->range_exp = 0; - devc->enc_digits = devc->spec_digits - 1; + devc->sr_digits = devc->digits - 1; } else return SR_ERR_DATA; @@ -188,25 +188,25 @@ static int parse_range_ohm(struct dev_context *devc, uint8_t range_byte) { if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30R) { devc->range_exp = 1; - devc->enc_digits = devc->spec_digits - 2; + devc->sr_digits = devc->digits - 2; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300R) { devc->range_exp = 2; - devc->enc_digits = devc->spec_digits - 3; + devc->sr_digits = devc->digits - 3; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3KR) { devc->range_exp = 3; - devc->enc_digits = devc->spec_digits - 1; + devc->sr_digits = devc->digits - 4; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30KR) { devc->range_exp = 4; - devc->enc_digits = devc->spec_digits - 2; + devc->sr_digits = devc->digits - 5; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300KR) { devc->range_exp = 5; - devc->enc_digits = devc->spec_digits - 3; + devc->sr_digits = devc->digits - 6; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3MR) { devc->range_exp = 6; - devc->enc_digits = devc->spec_digits - 1; + devc->sr_digits = devc->digits - 7; } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30MR) { devc->range_exp = 7; - devc->enc_digits = devc->spec_digits - 2; + devc->sr_digits = devc->digits - 8; } else return SR_ERR_DATA; @@ -215,13 +215,13 @@ static int parse_range_ohm(struct dev_context *devc, uint8_t range_byte) static int parse_function_byte(struct dev_context *devc, uint8_t function_byte) { - /* Digits / Resolution (spec_digits must be set before range parsing) */ + /* Digits / Resolution (digits must be set before range parsing) */ if ((function_byte & SB1_DIGITS_BLOCK) == DIGITS_5_5) - devc->spec_digits = 6; + devc->digits = 6; else if ((function_byte & SB1_DIGITS_BLOCK) == DIGITS_4_5) - devc->spec_digits = 5; + devc->digits = 5; else if ((function_byte & SB1_DIGITS_BLOCK) == DIGITS_3_5) - devc->spec_digits = 4; + devc->digits = 4; else return SR_ERR_DATA; @@ -443,7 +443,7 @@ static void acq_send_measurement(struct sr_dev_inst *sdi) packet.type = SR_DF_ANALOG; packet.payload = &analog; - sr_analog_init(&analog, &encoding, &meaning, &spec, devc->enc_digits); + sr_analog_init(&analog, &encoding, &meaning, &spec, devc->sr_digits); /* TODO: Implement NAN, depending on counts, range and value. */ f = devc->measurement; @@ -452,14 +452,14 @@ static void acq_send_measurement(struct sr_dev_inst *sdi) encoding.unitsize = sizeof(float); encoding.is_float = TRUE; - encoding.digits = devc->enc_digits; + encoding.digits = devc->sr_digits; meaning.mq = devc->measurement_mq; meaning.mqflags = devc->acquisition_mq_flags; meaning.unit = devc->measurement_unit; meaning.channels = sdi->channels; - spec.spec_digits = devc->spec_digits; + spec.spec_digits = devc->sr_digits; sr_session_send(sdi, &packet); } diff --git a/src/hardware/hp-3478a/protocol.h b/src/hardware/hp-3478a/protocol.h index a6318f92..2b394aa5 100644 --- a/src/hardware/hp-3478a/protocol.h +++ b/src/hardware/hp-3478a/protocol.h @@ -150,8 +150,16 @@ struct dev_context { enum sr_mqflag acquisition_mq_flags; enum sr_unit measurement_unit; int range_exp; - uint8_t enc_digits; - uint8_t spec_digits; + /** + * The total number of digits. Rounded up from the resoultion of + * the device, so a 5.5 resolution would be 6 digits. + */ + uint8_t digits; + /** + * The digits used for encoding.digits and spec.spec_digits in + * the analog payload. + */ + uint8_t sr_digits; enum terminal_connector terminal; enum trigger_state trigger; diff --git a/src/hardware/icstation-usbrelay/api.c b/src/hardware/icstation-usbrelay/api.c new file mode 100644 index 00000000..db576737 --- /dev/null +++ b/src/hardware/icstation-usbrelay/api.c @@ -0,0 +1,254 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2021-2023 Frank Stettner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "protocol.h" + +#define SERIALCOMM "9600/8n1" + +static const uint32_t scanopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, +}; + +static const uint32_t drvopts[] = { + SR_CONF_MULTIPLEXER, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONN | SR_CONF_GET, + SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */ +}; + +static const uint32_t devopts_cg[] = { + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, +}; + +static const struct ics_usbrelay_profile supported_ics_usbrelay[] = { + { ICSE012A, 0xAB, "ICSE012A", 4 }, + { ICSE013A, 0xAD, "ICSE013A", 2 }, + { ICSE014A, 0xAC, "ICSE014A", 8 }, +}; + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + GSList *devices; + size_t i, ch_idx; + const char *conn, *serialcomm; + int ret; + uint8_t device_id; + const struct ics_usbrelay_profile *profile; + struct sr_channel_group *cg; + struct channel_group_context *cgc; + + devices = NULL; + + /* Only scan for a device when conn= was specified. */ + conn = NULL; + serialcomm = SERIALCOMM; + if (sr_serial_extract_options(options, &conn, &serialcomm) != SR_OK) + return NULL; + + serial = sr_serial_dev_inst_new(conn, serialcomm); + if (serial_open(serial, SERIAL_RDWR) != SR_OK) + return NULL; + + /* Get device model. */ + ret = icstation_usbrelay_identify(serial, &device_id); + if (ret != SR_OK) { + sr_err("Cannot retrieve identification details."); + serial_close(serial); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(supported_ics_usbrelay); i++) { + profile = &supported_ics_usbrelay[i]; + if (device_id != profile->id) + continue; + sdi = g_malloc0(sizeof(*sdi)); + sdi->status = SR_ST_INACTIVE; + sdi->vendor = g_strdup("ICStation"); + sdi->model = g_strdup(profile->modelname); + sdi->inst_type = SR_INST_SERIAL; + sdi->conn = serial; + sdi->connection_id = g_strdup(conn); + + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; + devc->relay_count = profile->nb_channels; + devc->relay_mask = (1U << devc->relay_count) - 1; + /* Assume that all relays are off at the start. */ + devc->relay_state = 0; + for (ch_idx = 0; ch_idx < devc->relay_count; ch_idx++) { + cg = g_malloc0(sizeof(*cg)); + cg->name = g_strdup_printf("R%zu", ch_idx + 1); + cgc = g_malloc0(sizeof(*cgc)); + cg->priv = cgc; + cgc->index = ch_idx; + sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + } + + devices = g_slist_append(devices, sdi); + break; + } + + serial_close(serial); + if (!devices) { + sr_serial_dev_inst_free(serial); + sr_warn("Unknown device identification 0x%02hhx.", device_id); + } + + return std_scan_complete(di, devices); +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + struct channel_group_context *cgc; + uint8_t mask; + gboolean on; + + if (!sdi || !data) + return SR_ERR_ARG; + devc = sdi->priv; + + if (!cg) { + switch (key) { + case SR_CONF_CONN: + *data = g_variant_new_string(sdi->connection_id); + break; + default: + return SR_ERR_NA; + } + } + + switch (key) { + case SR_CONF_ENABLED: + cgc = cg->priv; + mask = 1U << cgc->index; + on = devc->relay_state & mask; + *data = g_variant_new_boolean(on); + return SR_OK; + default: + return SR_ERR_NA; + } + + return SR_OK; +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + gboolean on; + + if (!cg) { + switch (key) { + case SR_CONF_ENABLED: + /* Enable/disable all channels at the same time. */ + on = g_variant_get_boolean(data); + return icstation_usbrelay_switch_cg(sdi, cg, on); + default: + return SR_ERR_NA; + } + } else { + switch (key) { + case SR_CONF_ENABLED: + on = g_variant_get_boolean(data); + return icstation_usbrelay_switch_cg(sdi, cg, on); + default: + return SR_ERR_NA; + } + } + + return SR_OK; +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + if (!cg) { + switch (key) { + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); + default: + return SR_ERR_NA; + } + } + + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg)); + return SR_OK; + default: + return SR_ERR_NA; + } +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + struct sr_serial_dev_inst *serial; + int ret; + + if (!sdi) + return SR_ERR_ARG; + serial = sdi->conn; + if (!serial) + return SR_ERR_ARG; + + ret = std_serial_dev_open(sdi); + if (ret != SR_OK) + return ret; + + /* Start command mode. */ + ret = icstation_usbrelay_start(sdi); + if (ret != SR_OK) { + sr_err("Cannot initiate command mode."); + serial_close(serial); + return SR_ERR_IO; + } + + return SR_OK; +} + +static struct sr_dev_driver icstation_usbrelay_driver_info = { + .name = "icstation-usbrelay", + .longname = "ICStation USBRelay", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = std_dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = dev_open, + .dev_close = std_serial_dev_close, + .dev_acquisition_start = std_dummy_dev_acquisition_start, + .dev_acquisition_stop = std_dummy_dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(icstation_usbrelay_driver_info); diff --git a/src/hardware/icstation-usbrelay/protocol.c b/src/hardware/icstation-usbrelay/protocol.c new file mode 100644 index 00000000..78a99965 --- /dev/null +++ b/src/hardware/icstation-usbrelay/protocol.c @@ -0,0 +1,153 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2021-2023 Frank Stettner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "protocol.h" + +#define SERIAL_TIMEOUT_MS 1000 + +#define ICSTATION_USBRELAY_CMD_ID 0x50 +#define ICSTATION_USBRELAY_CMD_START 0x51 + +static int icstation_usbrelay_send_byte(struct sr_serial_dev_inst *serial, + uint8_t b) +{ + int ret; + + ret = serial_write_blocking(serial, &b, sizeof(b), SERIAL_TIMEOUT_MS); + if (ret < SR_OK) + return SR_ERR_IO; + if ((size_t)ret != sizeof(b)) + return SR_ERR_IO; + + return SR_OK; +} + +static int icstation_usbrelay_recv_byte(struct sr_serial_dev_inst *serial, + uint8_t *b) +{ + int ret; + + ret = serial_read_blocking(serial, b, sizeof(*b), SERIAL_TIMEOUT_MS); + if (ret < SR_OK) + return SR_ERR_IO; + if ((size_t)ret != sizeof(*b)) + return SR_ERR_IO; + + return SR_OK; +} + +SR_PRIV int icstation_usbrelay_identify(struct sr_serial_dev_inst *serial, + uint8_t *id) +{ + int ret; + + if (!id) + return SR_ERR_ARG; + + /* + * Send the identification request. Receive the device firmware's + * identification response. + * + * BEWARE! + * A vendor firmware implementation detail prevents the host from + * identifying the device again once command mode was entered. + * The UART protocol provides no means to leave command mode. + * The subsequent identification request is mistaken instead as + * another relay control request! Identifying the device will fail. + * The device must be power cycled before it identifies again. + */ + ret = icstation_usbrelay_send_byte(serial, ICSTATION_USBRELAY_CMD_ID); + if (ret != SR_OK) { + sr_dbg("Could not send identification request."); + return SR_ERR_IO; + } + ret = icstation_usbrelay_recv_byte(serial, id); + if (ret != SR_OK) { + sr_dbg("Could not receive identification response."); + return SR_ERR_IO; + } + sr_dbg("Identification response 0x%02hhx.", *id); + + return SR_OK; +} + +SR_PRIV int icstation_usbrelay_start(const struct sr_dev_inst *sdi) +{ + struct sr_serial_dev_inst *serial; + + if (!sdi) + return SR_ERR_ARG; + serial = sdi->conn; + if (!serial) + return SR_ERR_ARG; + + return icstation_usbrelay_send_byte(serial, + ICSTATION_USBRELAY_CMD_START); +} + +SR_PRIV int icstation_usbrelay_switch_cg(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, gboolean on) +{ + struct dev_context *devc; + struct channel_group_context *cgc; + uint8_t state, mask; + uint8_t tx_state; + + devc = sdi->priv; + + /* + * The device requires the communication of all relay states + * at the same time. Calling applications control individual + * relays. The device wants active-low state in the physical + * transport. Application uses positive logic (active-high). + * + * Update the locally cached state from the most recent request. + * Invert the result and send it to the device. Only update + * the internal cache after successful transmission. + */ + + state = devc->relay_state; + if (!cg) { + /* Set all relays. */ + if (on) + state |= devc->relay_mask; + else + state &= ~devc->relay_mask; + } else { + cgc = cg->priv; + mask = 1UL << cgc->index; + if (on) + state |= mask; + else + state &= ~mask; + } + + tx_state = ~state & devc->relay_mask; + sr_spew("Sending status byte: %x", tx_state); + if (icstation_usbrelay_send_byte(sdi->conn, tx_state) != SR_OK) { + sr_err("Unable to send status byte."); + return SR_ERR_IO; + } + + devc->relay_state = state; + + return SR_OK; +} diff --git a/src/hardware/icstation-usbrelay/protocol.h b/src/hardware/icstation-usbrelay/protocol.h new file mode 100644 index 00000000..bcf9dd07 --- /dev/null +++ b/src/hardware/icstation-usbrelay/protocol.h @@ -0,0 +1,62 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2021-2023 Frank Stettner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H +#define LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H + +#include +#include +#include + +#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 diff --git a/src/hardware/juntek-jds6600/api.c b/src/hardware/juntek-jds6600/api.c new file mode 100644 index 00000000..3aa07f87 --- /dev/null +++ b/src/hardware/juntek-jds6600/api.c @@ -0,0 +1,419 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include "protocol.h" + +#define DFLT_SERIALCOMM "115200/8n1" + +#define VENDOR_TEXT "Juntek" +#define MODEL_TEXT "JDS6600" + +static const uint32_t scanopts[] = { + SR_CONF_CONN, +}; + +static const uint32_t drvopts[] = { + SR_CONF_SIGNAL_GENERATOR, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONN | SR_CONF_GET, + SR_CONF_ENABLED | SR_CONF_SET, + SR_CONF_PHASE | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg[] = { + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, + SR_CONF_PATTERN_MODE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_AMPLITUDE | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OFFSET | SR_CONF_GET | SR_CONF_SET, + SR_CONF_DUTY_CYCLE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + GSList *devices; + const char *conn, *serialcomm; + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_serial_dev_inst *ser; + int ret; + size_t ch_idx, idx, ch_nr; + char cg_name[8]; + struct sr_channel_group *cg; + struct sr_channel *ch; + + devices = NULL; + + conn = NULL; + serialcomm = DFLT_SERIALCOMM; + (void)sr_serial_extract_options(options, &conn, &serialcomm); + if (!conn) + return devices; + + ser = sr_serial_dev_inst_new(conn, serialcomm); + if (!ser) + return devices; + ret = serial_open(ser, SERIAL_RDWR); + if (ret != SR_OK) { + sr_serial_dev_inst_free(ser); + return devices; + } + + sdi = g_malloc0(sizeof(*sdi)); + sdi->status = SR_ST_INACTIVE; + sdi->inst_type = SR_INST_USB; + sdi->conn = ser; + sdi->connection_id = g_strdup(conn); + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; + + ret = jds6600_identify(sdi); + if (ret != SR_OK) + goto fail; + ret = jds6600_setup_devc(sdi); + if (ret != SR_OK) + goto fail; + (void)serial_close(ser); + + sdi->vendor = g_strdup(VENDOR_TEXT); + sdi->model = g_strdup(MODEL_TEXT); + if (devc->device.serial_number) + sdi->serial_num = g_strdup(devc->device.serial_number); + + ch_idx = 0; + for (idx = 0; idx < MAX_GEN_CHANNELS; idx++) { + ch_nr = idx + 1; + snprintf(cg_name, sizeof(cg_name), "CH%zu", ch_nr); + cg = sr_channel_group_new(sdi, cg_name, NULL); + (void)cg; + ch = sr_channel_new(sdi, ch_idx, + SR_CHANNEL_ANALOG, FALSE, cg_name); + cg->channels = g_slist_append(cg->channels, ch); + ch_idx++; + } + + devices = g_slist_append(devices, sdi); + return std_scan_complete(di, devices); + +fail: + (void)serial_close(ser); + sr_serial_dev_inst_free(ser); + if (devc) { + g_free(devc->device.serial_number); + g_free(devc->waveforms.fw_codes); + g_free(devc->waveforms.names); + } + g_free(devc); + sr_dev_inst_free(sdi); + + return devices; +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + int ret; + size_t cg_idx; + struct devc_wave *waves; + struct devc_chan *chan; + double dvalue; + const char *s; + + devc = sdi ? sdi->priv : NULL; + if (!cg) { + switch (key) { + case SR_CONF_CONN: + if (!sdi->connection_id) + return SR_ERR_NA; + *data = g_variant_new_string(sdi->connection_id); + return SR_OK; + case SR_CONF_PHASE: + if (!devc) + return SR_ERR_NA; + ret = jds6600_get_phase_chans(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = devc->channels_phase; + *data = g_variant_new_double(dvalue); + return SR_OK; + default: + return SR_ERR_NA; + } + } + + if (!devc) + return SR_ERR_NA; + ret = g_slist_index(sdi->channel_groups, cg); + if (ret < 0) + return SR_ERR_NA; + cg_idx = (size_t)ret; + if (cg_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_NA; + chan = &devc->channel_config[cg_idx]; + + switch (key) { + case SR_CONF_ENABLED: + ret = jds6600_get_chans_enable(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + *data = g_variant_new_boolean(chan->enabled); + return SR_OK; + case SR_CONF_PATTERN_MODE: + ret = jds6600_get_waveform(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + waves = &devc->waveforms; + s = waves->names[chan->waveform_index]; + *data = g_variant_new_string(s); + return SR_OK; + case SR_CONF_OUTPUT_FREQUENCY: + ret = jds6600_get_frequency(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->output_frequency; + *data = g_variant_new_double(dvalue); + return SR_OK; + case SR_CONF_AMPLITUDE: + ret = jds6600_get_amplitude(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->amplitude; + *data = g_variant_new_double(dvalue); + return SR_OK; + case SR_CONF_OFFSET: + ret = jds6600_get_offset(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->offset; + *data = g_variant_new_double(dvalue); + return SR_OK; + case SR_CONF_DUTY_CYCLE: + ret = jds6600_get_dutycycle(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->dutycycle; + *data = g_variant_new_double(dvalue); + return SR_OK; + default: + return SR_ERR_NA; + } +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + struct devc_wave *waves; + struct devc_chan *chan; + size_t cg_idx; + double dvalue; + gboolean on; + int ret, idx; + + devc = sdi ? sdi->priv : NULL; + + if (!cg) { + switch (key) { + case SR_CONF_ENABLED: + /* Enable/disable all channels at the same time. */ + on = g_variant_get_boolean(data); + if (!devc) + return SR_ERR_ARG; + cg_idx = devc->device.channel_count_gen; + while (cg_idx) { + chan = &devc->channel_config[--cg_idx]; + chan->enabled = on; + } + ret = jds6600_set_chans_enable(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_PHASE: + if (!devc) + return SR_ERR_ARG; + dvalue = g_variant_get_double(data); + devc->channels_phase = dvalue; + ret = jds6600_set_phase_chans(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + default: + return SR_ERR_NA; + } + } + + ret = g_slist_index(sdi->channel_groups, cg); + if (ret < 0) + return SR_ERR_NA; + cg_idx = (size_t)ret; + if (cg_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_NA; + chan = &devc->channel_config[cg_idx]; + + switch (key) { + case SR_CONF_ENABLED: + on = g_variant_get_boolean(data); + chan->enabled = on; + ret = jds6600_set_chans_enable(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_PATTERN_MODE: + waves = &devc->waveforms; + idx = std_str_idx(data, waves->names, waves->names_count); + if (idx < 0) + return SR_ERR_NA; + if ((size_t)idx >= waves->names_count) + return SR_ERR_NA; + chan->waveform_index = idx; + chan->waveform_code = waves->fw_codes[chan->waveform_index]; + ret = jds6600_set_waveform(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_OUTPUT_FREQUENCY: + dvalue = g_variant_get_double(data); + chan->output_frequency = dvalue; + ret = jds6600_set_frequency(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_AMPLITUDE: + dvalue = g_variant_get_double(data); + chan->amplitude = dvalue; + ret = jds6600_set_amplitude(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_OFFSET: + dvalue = g_variant_get_double(data); + chan->offset = dvalue; + ret = jds6600_set_offset(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_DUTY_CYCLE: + dvalue = g_variant_get_double(data); + chan->dutycycle = dvalue; + ret = jds6600_set_dutycycle(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + default: + return SR_ERR_NA; + } +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + struct devc_wave *waves; + double fspec[3]; + + if (!cg) { + switch (key) { + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); + default: + return SR_ERR_NA; + } + } + + if (!sdi) + return SR_ERR_NA; + devc = sdi->priv; + if (!devc) + return SR_ERR_NA; + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + *data =std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg)); + return SR_OK; + case SR_CONF_PATTERN_MODE: + waves = &devc->waveforms; + *data = std_gvar_array_str(waves->names, waves->names_count); + return SR_OK; + case SR_CONF_OUTPUT_FREQUENCY: + /* Announce range as tuple of min, max, step. */ + fspec[0] = 0.01; + fspec[1] = devc->device.max_output_frequency; + fspec[2] = 0.01; + *data = std_gvar_min_max_step_array(fspec); + return SR_OK; + case SR_CONF_DUTY_CYCLE: + /* Announce range as tuple of min, max, step. */ + fspec[0] = 0.0; + fspec[1] = 1.0; + fspec[2] = 0.001; + *data = std_gvar_min_max_step_array(fspec); + return SR_OK; + default: + return SR_ERR_NA; + } +} + +static void clear_helper(struct dev_context *devc) +{ + struct devc_wave *waves; + + if (!devc) + return; + + g_free(devc->device.serial_number); + waves = &devc->waveforms; + while (waves->names_count) + g_free((char *)waves->names[--waves->names_count]); + g_free(waves->names); + g_free(waves->fw_codes); + if (devc->quick_req) + g_string_free(devc->quick_req, TRUE); +} + +static int dev_clear(const struct sr_dev_driver *driver) +{ + return std_dev_clear_with_callback(driver, + (std_dev_clear_callback)clear_helper); +} + +static struct sr_dev_driver juntek_jds6600_driver_info = { + .name = "juntek-jds6600", + .longname = "JUNTEK JDS6600", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = std_serial_dev_open, + .dev_close = std_serial_dev_close, + .dev_acquisition_start = std_dummy_dev_acquisition_start, + .dev_acquisition_stop = std_dummy_dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(juntek_jds6600_driver_info); diff --git a/src/hardware/juntek-jds6600/protocol.c b/src/hardware/juntek-jds6600/protocol.c new file mode 100644 index 00000000..5759c6a0 --- /dev/null +++ b/src/hardware/juntek-jds6600/protocol.c @@ -0,0 +1,1651 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * 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 . + */ + +/* + * 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 +#include +#include + +#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, + */ +#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." + * ":r01=0." + * ":r01=0" + * ":r01=0" + * Normalizes to: + * ":r01=0." + */ +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; +} diff --git a/src/hardware/juntek-jds6600/protocol.h b/src/hardware/juntek-jds6600/protocol.h new file mode 100644 index 00000000..9818ecc6 --- /dev/null +++ b/src/hardware/juntek-jds6600/protocol.h @@ -0,0 +1,79 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H +#define LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H + +#include +#include +#include + +#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 diff --git a/src/hardware/kingst-la2016/api.c b/src/hardware/kingst-la2016/api.c index 742f3e3a..5a4ee6fa 100644 --- a/src/hardware/kingst-la2016/api.c +++ b/src/hardware/kingst-la2016/api.c @@ -198,15 +198,21 @@ static void kingst_la2016_free_devc(struct dev_context *devc) /* Convenience. Release an allocated sdi from error paths. */ static void kingst_la2016_free_sdi(struct sr_dev_inst *sdi) { + struct sr_usb_dev_inst *usb; + struct dev_context *devc; + if (!sdi) return; - 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). */ @@ -1035,7 +1041,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) 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; @@ -1045,21 +1051,35 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) 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); diff --git a/src/hardware/kingst-la2016/protocol.c b/src/hardware/kingst-la2016/protocol.c index 472fca72..dd806eaa 100644 --- a/src/hardware/kingst-la2016/protocol.c +++ b/src/hardware/kingst-la2016/protocol.c @@ -54,6 +54,7 @@ static const struct kingst_model models[] = { { 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), }, @@ -1283,7 +1284,7 @@ static int la2016_start_download(const struct sr_dev_inst *sdi) 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; @@ -1322,15 +1323,31 @@ static int la2016_start_download(const struct sr_dev_inst *sdi) } /* - * 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; @@ -1360,12 +1377,11 @@ static void send_chunk(struct sr_dev_inst *sdi, /* 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) @@ -1375,7 +1391,7 @@ static void send_chunk(struct sr_dev_inst *sdi, 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); @@ -1390,7 +1406,10 @@ static void send_chunk(struct sr_dev_inst *sdi, } } } - (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); } /* @@ -1422,21 +1441,22 @@ static void send_chunk(struct sr_dev_inst *sdi, * 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) @@ -1461,30 +1481,15 @@ static void stream_data(struct sr_dev_inst *sdi, /* 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]; @@ -1503,7 +1508,8 @@ static void stream_data(struct sr_dev_inst *sdi, 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; diff --git a/src/hardware/kingst-la2016/protocol.h b/src/hardware/kingst-la2016/protocol.h index 995ae64b..371a3ccd 100644 --- a/src/hardware/kingst-la2016/protocol.h +++ b/src/hardware/kingst-la2016/protocol.h @@ -81,8 +81,6 @@ #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. */ @@ -143,6 +141,8 @@ struct dev_context { 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; diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c index d04152af..82e43797 100644 --- a/src/hardware/korad-kaxxxxp/api.c +++ b/src/hardware/korad-kaxxxxp/api.c @@ -70,9 +70,12 @@ static const struct korad_kaxxxxp_model models[] = { {"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 }; @@ -195,6 +198,30 @@ static gboolean model_matches(const struct korad_kaxxxxp_model *model, 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:"; @@ -206,7 +233,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) 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; @@ -271,32 +297,22 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) 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); @@ -309,11 +325,11 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V"); sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I"); - devc = g_malloc0(sizeof(struct dev_context)); + devc = g_malloc0(sizeof(*devc)); sr_sw_limits_init(&devc->limits); g_mutex_init(&devc->rw_mutex); devc->model = 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; @@ -502,7 +518,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) sr_sw_limits_acquisition_start(&devc->limits); std_session_send_df_header(sdi); - devc->req_sent_at = 0; + devc->next_req_time = 0; serial = sdi->conn; serial_source_add(sdi->session, serial, G_IO_IN, KAXXXXP_POLL_INTERVAL_MS, diff --git a/src/hardware/korad-kaxxxxp/protocol.c b/src/hardware/korad-kaxxxxp/protocol.c index c52bd901..bac8c8ef 100644 --- a/src/hardware/korad-kaxxxxp/protocol.c +++ b/src/hardware/korad-kaxxxxp/protocol.c @@ -22,9 +22,10 @@ #include "protocol.h" #define DEVICE_PROCESSING_TIME_MS 80 +#define EXTRA_PROCESSING_TIME_MS 450 SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial, - const char *cmd) + const char *cmd) { int ret; @@ -155,96 +156,112 @@ static void give_device_time_to_process(struct dev_context *devc) { int64_t sleeping_time; - sleeping_time = devc->req_sent_at + (DEVICE_PROCESSING_TIME_MS * 1000); - sleeping_time -= g_get_monotonic_time(); + if (!devc->next_req_time) + return; + sleeping_time = devc->next_req_time - g_get_monotonic_time(); if (sleeping_time > 0) { g_usleep(sleeping_time); sr_spew("Sleeping for processing %" PRIi64 " usec", sleeping_time); } } +static int64_t next_req_time(struct dev_context *devc, + gboolean is_set, int target) +{ + gboolean is_slow_device, is_long_command; + int64_t processing_time_us; + + is_slow_device = devc->model->quirks & KORAD_QUIRK_SLOW_PROCESSING; + is_long_command = is_set; + is_long_command |= target == KAXXXXP_STATUS; + + processing_time_us = DEVICE_PROCESSING_TIME_MS; + if (is_slow_device && is_long_command) + processing_time_us += EXTRA_PROCESSING_TIME_MS; + processing_time_us *= 1000; + + return g_get_monotonic_time() + processing_time_us; +} + SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial, - int target, struct dev_context *devc) + int target, struct dev_context *devc) { - char *msg; - const char *cmd; - float value; + char msg[20]; int ret; g_mutex_lock(&devc->rw_mutex); give_device_time_to_process(devc); + msg[0] = '\0'; + ret = SR_OK; switch (target) { case KAXXXXP_CURRENT: case KAXXXXP_VOLTAGE: case KAXXXXP_STATUS: - sr_err("Can't set measurable parameter %d.", target); - g_mutex_unlock(&devc->rw_mutex); - return SR_ERR; + sr_err("Can't set measured value %d.", target); + ret = SR_ERR; + break; case KAXXXXP_CURRENT_LIMIT: - cmd = "ISET1:%05.3f"; - value = devc->set_current_limit; + sr_snprintf_ascii(msg, sizeof(msg), + "ISET1:%05.3f", devc->set_current_limit); break; case KAXXXXP_VOLTAGE_TARGET: - cmd = "VSET1:%05.2f"; - value = devc->set_voltage_target; + sr_snprintf_ascii(msg, sizeof(msg), + "VSET1:%05.2f", devc->set_voltage_target); break; case KAXXXXP_OUTPUT: - cmd = "OUT%01.0f"; - value = (devc->set_output_enabled) ? 1 : 0; + sr_snprintf_ascii(msg, sizeof(msg), + "OUT%1d", (devc->set_output_enabled) ? 1 : 0); /* Set value back to recognize changes */ devc->output_enabled = devc->set_output_enabled; break; case KAXXXXP_BEEP: - cmd = "BEEP%01.0f"; - value = (devc->set_beep_enabled) ? 1 : 0; + sr_snprintf_ascii(msg, sizeof(msg), + "BEEP%1d", (devc->set_beep_enabled) ? 1 : 0); break; case KAXXXXP_OCP: - cmd = "OCP%01.0f"; - value = (devc->set_ocp_enabled) ? 1 : 0; + sr_snprintf_ascii(msg, sizeof(msg), + "OCP%1d", (devc->set_ocp_enabled) ? 1 : 0); /* Set value back to recognize changes */ devc->ocp_enabled = devc->set_ocp_enabled; break; case KAXXXXP_OVP: - cmd = "OVP%01.0f"; - value = (devc->set_ovp_enabled) ? 1 : 0; + sr_snprintf_ascii(msg, sizeof(msg), + "OVP%1d", (devc->set_ovp_enabled) ? 1 : 0); /* Set value back to recognize changes */ devc->ovp_enabled = devc->set_ovp_enabled; break; case KAXXXXP_SAVE: - cmd = "SAV%01.0f"; if (devc->program < 1 || devc->program > 5) { - sr_err("Only programs 1-5 supported and %d isn't " - "between them.", devc->program); - g_mutex_unlock(&devc->rw_mutex); - return SR_ERR; + sr_err("Program %d is not in the supported 1-5 range.", + devc->program); + ret = SR_ERR; + break; } - value = devc->program; + sr_snprintf_ascii(msg, sizeof(msg), + "SAV%1d", devc->program); break; case KAXXXXP_RECALL: - cmd = "RCL%01.0f"; if (devc->program < 1 || devc->program > 5) { - sr_err("Only programs 1-5 supported and %d isn't " - "between them.", devc->program); - g_mutex_unlock(&devc->rw_mutex); - return SR_ERR; + sr_err("Program %d is not in the supported 1-5 range.", + devc->program); + ret = SR_ERR; + break; } - value = devc->program; + sr_snprintf_ascii(msg, sizeof(msg), + "RCL%1d", devc->program); break; default: - sr_err("Don't know how to set %d.", target); - g_mutex_unlock(&devc->rw_mutex); - return SR_ERR; + sr_err("Don't know how to set target %d.", target); + ret = SR_ERR; + break; } - msg = g_malloc0(20 + 1); - if (cmd) - sr_snprintf_ascii(msg, 20, cmd, value); - - ret = korad_kaxxxxp_send_cmd(serial, msg); - devc->req_sent_at = g_get_monotonic_time(); - g_free(msg); + if (ret == SR_OK && msg[0]) { + ret = korad_kaxxxxp_send_cmd(serial, msg); + devc->next_req_time = next_req_time(devc, TRUE, target); + } g_mutex_unlock(&devc->rw_mutex); @@ -252,7 +269,7 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial, } SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, - int target, struct dev_context *devc) + int target, struct dev_context *devc) { int ret, count; char reply[6]; @@ -305,7 +322,7 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, 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); @@ -377,7 +394,7 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, } SR_PRIV int korad_kaxxxxp_get_all_values(struct sr_serial_dev_inst *serial, - struct dev_context *devc) + struct dev_context *devc) { int ret, target; diff --git a/src/hardware/korad-kaxxxxp/protocol.h b/src/hardware/korad-kaxxxxp/protocol.h index 9dcc19ca..005d94ab 100644 --- a/src/hardware/korad-kaxxxxp/protocol.h +++ b/src/hardware/korad-kaxxxxp/protocol.h @@ -37,7 +37,8 @@ enum korad_quirks_flag { 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 */ @@ -70,7 +71,7 @@ struct dev_context { const struct korad_kaxxxxp_model *model; /**< Model information. */ struct sr_sw_limits limits; - int64_t req_sent_at; + int64_t next_req_time; GMutex rw_mutex; float current; /**< Last current value [A] read from device. */ @@ -102,15 +103,15 @@ struct dev_context { }; SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial, - const char *cmd); + const char *cmd); SR_PRIV int korad_kaxxxxp_read_chars(struct sr_serial_dev_inst *serial, - size_t count, char *buf); + size_t count, char *buf); SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial, - int target, struct dev_context *devc); + int target, struct dev_context *devc); SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, - int target, struct dev_context *devc); + int target, struct dev_context *devc); SR_PRIV int korad_kaxxxxp_get_all_values(struct sr_serial_dev_inst *serial, - struct dev_context *devc); + struct dev_context *devc); SR_PRIV int korad_kaxxxxp_receive_data(int fd, int revents, void *cb_data); #endif diff --git a/src/hardware/microchip-pickit2/api.c b/src/hardware/microchip-pickit2/api.c index 79c79d3e..97d5b13c 100644 --- a/src/hardware/microchip-pickit2/api.c +++ b/src/hardware/microchip-pickit2/api.c @@ -72,6 +72,7 @@ static const char *channel_names[] = { static const uint32_t scanopts[] = { SR_CONF_CONN, + SR_CONF_PROBE_NAMES, }; static const uint32_t drvopts[] = { @@ -119,6 +120,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) { struct drv_context *drvc; const char *conn; + const char *probe_names; GSList *l, *devices, *usb_devices; struct sr_config *cfg; struct sr_usb_dev_inst *usb; @@ -131,12 +133,16 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) drvc = di->context; conn = PICKIT2_DEFAULT_ADDRESS; + probe_names = NULL; for (l = options; l; l = l->next) { cfg = l->data; switch (cfg->key) { case SR_CONF_CONN: conn = g_variant_get_string(cfg->data, NULL); break; + case SR_CONF_PROBE_NAMES: + probe_names = g_variant_get_string(cfg->data, NULL); + break; } } @@ -158,15 +164,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->conn = usb; sdi->connection_id = g_strdup(conn); - /* Create the logic channels group. */ - cg = 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. @@ -180,6 +177,17 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) devc->num_captureratios = ARRAY_SIZE(captureratios); devc->curr_captureratio_idx = 0; devc->sw_limits.limit_samples = PICKIT2_SAMPLE_COUNT; + devc->channel_names = sr_parse_probe_names(probe_names, + channel_names, ARRAY_SIZE(channel_names), + ARRAY_SIZE(channel_names), &ch_count); + + /* Create the logic channels group. */ + cg = sr_channel_group_new(sdi, "Logic", NULL); + for (ch_idx = 0; ch_idx < ch_count; ch_idx++) { + ch = sr_channel_new(sdi, ch_idx, SR_CHANNEL_LOGIC, + TRUE, devc->channel_names[ch_idx]); + cg->channels = g_slist_append(cg->channels, ch); + } } return std_scan_complete(di, devices); diff --git a/src/hardware/microchip-pickit2/protocol.h b/src/hardware/microchip-pickit2/protocol.h index 50096473..28b152f0 100644 --- a/src/hardware/microchip-pickit2/protocol.h +++ b/src/hardware/microchip-pickit2/protocol.h @@ -40,6 +40,7 @@ enum pickit_state { }; struct dev_context { + char **channel_names; enum pickit_state state; const uint64_t *samplerates; size_t num_samplerates; diff --git a/src/hardware/mooshimeter-dmm/api.c b/src/hardware/mooshimeter-dmm/api.c index b1b94068..6e0b0184 100644 --- a/src/hardware/mooshimeter-dmm/api.c +++ b/src/hardware/mooshimeter-dmm/api.c @@ -111,7 +111,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) * descr - handle: 0x0016, uuid: 00002902-0000-1000-8000-00805f9b34fb * descr - handle: 0x0017, uuid: 00002901-0000-1000-8000-00805f9b34fb */ - ret = sr_bt_config_notify(desc, 0x0015, 0x0012, 0x0016, 0x0001); + ret = sr_bt_config_notify(desc, 0x0015, 0x0012, 0x0016, 0x0001, 0); if (ret < 0) goto err; diff --git a/src/hardware/openbench-logic-sniffer/protocol.c b/src/hardware/openbench-logic-sniffer/protocol.c index 528b05ce..125c279d 100644 --- a/src/hardware/openbench-logic-sniffer/protocol.c +++ b/src/hardware/openbench-logic-sniffer/protocol.c @@ -292,10 +292,8 @@ SR_PRIV int ols_get_metadata(struct sr_dev_inst *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); /* Optionally amend received metadata, model specific quirks. */ ols_metadata_quirks(sdi); diff --git a/src/hardware/pipistrello-ols/protocol.c b/src/hardware/pipistrello-ols/protocol.c index 1b54dd16..8f6f97f7 100644 --- a/src/hardware/pipistrello-ols/protocol.c +++ b/src/hardware/pipistrello-ols/protocol.c @@ -325,10 +325,8 @@ SR_PRIV struct sr_dev_inst *p_ols_get_metadata(uint8_t *buf, int bytes_read, str } } - sdi->model = devname->str; - sdi->version = version->str; - g_string_free(devname, FALSE); - g_string_free(version, FALSE); + sdi->model = g_string_free(devname, FALSE); + sdi->version = g_string_free(version, FALSE); return sdi; } diff --git a/src/hardware/raspberrypi-pico/api.c b/src/hardware/raspberrypi-pico/api.c new file mode 100644 index 00000000..171b5eaa --- /dev/null +++ b/src/hardware/raspberrypi-pico/api.c @@ -0,0 +1,860 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2022 Shawn Walker + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libsigrok-internal.h" +#include "protocol.h" + +/* Baud rate is really a don't care because we run USB CDC, dtr must be 1. + * flow should be zero since we don't use xon/xoff */ +#define SERIALCOMM "115200/8n1/dtr=1/rts=0/flow=0" + +/* Use the force_detect scan option as a way to pass user information to the + * device the string must use only 0-9,a-z,A-Z,'.','=' and '-'* and be less than + * 60 characters */ + +static const uint32_t scanopts[] = { + SR_CONF_CONN, /* Required OS name for the port, i.e. /dev/ttyACM0 */ + SR_CONF_SERIALCOMM, /* Optional config of the port, i.e. 115200/8n1 */ + SR_CONF_FORCE_DETECT +}; + +/* Sample rate can either provide a std_gvar_samplerates_steps or a + * std_gvar_samplerates. The latter is just a long list of every supported rate. + * For the steps, pulseview/pv/toolbars/mainbar.cpp will do a min,max,step. If + * step is 1 then it provides a 1,2,5,10 select otherwise it allows a spin box. + * Going with the full list because while the spin box is more flexible, it is + * harder to read */ +static const uint64_t samplerates[] = { + SR_KHZ(5), + SR_KHZ(6), + SR_KHZ(8), + SR_KHZ(10), + SR_KHZ(20), + SR_KHZ(30), + SR_KHZ(40), + SR_KHZ(50), + SR_KHZ(60), + SR_KHZ(80), + SR_KHZ(100), + SR_KHZ(125), + SR_KHZ(150), + SR_KHZ(160), /* max rate of 3 ADC chans that has integer divisor/dividend */ + SR_KHZ(200), + SR_KHZ(250), /* max rate of 2 ADC chans */ + SR_KHZ(300), + SR_KHZ(400), + SR_KHZ(500), + SR_KHZ(600), + SR_KHZ(800), + /* Give finer granularity near the thresholds of RLE effectiveness ~1-4Msps + * Also use 1.2 and 2.4 as likely max values for ADC overclocking */ + SR_MHZ(1), + SR_MHZ(1.2), + SR_MHZ(1.5), + SR_MHZ(2), + SR_MHZ(2.4), + SR_MHZ(3), + SR_MHZ(4), + SR_MHZ(5), + SR_MHZ(6), + SR_MHZ(8), + SR_MHZ(10), + SR_MHZ(15), + SR_MHZ(20), + SR_MHZ(30), + SR_MHZ(40), + SR_MHZ(60), + /* The baseline 120Mhz PICO clock won't support an 80 or 100 + * with non fractional divisor, but an overclocked version or one + * that modified sysclk could */ + SR_MHZ(80), + SR_MHZ(100), + SR_MHZ(120), + /* These may not be practically useful, but someone might want to + * try to make it work with overclocking */ + SR_MHZ(150), + SR_MHZ(200), + SR_MHZ(240), +}; + +static const uint32_t drvopts[] = { + SR_CONF_OSCILLOSCOPE, + SR_CONF_LOGIC_ANALYZER, +}; + +static const int32_t trigger_matches[] = { + SR_TRIGGER_ZERO, + SR_TRIGGER_ONE, + SR_TRIGGER_RISING, + SR_TRIGGER_FALLING, + SR_TRIGGER_EDGE, +}; + + +static const uint32_t devopts[] = { + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_MATCH | SR_CONF_LIST, + SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; + +static struct sr_dev_driver raspberrypi_pico_driver_info; + + +static GSList *scan(struct sr_dev_driver *di, GSList * options) +{ + struct sr_config *src; + struct sr_dev_inst *sdi; + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + struct sr_channel *ch; + GSList *l; + int num_read; + int i; + const char *conn, *serialcomm, *force_detect; + char buf[32]; + char ustr[64]; + int len; + uint8_t num_a, num_d, a_size; + gchar *channel_name; + + conn = serialcomm = force_detect = NULL; + for (l = options; l; l = l->next) { + src = l->data; + switch (src->key) { + case SR_CONF_CONN: + conn = g_variant_get_string(src->data, NULL); + break; + case SR_CONF_SERIALCOMM: + serialcomm = g_variant_get_string(src->data, NULL); + break; + case SR_CONF_FORCE_DETECT: + force_detect = g_variant_get_string(src->data, NULL); + sr_info("Force detect string %s", force_detect); + break; + } + } + if (!conn) + return NULL; + + if (!serialcomm) + serialcomm = SERIALCOMM; + + serial = sr_serial_dev_inst_new(conn, serialcomm); + sr_info("Opening %s.", conn); + if (serial_open(serial, SERIAL_RDWR) != SR_OK) { + sr_err("1st serial open fail"); + return NULL; + } + + sr_info("Resetting device with *s at %s.", conn); + send_serial_char(serial, '*'); + g_usleep(10000); + do { + sr_warn("Drain reads"); + len = serial_read_blocking(serial, buf, 32, 100); + sr_warn("Drain reads done"); + if (len) + sr_dbg("Dropping in flight serial data"); + } while (len > 0); + /* Send the user string with the identify */ + if (force_detect && (strlen(force_detect) <= 60)) { + sprintf(ustr,"i%s\n", force_detect); + sr_info("User string %s", ustr); + num_read = send_serial_w_resp(serial, ustr, buf, 17); + } else { + num_read = send_serial_w_resp(serial, "i\n", buf, 17); + } + if (num_read < 16) { + sr_err("1st identify failed"); + serial_close(serial); + g_usleep(100000); + if (serial_open(serial, SERIAL_RDWR) != SR_OK) { + sr_err("2nd serial open fail"); + return NULL; + } + g_usleep(100000); + sr_err("Send second *"); + send_serial_char(serial, '*'); + g_usleep(100000); + num_read = send_serial_w_resp(serial, "i\n", buf, 17); + if (num_read < 10) { + sr_err("Second attempt failed"); + return NULL; + } + } + + /* Expected ID response is SRPICO,AxxyDzz,VV + * where xx are number of analog channels, y is bytes per analog sample + * (7 bits per byte), zz is number of digital channels, and VV is two digit + * version# which must be 02 */ + if ((num_read < 16) || (strncmp(buf, "SRPICO,A", 8)) \ + || (buf[11] != 'D') || (buf[15] != '0') || (buf[16] != '2')) { + sr_err("ERROR: Bad response string %s %d", buf, num_read); + return NULL; + } + + a_size = buf[10] - '0'; + buf[10] = '\0'; /*Null to end the str for atois */ + buf[14] = '\0'; + num_a = atoi(&buf[8]); + num_d = atoi(&buf[12]); + + sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi->status = SR_ST_INACTIVE; + sdi->vendor = g_strdup("Raspberry Pi"); + sdi->model = g_strdup("PICO"); + sdi->version = g_strdup("00"); + sdi->conn = serial; + sdi->driver = &raspberrypi_pico_driver_info; + sdi->inst_type = SR_INST_SERIAL; + sdi->serial_num = g_strdup("N/A"); + + if (((num_a == 0) && (num_d == 0)) \ + || (num_a > MAX_ANALOG_CHANNELS) || (num_d > MAX_DIGITAL_CHANNELS) + || (a_size < 1) || (a_size > 4)) { + sr_err("ERROR: invalid channel config a %d d %d asz %d", + num_a, num_d, a_size); + return NULL; + } + + devc = g_malloc0(sizeof(struct dev_context)); + devc->a_size = a_size; + devc->num_a_channels = num_a; + devc->num_d_channels = num_d; + devc->a_chan_mask = ((1 << num_a) - 1); + devc->d_chan_mask = ((1 << num_d) - 1); + + /* The number of bytes that each digital sample in the buffers sent to the + * session. All logical channels are packed together, where a slice of N + * channels takes roundup(N/8) bytes. This never changes even if channels + * are disabled because PV expects disabled channels to still be accounted + * for in the packing */ + devc->dig_sample_bytes = ((devc->num_d_channels + 7) / 8); + /* These are the slice sizes of the data on the wire + * 1 7 bit field per byte */ + devc->bytes_per_slice = (devc->num_a_channels * devc->a_size); + + if (devc->num_d_channels > 0) { + /* logic sent in groups of 7*/ + devc->bytes_per_slice += (devc->num_d_channels + 6) / 7; + } + sr_dbg("num channels a %d d %d bps %d dsb %d", num_a, num_d, + devc->bytes_per_slice, devc->dig_sample_bytes); + + /* Each analog channel is its own group; digital are just channels; + * Grouping of channels is rather arbitrary as parameters like sample rate + * and number of samples apply to all channels. Analog channels do have a + * scale and offset, but that is applied automatically. */ + devc->analog_groups = g_malloc0(sizeof(struct sr_channel_group *) * + devc->num_a_channels); + for (i = 0; i < devc->num_a_channels; i++) { + channel_name = g_strdup_printf("A%d", i); + ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_name); + devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); + devc->analog_groups[i]->name = channel_name; + devc->analog_groups[i]->channels = g_slist_append(NULL, ch); + sdi->channel_groups = g_slist_append(sdi->channel_groups, + devc->analog_groups[i]); + } + + if (devc->num_d_channels > 0) { + for (i = 0; i < devc->num_d_channels; i++) { + /* Name digital channels starting at D2 to match pico board names */ + channel_name = g_strdup_printf("D%d", i + 2); + sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name); + g_free(channel_name); + } + } + + /* In large sample usages we get the call to receive with large transfers. + * Since the CDC serial implemenation can silenty lose data as it gets close + * to full, allocate storage for a half buffer which in a worst case + * scenario has 2x ratio of transmitted bytes to storage bytes. + * Note: The intent of making this buffer large is to prevent CDC serial + * buffer overflows. However, it is likely that if the host is running slow + * (i.e. it's a raspberry pi model 3) that it becomes compute bound and + * doesn't service CDC serial responses in time to not overflow the internal + * CDC buffers. Thus no serial buffer is large enough. + * But, it's only 32K... */ + devc->serial_buffer_size = 32000; + devc->buffer = NULL; + sr_dbg("Setting serial buffer size: %i.", devc->serial_buffer_size); + + devc->cbuf_wrptr = 0; + /* While slices are sent as a group of one sample across all channels, + * sigrok wants analog channel data sent as separate packets. Logical trace + * values are packed together. An RLE byte in normal mode can represent up + * to 1640 samples. In D4 an RLE byte can represent up to 640 samples. + * Rather than making the sample_buf_size 1640x the size of serial buffer, + * we require that the process loops push samples to the session as we get + * anywhere close to full. */ + + devc->sample_buf_size = devc->serial_buffer_size; + for (i = 0; i < devc->num_a_channels; i++) { + devc->a_data_bufs[i] = NULL; + devc->a_pretrig_bufs[i] = NULL; + } + devc->d_data_buf = NULL; + devc->sample_rate = 5000; + devc->capture_ratio = 10; + devc->rxstate = RX_IDLE; + /*Set an initial value as various code relies on an inital value. */ + devc->limit_samples = 1000; + + sdi->priv = devc; + + if (raspberrypi_pico_get_dev_cfg(sdi) != SR_OK) { + return NULL; + }; + + serial_close(serial); + return std_scan_complete(di, g_slist_append(NULL, sdi)); +} + +/* Note that on the initial driver load we pull all values into local storage. + * Thus gets can return local data, but sets have to issue commands to device. */ +static int config_set(uint32_t key, GVariant * data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + int ret; + (void) cg; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + ret = SR_OK; + + sr_dbg("Got config_set key %d \n", key); + switch (key) { + case SR_CONF_SAMPLERATE: + devc->sample_rate = g_variant_get_uint64(data); + sr_dbg("config_set sr %lu\n", devc->sample_rate); + break; + case SR_CONF_LIMIT_SAMPLES: + devc->limit_samples = g_variant_get_uint64(data); + sr_dbg("config_set slimit %" PRIu64 "\n", devc->limit_samples); + break; + case SR_CONF_CAPTURE_RATIO: + devc->capture_ratio = g_variant_get_uint64(data); + break; + + default: + sr_err("ERROR: config_set given undefined key %d\n", key); + ret = SR_ERR_NA; + } + + return ret; +} + +static int config_get(uint32_t key, GVariant ** data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + sr_dbg("config_get given key %d", key); + + (void) cg; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + switch (key) { + case SR_CONF_SAMPLERATE: + *data = g_variant_new_uint64(devc->sample_rate); + sr_spew("sample rate get of %" PRIu64 "", devc->sample_rate); + break; + case SR_CONF_CAPTURE_RATIO: + if (!sdi) + return SR_ERR; + devc = sdi->priv; + *data = g_variant_new_uint64(devc->capture_ratio); + break; + case SR_CONF_LIMIT_SAMPLES: + sr_spew("config_get limit_samples of %lu", devc->limit_samples); + *data = g_variant_new_uint64(devc->limit_samples); + break; + default: + sr_spew("unsupported config_get key %d", key); + return SR_ERR_NA; + } + return SR_OK; +} + +static int config_list(uint32_t key, GVariant ** data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + (void) cg; + + /* Scan or device options are the only ones that can be called without a + * defined instance */ + if ((key == SR_CONF_SCAN_OPTIONS) || (key == SR_CONF_DEVICE_OPTIONS)) { + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + } + + if (!sdi) { + sr_err("ERROR: Call to config list with null sdi"); + return SR_ERR_ARG; + } + + sr_dbg("Start config_list with key %X", key); + switch (key) { + case SR_CONF_SAMPLERATE: + *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates)); + break; + /* This must be set to get SW trigger support */ + case SR_CONF_TRIGGER_MATCH: + *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches)); + break; + case SR_CONF_LIMIT_SAMPLES: + /* Really this limit is up to the memory capacity of the host, + * and users that pick huge values deserve what they get. + * But setting this limit to prevent really crazy things. */ + *data = std_gvar_tuple_u64(1LL, 1000000000LL); + break; + default: + sr_dbg("Reached default statement of config_list"); + + return SR_ERR_NA; + } + + return SR_OK; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + struct sr_channel *ch; + struct sr_trigger *trigger; + char tmpstr[20]; + char buf[32]; + GSList *l; + int a_enabled = 0, d_enabled = 0, len; + serial = sdi->conn; + int i, num_read; + + devc = sdi->priv; + sr_dbg("Enter acq start"); + sr_dbg("dsbstart %d", devc->dig_sample_bytes); + + devc->buffer = g_malloc(devc->serial_buffer_size); + if (!(devc->buffer)) { + sr_err("ERROR: serial buffer malloc fail"); + return SR_ERR_MALLOC; + } + + /* Get device in idle state */ + if (serial_drain(serial) != SR_OK) { + sr_err("Initial Drain Failed"); + return SR_ERR; + } + + send_serial_char(serial, '*'); + if (serial_drain(serial) != SR_OK) { + sr_err("Second Drain Failed"); + return SR_ERR; + } + + for (l = sdi->channels; l; l = l->next) { + ch = l->data; + sr_dbg("c %d enabled %d name %s\n", ch->index, ch->enabled, ch->name); + + if (ch->name[0] == 'A') { + devc->a_chan_mask &= ~(1 << ch->index); + if (ch->enabled) { + devc->a_chan_mask |= (ch->enabled << ch->index); + a_enabled++; + } + } + if (ch->name[0] == 'D') { + devc->d_chan_mask &= ~(1 << ch->index); + if (ch->enabled) { + devc->d_chan_mask |= (ch->enabled << ch->index); + d_enabled++; + } + } + + sr_info("Channel enable masks D 0x%X A 0x%X", + devc->d_chan_mask, devc->a_chan_mask); + sprintf(tmpstr, "%c%d%d\n", ch->name[0], ch->enabled, ch->index); + if (send_serial_w_ack(serial, tmpstr) != SR_OK) { + sr_err("ERROR: Channel enable fail"); + return SR_ERR; + } + } + + /* Ensure data channels are continuous */ + int invalid = 0; + for (i = 0; i < 32; i++) { + if ((devc->d_chan_mask >> i) & 1) { + if (invalid) { + sr_err("Digital channel mask 0x%X not continous", + devc->d_chan_mask); + return SR_ERR; + } + } else + invalid = 1; + } + + /* Recalculate bytes_per_slice based on which analog channels are enabled */ + devc->bytes_per_slice = (a_enabled * devc->a_size); + + for (i = 0; i < devc->num_d_channels; i += 7) + if (((devc->d_chan_mask) >> i) & (0x7F)) + (devc->bytes_per_slice)++; + + if ((a_enabled == 0) && (d_enabled == 0)) { + sr_err("ERROR:No channels enabled"); + return SR_ERR; + } + + sr_dbg("bps %d\n", devc->bytes_per_slice); + + /* Apply sample rate limits; while earlier versions forced a lower sample + * rate, the PICO seems to allow ADC overclocking, and by not enforcing + * these limits it may support other devices. Thus call sr_err to get + * something into the device logs, but allowing it to progress. */ + if ((a_enabled == 3) && (devc->sample_rate > 160000)) + sr_err("WARN: 3 channel ADC sample rate above 160khz"); + if ((a_enabled == 2) && (devc->sample_rate > 250000)) + sr_err("WARN: 2 channel ADC sample rate above 250khz"); + if ((a_enabled == 1) && (devc->sample_rate > 500000)) + sr_err("WARN: 1 channel ADC sample rate above 500khz"); + + /* Depending on channel configs, rates below 5ksps are possible but such a + * low rate can easily stream and this eliminates a lot of special cases. */ + if (devc->sample_rate < 5000) { + sr_err("Sample rate override to min of 5ksps"); + devc->sample_rate = 5000; + } + + /* While PICO specs a max clock ~120-125Mhz, it does overclock in many cases + * so leaving a warning. */ + if (devc->sample_rate > 120000000) + sr_warn("WARN: Sample rate above 120Msps"); + + /* It may take a very large number of samples to notice, but if digital and + * analog are enabled and either PIO or ADC are fractional the samples will + * skew over time. 24Mhz is the max common divisor to the 120Mhz and 48Mhz + * ADC clock so force an integer divisor to 24Mhz. */ + if ((a_enabled > 0) && (d_enabled > 0)) { + if (24000000ULL % (devc->sample_rate)) { + uint32_t commondivint = 24000000ULL / (devc->sample_rate); + /* Always increment the divisor so that we go down in frequency to + * avoid max sample rate issues */ + commondivint++; + devc->sample_rate = 24000000ULL / commondivint; + /* Make sure the divisor increment didn't make us go too low. */ + if (devc->sample_rate < 5000) + devc->sample_rate = 50000; + sr_warn("WARN: Forcing common integer divisor sample rate of " \ + "%lu div %u", devc->sample_rate, commondivint); + } + } + + /* If we are only digital or only analog print a warning that the fractional + * divisors aren't a true PLL fractional feedback loop and thus could have + * sample to sample variation. These warnings of course assume that the + * device is programmed with the expected ratios but non PICO + * implementations, or PICO implementations that use different divisors + * could avoid. This generally won't be a problem because most of the + * sample_rate pulldown values are integer divisors. */ + if ((a_enabled > 0) && (48000000ULL % (devc->sample_rate * a_enabled))) + sr_warn("WARN: Non integer ADC divisor of 48Mhz clock for sample " \ + "rate %lu may cause sample to sample variability.", + devc->sample_rate); + if ((d_enabled > 0) && (120000000ULL % (devc->sample_rate))) + sr_warn("WARN: Non integer PIO divisor of 120Mhz for sample rate " \ + "%lu may cause sample to sample variability.", devc->sample_rate); + + sprintf(tmpstr, "L%" PRIu64 "\n", devc->limit_samples); + if (send_serial_w_ack(serial, tmpstr) != SR_OK) { + sr_err("Sample limit to device failed"); + return SR_ERR; + } + + /* To support future devices that may allow the analog scale/offset to + * change, call get_dev_cfg again to get new values */ + if (raspberrypi_pico_get_dev_cfg(sdi) != SR_OK) { + sr_err("get_dev_cfg failure on start"); + return SR_ERR; + } + + /* With all other params set, we use the final sample rate setting as an + * opportunity for the device to communicate any errors in configuration. + * A single "*" indicates success. + * A "*" with subsequent data is success, but allows for the device to + * print something to the error console without aborting. + * A non "*" in the first character blocks the start. */ + sprintf(tmpstr, "R%lu\n", devc->sample_rate); + num_read = send_serial_w_resp(serial, tmpstr, buf, 30); + buf[num_read] = 0; + if ((num_read > 1) && (buf[0] == '*')) + sr_dbg("Sample rate to device success with resp %s", buf); + else if (!((num_read == 1) && (buf[0] == '*'))) { + sr_err("Sample rate to device failed"); + if (num_read > 0) { + buf[num_read]=0; + sr_err("sample_rate error string %s",buf); + } + return SR_ERR; + } + + devc->sent_samples = 0; + devc->byte_cnt = 0; + devc->bytes_avail = 0; + devc->wrptr = 0; + devc->cbuf_wrptr = 0; + len = serial_read_blocking(serial, devc->buffer, devc->serial_buffer_size, + serial_timeout(serial, 4)); + + if (len > 0) { + sr_info("Pre-ARM drain had %d characters:", len); + devc->buffer[len] = 0; + sr_info("%s", devc->buffer); + } + + for (i = 0; i < devc->num_a_channels; i++) { + devc->a_data_bufs[i] = g_malloc(devc->sample_buf_size * sizeof(float)); + if (!(devc->a_data_bufs[i])) { + sr_err("ERROR: analog buffer malloc fail"); + return SR_ERR_MALLOC; + } + } + + if (devc->num_d_channels > 0) { + devc->d_data_buf = g_malloc(devc->sample_buf_size * + devc->dig_sample_bytes); + if (!(devc->d_data_buf)) { + sr_err("ERROR: logic buffer malloc fail"); + return SR_ERR_MALLOC; + } + } + + devc->pretrig_entries = (devc->capture_ratio * devc->limit_samples) / 100; + /* While the driver supports the passing of trigger info to the device + * it has been found that the sw overhead of supporting triggering and + * pretrigger buffer entries etc.. ends up slowing the cores down enough + * that the effective continous sample rate isn't much higher than that of + * sending untriggered samples across USB. Thus this code will remain but + * likely may not be used by the device, unless HW based triggers are + * implemented */ + if ((trigger = sr_session_trigger_get(sdi->session))) { + if (g_slist_length(trigger->stages) > 1) + return SR_ERR_NA; + + struct sr_trigger_stage *stage; + struct sr_trigger_match *match; + GSList *l; + stage = g_slist_nth_data(trigger->stages, 0); + if (!stage) + return SR_ERR_ARG; + for (l = stage->matches; l; l = l->next) { + match = l->data; + if (!match->match) + continue; + if (!match->channel->enabled) + continue; + int idx = match->channel->index; + int8_t val; + switch(match->match) { + case SR_TRIGGER_ZERO: + val = 0; break; + case SR_TRIGGER_ONE: + val = 1; break; + case SR_TRIGGER_RISING: + val = 2; break; + case SR_TRIGGER_FALLING: + val = 3; break; + case SR_TRIGGER_EDGE: + val = 4; break; + default: + val = -1; + } + sr_info("Trigger value idx %d match %d", idx, match->match); + /* Only set trigger on enabled channels */ + if ((val >= 0) && ((devc->d_chan_mask >> idx) & 1)) { + sprintf(&tmpstr[0], "t%d%02d\n", val, idx+2); + if (send_serial_w_ack(serial, tmpstr) != SR_OK) { + sr_err("Trigger cfg to device failed"); + return SR_ERR; + } + } + } + + sprintf(&tmpstr[0], "p%d\n", devc->pretrig_entries); + if (send_serial_w_ack(serial, tmpstr) != SR_OK) { + sr_err("Pretrig to device failed"); + return SR_ERR; + } + + devc->stl = soft_trigger_logic_new(sdi, trigger, devc->pretrig_entries); + if (!devc->stl) + return SR_ERR_MALLOC; + + devc->trigger_fired = FALSE; + if (devc->pretrig_entries > 0) { + sr_dbg("Allocating pretrig buffers size %d", devc->pretrig_entries); + for (i = 0; i < devc->num_a_channels; i++) { + if ((devc->a_chan_mask >> i) & 1) { + devc->a_pretrig_bufs[i] = g_malloc0(sizeof(float) * + devc->pretrig_entries); + if (!devc->a_pretrig_bufs[i]) { + sr_err("ERROR:Analog pretrigger buffer malloc " \ + "failure, disabling"); + devc->trigger_fired = TRUE; + } + } + } + } + + sr_info("Entering sw triggered mode"); + /* Post the receive before starting the device to ensure we are ready + * to receive data ASAP */ + serial_source_add(sdi->session, serial, G_IO_IN, 200, + raspberrypi_pico_receive, (void*)sdi); + + sprintf(tmpstr, "C\n"); + if (send_serial_str(serial, tmpstr) != SR_OK) + return SR_ERR; + + } else { + devc->trigger_fired = TRUE; + devc->pretrig_entries = 0; + sr_info("Entering fixed sample mode"); + serial_source_add(sdi->session, serial, G_IO_IN, 200, + raspberrypi_pico_receive, (void*)sdi); + + sprintf(tmpstr, "F\n"); + if (send_serial_str(serial, tmpstr) != SR_OK) + return SR_ERR; + } + + std_session_send_df_header(sdi); + + sr_dbg("dsbstartend %d", devc->dig_sample_bytes); + + if (devc->trigger_fired) + std_session_send_df_trigger(sdi); + + /* Keep this at the end as we don't want to be RX_ACTIVE unless everything + * is ok */ + devc->rxstate = RX_ACTIVE; + + return SR_OK; +} + +/* This function is called either by the protocol code if we reached all of the + * samples or an error condition, and also by the user clicking stop in + * pulseview. It must always be called for any acquistion that was started to + * free memory. */ +static int dev_acquisition_stop(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + int len; + devc = sdi->priv; + serial = sdi->conn; + + sr_dbg("At dev_acquisition_stop"); + + std_session_send_df_end(sdi); + + /* If we reached this while still active it is likely because the stop + * button was pushed in pulseview. That is generally some kind of error + * condition, so we don't try to check the bytenct */ + if (devc->rxstate == RX_ACTIVE) + sr_err("Reached dev_acquisition_stop in RX_ACTIVE"); + + if (devc->rxstate != RX_IDLE) { + sr_err("Sending plus to stop device stream"); + send_serial_char(serial, '+'); + } + + /* In case we get calls to receive force it to exit */ + devc->rxstate = RX_IDLE; + + /* Drain data from device so that it doesn't confuse subsequent commands */ + do { + len = serial_read_blocking(serial, devc->buffer, + devc->serial_buffer_size, 100); + if (len) + sr_err("Dropping %d device bytes", len); + } while (len > 0); + + if (devc->buffer) { + g_free(devc->buffer); + devc->buffer = NULL; + } + + for (int i = 0; i < devc->num_a_channels; i++) { + if (devc->a_data_bufs[i]) { + g_free(devc->a_data_bufs[i]); + devc->a_data_bufs[i] = NULL; + } + } + if (devc->d_data_buf) { + g_free(devc->d_data_buf); + devc->d_data_buf = NULL; + } + + for (int i = 0; i < devc->num_a_channels; i++) { + if (devc->a_pretrig_bufs[i]) + g_free(devc->a_pretrig_bufs[i]); + devc->a_pretrig_bufs[i] = NULL; + } + + serial = sdi->conn; + serial_source_remove(sdi->session, serial); + + return SR_OK; +} + +static struct sr_dev_driver raspberrypi_pico_driver_info = { + .name = "raspberrypi-pico", + .longname = "RaspberryPI PICO", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = std_dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = std_serial_dev_open, + .dev_close = std_serial_dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = dev_acquisition_stop, + .context = NULL, +}; + +SR_REGISTER_DEV_DRIVER(raspberrypi_pico_driver_info); diff --git a/src/hardware/raspberrypi-pico/protocol.c b/src/hardware/raspberrypi-pico/protocol.c new file mode 100644 index 00000000..d3132d8a --- /dev/null +++ b/src/hardware/raspberrypi-pico/protocol.c @@ -0,0 +1,864 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2022 Shawn Walker + * + * 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 . + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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;aa_size;a++){ + tmp32+=(devc->buffer[(devc->ser_rdptr)+a] - 0x80)<<(7*a); + } + devc->a_data_bufs[i][devc->cbuf_wrptr] = + ((float) tmp32 * devc->a_scale[i]) + + devc->a_offset[i]; + devc->a_last[i] = + devc->a_data_bufs[i][devc->cbuf_wrptr]; + sr_spew + ("AChan %d t32 %d value %f wrptr %d rdptr %d sc %f off %f", + i, tmp32, + devc-> + a_data_bufs[i][devc->cbuf_wrptr], + devc->cbuf_wrptr, devc->ser_rdptr, + devc->a_scale[i], devc->a_offset[i]); + devc->ser_rdptr+=devc->a_size; + } /*if channel enabled*/ + } /*for num_a_channels*/ + devc->cbuf_wrptr++; + }/*Not an RLE */ + /*RLEs can create a large number of samples relative to the incoming serial buffer + To prevent overflow of the sample data buffer we call process_group. + cbuf_wrptr and sample_buf_size are both in terms of slices + 2048 is more than needed for a max rle of 1640 on the next incoming character */ + if((devc->cbuf_wrptr +2048) > devc->sample_buf_size){ + sr_spew("Drain large buff %d %d\n\r",devc->cbuf_wrptr,devc->sample_buf_size); + process_group(sdi, devc, devc->cbuf_wrptr); + + } + }/* While another slice or RLE available */ + if (devc->cbuf_wrptr){ + process_group(sdi, devc, devc->cbuf_wrptr); + } + +} + +/* Send the processed analog values to the session */ +int send_analog(struct sr_dev_inst *sdi, struct dev_context *devc, + uint32_t num_samples, uint32_t offset) +{ + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; + struct sr_channel *ch; + uint32_t i; + float *fptr; + + sr_analog_init(&analog, &encoding, &meaning, &spec, ANALOG_DIGITS); + for (i = 0; i < devc->num_a_channels; i++) { + if ((devc->a_chan_mask >> i) & 1) { + ch = devc->analog_groups[i]->channels->data; + analog.meaning->channels = + g_slist_append(NULL, ch); + analog.num_samples = num_samples; + analog.data = (devc->a_data_bufs[i]) + offset; + fptr = analog.data; + sr_spew + ("send analog num %d offset %d first %f 2 %f", + num_samples, offset, *(devc->a_data_bufs[i]), + *fptr); + analog.meaning->mq = SR_MQ_VOLTAGE; + analog.meaning->unit = SR_UNIT_VOLT; + analog.meaning->mqflags = 0; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_session_send(sdi, &packet); + g_slist_free(analog.meaning->channels); + }/* if enabled */ + }/* for channels */ + return 0; + +} + +/*Send the ring buffer of pre-trigger analog samples. + The entire buffer is sent (as long as it filled once), but need send two payloads split at the + the writeptr */ +int send_analog_ring(struct sr_dev_inst *sdi, struct dev_context *devc, + uint32_t num_samples) +{ + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; + struct sr_channel *ch; + int i; + uint32_t num_pre, start_pre; + uint32_t num_post, start_post; + num_pre = + (num_samples >= + devc->pretrig_wr_ptr) ? devc->pretrig_wr_ptr : num_samples; + start_pre = devc->pretrig_wr_ptr - num_pre; + num_post = num_samples - num_pre; + start_post = devc->pretrig_entries - num_post; + sr_spew + ("send_analog ring wrptr %u ns %d npre %u spre %u npost %u spost %u", + devc->pretrig_wr_ptr, num_samples, num_pre, start_pre, + num_post, start_post); + float *fptr; + sr_analog_init(&analog, &encoding, &meaning, &spec, ANALOG_DIGITS); + for (i = 0; i < devc->num_a_channels; i++) { + if ((devc->a_chan_mask >> i) & 1) { + ch = devc->analog_groups[i]->channels->data; + analog.meaning->channels = + g_slist_append(NULL, ch); + analog.meaning->mq = SR_MQ_VOLTAGE; + analog.meaning->unit = SR_UNIT_VOLT; + analog.meaning->mqflags = 0; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + /*First send what is after the write pointer because it is oldest */ + if (num_post) { + analog.num_samples = num_post; + analog.data = + (devc->a_pretrig_bufs[i]) + start_post; + for (uint32_t j = 0; + j < analog.num_samples; j++) { + fptr = + analog.data + + (j * sizeof(float)); + } + sr_session_send(sdi, &packet); + } + if (num_pre) { + analog.num_samples = num_pre; + analog.data = + (devc->a_pretrig_bufs[i]) + start_pre; + sr_dbg("Sending A%d ring buffer newest ", + i); + for (uint32_t j = 0; + j < analog.num_samples; j++) { + fptr = + analog.data + + (j * sizeof(float)); + sr_spew("RNGDCW%d j %d %f %p", i, + j, *fptr, (void *) fptr); + } + sr_session_send(sdi, &packet); + } + g_slist_free(analog.meaning->channels); + sr_dbg("Sending A%d ring buffer done ", i); + }/*if enabled */ + }/* for channels */ + return 0; + +} + +/* Given a chunk of slices forward to trigger check or session as appropriate and update state + these could be real slices or those generated by rles */ +int process_group(struct sr_dev_inst *sdi, struct dev_context *devc, + uint32_t num_slices) +{ + int trigger_offset; + int pre_trigger_samples; + /* These are samples sent to session and are less than num_slices if we reach limit_samples */ + size_t num_samples; + struct sr_datafeed_logic logic; + struct sr_datafeed_packet packet; + int i; + size_t cbuf_wrptr_cpy; + cbuf_wrptr_cpy = devc->cbuf_wrptr; + /*regardless of whether we forward samples on or not (because we aren't triggered), always reset the + pointer into the device data buffers */ + devc->cbuf_wrptr = 0; + if (devc->trigger_fired) { /*send directly to session */ + if (devc->limit_samples && + num_slices > + devc->limit_samples - devc->sent_samples) { + num_samples = + devc->limit_samples - devc->sent_samples; + } else { + num_samples = num_slices; + } + if (num_samples > 0) { + sr_spew("Process_group sending %lu post trig samples dsb %d", + num_samples, devc->dig_sample_bytes); + if (devc->num_d_channels) { + packet.type = SR_DF_LOGIC; + packet.payload = &logic; + /* The number of bytes required to fit all of the channels */ + logic.unitsize = devc->dig_sample_bytes; + /* The total length of the array sent */ + logic.length = num_samples * logic.unitsize; + logic.data = devc->d_data_buf; + sr_session_send(sdi, &packet); + } + send_analog(sdi, devc, num_samples, 0); + } + + devc->sent_samples += num_samples; + return 0; + + } else { + /* Trigger_fired */ + size_t num_ring_samples; + size_t sptr, eptr; + size_t numtail, numwrap; + size_t srcptr; + /* The trigger_offset is -1 if no trigger is found, but if a trigger is + * found then trigger_offset is the offset into the data buffer sent to + * it. The pre_trigger_samples is the total number of samples before + * the trigger, but limited to the size of the ring buffer set by the + * capture_ratio. So the pre_trigger_samples can include both the new + * samples and the ring buffer, but trigger_offset is only in relation + * to the new samples */ + trigger_offset = soft_trigger_logic_check(devc->stl, devc->d_data_buf, + num_slices * devc->dig_sample_bytes, &pre_trigger_samples); + + /* A trigger offset >=0 indicates a trigger was seen. The stl will issue + * the trigger to the session and will forward all pre trigger logic + * samples, but we must send any post trigger logic and all pre and post + * trigger analog signals */ + if (trigger_offset > -1) { + devc->trigger_fired = TRUE; + devc->sent_samples += pre_trigger_samples; + packet.type = SR_DF_LOGIC; + packet.payload = &logic; + num_samples = num_slices - trigger_offset; + + /* Since we are in continuous mode for SW triggers it is possible to + * get more samples than limit_samples, so once the trigger fires, + * make sure we don't get beyond limit samples. At this point + * sent_samples should be equal to pre_trigger_samples (just added + * above) because without being triggered we'd never increment + * sent_samples. + * This number is the number of post trigger logic samples to send + * to the session, the number of floats is larger because of the + * analog ring buffer we track. */ + if (devc->limit_samples && \ + (num_samples > devc->limit_samples - devc->sent_samples)) + num_samples = devc->limit_samples - devc->sent_samples; + + /* The soft trigger logic issues the trigger and sends packets for + * all logic data that was pretrigger so only send what is left */ + if (num_samples > 0) { + sr_dbg("Sending post trigger logical remainder of %lu", + num_samples); + logic.length = num_samples * devc->dig_sample_bytes; + logic.unitsize = devc->dig_sample_bytes; + logic.data = devc->d_data_buf + + (trigger_offset * devc->dig_sample_bytes); + devc->sent_samples += num_samples; + sr_session_send(sdi, &packet); + } + + size_t new_start, new_end, new_samples, ring_samples; + /* Figure out the analog data to send. We might need to send: + * -some or all of incoming data + * -all of incoming data and some of ring buffer + * -all of incoming data and all of ring buffer (and still might be + * short) + * We don't need to compare to limit_samples because pretrig_entries + * can never be more than limit_samples trigger offset indicatese + * where in the new samples the trigger was, but we need to go back + * pretrig_entries before it */ + new_start = (trigger_offset > (int)devc->pretrig_entries) ? + trigger_offset - devc->pretrig_entries : 0; + + /* Note that we might not have gotten all the pre triggerstore data + * we were looking for. In such a case the sw trigger logic seems to + * fill up to the limit_samples and thus the ratio is off, but we + * get the full number of samples. + * The number of entries in the ring buffer is + * pre_trigger_samples-trigger_offset so subtract that from limit + * samples as a threshold */ + new_end = MIN(num_slices - 1, + devc->limit_samples - (pre_trigger_samples - trigger_offset) - 1); + + /* This includes pre and post trigger storage. */ + new_samples = new_end - new_start + 1; + + /* pre_trigger_samples can never be greater than trigger_offset by + * more than the ring buffer depth (pretrig entries) */ + ring_samples = (pre_trigger_samples > trigger_offset) ? + pre_trigger_samples - trigger_offset : 0; + sr_spew("SW trigger float info newstart %zu new_end %zu " \ + "new_samp %zu ring_samp %zu", + new_start, new_end, new_samples, ring_samples); + + if (ring_samples > 0) + send_analog_ring(sdi, devc, ring_samples); + if (new_samples) + send_analog(sdi, devc, new_samples, new_start); + } else { + /* We didn't trigger but need to copy to ring buffer */ + if ((devc->a_chan_mask) && (devc->pretrig_entries)) { + /*The incoming data buffer could be much larger than the ring + * buffer, so never copy more than the size of the ring buffer */ + num_ring_samples = num_slices > devc->pretrig_entries ? + devc->pretrig_entries : num_slices; + sptr = devc->pretrig_wr_ptr; /* Starting pointer to copy to */ + + /* endptr can't go past the end */ + eptr = (sptr + num_ring_samples) >= devc-> pretrig_entries ? + devc->pretrig_entries - 1 : sptr + num_ring_samples - 1; + + /* Number of samples to copy to the tail of ring buffer without + * wrapping */ + numtail = (eptr - sptr) + 1; + + numwrap = (num_ring_samples > numtail) ? + num_ring_samples - numtail : 0; + + /* cbuf_wrptr points to where the next write should go, + * not the actual write data */ + srcptr = cbuf_wrptr_cpy - num_ring_samples; + sr_spew("RNG num %zu sptr %zu eptr %zu ", + num_ring_samples, sptr, eptr); + + /* Copy tail */ + for (i = 0; i < devc->num_a_channels; i++) + if ((devc->a_chan_mask >> i) & 1) + for (uint32_t j = 0; j < numtail; j++) + devc->a_pretrig_bufs[i][sptr + j] = + devc->a_data_bufs[i][srcptr + j]; + + /* Copy wrap */ + srcptr += numtail; + for (i = 0; i < devc->num_a_channels; i++) + if ((devc->a_chan_mask >> i) & 1) + for (uint32_t j = 0; j < numwrap; j++) + devc->a_pretrig_bufs[i][j] = + devc->a_data_bufs[i][srcptr + j]; + + devc->pretrig_wr_ptr = (numwrap) ? + numwrap : (eptr + 1) % devc->pretrig_entries; + } + } + } + + return 0; +} + +/* Duplicate previous sample values + * This function relies on the caller to ensure d_data_buf has samples to handle + * the full value of the rle */ +void rle_memset(struct dev_context *devc, uint32_t num_slices) +{ + uint32_t j, k, didx; + sr_spew("rle_memset vals 0x%X, 0x%X, 0x%X slices %d dsb %d", + devc->d_last[0], devc->d_last[1], devc->d_last[2], + num_slices, devc->dig_sample_bytes); + + /* Even if a channel is disabled, PV expects the same location and size for + * the enabled channels as if the channel were enabled. */ + for (j = 0; j < num_slices; j++) { + didx = devc->cbuf_wrptr * devc->dig_sample_bytes; + for (k = 0; k < devc->dig_sample_bytes; k++) + devc->d_data_buf[didx + k] = devc->d_last[k]; + /* cbuf_wrptr always counts slices/samples (and not the bytes in the + * buffer) regardless of mode */ + devc->cbuf_wrptr++; + } +} + +/* This callback function is mapped from api.c with serial_source_add and is + * created after a capture has been setup and is responsible for querying the + * device trigger status, downloading data and forwarding packets */ +SR_PRIV int raspberrypi_pico_receive(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + int len; + uint32_t i, bytes_rem, residual_bytes; + (void) fd; + + if (!(sdi = cb_data)) + return TRUE; + + if (!(devc = sdi->priv)) + return TRUE; + + if (devc->rxstate != RX_ACTIVE) { + /* This condition is normal operation and expected to happen + * but printed as information */ + sr_dbg("Reached non active state in receive %d", devc->rxstate); + /* Don't return - we may be waiting for a final bytecnt */ + } + + if (devc->rxstate == RX_IDLE) { + /* This is the normal end condition where we do one more receive + * to make sure we get the full byte_cnt */ + sr_dbg("Reached idle state in receive %d", devc->rxstate); + return FALSE; + } + + serial = sdi->conn; + + /* Return true if it is some kind of event we don't handle */ + if (!(revents == G_IO_IN || revents == 0)) + return TRUE; + + /* Fill the buffer, note the end may have partial slices */ + bytes_rem = devc->serial_buffer_size - devc->wrptr; + + /* Read one byte less so that we can null it and print as a string. Do a + * small 10ms timeout, if we get nothing, we'll always come back again */ + len = serial_read_blocking(serial, &(devc->buffer[devc->wrptr]), + bytes_rem - 1, 10); + sr_spew("Entry wrptr %u bytes_rem %u len %d", devc->wrptr, bytes_rem, len); + + if (len > 0) { + devc->buffer[devc->wrptr + len] = 0; + /* Add the "#" so that spaces in the string are clearly seen */ + sr_dbg("rx string %s#", devc->buffer); + devc->bytes_avail = (devc->wrptr + len); + sr_spew("rx len %d bytes_avail %ul sent_samples %ul wrptr %u", + len, devc->bytes_avail, devc->sent_samples, devc->wrptr); + } else { + if (len == 0) { + return TRUE; + } else { + sr_err("ERROR: Negative serial read code %d", len); + sdi->driver->dev_acquisition_stop(sdi); + return FALSE; + } + } + + /* Process the serial read data */ + devc->ser_rdptr = 0; + if (devc->rxstate == RX_ACTIVE) { + if ((devc->a_chan_mask == 0) \ + && ((devc->d_chan_mask & 0xFFFFFFF0) == 0)) + process_D4(sdi, devc); + else + process_slice(sdi, devc); + } + + /* process_slice/process_D4 increment ser_rdptr as bytes of the serial + * buffer are used. But they may not use all of it, and thus the residual + * unused bytes are shifted to the start of the buffer for the next call. */ + residual_bytes = devc->bytes_avail - devc->ser_rdptr; + if (residual_bytes) { + for (i = 0; i < residual_bytes; i++) + devc->buffer[i] = devc->buffer[i + devc->ser_rdptr]; + + devc->ser_rdptr = 0; + devc->wrptr = residual_bytes; + sr_spew("Residual shift rdptr %u wrptr %u", devc->ser_rdptr, devc->wrptr); + } else { + /* If there are no residuals shifted then zero the wrptr since all data + * is used */ + devc->wrptr = 0; + } + + /* ABORT ends immediately */ + if (devc->rxstate == RX_ABORT) { + sr_err("Ending receive on abort"); + sdi->driver->dev_acquisition_stop(sdi); + return FALSE; + } + + /* If stopped, look for final '+' indicating the full byte_cnt is received */ + if (devc->rxstate == RX_STOPPED) { + sr_dbg("Stopped, checking byte_cnt"); + if (devc->buffer[0] != '$') { + /* If this happens it means that we got a set of data that was not + * processed as whole groups of slice bytes. So either we lost data + * or are not parsing it correctly. */ + sr_err("ERROR: Stop marker should be byte zero"); + devc->rxstate = RX_ABORT; + sdi->driver->dev_acquisition_stop(sdi); + return FALSE; + } + + for (i = 1; i < devc->wrptr; i++) { + if (devc->buffer[i] == '+') { + devc->buffer[i] = 0; + uint64_t rxbytecnt; + rxbytecnt = atol((char*)&(devc->buffer[1])); + sr_dbg("Byte_cnt check device cnt %lu host cnt %lu", + rxbytecnt, devc->byte_cnt); + if (rxbytecnt != devc->byte_cnt) + sr_err("ERROR: received %lu and counted %lu bytecnts " \ + "don't match, data may be lost", + rxbytecnt, devc->byte_cnt); + + /* Since we got the bytecnt we know the device is done + * sending data */ + devc->rxstate = RX_IDLE; + + /* We must always call acquisition_stop on all completed runs */ + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + } + + /*It's possible we need one more serial transfer to get the byte_cnt, + * so print that here */ + sr_dbg("Haven't seen byte_cnt + yet"); + } + /* If at the sample limit, send a "+" in case we are in continuous mode and + * need to stop the device. Not that even in non continous mode there might + * be cases where get an extra sample or two... */ + + if ((devc->sent_samples >= devc->limit_samples) \ + && (devc->rxstate == RX_ACTIVE)) { + sr_dbg("Ending: sent %u of limit %lu samples byte_cnt %lu", + devc->sent_samples, devc->limit_samples, devc->byte_cnt); + send_serial_char(serial, '+'); + } + + sr_spew("Receive function done: sent %u limit %lu wrptr %u len %d", + devc->sent_samples, devc->limit_samples, devc->wrptr, len); + + return TRUE; +} + +/* Read device specific information from the device */ +SR_PRIV int raspberrypi_pico_get_dev_cfg(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + char *cmd, response[20]; + gchar **tokens; + unsigned int i; + int ret, num_tokens; + + devc = sdi->priv; + sr_dbg("At get_dev_cfg"); + serial = sdi->conn; + for (i = 0; i < devc->num_a_channels; i++) { + cmd = g_strdup_printf("a%d\n", i); + ret = send_serial_w_resp(serial, cmd, response, 20); + if (ret <= 0) { + sr_err("ERROR: No response from device for analog channel query"); + return SR_ERR; + } + response[ret] = 0; + tokens = NULL; + tokens = g_strsplit(response, "x", 0); + num_tokens = g_strv_length(tokens); + + if (num_tokens == 2) { + devc->a_scale[i] = ((float) atoi(tokens[0])) / 1000000.0; + devc->a_offset[i] = ((float) atoi(tokens[1])) / 1000000.0; + sr_dbg("A%d scale %f offset %f response #%s# tokens #%s# #%s#", + i, devc->a_scale[i], devc->a_offset[i], + response, tokens[0], tokens[1]); + } else { + sr_err("ERROR: Ascale read c%d got unparseable response %s tokens %d", + i, response, num_tokens); + /* Force a legal fixed value assuming a 3.3V scale */ + devc->a_scale[i] = 0.0257; + devc->a_offset[i] = 0.0; + } + + g_strfreev(tokens); + g_free(cmd); + } + + return SR_OK; +} diff --git a/src/hardware/raspberrypi-pico/protocol.h b/src/hardware/raspberrypi-pico/protocol.h new file mode 100644 index 00000000..45a8a2a3 --- /dev/null +++ b/src/hardware/raspberrypi-pico/protocol.h @@ -0,0 +1,162 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2022 Shawn Walker + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_RASPBERRYPI_PICO_PROTOCOL_H +#define LIBSIGROK_HARDWARE_RASPBERRYPI_PICO_PROTOCOL_H + +#include +#include +#include +#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 diff --git a/src/hardware/rdtech-dps/api.c b/src/hardware/rdtech-dps/api.c index 1c67ea49..c55b5896 100644 --- a/src/hardware/rdtech-dps/api.c +++ b/src/hardware/rdtech-dps/api.c @@ -19,9 +19,8 @@ * along with this program. If not, see . */ -#include +#include "config.h" -#include #include #include "protocol.h" @@ -52,18 +51,95 @@ static const uint32_t devopts[] = { SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, }; -/* Model ID, model name, max current/voltage/power, current/voltage digits. */ +static const uint32_t devopts_w_range[] = { + SR_CONF_CONTINUOUS, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_VOLTAGE | SR_CONF_GET, + SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_CURRENT | SR_CONF_GET, + SR_CONF_CURRENT_LIMIT | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, + SR_CONF_REGULATION | SR_CONF_GET, + SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET, + SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET, + SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, + SR_CONF_RANGE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; + +/* Range name, max current/voltage/power, current/voltage digits. */ +static const struct rdtech_dps_range ranges_dps3005[] = { + { "5A", 5, 30, 160, 3, 2 } +}; + +static const struct rdtech_dps_range ranges_dps5005[] = { + { "5A", 5, 50, 250, 3, 2 } +}; + +static const struct rdtech_dps_range ranges_dps5015[] = { + { "15A", 15, 50, 750, 2, 2 } +}; + +static const struct rdtech_dps_range ranges_dps5020[] = { + { "20A", 20, 50, 1000, 2, 2 } +}; + +static const struct rdtech_dps_range ranges_dps8005[] = { + { "5A", 5, 80, 408, 3, 2 } +}; + +static const struct rdtech_dps_range ranges_rd6006[] = { + { "6A", 6, 60, 360, 3, 2 } +}; + +static const struct rdtech_dps_range ranges_rd6006p[] = { + { "6A", 6, 60, 360, 4, 3 } +}; + +static const struct rdtech_dps_range ranges_rd6012[] = { + { "12A", 12, 60, 720, 2, 2 } +}; + +/* + * RD6012P supports multiple current ranges with differing resolution. + * Up to 6A with 4 digits (when RTU reg 20 == 0), up to 12A with 3 digits + * (when RTU reg 20 == 1). + */ +static const struct rdtech_dps_range ranges_rd6012p[] = { + { "6A", 6, 60, 360, 4, 3 }, + { "12A", 12, 60, 720, 3, 3 } +}; + +static const struct rdtech_dps_range ranges_rd6018[] = { + { "18A", 18, 60, 1080, 2, 2 } +}; + +static const struct rdtech_dps_range ranges_rd6024[] = { + { "24A", 24, 60, 1440, 2, 2 } +}; + +/* Model ID, model name, model dependent ranges. */ static const struct rdtech_dps_model supported_models[] = { - { MODEL_DPS, 3005, "DPS3005", 5, 30, 160, 3, 2 }, - { MODEL_DPS, 5005, "DPS5005", 5, 50, 250, 3, 2 }, - { MODEL_DPS, 5205, "DPH5005", 5, 50, 250, 3, 2 }, - { MODEL_DPS, 5015, "DPS5015", 15, 50, 750, 2, 2 }, - { MODEL_DPS, 5020, "DPS5020", 20, 50, 1000, 2, 2 }, - { MODEL_DPS, 8005, "DPS8005", 5, 80, 408, 3, 2 }, - /* All RD specs taken from the 2020.12.2 instruction manual. */ - { MODEL_RD , 6006, "RD6006" , 6, 60, 360, 3, 2 }, - { MODEL_RD , 6012, "RD6012" , 12, 60, 720, 2, 2 }, - { MODEL_RD , 6018, "RD6018" , 18, 60, 1080, 2, 2 }, + { MODEL_DPS, 3005, "DPS3005", ARRAY_AND_SIZE(ranges_dps3005), }, + { MODEL_DPS, 5005, "DPS5005", ARRAY_AND_SIZE(ranges_dps5005), }, + { MODEL_DPS, 5205, "DPH5005", ARRAY_AND_SIZE(ranges_dps5005), }, + { MODEL_DPS, 5015, "DPS5015", ARRAY_AND_SIZE(ranges_dps5015), }, + { MODEL_DPS, 5020, "DPS5020", ARRAY_AND_SIZE(ranges_dps5020), }, + { MODEL_DPS, 8005, "DPS8005", ARRAY_AND_SIZE(ranges_dps8005), }, + /* + * Specs for models RD60nn taken from the 2020.12.2 instruction manual, + * specs for RD6006P from the 2021.2.26 (english) manual, + * specs for RD6012P from the 2021.10.26 (english) manual, + * and specs for RD6024P from the 2021.1.7 (english) manual. + */ + { MODEL_RD, 60061, "RD6006" , ARRAY_AND_SIZE(ranges_rd6006), }, + { MODEL_RD, 60062, "RD6006" , ARRAY_AND_SIZE(ranges_rd6006), }, + { MODEL_RD, 60065, "RD6006P", ARRAY_AND_SIZE(ranges_rd6006p), }, + { MODEL_RD, 60121, "RD6012" , ARRAY_AND_SIZE(ranges_rd6012), }, + { MODEL_RD, 60125, "RD6012P", ARRAY_AND_SIZE(ranges_rd6012p), }, + { MODEL_RD, 60181, "RD6018" , ARRAY_AND_SIZE(ranges_rd6018), }, + { MODEL_RD, 60241, "RD6024" , ARRAY_AND_SIZE(ranges_rd6024), }, }; static struct sr_dev_driver rdtech_dps_driver_info; @@ -137,12 +213,12 @@ static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus, sr_channel_new(sdi, 2, SR_CHANNEL_ANALOG, TRUE, "P"); devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; sr_sw_limits_init(&devc->limits); devc->model = model; - devc->current_multiplier = pow(10.0, model->current_digits); - devc->voltage_multiplier = pow(10.0, model->voltage_digits); - - sdi->priv = devc; + ret = rdtech_dps_update_range(sdi); + if (ret != SR_OK) + return NULL; return sdi; } @@ -266,7 +342,7 @@ static int config_get(uint32_t key, GVariant **data, struct dev_context *devc; struct rdtech_dps_state state; int ret; - const char *cc_text; + const char *cc_text, *range_text; (void)cg; @@ -375,6 +451,15 @@ static int config_get(uint32_t key, GVariant **data, return SR_ERR_DATA; *data = g_variant_new_double(state.ocp_threshold); break; + case SR_CONF_RANGE: + ret = rdtech_dps_get_state(sdi, &state, ST_CTX_CONFIG); + if (ret != SR_OK) + return ret; + if (!(state.mask & STATE_RANGE)) + return SR_ERR_DATA; + range_text = devc->model->ranges[state.range].range_str; + *data = g_variant_new_string(range_text); + break; default: return SR_ERR_NA; } @@ -387,6 +472,9 @@ static int config_set(uint32_t key, GVariant *data, { struct dev_context *devc; struct rdtech_dps_state state; + const char *range_str; + const struct rdtech_dps_range *range; + size_t i; (void)cg; @@ -417,6 +505,17 @@ static int config_set(uint32_t key, GVariant *data, state.ocp_threshold = g_variant_get_double(data); state.mask |= STATE_OCP_THRESHOLD; return rdtech_dps_set_state(sdi, &state); + case SR_CONF_RANGE: + range_str = g_variant_get_string(data, NULL); + for (i = 0; i < devc->model->n_ranges; i++) { + range = &devc->model->ranges[i]; + if (g_strcmp0(range->range_str, range_str) != 0) + continue; + state.range = i; + state.mask |= STATE_RANGE; + return rdtech_dps_set_state(sdi, &state); + } + return SR_ERR_NA; default: return SR_ERR_NA; } @@ -428,21 +527,43 @@ static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; + const struct rdtech_dps_range *range; + GVariantBuilder gvb; + size_t i; + const char *s; devc = (sdi) ? sdi->priv : NULL; switch (key) { case SR_CONF_SCAN_OPTIONS: case SR_CONF_DEVICE_OPTIONS: - return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + if (devc && devc->model->n_ranges > 1) { + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts_w_range); + } else { + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); + } case SR_CONF_VOLTAGE_TARGET: - *data = std_gvar_min_max_step(0.0, devc->model->max_voltage, + rdtech_dps_update_range(sdi); + range = &devc->model->ranges[devc->curr_range]; + *data = std_gvar_min_max_step(0.0, range->max_voltage, 1 / devc->voltage_multiplier); break; case SR_CONF_CURRENT_LIMIT: - *data = std_gvar_min_max_step(0.0, devc->model->max_current, + rdtech_dps_update_range(sdi); + range = &devc->model->ranges[devc->curr_range]; + *data = std_gvar_min_max_step(0.0, range->max_current, 1 / devc->current_multiplier); break; + case SR_CONF_RANGE: + g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY); + for (i = 0; i < devc->model->n_ranges; i++) { + s = devc->model->ranges[i].range_str; + g_variant_builder_add(&gvb, "s", s); + } + *data = g_variant_builder_end(&gvb); + break; default: return SR_ERR_NA; } @@ -459,16 +580,22 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) modbus = sdi->conn; devc = sdi->priv; + devc->acquisition_started = TRUE; + /* Seed internal state from current data. */ ret = rdtech_dps_seed_receive(sdi); - if (ret != SR_OK) + if (ret != SR_OK) { + devc->acquisition_started = FALSE; return ret; + } /* Register the periodic data reception callback. */ ret = sr_modbus_source_add(sdi->session, modbus, G_IO_IN, 10, rdtech_dps_receive_data, (void *)sdi); - if (ret != SR_OK) + if (ret != SR_OK) { + devc->acquisition_started = FALSE; return ret; + } sr_sw_limits_acquisition_start(&devc->limits); std_session_send_df_header(sdi); @@ -478,9 +605,13 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) static int dev_acquisition_stop(struct sr_dev_inst *sdi) { + struct dev_context *devc; struct sr_modbus_dev_inst *modbus; + devc = sdi->priv; + std_session_send_df_end(sdi); + devc->acquisition_started = FALSE; modbus = sdi->conn; sr_modbus_source_remove(sdi->session, modbus); diff --git a/src/hardware/rdtech-dps/protocol.c b/src/hardware/rdtech-dps/protocol.c index 83090d86..dfcffbb1 100644 --- a/src/hardware/rdtech-dps/protocol.c +++ b/src/hardware/rdtech-dps/protocol.c @@ -19,12 +19,14 @@ * along with this program. If not, see . */ -#include +#include "config.h" +#include #include #include "protocol.h" +/* These are the Modbus RTU registers for the DPS family of devices. */ enum rdtech_dps_register { REG_DPS_USET = 0x00, /* Mirror of 0x50 */ REG_DPS_ISET = 0x01, /* Mirror of 0x51 */ @@ -69,6 +71,11 @@ enum rdtech_dps_regulation_mode { MODE_CC = 1, }; +/* + * These are the Modbus RTU registers for the RD family of devices. + * Some registers are device specific, like REG_RD_RANGE of RD6012P + * which could be battery related in other devices. + */ enum rdtech_rd_register { REG_RD_MODEL = 0, /* u16 */ REG_RD_SERIAL = 1, /* u32 */ @@ -85,6 +92,8 @@ enum rdtech_rd_register { REG_RD_PROTECT = 16, /* u16 */ REG_RD_REGULATION = 17, /* u16 */ REG_RD_ENABLE = 18, /* u16 */ + REG_RD_PRESET = 19, /* u16 */ + REG_RD_RANGE = 20, /* u16 */ /* * Battery at 32 == 0x20 pp: * Mode, voltage, temperature, capacity, energy. @@ -200,7 +209,7 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, if (ret != SR_OK) return ret; rdptr = (void *)registers; - *model = read_u16be_inc(&rdptr) / 10; + *model = read_u16be_inc(&rdptr); *serno = read_u32be_inc(&rdptr); *version = read_u16be_inc(&rdptr); sr_info("RDTech RD model: %u version: %u, serno %u", @@ -213,6 +222,49 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, /* UNREACH */ } +SR_PRIV void rdtech_dps_update_multipliers(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + const struct rdtech_dps_range *range; + + devc = sdi->priv; + range = &devc->model->ranges[devc->curr_range]; + devc->current_multiplier = pow(10.0, range->current_digits); + devc->voltage_multiplier = pow(10.0, range->voltage_digits); +} + +/* + * Determine range of connected device. Don't do anything once + * acquisition has started (since the range will then be tracked). + */ +SR_PRIV int rdtech_dps_update_range(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + uint16_t range; + int ret; + + devc = sdi->priv; + + /* + * Only update range if there are multiple ranges and data + * acquisition hasn't started. + */ + if (devc->model->n_ranges <= 1 || devc->acquisition_started) + return SR_OK; + if (devc->model->model_type != MODEL_RD) + return SR_ERR; + + ret = rdtech_dps_read_holding_registers(sdi->conn, + REG_RD_RANGE, 1, &range); + if (ret != SR_OK) + return ret; + range = range ? 1 : 0; + devc->curr_range = range; + rdtech_dps_update_multipliers(sdi); + + return SR_OK; +} + /* Send a measured value to the session feed. */ static int send_value(const struct sr_dev_inst *sdi, struct sr_channel *ch, float value, @@ -259,13 +311,15 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, struct dev_context *devc; struct sr_modbus_dev_inst *modbus; gboolean get_config, get_init_state, get_curr_meas; - uint16_t registers[12]; + uint16_t registers[14]; int ret; const uint8_t *rdptr; uint16_t uset_raw, iset_raw, uout_raw, iout_raw, power_raw; uint16_t reg_val, reg_state, out_state, ovpset_raw, ocpset_raw; gboolean is_lock, is_out_enabled, is_reg_cc; gboolean uses_ovp, uses_ocp; + gboolean have_range; + uint16_t range; float volt_target, curr_limit; float ovp_threshold, ocp_threshold; float curr_voltage, curr_current, curr_power; @@ -308,6 +362,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, (void)get_init_state; (void)get_curr_meas; + have_range = devc->model->n_ranges > 1; + if (!have_range) + range = 0; + switch (devc->model->model_type) { case MODEL_DPS: /* @@ -320,7 +378,8 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, */ g_mutex_lock(&devc->rw_mutex); ret = rdtech_dps_read_holding_registers(modbus, - REG_DPS_USET, 10, registers); + REG_DPS_USET, REG_DPS_ENABLE - REG_DPS_USET + 1, + registers); g_mutex_unlock(&devc->rw_mutex); if (ret != SR_OK) return ret; @@ -369,7 +428,11 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, /* Retrieve a set of adjacent registers. */ g_mutex_lock(&devc->rw_mutex); ret = rdtech_dps_read_holding_registers(modbus, - REG_RD_VOLT_TGT, 11, registers); + REG_RD_VOLT_TGT, + devc->model->n_ranges > 1 + ? REG_RD_RANGE - REG_RD_VOLT_TGT + 1 + : REG_RD_ENABLE - REG_RD_VOLT_TGT + 1, + registers); g_mutex_unlock(&devc->rw_mutex); if (ret != SR_OK) return ret; @@ -396,6 +459,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, is_reg_cc = reg_state == MODE_CC; out_state = read_u16be_inc(&rdptr); /* ENABLE */ is_out_enabled = out_state != 0; + if (have_range) { + (void)read_u16be_inc(&rdptr); /* PRESET */ + range = read_u16be_inc(&rdptr) ? 1 : 0; /* RANGE */ + } /* Retrieve a set of adjacent registers. */ g_mutex_lock(&devc->rw_mutex); @@ -456,6 +523,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, state->mask |= STATE_CURRENT; state->power = curr_power; state->mask |= STATE_POWER; + if (have_range) { + state->range = range; + state->mask |= STATE_RANGE; + } return SR_OK; } @@ -575,6 +646,42 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi, return SR_ERR_ARG; } } + if (state->mask & STATE_RANGE) { + reg_value = state->range; + switch (devc->model->model_type) { + case MODEL_DPS: + /* DPS models don't support current ranges at all. */ + if (reg_value > 0) + return SR_ERR_ARG; + break; + case MODEL_RD: + /* + * Reject unsupported range indices. + * Need not set the range when the device only + * supports a single fixed range. + */ + if (reg_value >= devc->model->n_ranges) + return SR_ERR_NA; + if (devc->model->n_ranges <= 1) + return SR_OK; + ret = rdtech_rd_set_reg(sdi, REG_RD_RANGE, reg_value); + if (ret != SR_OK) + return ret; + /* + * Immediately update internal state outside of + * an acquisition. Assume that in-acquisition + * activity will update internal state. This is + * essential for meta package emission. + */ + if (!devc->acquisition_started) { + devc->curr_range = reg_value; + rdtech_dps_update_multipliers(sdi); + } + break; + default: + return SR_ERR_ARG; + } + } return SR_OK; } @@ -602,6 +709,10 @@ SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi) devc->curr_cc_state = state.regulation_cc; if (state.mask & STATE_OUTPUT_ENABLED) devc->curr_out_state = state.output_enabled; + if (state.mask & STATE_RANGE) { + devc->curr_range = state.range; + rdtech_dps_update_multipliers(sdi); + } return SR_OK; } @@ -614,7 +725,7 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) struct rdtech_dps_state state; int ret; struct sr_channel *ch; - const char *regulation_text; + const char *regulation_text, *range_text; (void)fd; (void)revents; @@ -635,11 +746,11 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) ch = g_slist_nth_data(sdi->channels, 0); send_value(sdi, ch, state.voltage, SR_MQ_VOLTAGE, SR_MQFLAG_DC, SR_UNIT_VOLT, - devc->model->voltage_digits); + devc->model->ranges[devc->curr_range].voltage_digits); ch = g_slist_nth_data(sdi->channels, 1); send_value(sdi, ch, state.current, SR_MQ_CURRENT, SR_MQFLAG_DC, SR_UNIT_AMPERE, - devc->model->current_digits); + devc->model->ranges[devc->curr_range].current_digits); ch = g_slist_nth_data(sdi->channels, 2); send_value(sdi, ch, state.power, SR_MQ_POWER, 0, SR_UNIT_WATT, 2); @@ -669,6 +780,13 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) g_variant_new_boolean(state.output_enabled)); devc->curr_out_state = state.output_enabled; } + if (devc->curr_range != state.range) { + range_text = devc->model->ranges[state.range].range_str; + (void)sr_session_send_meta(sdi, SR_CONF_RANGE, + g_variant_new_string(range_text)); + devc->curr_range = state.range; + rdtech_dps_update_multipliers(sdi); + } /* Check optional acquisition limits. */ sr_sw_limits_update_samples_read(&devc->limits, 1); diff --git a/src/hardware/rdtech-dps/protocol.h b/src/hardware/rdtech-dps/protocol.h index 456e2041..1170dbf9 100644 --- a/src/hardware/rdtech-dps/protocol.h +++ b/src/hardware/rdtech-dps/protocol.h @@ -22,7 +22,7 @@ #ifndef LIBSIGROK_HARDWARE_RDTECH_DPS_PROTOCOL_H #define LIBSIGROK_HARDWARE_RDTECH_DPS_PROTOCOL_H -#include +#include "config.h" #include #include @@ -38,10 +38,8 @@ enum rdtech_dps_model_type { MODEL_RD, }; -struct rdtech_dps_model { - enum rdtech_dps_model_type model_type; - unsigned int id; - const char *name; +struct rdtech_dps_range { + const char *range_str; unsigned int max_current; unsigned int max_voltage; unsigned int max_power; @@ -49,6 +47,14 @@ struct rdtech_dps_model { unsigned int voltage_digits; }; +struct rdtech_dps_model { + enum rdtech_dps_model_type model_type; + unsigned int id; + const char *name; + const struct rdtech_dps_range *ranges; + size_t n_ranges; +}; + struct dev_context { const struct rdtech_dps_model *model; double current_multiplier; @@ -59,6 +65,8 @@ struct dev_context { gboolean curr_ocp_state; gboolean curr_cc_state; gboolean curr_out_state; + size_t curr_range; + gboolean acquisition_started; }; /* Container to get and set parameter values. */ @@ -77,6 +85,7 @@ struct rdtech_dps_state { STATE_VOLTAGE = 1 << 10, STATE_CURRENT = 1 << 11, STATE_POWER = 1 << 12, + STATE_RANGE = 1 << 13, } mask; gboolean lock; gboolean output_enabled, regulation_cc; @@ -84,6 +93,7 @@ struct rdtech_dps_state { float voltage_target, current_limit; float ovp_threshold, ocp_threshold; float voltage, current, power; + size_t range; }; enum rdtech_dps_state_context { @@ -100,6 +110,8 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi, SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, enum rdtech_dps_model_type model_type, uint16_t *model, uint16_t *version, uint32_t *serno); +SR_PRIV void rdtech_dps_update_multipliers(const struct sr_dev_inst *sdi); +SR_PRIV int rdtech_dps_update_range(const struct sr_dev_inst *sdi); SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi); SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data); diff --git a/src/hardware/rdtech-tc/api.c b/src/hardware/rdtech-tc/api.c index eacd17d3..ae86fccf 100644 --- a/src/hardware/rdtech-tc/api.c +++ b/src/hardware/rdtech-tc/api.c @@ -18,12 +18,14 @@ */ #include + +#include #include +#include +#include #include #include -#include -#include -#include + #include "libsigrok-internal.h" #include "protocol.h" @@ -40,23 +42,27 @@ static const uint32_t drvopts[] = { static const uint32_t devopts[] = { SR_CONF_CONTINUOUS, - SR_CONF_LIMIT_SAMPLES | SR_CONF_SET, - SR_CONF_LIMIT_MSEC | SR_CONF_SET, + SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, }; -static GSList *rdtech_tc_scan(struct sr_dev_driver *di, const char *conn, - const char *serialcomm) +static GSList *rdtech_tc_scan(struct sr_dev_driver *di, + const char *conn, const char *serialcomm) { struct sr_serial_dev_inst *serial; - GSList *devices = NULL; - struct dev_context *devc = NULL; - struct sr_dev_inst *sdi = NULL; + GSList *devices; + struct dev_context *devc; + struct sr_dev_inst *sdi; + size_t i; + const struct rdtech_tc_channel_desc *pch; + struct sr_channel *ch; + struct feed_queue_analog *feed; serial = sr_serial_dev_inst_new(conn, serialcomm); if (serial_open(serial, SERIAL_RDWR) != SR_OK) goto err_out; - devc = g_malloc0(sizeof(struct dev_context)); + devc = g_malloc0(sizeof(*devc)); sr_sw_limits_init(&devc->limits); if (rdtech_tc_probe(serial, devc) != SR_OK) { @@ -64,7 +70,8 @@ static GSList *rdtech_tc_scan(struct sr_dev_driver *di, const char *conn, goto err_out_serial; } - sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi = g_malloc0(sizeof(*sdi)); + sdi->priv = devc; sdi->status = SR_ST_INACTIVE; sdi->vendor = g_strdup("RDTech"); sdi->model = g_strdup(devc->dev_info.model_name); @@ -72,12 +79,18 @@ static GSList *rdtech_tc_scan(struct sr_dev_driver *di, const char *conn, sdi->serial_num = g_strdup_printf("%08" PRIu32, devc->dev_info.serial_num); sdi->inst_type = SR_INST_SERIAL; sdi->conn = serial; - sdi->priv = devc; - for (int i = 0; devc->channels[i].name; i++) - sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, devc->channels[i].name); + devc->feeds = g_malloc0(devc->channel_count * sizeof(devc->feeds[0])); + for (i = 0; i < devc->channel_count; i++) { + pch = &devc->channels[i]; + ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, pch->name); + feed = feed_queue_analog_alloc(sdi, 1, pch->digits, ch); + feed_queue_analog_mq_unit(feed, pch->mq, 0, pch->unit); + feed_queue_analog_scale_offset(feed, &pch->scale, NULL); + devc->feeds[i] = feed; + } - devices = g_slist_append(devices, sdi); + devices = g_slist_append(NULL, sdi); serial_close(serial); if (!devices) sr_serial_dev_inst_free(serial); @@ -93,31 +106,53 @@ err_out: return NULL; } -static GSList *scan(struct sr_dev_driver *di, GSList *options) +static void clear_helper(struct dev_context *devc) { - struct sr_config *src; - const char *conn = NULL; - const char *serialcomm = RDTECH_TC_SERIALCOMM; - - for (GSList *l = options; l; l = l->next) { - src = l->data; - switch (src->key) { - case SR_CONF_CONN: - conn = g_variant_get_string(src->data, NULL); - break; - case SR_CONF_SERIALCOMM: - serialcomm = g_variant_get_string(src->data, NULL); - break; - } + size_t idx; + + if (!devc) + return; + + if (devc->feeds) { + for (idx = 0; idx < devc->channel_count; idx++) + feed_queue_analog_free(devc->feeds[idx]); + g_free(devc->feeds); } +} + +static int dev_clear(const struct sr_dev_driver *driver) +{ + return std_dev_clear_with_callback(driver, (std_dev_clear_callback)clear_helper); +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + const char *conn; + const char *serialcomm; + + conn = NULL; + serialcomm = RDTECH_TC_SERIALCOMM; + (void)sr_serial_extract_options(options, &conn, &serialcomm); if (!conn) return NULL; return rdtech_tc_scan(di, conn, serialcomm); } +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + (void)cg; + + devc = sdi->priv; + + return sr_sw_limits_config_get(&devc->limits, key, data); +} + static int config_set(uint32_t key, GVariant *data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; @@ -129,23 +164,25 @@ static int config_set(uint32_t key, GVariant *data, } static int config_list(uint32_t key, GVariant **data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); } static int dev_acquisition_start(const struct sr_dev_inst *sdi) { - struct dev_context *devc = sdi->priv; - struct sr_serial_dev_inst *serial = sdi->conn; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + devc = sdi->priv; sr_sw_limits_acquisition_start(&devc->limits); std_session_send_df_header(sdi); + serial = sdi->conn; serial_source_add(sdi->session, serial, G_IO_IN, 50, - rdtech_tc_receive_data, (void *)sdi); + rdtech_tc_receive_data, (void *)sdi); - return rdtech_tc_poll(sdi); + return rdtech_tc_poll(sdi, TRUE); } static struct sr_dev_driver rdtech_tc_driver_info = { @@ -156,8 +193,8 @@ static struct sr_dev_driver rdtech_tc_driver_info = { .cleanup = std_cleanup, .scan = scan, .dev_list = std_dev_list, - .dev_clear = std_dev_clear, - .config_get = NULL, + .dev_clear = dev_clear, + .config_get = config_get, .config_set = config_set, .config_list = config_list, .dev_open = std_serial_dev_open, diff --git a/src/hardware/rdtech-tc/protocol.c b/src/hardware/rdtech-tc/protocol.c index 3b94890d..291604a0 100644 --- a/src/hardware/rdtech-tc/protocol.c +++ b/src/hardware/rdtech-tc/protocol.c @@ -18,36 +18,44 @@ */ #include -#include -#include -#include + #include -#include #include +#include +#include +#include +#include + #include "libsigrok-internal.h" #include "protocol.h" -#define SERIAL_WRITE_TIMEOUT_MS 1 - -#define TC_POLL_LEN 192 -#define TC_POLL_PERIOD_MS 100 -#define TC_TIMEOUT_MS 1000 +#define PROBE_TO_MS 1000 +#define WRITE_TO_MS 1 +#define POLL_PERIOD_MS 100 -static const char POLL_CMD[] = "getva"; +/* + * Response data (raw sample data) consists of three adjacent chunks + * of 64 bytes each. These chunks start with their magic string, and + * end in a 32bit checksum field. Measurement values are scattered + * across these 192 bytes total size. All multi-byte integer values + * are represented in little endian format. Typical size is 32 bits. + */ -#define MAGIC_PAC1 0x31636170UL -#define MAGIC_PAC2 0x32636170UL -#define MAGIC_PAC3 0x33636170UL +#define MAGIC_PAC1 0x70616331 /* 'pac1' */ +#define MAGIC_PAC2 0x70616332 /* 'pac2' */ +#define MAGIC_PAC3 0x70616333 /* 'pac3' */ -/* Length of PAC block excluding CRC */ -#define PAC_DATA_LEN 60 -/* Length of PAC block including CRC */ #define PAC_LEN 64 +#define PAC_CRC_POS (PAC_LEN - sizeof(uint32_t)) /* Offset to PAC block from start of poll data */ #define OFF_PAC1 (0 * PAC_LEN) #define OFF_PAC2 (1 * PAC_LEN) #define OFF_PAC3 (2 * PAC_LEN) +#define TC_POLL_LEN (3 * PAC_LEN) +#if TC_POLL_LEN > RDTECH_TC_RSPBUFSIZE +# error "response length exceeds receive buffer space" +#endif #define OFF_MODEL 4 #define LEN_MODEL 4 @@ -57,76 +65,115 @@ static const char POLL_CMD[] = "getva"; #define OFF_SERIAL 12 -static const uint8_t AES_KEY[] = { +static const uint8_t aes_key[] = { 0x58, 0x21, 0xfa, 0x56, 0x01, 0xb2, 0xf0, 0x26, 0x87, 0xff, 0x12, 0x04, 0x62, 0x2a, 0x4f, 0xb0, 0x86, 0xf4, 0x02, 0x60, 0x81, 0x6f, 0x9a, 0x0b, 0xa7, 0xf1, 0x06, 0x61, 0x9a, 0xb8, 0x72, 0x88, }; -static const struct binary_analog_channel rdtech_tc_channels[] = { - { "V", { 0 + 48, BVT_LE_UINT32, 1e-4, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "I", { 0 + 52, BVT_LE_UINT32, 1e-5, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE }, - { "D+", { 64 + 32, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "D-", { 64 + 36, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "E0", { 64 + 12, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, - { "E1", { 64 + 20, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, - ALL_ZERO, +static const struct rdtech_tc_channel_desc rdtech_tc_channels[] = { + { "V", { 0 + 48, BVT_LE_UINT32, }, { 100, 1e6, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "I", { 0 + 52, BVT_LE_UINT32, }, { 10, 1e6, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE }, + { "D+", { 64 + 32, BVT_LE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "D-", { 64 + 36, BVT_LE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "E0", { 64 + 12, BVT_LE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, + { "E1", { 64 + 20, BVT_LE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, }; -static int check_pac_crc(uint8_t *data) +static gboolean check_pac_crc(uint8_t *data) { - uint16_t crc; - uint32_t crc_field; - - crc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_DATA_LEN); - crc_field = RL32(data + PAC_DATA_LEN); + uint16_t crc_calc; + uint32_t crc_recv; - if (crc != crc_field) { + crc_calc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_CRC_POS); + crc_recv = read_u32le(&data[PAC_CRC_POS]); + if (crc_calc != crc_recv) { sr_spew("CRC error. Calculated: %0x" PRIx16 ", expected: %0x" PRIx32, - crc, crc_field); - return 0; - } else { - return 1; + crc_calc, crc_recv); + return FALSE; } + + return TRUE; } -static int process_poll_pkt(struct dev_context *devc, uint8_t *dst) +static int process_poll_pkt(struct dev_context *devc, uint8_t *dst) { struct aes256_ctx ctx; + gboolean ok; - aes256_set_decrypt_key(&ctx, AES_KEY); + aes256_set_decrypt_key(&ctx, aes_key); aes256_decrypt(&ctx, TC_POLL_LEN, dst, devc->buf); - if (RL32(dst + OFF_PAC1) != MAGIC_PAC1 || - RL32(dst + OFF_PAC2) != MAGIC_PAC2 || - RL32(dst + OFF_PAC3) != MAGIC_PAC3) { - sr_err("Invalid poll packet magic values!"); - return SR_ERR; + ok = TRUE; + ok &= read_u32be(&dst[OFF_PAC1]) == MAGIC_PAC1; + ok &= read_u32be(&dst[OFF_PAC2]) == MAGIC_PAC2; + ok &= read_u32be(&dst[OFF_PAC3]) == MAGIC_PAC3; + if (!ok) { + sr_err("Invalid poll response packet (magic values)."); + return SR_ERR_DATA; } - if (!check_pac_crc(dst + OFF_PAC1) || - !check_pac_crc(dst + OFF_PAC2) || - !check_pac_crc(dst + OFF_PAC3)) { - sr_err("Invalid poll checksum!"); - return SR_ERR; + ok &= check_pac_crc(&dst[OFF_PAC1]); + ok &= check_pac_crc(&dst[OFF_PAC2]); + ok &= check_pac_crc(&dst[OFF_PAC3]); + if (!ok) { + sr_err("Invalid poll response packet (checksum)."); + return SR_ERR_DATA; + } + + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + static const size_t chunk_max = 32; + + const uint8_t *rdptr; + size_t rdlen, chunk_addr, chunk_len; + GString *txt; + + sr_spew("check passed on decrypted receive data"); + rdptr = dst; + rdlen = TC_POLL_LEN; + chunk_addr = 0; + while (rdlen) { + chunk_len = rdlen; + if (chunk_len > chunk_max) + chunk_len = chunk_max; + txt = sr_hexdump_new(rdptr, chunk_len); + sr_spew("%04zx %s", chunk_addr, txt->str); + sr_hexdump_free(txt); + chunk_addr += chunk_len; + rdptr += chunk_len; + rdlen -= chunk_len; + } } return SR_OK; } -SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc) +SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc) { + static const char *poll_cmd_cdc = "getva"; + static const char *poll_cmd_ble = "bgetva\r\n"; + int len; uint8_t poll_pkt[TC_POLL_LEN]; - if (serial_write_blocking(serial, &POLL_CMD, sizeof(POLL_CMD) - 1, - SERIAL_WRITE_TIMEOUT_MS) < 0) { - sr_err("Unable to send probe request."); + /* Construct the request text. Which differs across transports. */ + devc->is_bluetooth = ser_name_is_bt(serial); + snprintf(devc->req_text, sizeof(devc->req_text), "%s", + devc->is_bluetooth ? poll_cmd_ble : poll_cmd_cdc); + sr_dbg("is bluetooth %d -> poll request '%s'.", + devc->is_bluetooth, devc->req_text); + + /* Transmit the request. */ + len = serial_write_blocking(serial, + devc->req_text, strlen(devc->req_text), WRITE_TO_MS); + if (len < 0) { + sr_err("Failed to send probe request."); return SR_ERR; } - len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, TC_TIMEOUT_MS); + /* Receive a response. */ + len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, PROBE_TO_MS); if (len != TC_POLL_LEN) { sr_err("Failed to read probe response."); return SR_ERR; @@ -138,73 +185,139 @@ SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_contex } devc->channels = rdtech_tc_channels; - devc->dev_info.model_name = g_strndup((const char *)poll_pkt + OFF_MODEL, LEN_MODEL); - devc->dev_info.fw_ver = g_strndup((const char *)poll_pkt + OFF_FW_VER, LEN_FW_VER); - devc->dev_info.serial_num = RL32(poll_pkt + OFF_SERIAL); + devc->channel_count = ARRAY_SIZE(rdtech_tc_channels); + devc->dev_info.model_name = g_strndup((const char *)&poll_pkt[OFF_MODEL], LEN_MODEL); + devc->dev_info.fw_ver = g_strndup((const char *)&poll_pkt[OFF_FW_VER], LEN_FW_VER); + devc->dev_info.serial_num = read_u32le(&poll_pkt[OFF_SERIAL]); return SR_OK; } -SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi) +SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi, gboolean force) { - struct dev_context *devc = sdi->priv; - struct sr_serial_dev_inst *serial = sdi->conn; + struct dev_context *devc; + int64_t now, elapsed; + struct sr_serial_dev_inst *serial; + int len; + + /* + * Don't send the request while receive data is being accumulated. + * Defer request transmission when a previous request has not yet + * seen any response data at all (more probable to happen shortly + * after connecting to the peripheral). + */ + devc = sdi->priv; + if (!force) { + if (devc->rdlen) + return SR_OK; + if (!devc->rx_after_tx) + return SR_OK; + } + + /* + * Send the request when the transmit interval was reached. Or + * when the caller forced the transmission. + */ + now = g_get_monotonic_time() / 1000; + elapsed = now - devc->cmd_sent_at; + if (!force && elapsed < POLL_PERIOD_MS) + return SR_OK; - if (serial_write_blocking(serial, &POLL_CMD, sizeof(POLL_CMD) - 1, - SERIAL_WRITE_TIMEOUT_MS) < 0) { + /* + * Transmit another measurement request. Only advance the + * interval after successful transmission. + */ + serial = sdi->conn; + len = serial_write_blocking(serial, + devc->req_text, strlen(devc->req_text), WRITE_TO_MS); + if (len < 0) { sr_err("Unable to send poll request."); return SR_ERR; } - - devc->cmd_sent_at = g_get_monotonic_time() / 1000; + devc->cmd_sent_at = now; + devc->rx_after_tx = 0; return SR_OK; } -static void handle_poll_data(const struct sr_dev_inst *sdi) +static int handle_poll_data(struct sr_dev_inst *sdi) { - struct dev_context *devc = sdi->priv; + struct dev_context *devc; uint8_t poll_pkt[TC_POLL_LEN]; - int i; - GSList *ch; - - sr_spew("Received poll packet (len: %d).", devc->buflen); - if (devc->buflen != TC_POLL_LEN) { - sr_err("Unexpected poll packet length: %i", devc->buflen); - return; + size_t ch_idx; + const struct rdtech_tc_channel_desc *pch; + int ret; + float v; + + devc = sdi->priv; + sr_spew("Received poll packet (len: %zu).", devc->rdlen); + if (devc->rdlen < TC_POLL_LEN) { + sr_err("Insufficient poll packet length: %zu", devc->rdlen); + return SR_ERR_DATA; } if (process_poll_pkt(devc, poll_pkt) != SR_OK) { sr_err("Failed to process poll packet."); - return; + return SR_ERR_DATA; + } + + ret = SR_OK; + std_session_send_df_frame_begin(sdi); + for (ch_idx = 0; ch_idx < devc->channel_count; ch_idx++) { + pch = &devc->channels[ch_idx]; + ret = bv_get_value_len(&v, &pch->spec, poll_pkt, TC_POLL_LEN); + if (ret != SR_OK) + break; + ret = feed_queue_analog_submit_one(devc->feeds[ch_idx], v, 1); + if (ret != SR_OK) + break; } + std_session_send_df_frame_end(sdi); - for (ch = sdi->channels, i = 0; ch; ch = g_slist_next(ch), i++) { - bv_send_analog_channel(sdi, ch->data, - &devc->channels[i], poll_pkt, TC_POLL_LEN); - } + sr_sw_limits_update_frames_read(&devc->limits, 1); + if (sr_sw_limits_check(&devc->limits)) + sr_dev_acquisition_stop(sdi); - sr_sw_limits_update_samples_read(&devc->limits, 1); + return ret; } -static void recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial) +static int recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial) { - struct dev_context *devc = sdi->priv; + struct dev_context *devc; + size_t space; int len; - - /* Serial data arrived. */ - while (devc->buflen < TC_POLL_LEN) { - len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1); - if (len < 1) - return; - - devc->buflen++; + int ret; + + /* Receive data became available. Drain the transport layer. */ + devc = sdi->priv; + while (devc->rdlen < TC_POLL_LEN) { + space = sizeof(devc->buf) - devc->rdlen; + len = serial_read_nonblocking(serial, + &devc->buf[devc->rdlen], space); + if (len < 0) + return SR_ERR_IO; + if (len == 0) + return SR_OK; + devc->rdlen += len; + devc->rx_after_tx += len; } - if (devc->buflen == TC_POLL_LEN) - handle_poll_data(sdi); + /* + * TODO Want to (re-)synchronize to the packet stream? The + * 'pac1' string literal would be a perfect match for that. + */ + + /* Process packets when their reception has completed. */ + while (devc->rdlen >= TC_POLL_LEN) { + ret = handle_poll_data(sdi); + if (ret != SR_OK) + return ret; + devc->rdlen -= TC_POLL_LEN; + if (devc->rdlen) + memmove(devc->buf, &devc->buf[TC_POLL_LEN], devc->rdlen); + } - devc->buflen = 0; + return SR_OK; } SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data) @@ -212,30 +325,31 @@ SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data) struct sr_dev_inst *sdi; struct dev_context *devc; struct sr_serial_dev_inst *serial; - int64_t now, elapsed; + int ret; (void)fd; if (!(sdi = cb_data)) return TRUE; - if (!(devc = sdi->priv)) return TRUE; + /* Handle availability of receive data. */ serial = sdi->conn; - if (revents == G_IO_IN) - recv_poll_data(sdi, serial); + if (revents == G_IO_IN) { + ret = recv_poll_data(sdi, serial); + if (ret != SR_OK) + sr_dev_acquisition_stop(sdi); + } + /* Check configured acquisition limits. */ if (sr_sw_limits_check(&devc->limits)) { sr_dev_acquisition_stop(sdi); return TRUE; } - now = g_get_monotonic_time() / 1000; - elapsed = now - devc->cmd_sent_at; - - if (elapsed > TC_POLL_PERIOD_MS) - rdtech_tc_poll(sdi); + /* Periodically retransmit measurement requests. */ + (void)rdtech_tc_poll(sdi, FALSE); return TRUE; } diff --git a/src/hardware/rdtech-tc/protocol.h b/src/hardware/rdtech-tc/protocol.h index 963c7765..e495375c 100644 --- a/src/hardware/rdtech-tc/protocol.h +++ b/src/hardware/rdtech-tc/protocol.h @@ -25,7 +25,16 @@ #define LOG_PREFIX "rdtech-tc" -#define RDTECH_TC_BUFSIZE 256 +/* + * Keep request and response buffers of sufficient size. The maximum + * request text currently involved is "bgetva\r\n" which translates + * to 9 bytes. The poll response (a measurement, the largest amount + * of data that is currently received) is 192 bytes in length. Add + * some slack for alignment, and for in-flight messages or adjacent + * data during synchronization to the data stream. + */ +#define RDTECH_TC_MAXREQLEN 12 +#define RDTECH_TC_RSPBUFSIZE 256 struct rdtech_dev_info { char *model_name; @@ -33,18 +42,31 @@ struct rdtech_dev_info { uint32_t serial_num; }; +struct rdtech_tc_channel_desc { + const char *name; + struct binary_value_spec spec; + struct sr_rational scale; + int digits; + enum sr_mq mq; + enum sr_unit unit; +}; + struct dev_context { + gboolean is_bluetooth; + char req_text[RDTECH_TC_MAXREQLEN]; struct rdtech_dev_info dev_info; - const struct binary_analog_channel *channels; + const struct rdtech_tc_channel_desc *channels; + size_t channel_count; + struct feed_queue_analog **feeds; struct sr_sw_limits limits; - - uint8_t buf[RDTECH_TC_BUFSIZE]; - int buflen; + uint8_t buf[RDTECH_TC_RSPBUFSIZE]; + size_t rdlen; int64_t cmd_sent_at; + size_t rx_after_tx; }; -SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc); +SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc); SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data); -SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi); +SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi, gboolean force); #endif diff --git a/src/hardware/rdtech-um/api.c b/src/hardware/rdtech-um/api.c index fbc38b1a..67ff37d8 100644 --- a/src/hardware/rdtech-um/api.c +++ b/src/hardware/rdtech-um/api.c @@ -17,17 +17,16 @@ * along with this program. If not, see . */ - #include + #include -#include -#include #include +#include #include +#include +#include -#include #include "libsigrok-internal.h" - #include "protocol.h" #define RDTECH_UM_SERIALCOMM "115200/8n1" @@ -43,18 +42,22 @@ static const uint32_t drvopts[] = { static const uint32_t devopts[] = { SR_CONF_CONTINUOUS, - SR_CONF_LIMIT_SAMPLES | SR_CONF_SET, - SR_CONF_LIMIT_MSEC | SR_CONF_SET, + SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, }; -static GSList *rdtech_um_scan(struct sr_dev_driver *di, const char *conn, - const char *serialcomm) +static GSList *rdtech_um_scan(struct sr_dev_driver *di, + const char *conn, const char *serialcomm) { struct sr_serial_dev_inst *serial; - const struct rdtech_um_profile *p = NULL; - GSList *devices = NULL; - struct dev_context *devc = NULL; - struct sr_dev_inst *sdi = NULL; + const struct rdtech_um_profile *p; + GSList *devices; + struct dev_context *devc; + struct sr_dev_inst *sdi; + size_t ch_idx; + const struct rdtech_um_channel_desc *pch; + struct sr_channel *ch; + struct feed_queue_analog *feed; serial = sr_serial_dev_inst_new(conn, serialcomm); if (serial_open(serial, SERIAL_RDWR) != SR_OK) @@ -66,25 +69,31 @@ static GSList *rdtech_um_scan(struct sr_dev_driver *di, const char *conn, goto err_out_serial; } - devc = g_malloc0(sizeof(struct dev_context)); - sdi = g_malloc0(sizeof(struct sr_dev_inst)); - + devc = g_malloc0(sizeof(*devc)); sr_sw_limits_init(&devc->limits); devc->profile = p; + sdi = g_malloc0(sizeof(*sdi)); + sdi->priv = devc; sdi->status = SR_ST_INACTIVE; sdi->vendor = g_strdup("RDTech"); sdi->model = g_strdup(p->model_name); sdi->version = NULL; sdi->inst_type = SR_INST_SERIAL; sdi->conn = serial; - sdi->priv = devc; - for (int i = 0; p->channels[i].name; i++) - sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, - p->channels[i].name); + devc->feeds = g_malloc0(p->channel_count * sizeof(devc->feeds[0])); + for (ch_idx = 0; ch_idx < p->channel_count; ch_idx++) { + pch = &p->channels[ch_idx]; + ch = sr_channel_new(sdi, ch_idx, + SR_CHANNEL_ANALOG, TRUE, pch->name); + feed = feed_queue_analog_alloc(sdi, 1, pch->digits, ch); + feed_queue_analog_mq_unit(feed, pch->mq, 0, pch->unit); + feed_queue_analog_scale_offset(feed, &pch->scale, NULL); + devc->feeds[ch_idx] = feed; + } - devices = g_slist_append(devices, sdi); + devices = g_slist_append(NULL, sdi); serial_close(serial); if (!devices) sr_serial_dev_inst_free(serial); @@ -99,31 +108,55 @@ err_out: return NULL; } -static GSList *scan(struct sr_dev_driver *di, GSList *options) +static void clear_helper(struct dev_context *devc) { - struct sr_config *src; - const char *conn = NULL; - const char *serialcomm = RDTECH_UM_SERIALCOMM; - - for (GSList *l = options; l; l = l->next) { - src = l->data; - switch (src->key) { - case SR_CONF_CONN: - conn = g_variant_get_string(src->data, NULL); - break; - case SR_CONF_SERIALCOMM: - serialcomm = g_variant_get_string(src->data, NULL); - break; - } + const struct rdtech_um_profile *p; + size_t ch_idx; + + if (!devc) + return; + + p = devc->profile; + if (p && devc->feeds) { + for (ch_idx = 0; ch_idx < p->channel_count; ch_idx++) + feed_queue_analog_free(devc->feeds[ch_idx]); + g_free(devc->feeds); } +} + +static int dev_clear(const struct sr_dev_driver *di) +{ + return std_dev_clear_with_callback(di, (std_dev_clear_callback)clear_helper); +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + const char *conn; + const char *serialcomm; + + conn = NULL; + serialcomm = RDTECH_UM_SERIALCOMM; + (void)sr_serial_extract_options(options, &conn, &serialcomm); if (!conn) return NULL; return rdtech_um_scan(di, conn, serialcomm); } +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + (void)cg; + + devc = sdi->priv; + + return sr_sw_limits_config_get(&devc->limits, key, data); +} + static int config_set(uint32_t key, GVariant *data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; @@ -135,23 +168,25 @@ static int config_set(uint32_t key, GVariant *data, } static int config_list(uint32_t key, GVariant **data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); } static int dev_acquisition_start(const struct sr_dev_inst *sdi) { - struct dev_context *devc = sdi->priv; - struct sr_serial_dev_inst *serial = sdi->conn; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + devc = sdi->priv; sr_sw_limits_acquisition_start(&devc->limits); std_session_send_df_header(sdi); + serial = sdi->conn; serial_source_add(sdi->session, serial, G_IO_IN, 50, - rdtech_um_receive_data, (void *)sdi); + rdtech_um_receive_data, (void *)sdi); - return rdtech_um_poll(sdi); + return rdtech_um_poll(sdi, TRUE); } static struct sr_dev_driver rdtech_um_driver_info = { @@ -162,8 +197,8 @@ static struct sr_dev_driver rdtech_um_driver_info = { .cleanup = std_cleanup, .scan = scan, .dev_list = std_dev_list, - .dev_clear = std_dev_clear, - .config_get = NULL, + .dev_clear = dev_clear, + .config_get = config_get, .config_set = config_set, .config_list = config_list, .dev_open = std_serial_dev_open, diff --git a/src/hardware/rdtech-um/protocol.c b/src/hardware/rdtech-um/protocol.c index 04de7687..806f9c2d 100644 --- a/src/hardware/rdtech-um/protocol.c +++ b/src/hardware/rdtech-um/protocol.c @@ -18,83 +18,100 @@ */ #include -#include -#include -#include + #include #include +#include +#include +#include + #include "libsigrok-internal.h" #include "protocol.h" -#define SERIAL_WRITE_TIMEOUT_MS 1 +/* Read/write timeouts, poll request intervals. */ +#define PROBE_TO_MS 1000 +#define WRITE_TO_MS 1 +#define POLL_PERIOD_MS 100 -#define UM_POLL_LEN 130 -#define UM_POLL_PERIOD_MS 100 -#define UM_TIMEOUT_MS 1000 +/* Expected receive data size for poll responses. */ +#define POLL_RECV_LEN 130 +/* Command code to request another poll response. */ #define UM_CMD_POLL 0xf0 -static const struct binary_analog_channel rdtech_default_channels[] = { - { "V", { 2, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "I", { 4, BVT_BE_UINT16, 0.001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE }, - { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS }, +static const struct rdtech_um_channel_desc default_channels[] = { + { "V", { 2, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "I", { 4, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE }, + { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS }, /* Threshold-based recording (mWh) */ - { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, - ALL_ZERO, + { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, }; -static const struct binary_analog_channel rdtech_um25c_channels[] = { - { "V", { 2, BVT_BE_UINT16, 0.001, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "I", { 4, BVT_BE_UINT16, 0.0001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE }, - { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, - { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS }, +static const struct rdtech_um_channel_desc um25c_channels[] = { + { "V", { 2, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "I", { 4, BVT_BE_UINT16, }, { 100, 1e6, }, 4, SR_MQ_CURRENT, SR_UNIT_AMPERE }, + { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS }, /* Threshold-based recording (mWh) */ - { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, - ALL_ZERO, + { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, }; -static int poll_csum_fff1(char buf[], int len) +static gboolean csum_ok_fff1(const uint8_t *buf, size_t len) { - if (len != UM_POLL_LEN) - return 0; - else - return RB16(&buf[len - 2]) == 0xFFF1; + uint16_t csum_recv; + + if (len != POLL_RECV_LEN) + return FALSE; + + csum_recv = read_u16be(&buf[len - sizeof(uint16_t)]); + if (csum_recv != 0xfff1) + return FALSE; + + return TRUE; } -static int poll_csum_um34c(char buf[], int len) +static gboolean csum_ok_um34c(const uint8_t *buf, size_t len) { static const int positions[] = { 1, 3, 7, 9, 15, 17, 19, 23, 31, 39, 41, 45, 49, 53, 55, 57, 59, 63, 67, 69, 73, 79, 83, 89, 97, 99, 109, 111, 113, 119, 121, 127, }; - unsigned int i; - uint8_t csum = 0; - if (len != UM_POLL_LEN) - return 0; + size_t i; + uint8_t csum_calc, csum_recv; + + if (len != POLL_RECV_LEN) + return FALSE; + csum_calc = 0; for (i = 0; i < ARRAY_SIZE(positions); i++) - csum ^= buf[positions[i]]; + csum_calc ^= buf[positions[i]]; + csum_recv = read_u8(&buf[len - sizeof(uint8_t)]); + if (csum_recv != csum_calc) + return FALSE; - return csum == (uint8_t)buf[len - 1]; + return TRUE; } static const struct rdtech_um_profile um_profiles[] = { - { "UM24C", RDTECH_UM24C, rdtech_default_channels, &poll_csum_fff1, }, - { "UM25C", RDTECH_UM25C, rdtech_um25c_channels, &poll_csum_fff1, }, - { "UM34C", RDTECH_UM34C, rdtech_default_channels, &poll_csum_um34c, }, + { "UM24C", RDTECH_UM24C, ARRAY_AND_SIZE(default_channels), csum_ok_fff1, }, + { "UM25C", RDTECH_UM25C, ARRAY_AND_SIZE(um25c_channels), csum_ok_fff1, }, + { "UM34C", RDTECH_UM34C, ARRAY_AND_SIZE(default_channels), csum_ok_um34c, }, }; static const struct rdtech_um_profile *find_profile(uint16_t id) { - unsigned int i; + size_t i; + const struct rdtech_um_profile *profile; + for (i = 0; i < ARRAY_SIZE(um_profiles); i++) { - if (um_profiles[i].model_id == id) - return &um_profiles[i]; + profile = &um_profiles[i]; + if (profile->model_id == id) + return profile; } return NULL; } @@ -102,103 +119,213 @@ static const struct rdtech_um_profile *find_profile(uint16_t id) SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial) { const struct rdtech_um_profile *p; - static const uint8_t request = UM_CMD_POLL; - char buf[RDTECH_UM_BUFSIZE]; - int len; - - if (serial_write_blocking(serial, &request, sizeof(request), - SERIAL_WRITE_TIMEOUT_MS) < 0) { - sr_err("Unable to send probe request."); + uint8_t req; + int ret; + uint8_t buf[RDTECH_UM_BUFSIZE]; + int rcvd; + uint16_t model_id; + + req = UM_CMD_POLL; + ret = serial_write_blocking(serial, &req, sizeof(req), WRITE_TO_MS); + if (ret < 0) { + sr_err("Failed to send probe request."); return NULL; } - len = serial_read_blocking(serial, buf, UM_POLL_LEN, UM_TIMEOUT_MS); - if (len != UM_POLL_LEN) { + rcvd = serial_read_blocking(serial, buf, POLL_RECV_LEN, PROBE_TO_MS); + if (rcvd != POLL_RECV_LEN) { sr_err("Failed to read probe response."); return NULL; } - p = find_profile(RB16(&buf[0])); + model_id = read_u16be(&buf[0]); + p = find_profile(model_id); if (!p) { - sr_err("Unrecognized UM device (0x%.4" PRIx16 ")!", RB16(&buf[0])); + sr_err("Unrecognized UM device (0x%.4" PRIx16 ").", model_id); return NULL; } - if (!p->poll_csum(buf, len)) { - sr_err("Probe response contains illegal checksum or end marker.\n"); + if (!p->csum_ok(buf, rcvd)) { + sr_err("Probe response fails checksum verification."); return NULL; } return p; } -SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi) +SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi, gboolean force) { - struct dev_context *devc = sdi->priv; - struct sr_serial_dev_inst *serial = sdi->conn; - static const uint8_t request = UM_CMD_POLL; + struct dev_context *devc; + int64_t now, elapsed; + struct sr_serial_dev_inst *serial; + uint8_t req; + int ret; - if (serial_write_blocking(serial, &request, sizeof(request), - SERIAL_WRITE_TIMEOUT_MS) < 0) { + /* Don't send request when receive data is being accumulated. */ + devc = sdi->priv; + if (!force && devc->buflen) + return SR_OK; + + /* Check for expired intervals or forced requests. */ + now = g_get_monotonic_time() / 1000; + elapsed = now - devc->cmd_sent_at; + if (!force && elapsed < POLL_PERIOD_MS) + return SR_OK; + + /* Send another poll request. Update interval only on success. */ + serial = sdi->conn; + req = UM_CMD_POLL; + ret = serial_write_blocking(serial, &req, sizeof(req), WRITE_TO_MS); + if (ret < 0) { sr_err("Unable to send poll request."); return SR_ERR; } - - devc->cmd_sent_at = g_get_monotonic_time() / 1000; + devc->cmd_sent_at = now; return SR_OK; } -static void handle_poll_data(const struct sr_dev_inst *sdi) +static int process_data(struct sr_dev_inst *sdi, + const uint8_t *data, size_t dlen) { - struct dev_context *devc = sdi->priv; - int i; - GSList *ch; - - sr_spew("Received poll packet (len: %d).", devc->buflen); - if (devc->buflen != UM_POLL_LEN) { - sr_err("Unexpected poll packet length: %i", devc->buflen); - return; + struct dev_context *devc; + const struct rdtech_um_profile *p; + size_t ch_idx; + float v; + int ret; + + devc = sdi->priv; + p = devc->profile; + + sr_spew("Received poll packet (len: %zu).", dlen); + if (dlen < POLL_RECV_LEN) { + sr_err("Insufficient response data length: %zu", dlen); + return SR_ERR_DATA; } - for (ch = sdi->channels, i = 0; ch; ch = g_slist_next(ch), i++) - bv_send_analog_channel(sdi, ch->data, - &devc->profile->channels[i], - devc->buf, devc->buflen); + if (!p->csum_ok(data, POLL_RECV_LEN)) { + sr_err("Packet checksum verification failed."); + return SR_ERR_DATA; + } + + ret = SR_OK; + std_session_send_df_frame_begin(sdi); + for (ch_idx = 0; ch_idx < p->channel_count; ch_idx++) { + ret = bv_get_value_len(&v, &p->channels[ch_idx].spec, data, dlen); + if (ret != SR_OK) + break; + ret = feed_queue_analog_submit_one(devc->feeds[ch_idx], v, 1); + if (ret != SR_OK) + break; + } + std_session_send_df_frame_end(sdi); - sr_sw_limits_update_samples_read(&devc->limits, 1); + sr_sw_limits_update_frames_read(&devc->limits, 1); + if (sr_sw_limits_check(&devc->limits)) + sr_dev_acquisition_stop(sdi); + + return ret; } -static void recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial) +static int accum_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial) { - struct dev_context *devc = sdi->priv; - const struct rdtech_um_profile *p = devc->profile; - int len; - - /* Serial data arrived. */ - while (devc->buflen < UM_POLL_LEN) { - len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1); - if (len < 1) - return; - - devc->buflen++; - - /* Check if the poll model ID matches the profile. */ - if (devc->buflen == 2 && RB16(devc->buf) != p->model_id) { - sr_warn("Illegal model ID in poll response (0x%.4" PRIx16 ")," - " skipping 1 byte.", - RB16(devc->buf)); - devc->buflen--; - memmove(devc->buf, devc->buf + 1, devc->buflen); - } + struct dev_context *devc; + const struct rdtech_um_profile *p; + uint8_t *rdptr; + size_t space, rcvd, rdlen; + int ret; + gboolean do_sync_check; + size_t sync_len, sync_idx; + + /* + * Receive data became available. Drain the serial transport. + * Grab incoming data in as large a chunk as possible. Also + * copes with zero receive data length, as some transports may + * trigger periodically without data really being available. + */ + devc = sdi->priv; + p = devc->profile; + rdptr = &devc->buf[devc->buflen]; + space = sizeof(devc->buf) - devc->buflen; + do_sync_check = FALSE; + sync_len = sizeof(uint16_t); + while (space) { + ret = serial_read_nonblocking(serial, rdptr, space); + if (ret < 0) + return SR_ERR_IO; + rcvd = (size_t)ret; + if (rcvd == 0) + break; + if (rcvd > space) + return SR_ERR_BUG; + if (devc->buflen < sync_len) + do_sync_check = TRUE; + devc->buflen += rcvd; + if (devc->buflen < sync_len) + do_sync_check = FALSE; + space -= rcvd; + rdptr += rcvd; } - if (devc->buflen == UM_POLL_LEN && p->poll_csum(devc->buf, devc->buflen)) - handle_poll_data(sdi); - else - sr_warn("Skipping packet with illegal checksum / end marker."); + /* + * Synchronize to the packetized input stream. Check the model + * ID at the start of receive data. Which is a weak condition, + * but going out of sync should be rare, and repeated attempts + * to synchronize should eventually succeed. Try to rate limit + * the emission of diagnostics messages. (Re-)run this logic + * at the first reception which makes enough data available, + * but not during subsequent accumulation of more data. + * + * Reducing redundancy in the implementation at the same time as + * increasing robustness would involve the creation of a checker + * routine, which just gets called for every byte position until + * it succeeds. Similar to what a previous implementation of the + * read loop did, which was expensive on the serial transport. + */ + sync_idx = 0; + if (do_sync_check && read_u16be(&devc->buf[sync_idx]) != p->model_id) + sr_warn("Unexpected response data, trying to synchronize."); + while (do_sync_check) { + if (sync_idx + sync_len >= devc->buflen) + break; + if (read_u16be(&devc->buf[sync_idx]) == p->model_id) + break; + sync_idx++; + } + if (do_sync_check && sync_idx) { + sr_dbg("Skipping %zu bytes in attempt to sync.", sync_idx); + sync_len = devc->buflen - sync_idx; + if (sync_len) + memmove(&devc->buf[0], &devc->buf[sync_idx], sync_len); + devc->buflen -= sync_idx; + } + + /* + * Process packets as their reception completes. Periodically + * re-transmit poll requests. Discard consumed data after all + * processing has completed. + */ + rdptr = devc->buf; + rdlen = devc->buflen; + ret = SR_OK; + while (ret == SR_OK && rdlen >= POLL_RECV_LEN) { + ret = process_data(sdi, rdptr, rdlen); + if (ret != SR_OK) { + sr_err("Processing response packet failed."); + break; + } + rdptr += POLL_RECV_LEN; + rdlen -= POLL_RECV_LEN; - devc->buflen = 0; + if (0 && !sr_sw_limits_check(&devc->limits)) + (void)rdtech_um_poll(sdi, FALSE); + } + rcvd = rdptr - devc->buf; + devc->buflen -= rcvd; + if (devc->buflen) + memmove(&devc->buf[0], rdptr, devc->buflen); + + return ret; } SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data) @@ -206,30 +333,36 @@ SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data) struct sr_dev_inst *sdi; struct dev_context *devc; struct sr_serial_dev_inst *serial; - int64_t now, elapsed; + int ret; (void)fd; if (!(sdi = cb_data)) return TRUE; - if (!(devc = sdi->priv)) return TRUE; + /* + * Drain and process receive data as it becomes available. + * Terminate acquisition upon receive or processing error. + */ serial = sdi->conn; - if (revents == G_IO_IN) - recv_poll_data(sdi, serial); + if (revents == G_IO_IN) { + ret = accum_data(sdi, serial); + if (ret != SR_OK) { + sr_dev_acquisition_stop(sdi); + return TRUE; + } + } + /* Check configured acquisition limits. */ if (sr_sw_limits_check(&devc->limits)) { sr_dev_acquisition_stop(sdi); return TRUE; } - now = g_get_monotonic_time() / 1000; - elapsed = now - devc->cmd_sent_at; - - if (elapsed > UM_POLL_PERIOD_MS) - rdtech_um_poll(sdi); + /* Periodically retransmit measurement requests. */ + (void)rdtech_um_poll(sdi, FALSE); return TRUE; } diff --git a/src/hardware/rdtech-um/protocol.h b/src/hardware/rdtech-um/protocol.h index f344f1b9..ac147d8c 100644 --- a/src/hardware/rdtech-um/protocol.h +++ b/src/hardware/rdtech-um/protocol.h @@ -33,32 +33,34 @@ enum rdtech_um_model_id { RDTECH_UM34C = 0x0d4c, }; -enum rdtech_um_checksum { - RDTECH_CSUM_STATIC_FFF1, - RDTECH_CSUM_UM34C, +struct rdtech_um_channel_desc { + const char *name; + struct binary_value_spec spec; + struct sr_rational scale; + int digits; + enum sr_mq mq; + enum sr_unit unit; }; -/* Supported device profiles */ struct rdtech_um_profile { const char *model_name; enum rdtech_um_model_id model_id; - const struct binary_analog_channel *channels; - - /* Verify poll packet checksum; return 1 if OK, 0 otherwise. */ - int (*poll_csum)(char buf[], int len); + const struct rdtech_um_channel_desc *channels; + size_t channel_count; + gboolean (*csum_ok)(const uint8_t *buf, size_t len); }; struct dev_context { const struct rdtech_um_profile *profile; struct sr_sw_limits limits; - - char buf[RDTECH_UM_BUFSIZE]; - int buflen; + struct feed_queue_analog **feeds; + uint8_t buf[RDTECH_UM_BUFSIZE]; + size_t buflen; int64_t cmd_sent_at; }; SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial); SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data); -SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi); +SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi, gboolean force); #endif diff --git a/src/hardware/rigol-ds/api.c b/src/hardware/rigol-ds/api.c index aab47302..6703d1ce 100644 --- a/src/hardware/rigol-ds/api.c +++ b/src/hardware/rigol-ds/api.c @@ -273,14 +273,15 @@ static const struct rigol_ds_model supported_models[] = { {SERIES(DS1000Z), "DS1104Z", {5, 1000000000}, CH_INFO(4, false), std_cmd}, {SERIES(DS1000Z), "DS1074Z-S", {5, 1000000000}, CH_INFO(4, false), std_cmd}, {SERIES(DS1000Z), "DS1104Z-S", {5, 1000000000}, CH_INFO(4, false), std_cmd}, - {SERIES(DS1000Z), "DS1074Z Plus", {5, 1000000000}, CH_INFO(4, false), std_cmd}, - {SERIES(DS1000Z), "DS1104Z Plus", {5, 1000000000}, CH_INFO(4, false), std_cmd}, + {SERIES(DS1000Z), "DS1074Z Plus", {5, 1000000000}, CH_INFO(4, true), std_cmd}, + {SERIES(DS1000Z), "DS1104Z Plus", {5, 1000000000}, CH_INFO(4, true), std_cmd}, {SERIES(DS1000Z), "DS1102Z-E", {2, 1000000000}, CH_INFO(2, false), std_cmd}, {SERIES(DS1000Z), "DS1202Z-E", {2, 1000000000}, CH_INFO(2, false), std_cmd}, {SERIES(DS1000Z), "MSO1074Z", {5, 1000000000}, CH_INFO(4, true), std_cmd}, {SERIES(DS1000Z), "MSO1104Z", {5, 1000000000}, CH_INFO(4, true), std_cmd}, {SERIES(DS1000Z), "MSO1074Z-S", {5, 1000000000}, CH_INFO(4, true), std_cmd}, {SERIES(DS1000Z), "MSO1104Z-S", {5, 1000000000}, CH_INFO(4, true), std_cmd}, + {SERIES(DS4000), "DS4014", {1, 1000000000}, CH_INFO(4, false), std_cmd}, {SERIES(DS4000), "DS4024", {1, 1000000000}, CH_INFO(4, false), std_cmd}, {SERIES(MSO5000), "MSO5072", {1, 1000000000}, CH_INFO(2, true), std_cmd}, {SERIES(MSO5000), "MSO5074", {1, 1000000000}, CH_INFO(4, true), std_cmd}, diff --git a/src/hardware/scpi-dmm/api.c b/src/hardware/scpi-dmm/api.c index 077debc7..d0a448d7 100644 --- a/src/hardware/scpi-dmm/api.c +++ b/src/hardware/scpi-dmm/api.c @@ -199,6 +199,20 @@ static const struct mqopt_item mqopts_owon_xdm2041[] = { { SR_MQ_CAPACITANCE, 0, "CAP", "CAP", NO_DFLT_PREC, FLAGS_NONE, }, }; +static const struct mqopt_item mqopts_siglent_sdm3055[] = { + { SR_MQ_VOLTAGE, SR_MQFLAG_DC, "VOLT:DC", "VOLT ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_VOLTAGE, SR_MQFLAG_AC, "VOLT:AC", "VOLT:AC ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CURRENT, SR_MQFLAG_DC, "CURR:DC", "CURR ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CURRENT, SR_MQFLAG_AC, "CURR:AC", "CURR:AC ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_RESISTANCE, 0, "RES", "RES ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, "FRES", "FRES ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, FLAGS_NONE, }, + { SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, FLAGS_NONE, }, + { SR_MQ_FREQUENCY, 0, "FREQ", "FREQ ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_TIME, 0, "PER", "PER ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CAPACITANCE, 0, "CAP", "CAP", NO_DFLT_PREC, FLAGS_NONE, }, +}; + SR_PRIV const struct scpi_dmm_model models[] = { { "Agilent", "34405A", @@ -273,6 +287,14 @@ SR_PRIV const struct scpi_dmm_model models[] = { 0, 0, 10 * 1000, 0, FALSE, scpi_dmm_get_range_text, scpi_dmm_set_range_from_text, NULL, }, + { + "OWON", "XDM1041", + 1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm2041), + scpi_dmm_get_meas_gwinstek, + ARRAY_AND_SIZE(devopts_generic), + 0, 0, 0, 1e9, TRUE, + NULL, NULL, NULL, + }, { "OWON", "XDM2041", 1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm2041), @@ -281,6 +303,14 @@ SR_PRIV const struct scpi_dmm_model models[] = { 0, 0, 0, 1e9, TRUE, NULL, NULL, NULL, }, + { + "Siglent", "SDM3055", + 1, 5, cmdset_hp, ARRAY_AND_SIZE(mqopts_siglent_sdm3055), + scpi_dmm_get_meas_agilent, + ARRAY_AND_SIZE(devopts_generic), + 0, 0, 0, 0, FALSE, + NULL, NULL, NULL, + }, }; static const struct scpi_dmm_model *is_compatible(const char *vendor, const char *model) diff --git a/src/hardware/scpi-pps/api.c b/src/hardware/scpi-pps/api.c index bd11f082..116a5d46 100644 --- a/src/hardware/scpi-pps/api.c +++ b/src/hardware/scpi-pps/api.c @@ -437,6 +437,10 @@ static int config_get(uint32_t key, GVariant **data, gvtype = G_VARIANT_TYPE_DOUBLE; cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_THRESHOLD; break; + case SR_CONF_OVER_CURRENT_PROTECTION_DELAY: + gvtype = G_VARIANT_TYPE_DOUBLE; + cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_DELAY; + break; case SR_CONF_OVER_TEMPERATURE_PROTECTION: if (devc->device->dialect == SCPI_DIALECT_HMP) { /* OTP is always enabled. */ @@ -698,6 +702,12 @@ static int config_set(uint32_t key, GVariant *data, channel_group_cmd, channel_group_name, SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD, d); break; + case SR_CONF_OVER_CURRENT_PROTECTION_DELAY: + d = g_variant_get_double(data); + ret = sr_scpi_cmd(sdi, devc->device->commands, + channel_group_cmd, channel_group_name, + SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DELAY, d); + break; case SR_CONF_OVER_TEMPERATURE_PROTECTION: if (g_variant_get_boolean(data)) ret = sr_scpi_cmd(sdi, devc->device->commands, @@ -794,6 +804,9 @@ static int config_list(uint32_t key, GVariant **data, case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD: *data = std_gvar_min_max_step_array(ch_spec->ocp); break; + case SR_CONF_OVER_CURRENT_PROTECTION_DELAY: + *data = std_gvar_min_max_step_array(ch_spec->ocp_delay); + break; default: return SR_ERR_NA; } diff --git a/src/hardware/scpi-pps/profiles.c b/src/hardware/scpi-pps/profiles.c index e947f744..2b759e5d 100644 --- a/src/hardware/scpi-pps/profiles.c +++ b/src/hardware/scpi-pps/profiles.c @@ -29,6 +29,7 @@ #define FREQ_DC_ONLY {0, 0, 0, 0, 0} #define NO_OVP_LIMITS {0, 0, 0, 0, 0} #define NO_OCP_LIMITS {0, 0, 0, 0, 0} +#define NO_OCP_DELAY {0, 0, 0, 0, 0} /* Agilent/Keysight N5700A series */ static const uint32_t agilent_n5700a_devopts[] = { @@ -52,11 +53,11 @@ static const struct channel_group_spec agilent_n5700a_cg[] = { }; static const struct channel_spec agilent_n5767a_ch[] = { - { "1", { 0, 60, 0.0072, 3, 4 }, { 0, 25, 0.003, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 60, 0.0072, 3, 4 }, { 0, 25, 0.003, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec agilent_n5763a_ch[] = { - { "1", { 0, 12.5, 0.0015, 3, 4 }, { 0, 120, 0.0144, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 12.5, 0.0015, 3, 4 }, { 0, 120, 0.0144, 3, 4 }, { 0, 1500 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; /* @@ -103,9 +104,9 @@ static const uint32_t bk_9130_devopts_cg[] = { }; static const struct channel_spec bk_9130_ch[] = { - { "1", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "3", { 0, 5, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 15, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "2", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "3", { 0, 5, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 15, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_group_spec bk_9130_cg[] = { @@ -152,7 +153,7 @@ static const uint32_t chroma_61604_devopts_cg[] = { }; static const struct channel_spec chroma_61604_ch[] = { - { "1", { 0, 300, 0.1, 1, 1 }, { 0, 16, 0.1, 2, 2 }, { 0, 2000, 0, 1, 1 }, { 1.0, 1000.0, 0.01 }, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 300, 0.1, 1, 1 }, { 0, 16, 0.1, 2, 2 }, { 0, 2000, 0, 1, 1 }, { 1.0, 1000.0, 0.01 }, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_group_spec chroma_61604_cg[] = { @@ -468,11 +469,11 @@ static const uint32_t rigol_dp700_devopts_cg[] = { }; static const struct channel_spec rigol_dp711_ch[] = { - { "1", { 0, 30, 0.01, 3, 3 }, { 0, 5, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 33, 0.01}, { 0.01, 5.5, 0.01 } }, + { "1", { 0, 30, 0.01, 3, 3 }, { 0, 5, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 33, 0.01}, { 0.01, 5.5, 0.01 }, NO_OCP_DELAY }, }; static const struct channel_spec rigol_dp712_ch[] = { - { "1", { 0, 50, 0.01, 3, 3 }, { 0, 3, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 55, 0.01}, { 0.01, 3.3, 0.01 } }, + { "1", { 0, 50, 0.01, 3, 3 }, { 0, 3, 0.01, 3, 3 }, { 0, 150, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 55, 0.01}, { 0.01, 3.3, 0.01 }, NO_OCP_DELAY }, }; static const struct channel_group_spec rigol_dp700_cg[] = { @@ -537,20 +538,20 @@ static const uint32_t rigol_dp800_devopts_cg[] = { }; static const struct channel_spec rigol_dp821a_ch[] = { - { "1", { 0, 60, 0.001, 3, 3 }, { 0, 1, 0.0001, 4, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 8, 0.001, 3, 3 }, { 0, 10, 0.001, 3, 3 }, { 0, 80, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 60, 0.001, 3, 3 }, { 0, 1, 0.0001, 4, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "2", { 0, 8, 0.001, 3, 3 }, { 0, 10, 0.001, 3, 3 }, { 0, 80, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec rigol_dp831_ch[] = { - { "1", { 0, 8, 0.001, 3, 4 }, { 0, 5, 0.0003, 3, 4 }, { 0, 40, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "3", { 0, -30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 8, 0.001, 3, 4 }, { 0, 5, 0.0003, 3, 4 }, { 0, 40, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "2", { 0, 30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "3", { 0, -30, 0.001, 3, 4 }, { 0, 2, 0.0001, 3, 4 }, { 0, 60, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec rigol_dp832_ch[] = { - { "1", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "3", { 0, 5, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "2", { 0, 30, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "3", { 0, 5, 0.001, 3, 4 }, { 0, 3, 0.001, 3, 4 }, { 0, 90, 0, 3, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_group_spec rigol_dp820_cg[] = { @@ -622,15 +623,15 @@ static const uint32_t hp_6630a_devopts_cg[] = { }; static const struct channel_spec hp_6632a_ch[] = { - { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS }, + { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6633a_ch[] = { - { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS }, + { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6634a_ch[] = { - { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS }, + { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_group_spec hp_6630a_cg[] = { @@ -764,43 +765,43 @@ static const uint32_t hp_6630b_devopts_cg[] = { }; static const struct channel_spec hp_6611c_ch[] = { - { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 41.92297 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS }, + { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 41.92297 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6612c_ch[] = { - { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS }, + { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6613c_ch[] = { - { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 52.40627 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS }, + { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 52.40627 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6614c_ch[] = { - { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 0.5118, 0.000125, 4, 5 }, { 0, 52.39808 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS }, + { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 0.5118, 0.000125, 4, 5 }, { 0, 52.39808 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6631b_ch[] = { - { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 10.237, 0.00263, 4, 5 }, { 0, 83.84103 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS }, + { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 10.237, 0.00263, 4, 5 }, { 0, 83.84103 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6632b_ch[] = { - { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS }, + { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_66312a_ch[] = { - { "1", { 0, 20.475, 0.0001, 4, 5 }, { 0, 2.0475, 0.0001, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.01 }, NO_OCP_LIMITS }, + { "1", { 0, 20.475, 0.0001, 4, 5 }, { 0, 2.0475, 0.0001, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.01 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_66332a_ch[] = { - { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS }, + { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6633b_ch[] = { - { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.000526, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS }, + { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.000526, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec hp_6634b_ch[] = { - { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.000263, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS }, + { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.000263, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_group_spec hp_6630b_cg[] = { @@ -1025,11 +1026,11 @@ static const uint32_t owon_p4000_devopts_cg[] = { }; static const struct channel_spec owon_p4603_ch[] = { - { "1", { 0.01, 60, 0.001, 3, 3 }, { 0.001, 3, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 61, 0.001}, { 0.001, 3.1, 0.001} }, + { "1", { 0.01, 60, 0.001, 3, 3 }, { 0.001, 3, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 61, 0.001}, { 0.001, 3.1, 0.001}, NO_OCP_DELAY }, }; static const struct channel_spec owon_p4305_ch[] = { - { "1", { 0.01, 30, 0.001, 3, 3 }, { 0.001, 5, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 31, 0.001}, { 0.001, 3.1, 0.001} }, + { "1", { 0.01, 30, 0.001, 3, 3 }, { 0.001, 5, 0.001, 3, 3 }, { 0, 180, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 31, 0.001}, { 0.001, 3.1, 0.001}, NO_OCP_DELAY }, }; static const struct channel_group_spec owon_p4000_cg[] = { @@ -1054,6 +1055,32 @@ static const struct scpi_command owon_p4000_cmd[] = { ALL_ZERO }; +/* Owon SPE series*/ + +static const struct channel_spec owon_spe6103_ch[] = { + { "1", { 0.01, 60, 0.01, 3, 3 }, { 0.001, 10, 0.001, 3, 3 }, { 0, 300, 0, 3, 3 }, FREQ_DC_ONLY, { 0.01, 61, 0.01}, { 0.001, 10.1, 0.001}, NO_OCP_DELAY }, +}; + +static const struct scpi_command owon_spe6103_cmd[] = { + { SCPI_CMD_REMOTE, "SYST:REM" }, + { SCPI_CMD_LOCAL, "SYST:LOC" }, + { SCPI_CMD_GET_MEAS_VOLTAGE, "MEAS:VOLT?" }, + { SCPI_CMD_GET_MEAS_CURRENT, "MEAS:CURR?" }, + { SCPI_CMD_GET_MEAS_POWER, "MEAS:POW?" }, + { SCPI_CMD_GET_VOLTAGE_TARGET, "VOLT?" }, + { SCPI_CMD_SET_VOLTAGE_TARGET, "VOLT %.6f" }, + { SCPI_CMD_GET_CURRENT_LIMIT, "CURR?" }, + { SCPI_CMD_SET_CURRENT_LIMIT, "CURR %.6f" }, + { SCPI_CMD_GET_OUTPUT_ENABLED, "OUTP?" }, + { SCPI_CMD_SET_OUTPUT_ENABLE, "OUTP 1" }, + { SCPI_CMD_SET_OUTPUT_DISABLE, "OUTP 0" }, + { SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_THRESHOLD, "VOLT:LIM?" }, + { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_THRESHOLD, "VOLT:LIM %.6f" }, + { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_THRESHOLD, "CURR:LIM?" }, + { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD, "CURR:LIM %.6f" }, + ALL_ZERO +}; + /* Philips/Fluke PM2800 series */ static const uint32_t philips_pm2800_devopts[] = { SR_CONF_CONTINUOUS, @@ -1216,6 +1243,9 @@ static const uint32_t rs_hmc8043_devopts_cg[] = { SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED | SR_CONF_GET | SR_CONF_SET, SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET, SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OVER_CURRENT_PROTECTION_ENABLED | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET, + SR_CONF_OVER_CURRENT_PROTECTION_DELAY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, SR_CONF_VOLTAGE | SR_CONF_GET, SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, SR_CONF_CURRENT | SR_CONF_GET, @@ -1224,15 +1254,15 @@ static const uint32_t rs_hmc8043_devopts_cg[] = { }; static const struct channel_spec rs_hmc8043_ch[] = { - { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, { 0.01, 10, 0.001, 3, 4} }, + { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, { 0.01, 10, 0.001, 3, 4} }, + { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 3, 0.001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, { 0.01, 10, 0.001, 3, 4} }, }; static const struct channel_group_spec rs_hmc8043_cg[] = { - { "1", CH_IDX(0), PPS_OVP, SR_MQFLAG_DC }, - { "2", CH_IDX(1), PPS_OVP, SR_MQFLAG_DC }, - { "3", CH_IDX(2), PPS_OVP, SR_MQFLAG_DC }, + { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC }, + { "2", CH_IDX(1), PPS_OVP | PPS_OCP, SR_MQFLAG_DC }, + { "3", CH_IDX(2), PPS_OVP | PPS_OCP, SR_MQFLAG_DC }, }; static const struct scpi_command rs_hmc8043_cmd[] = { @@ -1252,6 +1282,12 @@ static const struct scpi_command rs_hmc8043_cmd[] = { { SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ENABLED, "VOLT:PROT:STAT?" }, { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_ENABLE, "VOLT:PROT:STAT ON" }, { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_DISABLE, "VOLT:PROT:STAT OFF" }, + { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE, "FUSE:TRIP?" }, + { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ENABLED, "FUSE:STAT?" }, + { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_ENABLE, "FUSE:STAT ON" }, + { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DISABLE, "FUSE:STAT OFF" }, + { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_DELAY, "FUSE:DEL?" }, + { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DELAY, "FUSE:DEL %.03f" }, ALL_ZERO }; @@ -1275,21 +1311,21 @@ static const uint32_t rs_hmp4040_devopts_cg[] = { }; static const struct channel_spec rs_hmp2020_ch[] = { - { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec rs_hmp2030_ch[] = { - { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 5.01, 0.0001, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_spec rs_hmp4040_ch[] = { - { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, - { "4", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS }, + { "1", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "2", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "3", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, + { "4", { 0, 32.050, 0.001, 3, 4 }, { 0.001, 10.01, 0.0002, 3, 4 }, { 0, 0, 0, 0, 4 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS, NO_OCP_DELAY }, }; static const struct channel_group_spec rs_hmp4040_cg[] = { @@ -1683,6 +1719,17 @@ SR_PRIV const struct scpi_pps pps_profiles[] = { .update_status = NULL, }, + { "OWON", "^SPE6103$", SCPI_DIALECT_UNKNOWN, 0, + ARRAY_AND_SIZE(owon_p4000_devopts), + ARRAY_AND_SIZE(owon_p4000_devopts_cg), + ARRAY_AND_SIZE(owon_spe6103_ch), + ARRAY_AND_SIZE(owon_p4000_cg), + owon_spe6103_cmd, + .probe_channels = NULL, + .init_acquisition = NULL, + .update_status = NULL, + }, + /* Philips/Fluke PM2800 series */ { "Philips", "^PM28[13][123]/[01234]{1,2}$", SCPI_DIALECT_PHILIPS, 0, ARRAY_AND_SIZE(philips_pm2800_devopts), @@ -1695,6 +1742,18 @@ SR_PRIV const struct scpi_pps pps_profiles[] = { .update_status = NULL, }, + /* Rohde & Schwarz HMC8042 */ + { "Rohde&Schwarz", "HMC8042", SCPI_DIALECT_UNKNOWN, 0, + ARRAY_AND_SIZE(rs_hmc8043_devopts), + ARRAY_AND_SIZE(rs_hmc8043_devopts_cg), + rs_hmc8043_ch, 2, + rs_hmc8043_cg, 2, + rs_hmc8043_cmd, + .probe_channels = NULL, + .init_acquisition = NULL, + .update_status = NULL, + }, + /* Rohde & Schwarz HMC8043 */ { "Rohde&Schwarz", "HMC8043", SCPI_DIALECT_UNKNOWN, 0, ARRAY_AND_SIZE(rs_hmc8043_devopts), diff --git a/src/hardware/scpi-pps/protocol.h b/src/hardware/scpi-pps/protocol.h index 279977f1..b75feb2e 100644 --- a/src/hardware/scpi-pps/protocol.h +++ b/src/hardware/scpi-pps/protocol.h @@ -66,6 +66,8 @@ enum pps_scpi_cmds { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE, SCPI_CMD_GET_OVER_CURRENT_PROTECTION_THRESHOLD, SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD, + SCPI_CMD_GET_OVER_CURRENT_PROTECTION_DELAY, + SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DELAY, }; /* Defines the SCPI dialect */ @@ -121,6 +123,7 @@ struct channel_spec { double frequency[5]; double ovp[5]; double ocp[5]; + double ocp_delay[5]; }; struct channel_group_spec { diff --git a/src/hardware/siglent-sds/protocol.c b/src/hardware/siglent-sds/protocol.c index 919df9b2..d59e52cb 100644 --- a/src/hardware/siglent-sds/protocol.c +++ b/src/hardware/siglent-sds/protocol.c @@ -364,71 +364,71 @@ static int siglent_sds_get_digital(const struct sr_dev_inst *sdi, struct sr_chan for (l = sdi->channels; l; l = l->next) { ch = l->data; samples_index = 0; - if (ch->type == SR_CHANNEL_LOGIC) { - if (ch->enabled) { - if (sr_scpi_send(sdi->conn, "D%d:WF? DAT2", ch->index) != SR_OK) - return SR_ERR; - if (sr_scpi_read_begin(scpi) != SR_OK) - return TRUE; - len = sr_scpi_read_data(scpi, buf, -1); - if (len < 0) - return TRUE; - len -= 15; - buffdata = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); - buf += 15; /* Skipping the data header. */ - g_array_append_vals(buffdata, buf, len); - tmp_samplebuf = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); /* New temp buffer. */ - for (uint64_t cur_sample_index = 0; cur_sample_index < devc->memory_depth_digital; cur_sample_index++) { - char sample = (char)g_array_index(buffdata, uint8_t, cur_sample_index); - for (int ii = 0; ii < 8; ii++, sample >>= 1) { - if (ch->index < 8) { - channel_index = ch->index; - if (data_low_channels->len <= samples_index) { - tmp_value = 0; /* New sample. */ - low_channels = TRUE; /* We have at least one enabled low channel. */ - } else { - /* Get previous stored sample from low channel buffer. */ - tmp_value = g_array_index(data_low_channels, uint8_t, samples_index); - } - } else { - channel_index = ch->index - 8; - if (data_high_channels->len <= samples_index) { - tmp_value = 0; /* New sample. */ - high_channels = TRUE; /* We have at least one enabled high channel. */ - } else { - /* Get previous stored sample from high channel buffer. */ - tmp_value = g_array_index(data_high_channels, uint8_t, samples_index); - } - } - /* Check if the current scope sample bit is set. */ - if (sample & 0x1) - tmp_value |= 1UL << channel_index; /* Set current scope sample bit based on channel index. */ - g_array_append_val(tmp_samplebuf, tmp_value); - samples_index++; - } - } - - /* Clear the buffers to prepare for the new samples */ + if (ch->type != SR_CHANNEL_LOGIC) + continue; + if (!ch->enabled) + continue; + if (sr_scpi_send(sdi->conn, "D%d:WF? DAT2", ch->index) != SR_OK) + return SR_ERR; + if (sr_scpi_read_begin(scpi) != SR_OK) + return TRUE; + len = sr_scpi_read_data(scpi, buf, -1); + if (len < 0) + return TRUE; + len -= 15; + buffdata = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); + buf += 15; /* Skipping the data header. */ + g_array_append_vals(buffdata, buf, len); + tmp_samplebuf = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); /* New temp buffer. */ + for (uint64_t cur_sample_index = 0; cur_sample_index < devc->memory_depth_digital; cur_sample_index++) { + char sample = (char)g_array_index(buffdata, uint8_t, cur_sample_index); + for (int ii = 0; ii < 8; ii++, sample >>= 1) { if (ch->index < 8) { - g_array_free(data_low_channels, FALSE); - data_low_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t)); + channel_index = ch->index; + if (data_low_channels->len <= samples_index) { + tmp_value = 0; /* New sample. */ + low_channels = TRUE; /* We have at least one enabled low channel. */ + } else { + /* Get previous stored sample from low channel buffer. */ + tmp_value = g_array_index(data_low_channels, uint8_t, samples_index); + } } else { - g_array_free(data_high_channels, FALSE); - data_high_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t)); - } - - /* Storing the converted temp values from the the scope into the buffers. */ - for (uint64_t index = 0; index < tmp_samplebuf->len; index++) { - uint8_t value = g_array_index(tmp_samplebuf, uint8_t, index); - if (ch->index < 8) - g_array_append_val(data_low_channels, value); - else - g_array_append_val(data_high_channels, value); + channel_index = ch->index - 8; + if (data_high_channels->len <= samples_index) { + tmp_value = 0; /* New sample. */ + high_channels = TRUE; /* We have at least one enabled high channel. */ + } else { + /* Get previous stored sample from high channel buffer. */ + tmp_value = g_array_index(data_high_channels, uint8_t, samples_index); + } } - g_array_free(tmp_samplebuf, TRUE); - g_array_free(buffdata, TRUE); + /* Check if the current scope sample bit is set. */ + if (sample & 0x1) + tmp_value |= 1UL << channel_index; /* Set current scope sample bit based on channel index. */ + g_array_append_val(tmp_samplebuf, tmp_value); + samples_index++; } } + + /* Clear the buffers to prepare for the new samples */ + if (ch->index < 8) { + g_array_free(data_low_channels, TRUE); + data_low_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t)); + } else { + g_array_free(data_high_channels, TRUE); + data_high_channels = g_array_new(FALSE, FALSE, sizeof(uint8_t)); + } + + /* Storing the converted temp values from the the scope into the buffers. */ + for (uint64_t index = 0; index < tmp_samplebuf->len; index++) { + uint8_t value = g_array_index(tmp_samplebuf, uint8_t, index); + if (ch->index < 8) + g_array_append_val(data_low_channels, value); + else + g_array_append_val(data_high_channels, value); + } + g_array_free(tmp_samplebuf, TRUE); + g_array_free(buffdata, TRUE); } /* Combining the lower and higher channel buffers into one buffer for sigrok. */ diff --git a/src/hardware/yokogawa-dlm/api.c b/src/hardware/yokogawa-dlm/api.c index f80e83dc..c1fe1b01 100644 --- a/src/hardware/yokogawa-dlm/api.c +++ b/src/hardware/yokogawa-dlm/api.c @@ -53,6 +53,7 @@ static const uint32_t devopts_cg_analog[] = { }; static const uint32_t devopts_cg_digital[] = { + /* EMPTY */ }; enum { @@ -423,12 +424,19 @@ static int config_list(uint32_t key, GVariant **data, switch (key) { case SR_CONF_DEVICE_OPTIONS: - if (cg_type == CG_ANALOG) + if (cg_type == CG_ANALOG) { *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_analog)); - else if (cg_type == CG_DIGITAL) + break; + } + if (cg_type == CG_DIGITAL) { + if (!ARRAY_SIZE(devopts_cg_digital)) { + *data = std_gvar_array_u32(NULL, 0); + break; + } *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_digital)); - else - *data = std_gvar_array_u32(NULL, 0); + break; + } + *data = std_gvar_array_u32(NULL, 0); break; case SR_CONF_COUPLING: if (!cg) diff --git a/src/hardware/zeroplus-logic-cube/api.c b/src/hardware/zeroplus-logic-cube/api.c index 9d15fe6c..49e0e1f9 100644 --- a/src/hardware/zeroplus-logic-cube/api.c +++ b/src/hardware/zeroplus-logic-cube/api.c @@ -169,7 +169,10 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) struct libusb_device_handle *hdl; libusb_device **devlist; GSList *devices; - int ret, i, j; + int ret; + size_t i, j; + uint8_t bus, addr; + const struct zp_model *check; char serial_num[64], connection_id[64]; (void)options; @@ -180,21 +183,47 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) /* Find all ZEROPLUS analyzers and add them to device list. */ libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); /* TODO: Errors. */ - for (i = 0; devlist[i]; i++) { libusb_get_device_descriptor(devlist[i], &des); - if ((ret = libusb_open(devlist[i], &hdl)) < 0) + /* + * Check for expected VID:PID first as soon as we got + * the descriptor's content. This avoids access to flaky + * unrelated devices which trouble the application even + * if they are unrelated to measurement purposes. + * + * See https://sigrok.org/bugzilla/show_bug.cgi?id=1115 + * and https://github.com/sigrokproject/libsigrok/pull/165 + * for a discussion. + */ + prof = NULL; + for (j = 0; zeroplus_models[j].vid; j++) { + check = &zeroplus_models[j]; + if (des.idVendor != check->vid) + continue; + if (des.idProduct != check->pid) + continue; + prof = check; + break; + } + if (!prof) continue; - if (des.iSerialNumber == 0) { - serial_num[0] = '\0'; - } else if ((ret = libusb_get_string_descriptor_ascii(hdl, - des.iSerialNumber, (unsigned char *) serial_num, - sizeof(serial_num))) < 0) { - sr_warn("Failed to get serial number string descriptor: %s.", - libusb_error_name(ret)); + /* Get the device's serial number from USB strings. */ + ret = libusb_open(devlist[i], &hdl); + if (ret < 0) continue; + + serial_num[0] = '\0'; + if (des.iSerialNumber != 0) { + ret = libusb_get_string_descriptor_ascii(hdl, + des.iSerialNumber, + (uint8_t *)serial_num, sizeof(serial_num)); + if (ret < 0) { + sr_warn("Cannot get USB serial number: %s.", + libusb_error_name(ret)); + continue; + } } libusb_close(hdl); @@ -202,26 +231,21 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0) continue; - prof = NULL; - for (j = 0; j < zeroplus_models[j].vid; j++) { - if (des.idVendor == zeroplus_models[j].vid && - des.idProduct == zeroplus_models[j].pid) { - prof = &zeroplus_models[j]; - } - } - - if (!prof) - continue; sr_info("Found ZEROPLUS %s.", prof->model_name); - sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi = g_malloc0(sizeof(*sdi)); sdi->status = SR_ST_INACTIVE; sdi->vendor = g_strdup("ZEROPLUS"); sdi->model = g_strdup(prof->model_name); sdi->serial_num = g_strdup(serial_num); sdi->connection_id = g_strdup(connection_id); - devc = g_malloc0(sizeof(struct dev_context)); + bus = libusb_get_bus_number(devlist[i]); + addr = libusb_get_device_address(devlist[i]); + sdi->inst_type = SR_INST_USB; + sdi->conn = sr_usb_dev_inst_new(bus, addr, NULL); + + devc = g_malloc0(sizeof(*devc)); sdi->priv = devc; devc->prof = prof; devc->num_channels = prof->channels; @@ -234,17 +258,13 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) #endif devc->max_samplerate *= SR_MHZ(1); devc->memory_size = MEMORY_SIZE_8K; - // memset(devc->trigger_buffer, 0, NUM_TRIGGER_STAGES); - for (j = 0; j < devc->num_channels; j++) + for (j = 0; j < devc->num_channels; j++) { sr_channel_new(sdi, j, SR_CHANNEL_LOGIC, TRUE, channel_names[j]); + } devices = g_slist_append(devices, sdi); - sdi->inst_type = SR_INST_USB; - sdi->conn = sr_usb_dev_inst_new( - libusb_get_bus_number(devlist[i]), - libusb_get_device_address(devlist[i]), NULL); } libusb_free_device_list(devlist, 1); diff --git a/src/hardware/zeroplus-logic-cube/protocol.c b/src/hardware/zeroplus-logic-cube/protocol.c index bc517c35..37fcbdcc 100644 --- a/src/hardware/zeroplus-logic-cube/protocol.c +++ b/src/hardware/zeroplus-logic-cube/protocol.c @@ -21,14 +21,13 @@ #include #include "protocol.h" -SR_PRIV unsigned int get_memory_size(int type) +SR_PRIV size_t get_memory_size(int type) { if (type == MEMORY_SIZE_8K) return (8 * 1024); - else if (type <= MEMORY_SIZE_8M) + if (type <= MEMORY_SIZE_8M) return (32 * 1024) << type; - else - return 0; + return 0; } static int clz(unsigned int x) @@ -59,6 +58,8 @@ static int clz(unsigned int x) SR_PRIV int set_limit_samples(struct dev_context *devc, uint64_t samples) { + size_t mem_kb; + if (samples > devc->max_sample_depth) samples = devc->max_sample_depth; @@ -71,8 +72,8 @@ SR_PRIV int set_limit_samples(struct dev_context *devc, uint64_t samples) else devc->memory_size = 19 - clz(samples - 1); - sr_info("Setting memory size to %dK.", - get_memory_size(devc->memory_size) / 1024); + mem_kb = get_memory_size(devc->memory_size) / 1024; + sr_info("Setting memory size to %zuK.", mem_kb); analyzer_set_memory_size(devc->memory_size); diff --git a/src/hardware/zeroplus-logic-cube/protocol.h b/src/hardware/zeroplus-logic-cube/protocol.h index e179f1f8..b0c358be 100644 --- a/src/hardware/zeroplus-logic-cube/protocol.h +++ b/src/hardware/zeroplus-logic-cube/protocol.h @@ -30,24 +30,21 @@ #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); diff --git a/src/hwdriver.c b/src/hwdriver.c index 572748ac..068a215a 100644 --- a/src/hwdriver.c +++ b/src/hwdriver.c @@ -214,6 +214,8 @@ static struct sr_key_info sr_key_info_config[] = { "Power Target", NULL}, {SR_CONF_RESISTANCE_TARGET, SR_T_FLOAT, "resistance_target", "Resistance Target", NULL}, + {SR_CONF_OVER_CURRENT_PROTECTION_DELAY, SR_T_FLOAT, "ocp_delay", + "Over-current protection delay", NULL}, /* Special stuff */ {SR_CONF_SESSIONFILE, SR_T_STRING, "sessionfile", @@ -331,6 +333,8 @@ SR_PRIV const GVariantType *sr_variant_type_get(int datatype) switch (datatype) { case SR_T_INT32: return G_VARIANT_TYPE_INT32; + case SR_T_UINT32: + return G_VARIANT_TYPE_UINT32; case SR_T_UINT64: return G_VARIANT_TYPE_UINT64; case SR_T_STRING: diff --git a/src/input/csv.c b/src/input/csv.c index 4dcc77b8..66ac41dc 100644 --- a/src/input/csv.c +++ b/src/input/csv.c @@ -379,8 +379,6 @@ static void set_analog_value(struct context *inc, size_t ch_idx, csv_analog_t va { if (ch_idx >= inc->analog_channels) return; - if (!value) - return; inc->analog_sample_buffer[ch_idx * inc->analog_datafeed_buf_size] = value; } @@ -1454,7 +1452,7 @@ static int initial_parse(const struct sr_input *in, GString *buf) inc->analog_datafeed_buf_size /= sample_size; inc->analog_datafeed_buf_size /= inc->analog_channels; sample_count = inc->analog_channels * inc->analog_datafeed_buf_size; - inc->analog_datafeed_buffer = g_malloc0(sample_count * sample_size); + inc->analog_datafeed_buffer = g_malloc(sample_count * sample_size); if (!inc->analog_datafeed_buffer) { sr_err("Cannot allocate datafeed send buffer (analog)."); ret = SR_ERR_MALLOC; diff --git a/src/input/feed_queue.c b/src/input/feed_queue.c index b25fa77a..d62f3f6b 100644 --- a/src/input/feed_queue.c +++ b/src/input/feed_queue.c @@ -57,14 +57,14 @@ SR_API struct feed_queue_logic *feed_queue_logic_alloc( return q; } -SR_API int feed_queue_logic_submit(struct feed_queue_logic *q, - const uint8_t *data, size_t count) +SR_API int feed_queue_logic_submit_one(struct feed_queue_logic *q, + const uint8_t *data, size_t repeat_count) { uint8_t *wrptr; int ret; wrptr = &q->data_bytes[q->fill_count * q->unit_size]; - while (count--) { + while (repeat_count--) { memcpy(wrptr, data, q->unit_size); wrptr += q->unit_size; q->fill_count++; @@ -79,6 +79,35 @@ SR_API int feed_queue_logic_submit(struct feed_queue_logic *q, return SR_OK; } +SR_API int feed_queue_logic_submit_many(struct feed_queue_logic *q, + const uint8_t *data, size_t samples_count) +{ + uint8_t *wrptr; + size_t space, copy_count; + int ret; + + wrptr = &q->data_bytes[q->fill_count * q->unit_size]; + while (samples_count) { + space = q->alloc_count - q->fill_count; + copy_count = samples_count; + if (copy_count > space) + copy_count = space; + memcpy(wrptr, data, copy_count * q->unit_size); + data += copy_count * q->unit_size; + samples_count -= copy_count; + wrptr += copy_count * q->unit_size; + q->fill_count += copy_count; + if (q->fill_count == q->alloc_count) { + ret = feed_queue_logic_flush(q); + if (ret != SR_OK) + return ret; + wrptr = &q->data_bytes[0]; + } + } + + return SR_OK; +} + SR_API int feed_queue_logic_flush(struct feed_queue_logic *q) { int ret; @@ -162,12 +191,51 @@ SR_API struct feed_queue_analog *feed_queue_analog_alloc( return q; } -SR_API int feed_queue_analog_submit(struct feed_queue_analog *q, - float data, size_t count) +SR_API int feed_queue_analog_mq_unit(struct feed_queue_analog *q, + enum sr_mq mq, enum sr_mqflag mq_flag, enum sr_unit unit) +{ + int ret; + + if (!q) + return SR_ERR_ARG; + + ret = feed_queue_analog_flush(q); + if (ret != SR_OK) + return ret; + + q->meaning.mq = mq; + q->meaning.mqflags = mq_flag; + q->meaning.unit = unit; + + return SR_OK; +} + +SR_API int feed_queue_analog_scale_offset(struct feed_queue_analog *q, + const struct sr_rational *scale, const struct sr_rational *offset) +{ + int ret; + + if (!q) + return SR_ERR_ARG; + + ret = feed_queue_analog_flush(q); + if (ret != SR_OK) + return ret; + + if (scale) + q->encoding.scale = *scale; + if (offset) + q->encoding.offset = *offset; + + return SR_OK; +} + +SR_API int feed_queue_analog_submit_one(struct feed_queue_analog *q, + float data, size_t repeat_count) { int ret; - while (count--) { + while (repeat_count--) { q->data_values[q->fill_count++] = data; if (q->fill_count == q->alloc_count) { ret = feed_queue_analog_flush(q); diff --git a/src/input/input.c b/src/input/input.c index c4957e76..cd0db1f1 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -63,33 +63,35 @@ */ /** @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, }; diff --git a/src/input/protocoldata.c b/src/input/protocoldata.c new file mode 100644 index 00000000..905e33e4 --- /dev/null +++ b/src/input/protocoldata.c @@ -0,0 +1,3633 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2019-2023 Gerhard Sittig + * + * 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 . + */ + +/* + * 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 +#include +#include +#include + +#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, +}; diff --git a/src/input/stf.c b/src/input/stf.c index 431b0cbc..ddef5de6 100644 --- a/src/input/stf.c +++ b/src/input/stf.c @@ -623,7 +623,7 @@ static void add_sample(const struct sr_input *in, uint16_t data, size_t count) count -= inc->submit.samples_to_trigger; } if (send_first) { - (void)feed_queue_logic_submit(inc->submit.feed, + (void)feed_queue_logic_submit_one(inc->submit.feed, unit_buffer, send_first); inc->submit.submit_count += send_first; inc->submit.samples_to_trigger -= send_first; @@ -632,7 +632,7 @@ static void add_sample(const struct sr_input *in, uint16_t data, size_t count) feed_queue_logic_send_trigger(inc->submit.feed); } if (count) { - (void)feed_queue_logic_submit(inc->submit.feed, + (void)feed_queue_logic_submit_one(inc->submit.feed, unit_buffer, count); inc->submit.submit_count += count; if (inc->submit.samples_to_trigger) diff --git a/src/input/trace32_ad.c b/src/input/trace32_ad.c index 3a4e1cd4..ba89ad2c 100644 --- a/src/input/trace32_ad.c +++ b/src/input/trace32_ad.c @@ -163,7 +163,7 @@ static int init(struct sr_input *in, GHashTable *options) /* Calculate the desired timestamp scaling factor. */ inc->samplerate = 1000000 * - g_variant_get_uint32(g_hash_table_lookup(options, "samplerate")); + g_variant_get_uint64(g_hash_table_lookup(options, "samplerate")); inc->timestamp_scale = ((1 / TIMESTAMP_RESOLUTION) / (double)inc->samplerate); @@ -891,7 +891,7 @@ static const struct sr_option *get_options(void) options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); options[11].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); - options[12].def = g_variant_ref_sink(g_variant_new_uint32(DEFAULT_SAMPLERATE)); + options[12].def = g_variant_ref_sink(g_variant_new_uint64(DEFAULT_SAMPLERATE)); } return options; diff --git a/src/input/vcd.c b/src/input/vcd.c index 1064ad6a..d5471a47 100644 --- a/src/input/vcd.c +++ b/src/input/vcd.c @@ -152,11 +152,6 @@ struct context { } conv_bits; GString *scope_prefix; struct feed_queue_logic *feed_logic; - struct split_state { - size_t alloced; - char **words; - gboolean in_use; - } split; struct ts_stats { size_t total_ts_seen; uint64_t last_ts_value; @@ -457,6 +452,11 @@ static void check_remove_bom(GString *buf) /* * Reads a single VCD section from input file and parses it to name/contents. * e.g. $timescale 1ps $end => "timescale" "1ps" + * + * The section (its content and its opening/closing markers) can span + * multiple text lines. This routine must not modify the caller's input + * buffer. Executes potentially multiple times on the same input data, + * and executes outside of the processing of the file's data section. */ static gboolean parse_section(GString *buf, char **name, char **contents) { @@ -521,142 +521,6 @@ static gboolean parse_section(GString *buf, char **name, char **contents) return status; } -/* - * The glib routine which splits an input text into a list of words also - * "provides empty strings" which application code then needs to remove. - * And copies of the input text get allocated for all words. - * - * The repeated memory allocation is acceptable for small workloads like - * parsing the header sections. But the heavy lifting for sample data is - * done by DIY code to speedup execution. The use of glib routines would - * severely hurt throughput. Allocated memory gets re-used while a strict - * ping-pong pattern is assumed (each text line of input data enters and - * leaves in a strict symmetrical manner, due to the organization of the - * receive() routine and parse calls). - */ - -/* Remove empty parts from an array returned by g_strsplit(). */ -static void remove_empty_parts(gchar **parts) -{ - gchar **src, **dest; - - src = dest = parts; - while (*src) { - if (!**src) { - g_free(*src); - } else { - if (dest != src) - *dest = *src; - dest++; - } - src++; - } - *dest = NULL; -} - -static char **split_text_line(struct context *inc, char *text, size_t *count) -{ - struct split_state *state; - size_t counted, alloced, wanted; - char **words, *p, **new_words; - - state = &inc->split; - - if (count) - *count = 0; - - if (state->in_use) { - sr_dbg("coding error, split() called while \"in use\"."); - return NULL; - } - - /* - * Seed allocation when invoked for the first time. Assume - * simple logic data, start with a few words per line. Will - * automatically adjust with subsequent use. - */ - if (!state->alloced) { - alloced = 20; - words = g_malloc(sizeof(words[0]) * alloced); - if (!words) - return NULL; - state->alloced = alloced; - state->words = words; - } - - /* Start with most recently allocated word list space. */ - alloced = state->alloced; - words = state->words; - counted = 0; - - /* As long as more input text remains ... */ - p = text; - while (*p) { - /* Resize word list if needed. Just double the size. */ - if (counted + 1 >= alloced) { - wanted = 2 * alloced; - new_words = g_realloc(words, sizeof(words[0]) * wanted); - if (!new_words) { - return NULL; - } - words = new_words; - alloced = wanted; - state->words = words; - state->alloced = alloced; - } - - /* Skip leading spaces. */ - while (g_ascii_isspace(*p)) - p++; - if (!*p) - break; - - /* Add found word to word list. */ - words[counted++] = p; - - /* Find end of the word. Terminate loop upon EOS. */ - while (*p && !g_ascii_isspace(*p)) - p++; - if (!*p) - break; - - /* More text follows. Terminate the word. */ - *p++ = '\0'; - } - - /* - * NULL terminate the word list. Provide its length so that - * calling code need not re-iterate the list to get the count. - */ - words[counted] = NULL; - if (count) - *count = counted; - state->in_use = TRUE; - - return words; -} - -static void free_text_split(struct context *inc, char **words) -{ - struct split_state *state; - - state = &inc->split; - - if (words && words != state->words) { - sr_dbg("coding error, free() arg differs from split() result."); - } - - /* "Double free" finally releases the memory. */ - if (!state->in_use) { - g_free(state->words); - state->words = NULL; - state->alloced = 0; - } - - /* Mark as no longer in use. */ - state->in_use = FALSE; -} - static gboolean have_header(GString *buf) { static const char *enddef_txt = "$enddefinitions"; @@ -670,7 +534,14 @@ static gboolean have_header(GString *buf) return FALSE; p += strlen(enddef_txt); - /* Search for end of section (content expected to be empty). */ + /* + * Search for end of section (content expected to be empty). + * Uses DIY logic to scan for the literals' presence including + * empty space between keywords. MUST NOT modify the caller's + * input data, potentially executes several times on the same + * receive buffer, and executes outside of the processing the + * file's data section. + */ p_stop = &buf->str[buf->len]; p_stop -= strlen(end_txt); while (p < p_stop && g_ascii_isspace(*p)) @@ -733,8 +604,7 @@ static int parse_timescale(struct context *inc, char *contents) */ static int parse_scope(struct context *inc, char *contents, gboolean is_up) { - char *sep_pos, *name_pos; - char **parts; + char *sep_pos, *name_pos, *type_pos; size_t length; /* @@ -774,15 +644,17 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up) * was emitted by libsigrok's VCD output module. */ sr_spew("$scope, got: \"%s\"", contents); - parts = g_strsplit_set(contents, " \r\n\t", 0); - remove_empty_parts(parts); - length = g_strv_length(parts); - if (length != 2) { - sr_err("Unsupported 'scope' syntax: %s", contents); - g_strfreev(parts); + type_pos = sr_text_next_word(contents, &contents); + if (!type_pos) { + sr_err("Cannot parse 'scope' directive"); + return SR_ERR_DATA; + } + name_pos = sr_text_next_word(contents, &contents); + if (!name_pos || contents) { + sr_err("Cannot parse 'scope' directive"); return SR_ERR_DATA; } - name_pos = parts[1]; + if (strcmp(name_pos, PACKAGE_NAME) == 0) { sr_info("Skipping scope with application's package name: %s", name_pos); @@ -794,7 +666,6 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up) g_string_append_printf(inc->scope_prefix, "%s%c%c", name_pos, SCOPE_SEP, '\0'); } - g_strfreev(parts); sr_dbg("$scope, prefix now: \"%s\"", inc->scope_prefix->str); return SR_OK; @@ -808,8 +679,6 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up) */ static int parse_header_var(struct context *inc, char *contents) { - char **parts; - size_t length; char *type, *size_txt, *id, *ref, *idx; gboolean is_reg, is_wire, is_real, is_int; gboolean is_str; @@ -821,22 +690,18 @@ static int parse_header_var(struct context *inc, char *contents) * Format of $var or $reg header specs: * $var type size identifier reference [opt-index] $end */ - parts = g_strsplit_set(contents, " \r\n\t", 0); - remove_empty_parts(parts); - length = g_strv_length(parts); - if (length != 4 && length != 5) { + type = sr_text_next_word(contents, &contents); + size_txt = sr_text_next_word(contents, &contents); + id = sr_text_next_word(contents, &contents); + ref = sr_text_next_word(contents, &contents); + idx = sr_text_next_word(contents, &contents); + if (idx && !*idx) + idx = NULL; + if (!type || !size_txt || !id || !ref || contents) { sr_warn("$var section should have 4 or 5 items"); - g_strfreev(parts); return SR_ERR_DATA; } - type = parts[0]; - size_txt = parts[1]; - id = parts[2]; - ref = parts[3]; - idx = parts[4]; - if (idx && !*idx) - idx = NULL; is_reg = g_strcmp0(type, "reg") == 0; is_wire = g_strcmp0(type, "wire") == 0; is_real = g_strcmp0(type, "real") == 0; @@ -852,11 +717,9 @@ static int parse_header_var(struct context *inc, char *contents) id, ref, idx ? idx : "", type); inc->ignored_signals = g_slist_append(inc->ignored_signals, g_strdup(id)); - g_strfreev(parts); return SR_OK; } else { sr_err("Unsupported signal type: '%s'", type); - g_strfreev(parts); return SR_ERR_DATA; } @@ -882,7 +745,6 @@ static int parse_header_var(struct context *inc, char *contents) } if (!size) { sr_warn("Unsupported signal size: '%s'", size_txt); - g_strfreev(parts); return SR_ERR_DATA; } if (inc->conv_bits.max_bits < size) @@ -893,7 +755,6 @@ static int parse_header_var(struct context *inc, char *contents) ref, idx ? idx : "", inc->options.maxchannels); inc->ignored_signals = g_slist_append(inc->ignored_signals, g_strdup(id)); - g_strfreev(parts); return SR_OK; } @@ -922,7 +783,6 @@ static int parse_header_var(struct context *inc, char *contents) vcd_ch->type == SR_CHANNEL_ANALOG ? "A" : "L", vcd_ch->array_index); inc->channels = g_slist_append(inc->channels, vcd_ch); - g_strfreev(parts); return SR_OK; } @@ -1292,7 +1152,7 @@ static void add_samples(const struct sr_input *in, size_t count, gboolean flush) inc = in->priv; if (inc->logic_count) { - feed_queue_logic_submit(inc->feed_logic, + feed_queue_logic_submit_one(inc->feed_logic, inc->current_logic, count); if (flush) feed_queue_logic_flush(inc->feed_logic); @@ -1305,7 +1165,7 @@ static void add_samples(const struct sr_input *in, size_t count, gboolean flush) if (!q) continue; value = inc->current_floats[vcd_ch->array_index]; - feed_queue_analog_submit(q, value, count); + feed_queue_analog_submit_one(q, value, count); if (flush) feed_queue_analog_flush(q); } @@ -1597,13 +1457,11 @@ static gboolean vcd_string_valid(const char *s) } /* Parse one text line of the data section. */ -static int parse_textline(const struct sr_input *in, char *lines) +static int parse_textline(const struct sr_input *in, char *line) { struct context *inc; int ret; - char **words; - size_t word_count, word_idx; - char *curr_word, *next_word, curr_first; + char *curr_word, curr_first; gboolean is_timestamp, is_section; gboolean is_real, is_multibit, is_singlebit, is_string; uint64_t timestamp; @@ -1613,30 +1471,33 @@ static int parse_textline(const struct sr_input *in, char *lines) inc = in->priv; /* - * Split the caller's text lines into a list of space separated - * words. Note that some of the branches consume the very next - * words as well, and assume that both adjacent words will be - * available when the first word is seen. This constraint applies - * to bit vector data, multi-bit integers and real (float) data, - * as well as single-bit data with whitespace before its - * identifier (if that's valid in VCD, we'd accept it here). + * Consume space separated words from a caller's text line. Note + * that many words are self contained, but some require another + * word to follow. This implementation assumes that both words + * (when involved) become available in the same invocation, that + * is that both words reside on the same text line of the file. * The fact that callers always pass complete text lines should - * make this assumption acceptable. + * make this assumption acceptable. No generator is known to + * split two corresponding words across text lines. + * + * This constraint applies to bit vector data, multi-bit integer + * and real (float) values, text strings, as well as single-bit + * values with whitespace before their identifiers (if that is + * valid in VCD, we'd accept it here; if generators don't create + * such input, then support for it does not harm). */ ret = SR_OK; - words = split_text_line(inc, lines, &word_count); - for (word_idx = 0; word_idx < word_count; word_idx++) { + while (line) { /* - * Make the next two words available, to simpilify code - * paths below. The second word is optional here. + * Lookup one word here which is mandatory. Locations + * below conditionally lookup another word as needed. */ - curr_word = words[word_idx]; - if (!curr_word && !curr_word[0]) + curr_word = sr_text_next_word(line, &line); + if (!curr_word) + break; + if (!*curr_word) continue; curr_first = g_ascii_tolower(curr_word[0]); - next_word = words[word_idx + 1]; - if (next_word && !next_word[0]) - next_word = NULL; /* * Optionally skip some sections that can be interleaved @@ -1819,8 +1680,7 @@ static int parse_textline(const struct sr_input *in, char *lines) float real_val; real_text = &curr_word[1]; - identifier = next_word; - word_idx++; + identifier = sr_text_next_word(line, &line); if (!*real_text || !identifier || !*identifier) { sr_err("Unexpected real format."); ret = SR_ERR_DATA; @@ -1854,8 +1714,7 @@ static int parse_textline(const struct sr_input *in, char *lines) * we may never unify code paths at all here. */ bits_text = &curr_word[1]; - identifier = next_word; - word_idx++; + identifier = sr_text_next_word(line, &line); if (!*bits_text || !identifier || !*identifier) { sr_err("Unexpected integer/vector format."); @@ -1940,10 +1799,8 @@ static int parse_textline(const struct sr_input *in, char *lines) break; } identifier = ++bits_text; - if (!*identifier) { - identifier = next_word; - word_idx++; - } + if (!*identifier) + identifier = sr_text_next_word(line, &line); if (!identifier || !*identifier) { sr_err("Identifier missing."); ret = SR_ERR_DATA; @@ -1965,8 +1822,7 @@ static int parse_textline(const struct sr_input *in, char *lines) const char *str_value; str_value = &curr_word[1]; - identifier = next_word; - word_idx++; + identifier = sr_text_next_word(line, &line); if (!vcd_string_valid(str_value)) { sr_err("Invalid string data: %s", str_value); ret = SR_ERR_DATA; @@ -1993,7 +1849,6 @@ static int parse_textline(const struct sr_input *in, char *lines) ret = SR_ERR_DATA; break; } - free_text_split(inc, words); return ret; } @@ -2004,8 +1859,8 @@ static int process_buffer(struct sr_input *in, gboolean is_eof) uint64_t samplerate; GVariant *gvar; int ret; - char *rdptr, *endptr, *trimptr; - size_t rdlen; + char *rdptr, *line; + size_t taken, rdlen; inc = in->priv; @@ -2036,28 +1891,19 @@ static int process_buffer(struct sr_input *in, gboolean is_eof) /* Find and process complete text lines in the input data. */ ret = SR_OK; rdptr = in->buf->str; - while (TRUE) { + taken = 0; + while (rdptr) { rdlen = &in->buf->str[in->buf->len] - rdptr; - endptr = g_strstr_len(rdptr, rdlen, "\n"); - if (!endptr) + line = sr_text_next_line(rdptr, rdlen, &rdptr, &taken); + if (!line) break; - trimptr = endptr; - *endptr++ = '\0'; - while (g_ascii_isspace(*rdptr)) - rdptr++; - while (trimptr > rdptr && g_ascii_isspace(trimptr[-1])) - *(--trimptr) = '\0'; - if (!*rdptr) { - rdptr = endptr; + if (!*line) continue; - } - ret = parse_textline(in, rdptr); - rdptr = endptr; + ret = parse_textline(in, line); if (ret != SR_OK) break; } - rdlen = rdptr - in->buf->str; - g_string_erase(in->buf, 0, rdlen); + g_string_erase(in->buf, 0, taken); return ret; } @@ -2210,7 +2056,6 @@ static void cleanup(struct sr_input *in) inc->scope_prefix = NULL; g_slist_free_full(inc->ignored_signals, g_free); inc->ignored_signals = NULL; - free_text_split(inc, NULL); } static int reset(struct sr_input *in) diff --git a/src/libsigrok-internal.h b/src/libsigrok-internal.h index e1cae559..68a45f27 100644 --- a/src/libsigrok-internal.h +++ b/src/libsigrok-internal.h @@ -172,6 +172,23 @@ static inline uint32_t read_u24le(const uint8_t *p) return u; } +/** + * Read a 24 bits big endian unsigned integer out of memory. + * @param x a pointer to the input memory + * @return the corresponding unsigned integer + */ +static inline uint32_t read_u24be(const uint8_t *p) +{ + uint32_t u; + + u = 0; + u <<= 8; u |= p[0]; + u <<= 8; u |= p[1]; + u <<= 8; u |= p[2]; + + return u; +} + /** * Read a 32 bits big endian unsigned integer out of memory. * @param x a pointer to the input memory @@ -615,6 +632,30 @@ static inline uint8_t read_u8_inc(const uint8_t **p) return v; } +/** + * Read unsigned 8bit integer, check length, increment read position. + * @param[in, out] p Pointer into byte stream. + * @param[in, out] l Remaining input payload length. + * @return Retrieved integer value, unsigned. + */ +static inline uint8_t read_u8_inc_len(const uint8_t **p, size_t *l) +{ + uint8_t v; + + if (!p || !*p) + return 0; + if (l && *l < sizeof(v)) { + *l = 0; + return 0; + } + v = read_u8(*p); + *p += sizeof(v); + if (l) + *l -= sizeof(v); + + return v; +} + /** * Read signed 8bit integer from raw memory, increment read position. * @param[in, out] p Pointer into byte stream. @@ -666,6 +707,30 @@ static inline uint16_t read_u16le_inc(const uint8_t **p) return v; } +/** + * Read unsigned 16bit integer (LE format), check length, increment position. + * @param[in, out] p Pointer into byte stream. + * @param[in, out] l Remaining input payload length. + * @return Retrieved integer value, unsigned. + */ +static inline uint16_t read_u16le_inc_len(const uint8_t **p, size_t *l) +{ + uint16_t v; + + if (!p || !*p) + return 0; + if (l && *l < sizeof(v)) { + *l = 0; + return 0; + } + v = read_u16le(*p); + *p += sizeof(v); + if (l) + *l -= sizeof(v); + + return v; +} + /** * Read signed 16bit integer from raw memory (big endian format), increment read position. * @param[in, out] p Pointer into byte stream. @@ -1523,6 +1588,13 @@ struct sr_usb_dev_inst { }; #endif +/** Raw TCP device instance. */ +struct sr_tcp_dev_inst { + char *host_addr; /**!< IP address or host name */ + char *tcp_port; /**!< TCP port number/name */ + int sock_fd; /**!< TCP socket's file descriptor */ +}; + struct sr_serial_dev_inst; #ifdef HAVE_SERIAL_COMM struct ser_lib_functions; @@ -1572,6 +1644,9 @@ struct sr_serial_dev_inst { SER_BT_CONN_BLE122, /**!< BLE, BLE122 module, indications */ SER_BT_CONN_NRF51, /**!< BLE, Nordic nRF51, notifications */ SER_BT_CONN_CC254x, /**!< BLE, TI CC254x, notifications */ + SER_BT_CONN_AC6328, /**!< BLE, JL AC6328B, notifications */ + SER_BT_CONN_DIALOG, /**!< BLE, dialog DA14580, notifications */ + SER_BT_CONN_NOTIFY, /**!< BLE, generic notifications */ SER_BT_CONN_MAX, /**!< sentinel */ } bt_conn_type; char *bt_addr_local; @@ -1581,9 +1656,11 @@ struct sr_serial_dev_inst { uint16_t bt_notify_handle_write; uint16_t bt_notify_handle_cccd; uint16_t bt_notify_value_cccd; + uint16_t bt_ble_mtu; struct sr_bt_desc *bt_desc; GSList *bt_source_args; #endif + struct sr_tcp_dev_inst *tcp_dev; }; #endif @@ -1601,17 +1678,20 @@ struct drv_context { /*--- log.c -----------------------------------------------------------------*/ +/* Provide a macro for other source code locations to re-use. */ #if defined(_WIN32) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) /* * On MinGW, we need to specify the gnu_printf format flavor or GCC * will assume non-standard Microsoft printf syntax. */ -SR_PRIV int sr_log(int loglevel, const char *format, ...) - __attribute__((__format__ (__gnu_printf__, 2, 3))); +#define ATTR_FMT_PRINTF(fmt_pos, arg_pos) \ + __attribute__((__format__ (__gnu_printf__, fmt_pos, arg_pos))) #else -SR_PRIV int sr_log(int loglevel, const char *format, ...) G_GNUC_PRINTF(2, 3); +#define ATTR_FMT_PRINTF(fmt_pos, arg_pos) G_GNUC_PRINTF(fmt_pos, arg_pos) #endif +SR_PRIV int sr_log(int loglevel, const char *format, ...) ATTR_FMT_PRINTF(2, 3); + /* Message logging helpers with subsystem-specific prefix string. */ #define sr_spew(...) sr_log(SR_LOG_SPEW, LOG_PREFIX ": " __VA_ARGS__) #define sr_dbg(...) sr_log(SR_LOG_DBG, LOG_PREFIX ": " __VA_ARGS__) @@ -2043,6 +2123,8 @@ SR_PRIV int ser_name_is_hid(struct sr_serial_dev_inst *serial); extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_hid; SR_PRIV int ser_name_is_bt(struct sr_serial_dev_inst *serial); extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_bt; +SR_PRIV int ser_name_is_tcpraw(struct sr_serial_dev_inst *serial); +extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_tcpraw; #ifdef HAVE_LIBHIDAPI struct vid_pid_item { @@ -2096,7 +2178,8 @@ SR_PRIV int sr_bt_config_addr_remote(struct sr_bt_desc *desc, const char *addr); SR_PRIV int sr_bt_config_rfcomm(struct sr_bt_desc *desc, size_t channel); SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc, uint16_t read_handle, uint16_t write_handle, - uint16_t cccd_handle, uint16_t cccd_value); + uint16_t cccd_handle, uint16_t cccd_value, + uint16_t ble_mtu); SR_PRIV int sr_bt_scan_le(struct sr_bt_desc *desc, int duration); SR_PRIV int sr_bt_scan_bt(struct sr_bt_desc *desc, int duration); @@ -2140,6 +2223,27 @@ SR_PRIV gboolean usb_match_manuf_prod(libusb_device *dev, const char *manufacturer, const char *product); #endif +/*--- tcp.c -----------------------------------------------------------------*/ + +SR_PRIV gboolean sr_fd_is_readable(int fd); + +SR_PRIV struct sr_tcp_dev_inst *sr_tcp_dev_inst_new( + const char *host_addr, const char *tcp_port); +SR_PRIV void sr_tcp_dev_inst_free(struct sr_tcp_dev_inst *tcp); +SR_PRIV int sr_tcp_get_port_path(struct sr_tcp_dev_inst *tcp, + const char *prefix, char separator, char *path, size_t path_len); +SR_PRIV int sr_tcp_connect(struct sr_tcp_dev_inst *tcp); +SR_PRIV int sr_tcp_disconnect(struct sr_tcp_dev_inst *tcp); +SR_PRIV int sr_tcp_write_bytes(struct sr_tcp_dev_inst *tcp, + const uint8_t *data, size_t dlen); +SR_PRIV int sr_tcp_read_bytes(struct sr_tcp_dev_inst *tcp, + uint8_t *data, size_t dlen, gboolean nonblocking); +SR_PRIV int sr_tcp_source_add(struct sr_session *session, + struct sr_tcp_dev_inst *tcp, int events, int timeout, + sr_receive_data_callback cb, void *cb_data); +SR_PRIV int sr_tcp_source_remove(struct sr_session *session, + struct sr_tcp_dev_inst *tcp); + /*--- binary_helpers.c ------------------------------------------------------*/ /** Binary value type */ @@ -2147,68 +2251,47 @@ enum binary_value_type { BVT_INVALID, BVT_UINT8, - BVT_BE_UINT8 = BVT_UINT8, - BVT_LE_UINT8 = BVT_UINT8, BVT_BE_UINT16, + BVT_BE_UINT24, BVT_BE_UINT32, - BVT_BE_UINT64, - BVT_BE_FLOAT, BVT_LE_UINT16, + BVT_LE_UINT24, BVT_LE_UINT32, - BVT_LE_UINT64, - BVT_LE_FLOAT, }; /** Binary value specification */ struct binary_value_spec { - /** Offset into binary blob */ - size_t offset; - /** Data type to decode */ - enum binary_value_type type; - /** Scale factor to get native units */ - float scale; -}; - -/** Binary channel definition */ -struct binary_analog_channel { - /** Channel name */ - const char *name; - /** Binary value in data stream */ - struct binary_value_spec spec; - /** Significant digits */ - int digits; - /** Measured quantity */ - enum sr_mq mq; - /** Measured unit */ - enum sr_unit unit; + size_t offset; /**!< Offset into binary image */ + enum binary_value_type type; /**!< Data type to decode */ }; /** - * Read extract a value from a binary blob. + * Read extract a value from a binary data image, ensuring no out-of-bounds + * read happens. + * + * @param[out] out Pointer to output buffer (conversion result) + * @param[in] spec Binary value specification + * @param[in] data Pointer to binary input data + * @param[in] length Size of binary input data * - * @param out Pointer to output buffer. - * @param spec Binary value specification - * @param data Pointer to binary blob - * @param length Size of binary blob * @return SR_OK on success, SR_ERR_* error code on failure. */ -SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const void *data, size_t length); +SR_PRIV int bv_get_value_len(float *out, const struct binary_value_spec *spec, + const uint8_t *data, size_t length); /** - * Send an analog channel packet based on a binary analog channel - * specification. + * Read extract a value from a binary data image, without bound check. + * + * @param[out] out Pointer to output buffer (conversion result) + * @param[in] spec Binary value specification + * @param[in] data Pointer to binary input data * - * @param sdi Device instance - * @param ch Sigrok channel - * @param spec Channel specification - * @param data Pointer to binary blob - * @param length Size of binary blob * @return SR_OK on success, SR_ERR_* error code on failure. */ -SR_PRIV int bv_send_analog_channel(const struct sr_dev_inst *sdi, struct sr_channel *ch, - const struct binary_analog_channel *spec, const void *data, size_t length); +SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, + const uint8_t *data); /*--- crc.c -----------------------------------------------------------------*/ @@ -2771,8 +2854,10 @@ struct feed_queue_analog; SR_API struct feed_queue_logic *feed_queue_logic_alloc( const struct sr_dev_inst *sdi, size_t sample_count, size_t unit_size); -SR_API int feed_queue_logic_submit(struct feed_queue_logic *q, - const uint8_t *data, size_t count); +SR_API int feed_queue_logic_submit_one(struct feed_queue_logic *q, + const uint8_t *data, size_t repeat_count); +SR_API int feed_queue_logic_submit_many(struct feed_queue_logic *q, + const uint8_t *data, size_t samples_count); SR_API int feed_queue_logic_flush(struct feed_queue_logic *q); SR_API int feed_queue_logic_send_trigger(struct feed_queue_logic *q); SR_API void feed_queue_logic_free(struct feed_queue_logic *q); @@ -2780,8 +2865,12 @@ SR_API void feed_queue_logic_free(struct feed_queue_logic *q); SR_API struct feed_queue_analog *feed_queue_analog_alloc( const struct sr_dev_inst *sdi, size_t sample_count, int digits, struct sr_channel *ch); -SR_API int feed_queue_analog_submit(struct feed_queue_analog *q, - float data, size_t count); +SR_API int feed_queue_analog_mq_unit(struct feed_queue_analog *q, + enum sr_mq mq, enum sr_mqflag mq_flag, enum sr_unit unit); +SR_API int feed_queue_analog_scale_offset(struct feed_queue_analog *q, + const struct sr_rational *scale, const struct sr_rational *offset); +SR_API int feed_queue_analog_submit_one(struct feed_queue_analog *q, + float data, size_t repeat_count); SR_API int feed_queue_analog_flush(struct feed_queue_analog *q); SR_API void feed_queue_analog_free(struct feed_queue_analog *q); diff --git a/src/log.c b/src/log.c index 701df645..1b01afee 100644 --- a/src/log.c +++ b/src/log.c @@ -184,16 +184,24 @@ SR_API int sr_log_callback_get(sr_log_callback *cb, void **cb_data) static int sr_logv(void *cb_data, int loglevel, const char *format, va_list args) { + int ret; uint64_t elapsed_us, minutes; unsigned int rest_us, seconds, microseconds; - char *raw_output, *output; - int raw_len, raw_idx, idx, ret; + char *raw_output, *output, c; + ssize_t print_len; + size_t raw_len; + const char *raw_ptr; + char *out_ptr; /* This specific log callback doesn't need the void pointer data. */ (void)cb_data; (void)loglevel; + /* Prefix with 'sr:'. Optionally prefix with timestamp. */ + ret = fputs("sr: ", stderr); + if (ret < 0) + return SR_ERR; if (cur_loglevel >= LOGLEVEL_TIMESTAMP) { elapsed_us = g_get_monotonic_time() - sr_log_start_time; @@ -202,30 +210,41 @@ static int sr_logv(void *cb_data, int loglevel, const char *format, va_list args seconds = rest_us / G_TIME_SPAN_SECOND; microseconds = rest_us % G_TIME_SPAN_SECOND; - ret = g_fprintf(stderr, "sr: [%.2" PRIu64 ":%.2u.%.6u] ", + ret = g_fprintf(stderr, "[%.2" PRIu64 ":%.2u.%.6u] ", minutes, seconds, microseconds); - } else { - ret = fputs("sr: ", stderr); + if (ret < 0) + return SR_ERR; } - if (ret < 0 || (raw_len = g_vasprintf(&raw_output, format, args)) < 0) + /* Print the caller's message into a local buffer. */ + raw_output = NULL; + print_len = g_vasprintf(&raw_output, format, args); + if (print_len < 0) { + g_free(raw_output); return SR_ERR; + } + raw_len = (size_t)print_len; - output = g_malloc0(raw_len + 1); - - /* Copy the string without any unwanted newlines. */ - raw_idx = idx = 0; - while (raw_idx < raw_len) { - if (raw_output[raw_idx] != '\n') { - output[idx] = raw_output[raw_idx]; - idx++; - } - raw_idx++; + /* Copy the string. Strip unwanted line breaks. */ + output = g_malloc(raw_len + 1); + if (!output) { + g_free(raw_output); + return SR_ERR; } + out_ptr = output; + raw_ptr = raw_output; + while (*raw_ptr) { + c = *raw_ptr++; + if (c == '\r' || c == '\n') + continue; + *out_ptr++ = c; + } + *out_ptr = '\0'; + g_free(raw_output); + /* Print the trimmed output text. */ g_fprintf(stderr, "%s\n", output); fflush(stderr); - g_free(raw_output); g_free(output); return SR_OK; @@ -241,6 +260,10 @@ SR_PRIV int sr_log(int loglevel, const char *format, ...) if (loglevel > cur_loglevel) return SR_OK; + /* Silently succeed when no logging callback is registered. */ + if (!sr_log_cb) + return SR_OK; + va_start(args, format); ret = sr_log_cb(sr_log_cb_data, loglevel, format, args); va_end(args); diff --git a/src/output/csv.c b/src/output/csv.c index 848595f9..c4801ef7 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -252,11 +252,13 @@ static GString *gen_header(const struct sr_output *o, /* Some metadata */ if (ctx->header && !ctx->did_header) { /* save_gnuplot knows how many lines we print. */ + time_t secs; + secs = hdr->starttime.tv_sec; g_string_append_printf(header, "%s CSV generated by %s %s\n%s from %s on %s", ctx->comment, PACKAGE_NAME, sr_package_version_string_get(), ctx->comment, - ctx->title, ctime(&hdr->starttime.tv_sec)); + ctx->title, ctime(&secs)); /* Columns / channels */ channels = o->sdi ? o->sdi->channels : NULL; diff --git a/src/output/srzip.c b/src/output/srzip.c index 28223c7c..3be0bccb 100644 --- a/src/output/srzip.c +++ b/src/output/srzip.c @@ -38,7 +38,7 @@ struct out_context { size_t analog_ch_count; gint *analog_index_map; struct logic_buff { - size_t unit_size; + size_t zip_unit_size; size_t alloc_size; uint8_t *samples; size_t fill_size; @@ -204,14 +204,14 @@ static int zip_create(const struct sr_output *o) * during execution. This simplifies other locations. */ alloc_size = CHUNK_SIZE; - outc->logic_buff.unit_size = logic_channels; - outc->logic_buff.unit_size += 8 - 1; - outc->logic_buff.unit_size /= 8; + outc->logic_buff.zip_unit_size = logic_channels; + outc->logic_buff.zip_unit_size += 8 - 1; + outc->logic_buff.zip_unit_size /= 8; outc->logic_buff.samples = g_try_malloc0(alloc_size); if (!outc->logic_buff.samples) return SR_ERR_MALLOC; - if (outc->logic_buff.unit_size) - alloc_size /= outc->logic_buff.unit_size; + if (outc->logic_buff.zip_unit_size) + alloc_size /= outc->logic_buff.zip_unit_size; outc->logic_buff.alloc_size = alloc_size; outc->logic_buff.fill_size = 0; @@ -396,19 +396,61 @@ static int zip_append(const struct sr_output *o, * @returns SR_OK et al error codes. */ static int zip_append_queue(const struct sr_output *o, - uint8_t *buf, size_t unitsize, size_t length, gboolean flush) + const uint8_t *buf, size_t feed_unitsize, size_t length, + gboolean flush) { + static gboolean sizes_seen; + struct out_context *outc; struct logic_buff *buff; - size_t send_size, remain, copy_size; - uint8_t *wrptr, *rdptr; + size_t sample_copy_size, sample_skip_size, sample_pad_size; + size_t send_count, remain, copy_count; + const uint8_t *rdptr; + uint8_t *wrptr; int ret; + /* + * Check input parameters. Prepare to either grab data as is, + * or to adjust between differing input and output unit sizes. + * Diagnostics is rate limited for improved usability, assumes + * that session feeds are consistent across calls. Processing + * would cope with inconsistent calls though when required. + */ outc = o->priv; buff = &outc->logic_buff; - if (length && unitsize != buff->unit_size) { - sr_warn("Unexpected unit size, discarding logic data."); - return SR_ERR_ARG; + if (length) { + if (!sizes_seen) { + sr_info("output unit size %zu, feed unit size %zu.", + buff->zip_unit_size, feed_unitsize); + } + if (feed_unitsize > buff->zip_unit_size) { + if (!sizes_seen) + sr_info("Large unit size, discarding excess logic data."); + sample_copy_size = buff->zip_unit_size; + sample_skip_size = feed_unitsize - buff->zip_unit_size; + sample_pad_size = 0; + } else if (feed_unitsize < buff->zip_unit_size) { + if (!sizes_seen) + sr_info("Small unit size, padding logic data."); + sample_copy_size = feed_unitsize; + sample_skip_size = 0; + sample_pad_size = buff->zip_unit_size - feed_unitsize; + } else { + if (!sizes_seen) + sr_dbg("Matching unit size, passing logic data as is."); + sample_copy_size = buff->zip_unit_size; + sample_skip_size = 0; + sample_pad_size = 0; + } + if (sample_copy_size + sample_skip_size != feed_unitsize) { + sr_err("Inconsistent input unit size. Implementation flaw?"); + return SR_ERR_BUG; + } + if (sample_copy_size + sample_pad_size != buff->zip_unit_size) { + sr_err("Inconsistent output unit size. Implementation flaw?"); + return SR_ERR_BUG; + } + sizes_seen = TRUE; } /* @@ -416,32 +458,39 @@ static int zip_append_queue(const struct sr_output *o, * Flush to the ZIP archive when the buffer space is exhausted. */ rdptr = buf; - send_size = buff->unit_size ? length / buff->unit_size : 0; - while (send_size) { + send_count = feed_unitsize ? length / feed_unitsize : 0; + while (send_count) { remain = buff->alloc_size - buff->fill_size; + wrptr = &buff->samples[buff->fill_size * buff->zip_unit_size]; if (remain) { - wrptr = &buff->samples[buff->fill_size * buff->unit_size]; - copy_size = MIN(send_size, remain); - send_size -= copy_size; - buff->fill_size += copy_size; - memcpy(wrptr, rdptr, copy_size * buff->unit_size); - rdptr += copy_size * buff->unit_size; - remain -= copy_size; + copy_count = MIN(send_count, remain); + if (sample_skip_size || sample_pad_size) + copy_count = 1; + send_count -= copy_count; + buff->fill_size += copy_count; + memcpy(wrptr, rdptr, copy_count * sample_copy_size); + if (sample_pad_size) { + wrptr += sample_copy_size; + memset(wrptr, 0, sample_pad_size); + } + rdptr += copy_count * sample_copy_size; + if (sample_skip_size) + rdptr += sample_skip_size; + remain -= copy_count; } - if (send_size && !remain) { - ret = zip_append(o, buff->samples, buff->unit_size, - buff->fill_size * buff->unit_size); + if (send_count && !remain) { + ret = zip_append(o, buff->samples, buff->zip_unit_size, + buff->fill_size * buff->zip_unit_size); if (ret != SR_OK) return ret; buff->fill_size = 0; - remain = buff->alloc_size - buff->fill_size; } } /* Flush to the ZIP archive if the caller wants us to. */ if (flush && buff->fill_size) { - ret = zip_append(o, buff->samples, buff->unit_size, - buff->fill_size * buff->unit_size); + ret = zip_append(o, buff->samples, buff->zip_unit_size, + buff->fill_size * buff->zip_unit_size); if (ret != SR_OK) return ret; buff->fill_size = 0; @@ -664,8 +713,9 @@ static int receive(const struct sr_output *o, const struct sr_datafeed_packet *p outc->zip_created = TRUE; } logic = packet->payload; - ret = zip_append_queue(o, logic->data, - logic->unitsize, logic->length, FALSE); + ret = zip_append_queue(o, + logic->data, logic->unitsize, logic->length, + FALSE); if (ret != SR_OK) return ret; break; diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index be829af4..8d24ceea 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -38,6 +38,7 @@ static const char *scpi_vendors[][2] = { { "Keysight Technologies", "Keysight" }, { "PHILIPS", "Philips" }, { "RIGOL TECHNOLOGIES", "Rigol" }, + { "Siglent Technologies", "Siglent" }, }; /** diff --git a/src/scpi/scpi_serial.c b/src/scpi/scpi_serial.c index 067838ad..92d2c531 100644 --- a/src/scpi/scpi_serial.c +++ b/src/scpi/scpi_serial.c @@ -46,6 +46,7 @@ static const struct { { 0x0aad, 0x0117, "115200/8n1" }, /* R&S HMO series, previously branded as Hameg HMO */ { 0x0aad, 0x0118, "115200/8n1" }, /* R&S HMO series, previously branded as Hameg HMO */ { 0x0aad, 0x0119, "115200/8n1" }, /* R&S HMO series, previously branded as Hameg HMO */ + { 0x0aad, 0x0135, "115200/8n1" }, /* R&S HMC series, previously branded as Hameg HMC */ { 0x2184, 0x0030, "115200/8n1" }, /* GW-Instek GDM-8341 (VCP, SiLabs CP210x) */ { 0x2184, 0x0058, "115200/8n1" }, /* GW-Instek GDM-9061 (USBCDC mode) */ }; diff --git a/src/scpi/scpi_tcp.c b/src/scpi/scpi_tcp.c index 29e7a439..c85cf3eb 100644 --- a/src/scpi/scpi_tcp.c +++ b/src/scpi/scpi_tcp.c @@ -17,38 +17,26 @@ * along with this program. If not, see . */ -#include -#ifdef _WIN32 -#define _WIN32_WINNT 0x0501 -#include -#include -#endif -#include -#include -#include -#ifndef _WIN32 -#include -#include -#include -#include -#endif +#include "config.h" + #include +#include #include +#include + #include "libsigrok-internal.h" #include "scpi.h" #define LOG_PREFIX "scpi_tcp" -#define LENGTH_BYTES 4 +#define LENGTH_BYTES sizeof(uint32_t) struct scpi_tcp { - char *address; - char *port; - int socket; - char length_buf[LENGTH_BYTES]; - int length_bytes_read; - int response_length; - int response_bytes_read; + struct sr_tcp_dev_inst *tcp_dev; + uint8_t length_buf[LENGTH_BYTES]; + size_t length_bytes_read; + size_t response_length; + size_t response_bytes_read; }; static int scpi_tcp_dev_inst_new(void *priv, struct drv_context *drvc, @@ -65,9 +53,9 @@ static int scpi_tcp_dev_inst_new(void *priv, struct drv_context *drvc, return SR_ERR; } - tcp->address = g_strdup(params[1]); - tcp->port = g_strdup(params[2]); - tcp->socket = -1; + tcp->tcp_dev = sr_tcp_dev_inst_new(params[1], params[2]); + if (!tcp->tcp_dev) + return SR_ERR; return SR_OK; } @@ -75,42 +63,11 @@ static int scpi_tcp_dev_inst_new(void *priv, struct drv_context *drvc, static int scpi_tcp_open(struct sr_scpi_dev_inst *scpi) { struct scpi_tcp *tcp = scpi->priv; - struct addrinfo hints; - struct addrinfo *results, *res; - int err; + int ret; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - err = getaddrinfo(tcp->address, tcp->port, &hints, &results); - - if (err) { - sr_err("Address lookup failed: %s:%s: %s", tcp->address, tcp->port, - gai_strerror(err)); - return SR_ERR; - } - - for (res = results; res; res = res->ai_next) { - if ((tcp->socket = socket(res->ai_family, res->ai_socktype, - res->ai_protocol)) < 0) - continue; - if (connect(tcp->socket, res->ai_addr, res->ai_addrlen) != 0) { - close(tcp->socket); - tcp->socket = -1; - continue; - } - break; - } - - freeaddrinfo(results); - - if (tcp->socket < 0) { - sr_err("Failed to connect to %s:%s: %s", tcp->address, tcp->port, - g_strerror(errno)); - return SR_ERR; - } + ret = sr_tcp_connect(tcp->tcp_dev); + if (ret != SR_OK) + return ret; return SR_OK; } @@ -119,10 +76,15 @@ static int scpi_tcp_connection_id(struct sr_scpi_dev_inst *scpi, char **connection_id) { struct scpi_tcp *tcp = scpi->priv; + char conn_text[128]; + int ret; - *connection_id = g_strdup_printf("%s/%s:%s", - scpi->prefix, tcp->address, tcp->port); + ret = sr_tcp_get_port_path(tcp->tcp_dev, scpi->prefix, '/', + conn_text, sizeof(conn_text)); + if (ret != SR_OK) + return ret; + *connection_id = g_strdup(conn_text); return SR_OK; } @@ -131,33 +93,36 @@ static int scpi_tcp_source_add(struct sr_session *session, void *priv, { struct scpi_tcp *tcp = priv; - return sr_session_source_add(session, tcp->socket, events, timeout, - cb, cb_data); + return sr_tcp_source_add(session, tcp->tcp_dev, + events, timeout, cb, cb_data); } static int scpi_tcp_source_remove(struct sr_session *session, void *priv) { struct scpi_tcp *tcp = priv; - return sr_session_source_remove(session, tcp->socket); + return sr_tcp_source_remove(session, tcp->tcp_dev); } +/* Transmit text, usually a command. tcp-raw and tcp-rigol modes. */ static int scpi_tcp_send(void *priv, const char *command) { struct scpi_tcp *tcp = priv; - int len, out; - - len = strlen(command); - out = send(tcp->socket, command, len, 0); - - if (out < 0) { + const uint8_t *wrptr; + size_t wrlen, written; + int ret; + + wrptr = (const uint8_t *)command; + wrlen = strlen(command); + ret = sr_tcp_write_bytes(tcp->tcp_dev, wrptr, wrlen); + if (ret < 0) { sr_err("Send error: %s", g_strerror(errno)); return SR_ERR; } - - if (out < len) { - sr_dbg("Only sent %d/%d bytes of SCPI command: '%s'.", out, - len, command); + written = (size_t)ret; + if (written < wrlen) { + sr_dbg("Only sent %zu/%zu bytes of SCPI command: '%s'.", + written, wrlen, command); } sr_spew("Successfully sent SCPI command: '%s'.", command); @@ -165,6 +130,7 @@ static int scpi_tcp_send(void *priv, const char *command) return SR_OK; } +/* Start reception across multiple read calls. tcp-raw and tcp-rigol modes. */ static int scpi_tcp_read_begin(void *priv) { struct scpi_tcp *tcp = priv; @@ -175,90 +141,126 @@ static int scpi_tcp_read_begin(void *priv) return SR_OK; } +/* Receive response data. tcp-raw mode. */ static int scpi_tcp_raw_read_data(void *priv, char *buf, int maxlen) { struct scpi_tcp *tcp = priv; - int len; - - len = recv(tcp->socket, buf, maxlen, 0); - - if (len < 0) { + uint8_t *rdptr; + size_t rdlen, rcvd; + int ret; + + /* Get another chunk of receive data. */ + rdptr = (uint8_t *)buf; + rdlen = maxlen; + ret = sr_tcp_read_bytes(tcp->tcp_dev, rdptr, rdlen, FALSE); + if (ret < 0) { sr_err("Receive error: %s", g_strerror(errno)); return SR_ERR; } - + rcvd = (size_t)ret; + + /* + * Raw data mode (in contrast to Rigol mode). Prepare to answer + * the "completed" condition while the payload's length is not + * known. Pretend that the length buffer had been received. + * Assume that short reads correspond to the end of a response, + * while full reads of the caller specified size suggest that + * more data can follow. + */ tcp->length_bytes_read = LENGTH_BYTES; - tcp->response_length = len < maxlen ? len : maxlen + 1; - tcp->response_bytes_read = len; + tcp->response_length = rcvd < rdlen ? rcvd : rdlen + 1; + tcp->response_bytes_read = rcvd; - return len; + return rcvd; } +/* Transmit data of given length. tcp-raw mode. */ static int scpi_tcp_raw_write_data(void *priv, char *buf, int len) { struct scpi_tcp *tcp = priv; - int sentlen; - - sentlen = send(tcp->socket, buf, len, 0); - - if (sentlen < 0) { + const uint8_t *wrptr; + size_t wrlen, sent; + int ret; + + wrptr = (const uint8_t *)buf; + wrlen = len; + ret = sr_tcp_write_bytes(tcp->tcp_dev, wrptr, wrlen); + if (ret < 0) { sr_err("Send error: %s.", g_strerror(errno)); return SR_ERR; } + sent = (size_t)ret; - return sentlen; + return sent; } +/* Receive response data. tcp-rigol mode. */ static int scpi_tcp_rigol_read_data(void *priv, char *buf, int maxlen) { struct scpi_tcp *tcp = priv; - int len; - - if (tcp->length_bytes_read < LENGTH_BYTES) { - len = recv(tcp->socket, tcp->length_buf + tcp->length_bytes_read, - LENGTH_BYTES - tcp->length_bytes_read, 0); - if (len < 0) { + uint8_t *rdptr; + size_t rdlen, rcvd; + int ret; + + /* + * Rigol mode, chunks are prefixed by a length spec. + * Get more length bytes when we haven't read them before. + * Return "zero length read" if length has yet to get received. + * Otherwise get chunk length from length bytes buffer. + */ + if (tcp->length_bytes_read < sizeof(tcp->length_buf)) { + rdptr = &tcp->length_buf[tcp->length_bytes_read]; + rdlen = sizeof(tcp->length_buf) - tcp->length_bytes_read; + ret = sr_tcp_read_bytes(tcp->tcp_dev, rdptr, rdlen, FALSE); + if (ret < 0) { sr_err("Receive error: %s", g_strerror(errno)); return SR_ERR; } - - tcp->length_bytes_read += len; - - if (tcp->length_bytes_read < LENGTH_BYTES) + rcvd = (size_t)ret; + tcp->length_bytes_read += rcvd; + if (tcp->length_bytes_read < sizeof(tcp->length_buf)) return 0; - else - tcp->response_length = RL32(tcp->length_buf); + tcp->response_length = read_u32le(tcp->length_buf); } + /* Received more chunk data than announced size? Fatal. */ if (tcp->response_bytes_read >= tcp->response_length) return SR_ERR; - len = recv(tcp->socket, buf, maxlen, 0); - - if (len < 0) { + /* Read another chunk of the receive data. */ + rdptr = (uint8_t *)buf; + rdlen = maxlen; + ret = sr_tcp_read_bytes(tcp->tcp_dev, rdptr, rdlen, FALSE); + if (ret < 0) { sr_err("Receive error: %s", g_strerror(errno)); return SR_ERR; } + rcvd = (size_t)ret; + tcp->response_bytes_read += rcvd; - tcp->response_bytes_read += len; - - return len; + return rcvd; } +/* Check reception completion. tcp-raw and tcp-rigol modes. */ static int scpi_tcp_read_complete(void *priv) { struct scpi_tcp *tcp = priv; + gboolean have_length, have_response; - return (tcp->length_bytes_read == LENGTH_BYTES && - tcp->response_bytes_read >= tcp->response_length); + have_length = tcp->length_bytes_read == LENGTH_BYTES; + have_response = tcp->response_bytes_read >= tcp->response_length; + + return have_length && have_response; } static int scpi_tcp_close(struct sr_scpi_dev_inst *scpi) { struct scpi_tcp *tcp = scpi->priv; + int ret; - if (close(tcp->socket) < 0) - return SR_ERR; + ret = sr_tcp_disconnect(tcp->tcp_dev); + if (ret != SR_OK) + return ret; return SR_OK; } @@ -267,8 +269,7 @@ static void scpi_tcp_free(void *priv) { struct scpi_tcp *tcp = priv; - g_free(tcp->address); - g_free(tcp->port); + sr_tcp_dev_inst_free(tcp->tcp_dev); } SR_PRIV const struct sr_scpi_dev_inst scpi_tcp_raw_dev = { diff --git a/src/serial.c b/src/serial.c index 353b7ae1..d78dea20 100644 --- a/src/serial.c +++ b/src/serial.c @@ -99,6 +99,8 @@ SR_PRIV int serial_open(struct sr_serial_dev_inst *serial, int flags) serial->lib_funcs = ser_lib_funcs_hid; else if (ser_name_is_bt(serial)) serial->lib_funcs = ser_lib_funcs_bt; + else if (ser_name_is_tcpraw(serial)) + serial->lib_funcs = ser_lib_funcs_tcpraw; else serial->lib_funcs = ser_lib_funcs_libsp; if (!serial->lib_funcs) diff --git a/src/serial_bt.c b/src/serial_bt.c index ea3531d3..977f248b 100644 --- a/src/serial_bt.c +++ b/src/serial_bt.c @@ -32,6 +32,13 @@ #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 * @@ -48,23 +55,68 @@ /* {{{ 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] = "", [SER_BT_CONN_RFCOMM] = "rfcomm", [SER_BT_CONN_BLE122] = "ble122", [SER_BT_CONN_NRF51] = "nrf51", [SER_BT_CONN_CC254x] = "cc254x", + [SER_BT_CONN_AC6328] = "ac6328", + [SER_BT_CONN_DIALOG] = "dialog", + [SER_BT_CONN_NOTIFY] = "notify", }; static enum ser_bt_conn_t lookup_conn_name(const char *name) @@ -119,14 +171,16 @@ static const char *conn_name_text(enum ser_bt_conn_t type) * - The next field is the remote device's address, either separated * by colons or dashes or spaces, or not separated at all. * - Other parameters (RFCOMM channel, notify handles and write values) - * get derived from the connection type. A future implementation may - * accept more fields, but the syntax is yet to get developed. + * get derived from the connection type. + * - More fields after the remote address are options which override + * builtin defaults (RFCOMM channels, BLE handles, etc). * * Supported formats resulting from these rules: - * bt// + * bt//[/]... * * Examples: * bt/rfcomm/11-22-33-44-55-66 + * bt/rfcomm/11-22-33-44-55-66/channel=2 * bt/ble122/88:6b:12:34:56:78 * bt/cc254x/0123456789ab * @@ -140,11 +194,16 @@ static int ser_bt_parse_conn_spec( enum ser_bt_conn_t *conn_type, const char **remote_addr, size_t *rfcomm_channel, uint16_t *read_hdl, uint16_t *write_hdl, - uint16_t *cccd_hdl, uint16_t *cccd_val) + uint16_t *cccd_hdl, uint16_t *cccd_val, + uint16_t *ble_mtu) { char **fields, *field; enum ser_bt_conn_t type; const char *addr; + int ret_parse, ret; + size_t fields_count, field_idx; + char *endp; + unsigned long parm_val; if (conn_type) *conn_type = SER_BT_CONN_UNKNOWN; @@ -160,6 +219,8 @@ static int ser_bt_parse_conn_spec( *cccd_hdl = 0; if (cccd_val) *cccd_val = 0; + if (ble_mtu) + *ble_mtu = 0; if (!serial || !spec || !spec[0]) return SR_ERR_ARG; @@ -237,14 +298,126 @@ static int ser_bt_parse_conn_spec( if (cccd_val) *cccd_val = 0x0001; break; + case SER_BT_CONN_AC6328: + if (read_hdl) + *read_hdl = 12; + if (write_hdl) + *write_hdl = 15; + if (cccd_hdl) + *cccd_hdl = 13; + if (cccd_val) + *cccd_val = 0x0001; + break; + case SER_BT_CONN_DIALOG: + if (read_hdl) + *read_hdl = 23; + if (write_hdl) + *write_hdl = 18; + if (cccd_hdl) + *cccd_hdl = 0; + if (cccd_val) + *cccd_val = 0x0001; + if (ble_mtu) + *ble_mtu = 400; + break; + case SER_BT_CONN_NOTIFY: + /* All other values must be provided externally. */ + if (cccd_val) + *cccd_val = 0x0001; + break; default: return SR_ERR_ARG; } - /* TODO Evaluate optionally trailing fields, override defaults? */ + /* + * Preset a successful return value for the conn= parse call. + * Scan optional additional fields which specify more params. + * Update the defaults which were setup above. Pessimize the + * routine's return value in error paths. + */ + ret_parse = SR_OK; + fields_count = g_strv_length(fields); + for (field_idx = 3; field_idx < fields_count; field_idx++) { + field = fields[field_idx]; + if (!field || !*field) + continue; + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_CHANNEL)) { + field += strlen(SER_BT_PARAM_PREFIX_CHANNEL); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (rfcomm_channel) + *rfcomm_channel = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_RX)) { + field += strlen(SER_BT_PARAM_PREFIX_HDL_RX); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (read_hdl) + *read_hdl = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_TX)) { + field += strlen(SER_BT_PARAM_PREFIX_HDL_TX); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (write_hdl) + *write_hdl = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_CCCD)) { + field += strlen(SER_BT_PARAM_PREFIX_HDL_CCCD); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (cccd_hdl) + *cccd_hdl = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_VAL_CCCD)) { + field += strlen(SER_BT_PARAM_PREFIX_VAL_CCCD); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (cccd_val) + *cccd_val = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_BLE_MTU)) { + field += strlen(SER_BT_PARAM_PREFIX_BLE_MTU); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (ble_mtu) + *ble_mtu = parm_val; + continue; + } + return SR_ERR_DATA; + } g_strfreev(fields); - return SR_OK; + return ret_parse; } static void ser_bt_mask_databits(struct sr_serial_dev_inst *serial, @@ -271,6 +444,11 @@ static int ser_bt_data_cb(void *cb_data, uint8_t *data, size_t dlen) if (!serial) return -1; + if (!data && dlen) + return -1; + if (!data || !dlen) + return 0; + ser_bt_mask_databits(serial, data, dlen); sr_ser_queue_rx_data(serial, data, dlen); @@ -309,6 +487,7 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) const char *remote_addr; size_t rfcomm_channel; uint16_t read_hdl, write_hdl, cccd_hdl, cccd_val; + uint16_t ble_mtu; int rc; struct sr_bt_desc *desc; @@ -319,7 +498,8 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) &conn_type, &remote_addr, &rfcomm_channel, &read_hdl, &write_hdl, - &cccd_hdl, &cccd_val); + &cccd_hdl, &cccd_val, + &ble_mtu); if (rc != SR_OK) return SR_ERR_ARG; @@ -347,14 +527,19 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: rc = sr_bt_config_notify(desc, - read_hdl, write_hdl, cccd_hdl, cccd_val); + read_hdl, write_hdl, cccd_hdl, cccd_val, + ble_mtu); if (rc < 0) return SR_ERR; serial->bt_notify_handle_read = read_hdl; serial->bt_notify_handle_write = write_hdl; serial->bt_notify_handle_cccd = cccd_hdl; serial->bt_notify_value_cccd = cccd_val; + serial->bt_ble_mtu = ble_mtu; break; default: /* Unsupported type, or incomplete implementation. */ @@ -379,6 +564,9 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: rc = sr_bt_connect_ble(desc); if (rc < 0) return SR_ERR; @@ -455,6 +643,9 @@ static int ser_bt_write(struct sr_serial_dev_inst *serial, case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: /* * Assume that when applications call the serial layer's * write routine, then the BLE chip/module does support @@ -520,6 +711,9 @@ static int ser_bt_read(struct sr_serial_dev_inst *serial, case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: dlen = sr_ser_has_queued_data(serial); rc = sr_bt_check_notify(serial->bt_desc); if (rc < 0) @@ -617,6 +811,9 @@ static int bt_source_cb(int fd, int revents, void *cb_data) case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: dlen = sr_ser_has_queued_data(serial); rc = sr_bt_check_notify(serial->bt_desc); if (rc < 0) @@ -694,23 +891,6 @@ static int ser_bt_setup_source_remove(struct sr_session *session, return SR_OK; } -static enum ser_bt_conn_t scan_is_supported(const char *name) -{ - size_t idx; - const struct scan_supported_item *item; - - for (idx = 0; idx < ARRAY_SIZE(scan_supported_items); idx++) { - item = &scan_supported_items[idx]; - if (!item->name) - break; - if (strcmp(name, item->name) != 0) - continue; - return item->type; - } - - return SER_BT_CONN_UNKNOWN; -} - struct bt_scan_args_t { GSList *port_list; sr_ser_list_append_t append; @@ -723,6 +903,7 @@ static void scan_cb(void *cb_args, const char *addr, const char *name) struct bt_scan_args_t *scan_args; GSList *l; char addr_text[20]; + const struct scan_supported_item *item; enum ser_bt_conn_t type; char *port_name, *port_desc; char *addr_copy; @@ -745,9 +926,11 @@ static void scan_cb(void *cb_args, const char *addr, const char *name) g_strcanon(addr_text, "0123456789abcdefABCDEF", '-'); /* Create a port name, and a description. */ - type = scan_is_supported(name); - port_name = g_strdup_printf("%s/%s/%s", - SER_BT_CONN_PREFIX, conn_name_text(type), addr_text); + item = scan_is_supported(name); + type = item ? item->type : SER_BT_CONN_UNKNOWN; + port_name = g_strdup_printf("%s/%s/%s%s", + SER_BT_CONN_PREFIX, conn_name_text(type), addr_text, + (item && item->add_params) ? item->add_params : ""); port_desc = g_strdup_printf("%s (%s)", name, scan_args->bt_type); scan_args->port_list = scan_args->append(scan_args->port_list, port_name, port_desc); @@ -761,7 +944,7 @@ static void scan_cb(void *cb_args, const char *addr, const char *name) static GSList *ser_bt_list(GSList *list, sr_ser_list_append_t append) { - static const int scan_duration = 2; + static const int scan_duration = 3; struct bt_scan_args_t scan_args; struct sr_bt_desc *desc; diff --git a/src/serial_tcpraw.c b/src/serial_tcpraw.c new file mode 100644 index 00000000..119abbef --- /dev/null +++ b/src/serial_tcpraw.c @@ -0,0 +1,328 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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// + */ +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; + +/** @} */ diff --git a/src/strutil.c b/src/strutil.c index 9376ff09..2136bd49 100644 --- a/src/strutil.c +++ b/src/strutil.c @@ -174,7 +174,7 @@ SR_PRIV int sr_atoul_base(const char *str, unsigned long *ret, char **end, int b /* Add "0b" prefix support which strtol(3) may be missing. */ while (str && isspace(*str)) str++; - if (!base && strncmp(str, "0b", strlen("0b")) == 0) { + if ((!base || base == 2) && strncmp(str, "0b", strlen("0b")) == 0) { str += strlen("0b"); base = 2; } @@ -788,10 +788,17 @@ SR_PRIV GString *sr_hexdump_new(const uint8_t *data, const size_t len) GString *s; size_t i; - s = g_string_sized_new(3 * len); + i = 3 * len; + i += len / 8; + i += len / 16; + s = g_string_sized_new(i); for (i = 0; i < len; i++) { if (i) g_string_append_c(s, ' '); + if (i && (i % 8) == 0) + g_string_append_c(s, ' '); + if (i && (i % 16) == 0) + g_string_append_c(s, ' '); g_string_append_printf(s, "%02x", data[i]); } @@ -1660,4 +1667,209 @@ SR_API void sr_free_probe_names(char **names) g_strfreev(names); } +/** + * Trim leading and trailing whitespace off text. + * + * @param[in] s The input text. + * + * @return Start of trimmed input text. + * + * Manipulates the caller's input text in place. + * + * @since 0.6.0 + */ +SR_API char *sr_text_trim_spaces(char *s) +{ + char *p; + + if (!s || !*s) + return s; + + p = s + strlen(s); + while (p > s && isspace((int)p[-1])) + *(--p) = '\0'; + while (isspace((int)*s)) + s++; + + return s; +} + +/** + * Check for another complete text line, trim, return consumed char count. + * + * @param[in] s The input text, current read position. + * @param[in] l The input text, remaining available characters. + * @param[out] next Position after the current text line. + * @param[out] taken Count of consumed chars in current text line. + * + * @return Start of trimmed and NUL terminated text line. + * Or #NULL when no text line was found. + * + * Checks for the availability of another text line of input data. + * Manipulates the caller's input text in place. + * + * The end-of-line condition is the LF character ('\n'). Which covers + * LF-only as well as CR/LF input data. CR-only and LF/CR are considered + * unpopular and are not supported. LF/CR may appear to work at the + * caller's when leading whitespace gets trimmed (line boundaries will + * be incorrect, but content may get processed as expected). Support for + * all of the above combinations breaks the detection of empty lines (or + * becomes unmaintainably complex). + * + * The input buffer must be end-of-line terminated, lack of EOL results + * in failure to detect the text line. This is motivated by accumulating + * input in chunks, and the desire to not process incomplete lines before + * their reception has completed. Callers should enforce EOL if their + * source of input provides an EOF condition and is unreliable in terms + * of text line termination. + * + * When another text line is available, it gets NUL terminated and + * space gets trimmed of both ends. The start position of the trimmed + * text line is returned. Optionally the number of consumed characters + * is returned to the caller. Optionally 'next' points to after the + * returned text line, or #NULL when no other text is available in the + * input buffer. + * + * The 'taken' value is not preset by this routine, only gets updated. + * This is convenient for callers which expect to find multiple text + * lines in a received chunk, before finally discarding processed data + * from the input buffer (which can involve expensive memory move + * operations, and may be desirable to defer as much as possible). + * + * @since 0.6.0 + */ +SR_API char *sr_text_next_line(char *s, size_t l, char **next, size_t *taken) +{ + char *p; + + if (next) + *next = NULL; + if (!l) + l = strlen(s); + + /* Immediate reject incomplete input data. */ + if (!s || !*s || !l) + return NULL; + + /* Search for the next line termination. NUL terminate. */ + p = g_strstr_len(s, l, "\n"); + if (!p) + return NULL; + *p++ = '\0'; + if (taken) + *taken += p - s; + l -= p - s; + if (next) + *next = l ? p : NULL; + + /* Trim NUL terminated text line at both ends. */ + s = sr_text_trim_spaces(s); + return s; +} + +/** + * Isolates another space separated word in a text line. + * + * @param[in] s The input text, current read position. + * @param[out] next The position after the current word. + * + * @return The start of the current word. Or #NULL if there is none. + * + * Advances over leading whitespace. Isolates (NUL terminates) the next + * whitespace separated word. Optionally returns the position after the + * current word. Manipulates the caller's input text in place. + * + * @since 0.6.0 + */ +SR_API char *sr_text_next_word(char *s, char **next) +{ + char *word, *p; + + word = s; + if (next) + *next = NULL; + + /* Immediately reject incomplete input data. */ + if (!word || !*word) + return NULL; + + /* Advance over optional leading whitespace. */ + while (isspace((int)*word)) + word++; + if (!*word) + return NULL; + + /* + * Advance until whitespace or end of text. Quick return when + * end of input is seen. Otherwise advance over whitespace and + * return the position of trailing text. + */ + p = word; + while (*p && !isspace((int)*p)) + p++; + if (!*p) + return word; + *p++ = '\0'; + while (isspace((int)*p)) + p++; + if (!*p) + return word; + if (next) + *next = p; + return word; +} + +/** + * Get the number of necessary bits to hold a given value. Also gets + * the next power-of-two value at or above the caller provided value. + * + * @param[in] value The value that must get stored. + * @param[out] bits The required number of bits. + * @param[out] power The corresponding power-of-two. + * + * @return SR_OK upon success, SR_ERR* otherwise. + * + * TODO Move this routine to a more appropriate location, it is not + * strictly string related. + * + * @since 0.6.0 + */ +SR_API int sr_next_power_of_two(size_t value, size_t *bits, size_t *power) +{ + size_t need_bits; + size_t check_mask; + + if (bits) + *bits = 0; + if (power) + *power = 0; + + /* + * Handle the special case of input value 0 (needs 1 bit + * and results in "power of two" value 1) here. It is not + * covered by the generic logic below. + */ + if (!value) { + if (bits) + *bits = 1; + if (power) + *power = 1; + return SR_OK; + } + + need_bits = 0; + check_mask = 0; + do { + need_bits++; + check_mask <<= 1; + check_mask |= 1UL << 0; + } while (value & ~check_mask); + + if (bits) + *bits = need_bits; + if (power) + *power = ++check_mask; + return SR_OK; +} + /** @} */ diff --git a/src/sw_limits.c b/src/sw_limits.c index 1a9c4c58..41052d0f 100644 --- a/src/sw_limits.c +++ b/src/sw_limits.c @@ -22,12 +22,14 @@ * Software limits helper functions */ -#include -#include -#include -#include +#include "config.h" + #include #include +#include +#include +#include + #include "libsigrok-internal.h" #define LOG_PREFIX "sw_limits" @@ -55,10 +57,11 @@ SR_PRIV void sr_sw_limits_init(struct sr_sw_limits *limits) * @param limits software limit instance * @param key config item key * @param data config item data + * * @return SR_ERR_NA if @p key is not a supported limit, SR_OK otherwise */ -SR_PRIV int sr_sw_limits_config_get(const struct sr_sw_limits *limits, uint32_t key, - GVariant **data) +SR_PRIV int sr_sw_limits_config_get(const struct sr_sw_limits *limits, + uint32_t key, GVariant **data) { switch (key) { case SR_CONF_LIMIT_SAMPLES: @@ -86,10 +89,11 @@ SR_PRIV int sr_sw_limits_config_get(const struct sr_sw_limits *limits, uint32_t * @param limits software limit instance * @param key config item key * @param data config item data + * * @return SR_ERR_NA if @p key is not a supported limit, SR_OK otherwise */ -SR_PRIV int sr_sw_limits_config_set(struct sr_sw_limits *limits, uint32_t key, - GVariant *data) +SR_PRIV int sr_sw_limits_config_set(struct sr_sw_limits *limits, + uint32_t key, GVariant *data) { switch (key) { case SR_CONF_LIMIT_SAMPLES: @@ -130,6 +134,7 @@ SR_PRIV void sr_sw_limits_acquisition_start(struct sr_sw_limits *limits) * processing has been done. * * @param limits software limits instance + * * @returns TRUE if any of the software limits has been reached and the driver * should stop data acquisition, otherwise FALSE. */ @@ -184,6 +189,7 @@ SR_PRIV gboolean sr_sw_limits_check(struct sr_sw_limits *limits) * @param[out] samples remaining samples count until the limit is reached * @param[out] frames remaining frames count until the limit is reached * @param[out] msecs remaining milliseconds until the limit is reached + * @param[out] exceeded whether configured limits were reached before * * @return SR_ERR_* upon error, SR_OK otherwise */ diff --git a/src/tcp.c b/src/tcp.c new file mode 100644 index 00000000..0870420c --- /dev/null +++ b/src/tcp.c @@ -0,0 +1,417 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Gerhard Sittig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "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 +#include +#endif + +#include +#include +#include +#include + +#if !defined _WIN32 +#include +#include +#include +#include +#include +#endif + +#if HAVE_POLL +#include +#elif HAVE_SELECT +#include +#endif + +#include +#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); +} diff --git a/src/version.c b/src/version.c index 402c7ca6..912aedad 100644 --- a/src/version.c +++ b/src/version.c @@ -18,6 +18,9 @@ */ #include + +/* Request inclusion of the git version suffix in version.h. */ +#define WANT_LIBSIGROK_GIT_VERSION_H #include /** diff --git a/tests/conv.c b/tests/conv.c index 551365e2..788c3672 100644 --- a/tests/conv.c +++ b/tests/conv.c @@ -114,6 +114,36 @@ START_TEST(test_endian_read) } END_TEST +START_TEST(test_endian_read_inc_len) +{ + const uint8_t *p; + size_t l; + + /* Position to the start of the input stream. */ + p = &buff1234[0]; + l = sizeof(buff1234); + + /* Read several fields of known type and values. */ + fail_unless(l == 8); + fail_unless(read_u8_inc_len(&p, &l) == 0x11); + fail_unless(l == 7); + fail_unless(read_u8_inc_len(&p, &l) == 0x22); + fail_unless(l == 6); + fail_unless(read_u16le_inc_len(&p, &l) == 0x4433); + fail_unless(l == 4); + fail_unless(read_u16le_inc_len(&p, &l) == 0x6655); + fail_unless(l == 2); + fail_unless(read_u16le_inc_len(&p, &l) == 0x8877); + fail_unless(l == 0); + + /* Read beyond the end of the input stream. */ + fail_unless(read_u8_inc_len(&p, &l) == 0x0); + fail_unless(l == 0); + fail_unless(read_u16le_inc_len(&p, &l) == 0x0); + fail_unless(l == 0); +} +END_TEST + START_TEST(test_endian_read_inc) { const uint8_t *p; @@ -249,6 +279,7 @@ Suite *suite_conv(void) tcase_add_test(tc, test_endian_macro); tcase_add_test(tc, test_endian_read); tcase_add_test(tc, test_endian_read_inc); + tcase_add_test(tc, test_endian_read_inc_len); tcase_add_test(tc, test_endian_write); tcase_add_test(tc, test_endian_write_inc); suite_add_tcase(s, tc); diff --git a/tests/strutil.c b/tests/strutil.c index f4bbe9ec..3e43dbdc 100644 --- a/tests/strutil.c +++ b/tests/strutil.c @@ -430,6 +430,184 @@ START_TEST(test_exponent) } END_TEST +START_TEST(test_text_line) +{ + /* + * Covers text line splitting as used in input modules. Accepts + * input with differing end-of-line conventions, accepts leading + * and trailing whitespace. Isolates "the core" of a text line. + * Supports repeated calls which accumulate what later needs to + * get discarded after input data got processed in pieces. + */ +#define EOL "\n" + +#define TEXT_CORE_1 "Need to provide" +#define TEXT_CORE_2 "an input text" +#define TEXT_CORE_3 "" +#define TEXT_CORE_4 "with empty lines and funny spacing perhaps?" + +#define TEXT_LINE_1 TEXT_CORE_1 " \n" +#define TEXT_LINE_2 " " TEXT_CORE_2 "\n" +#define TEXT_LINE_3 TEXT_CORE_3 "\r\n" +#define TEXT_LINE_4 TEXT_CORE_4 "\n" + +#define TEXT_INPUT TEXT_LINE_1 TEXT_LINE_2 TEXT_LINE_3 TEXT_LINE_4 + + char *input_text, *read_pos, *next_pos, *line; + size_t input_len, taken; + + input_text = g_strdup(TEXT_INPUT); + read_pos = input_text; + input_len = strlen(input_text); + + /* Cover first line in tests. */ + taken = 0; + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_1) == 0, "Unexpected line content"); + fail_unless(next_pos, "No next line found"); + fail_unless(strncmp(next_pos, TEXT_LINE_2, strlen(TEXT_LINE_2)) == 0, + "Unexpected next line content"); + fail_unless(taken == strlen(TEXT_LINE_1), + "Unexpected consumed count"); + read_pos = next_pos; + input_len -= taken; + taken = 0; + + /* Cover second line in tests. DO NOT void 'taken' yet. */ + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_2) == 0, + "Unexpected text line content"); + fail_unless(next_pos, "No next line found"); + fail_unless(strncmp(next_pos, TEXT_LINE_3, strlen(TEXT_LINE_3)) == 0, + "Unexpected next line content"); + fail_unless(taken == strlen(TEXT_LINE_2), + "Unexpected consumed count"); + input_len -= next_pos - read_pos; + read_pos = next_pos; + + /* Cover third line in tests. Accumulates 'taken'. */ + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_3) == 0, "Unexpected line content"); + fail_unless(next_pos, "No next line found"); + fail_unless(strncmp(next_pos, TEXT_LINE_4, strlen(TEXT_LINE_4)) == 0, + "Unexpected next line content"); + fail_unless(taken == strlen(TEXT_LINE_2) + strlen(TEXT_LINE_3), + "Unexpected consumed count (totalled)"); + input_len -= next_pos - read_pos; + read_pos = next_pos; + taken = 0; + + /* Cover last line in tests. */ + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_4) == 0, + "Unexpected text line content"); + fail_unless(!next_pos, "Next line found, unexpected"); + fail_unless(taken == strlen(TEXT_LINE_4), + "Unexpected consumed count"); + input_len -= taken; + read_pos = next_pos; + + /* All input must have been consumed. */ + fail_unless(!read_pos); + fail_unless(!input_len); + + g_free(input_text); +} +END_TEST + +/* + * TODO Ideally this table of test cases should reside within the + * test_text_word() routine. But compilation fails when it's put there + * (initializers are said to not be constant, cause is yet uncertain). + */ +static const struct { + const char *line; + const char **words; +} word_cases[] = { + { "", (const char *[]){ NULL, }, }, + { " ", (const char *[]){ NULL, }, }, + { "one", (const char *[]){ "one", NULL, }, }, + { "one ", (const char *[]){ "one", NULL, }, }, + { " one ", (const char *[]){ "one", NULL, }, }, + { " one two ", (const char *[]){ "one", "two", NULL, }, }, + { "one two three ", + (const char *[]){ "one", "two", "three", NULL, }, + }, +}; + +START_TEST(test_text_word) +{ + size_t case_idx, word_idx; + char *line; + const char **words, *want; + char *read_pos, *next_pos, *have; + + for (case_idx = 0; case_idx < ARRAY_SIZE(word_cases); case_idx++) { + line = g_strdup(word_cases[case_idx].line); + words = word_cases[case_idx].words; + word_idx = 0; + + read_pos = line; + while (read_pos) { + want = words[word_idx]; + have = sr_text_next_word(read_pos, &next_pos); + if (!want) { + fail_unless(!have, "word found, unexpected"); + fail_unless(!next_pos, "next found after end"); + break; + } + word_idx++; + read_pos = next_pos; + fail_unless(have, "word not found"); + fail_unless(strcmp(have, want) == 0, + "unexpected word found"); + } + fail_unless(!words[word_idx], "missed expected words"); + + g_free(line); + } +} +END_TEST + +static const struct power_case_t { + size_t value; + size_t want_bits; + size_t want_power; +} power_cases[] = { + { 0, 1, 1, }, + { 1, 1, 2, }, + { 2, 2, 4, }, + { 3, 2, 4, }, + { 4, 3, 8, }, + { 5, 3, 8, }, + { 6, 3, 8, }, + { 7, 3, 8, }, + { 8, 4, 16, }, + { 15, 4, 16, }, + { 16, 5, 32, }, + { 31, 5, 32, }, +}; + +START_TEST(test_calc_power_of_two) +{ + size_t case_idx, bits, power; + const struct power_case_t *tcase; + int ret; + + for (case_idx = 0; case_idx < ARRAY_SIZE(power_cases); case_idx++) { + tcase = &power_cases[case_idx]; + ret = sr_next_power_of_two(tcase->value, &bits, &power); + fail_unless(ret == SR_OK, "bits count not found"); + fail_unless(bits == tcase->want_bits, "bits count differs"); + fail_unless(power == tcase->want_power, "power differs"); + } +} +END_TEST + Suite *suite_strutil(void) { Suite *s; @@ -452,5 +630,14 @@ Suite *suite_strutil(void) tcase_add_test(tc, test_exponent); suite_add_tcase(s, tc); + tc = tcase_create("text"); + tcase_add_test(tc, test_text_line); + tcase_add_test(tc, test_text_word); + suite_add_tcase(s, tc); + + tc = tcase_create("calc"); + tcase_add_test(tc, test_calc_power_of_two); + suite_add_tcase(s, tc); + return s; } diff --git a/tests/version.c b/tests/version.c index fa3a63cb..2b0faff3 100644 --- a/tests/version.c +++ b/tests/version.c @@ -70,27 +70,31 @@ END_TEST * * The upper limit assumes: * - The major, minor, and micro parts won't contain more than two - * digits each (this is an arbitrary choice). + * digits each (this is an arbitrary choice). The three numbers + * are separated by a period character. * - An optional "-git-" 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