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=713a3f352496f928b9911bfdc7e87a67134113df 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 768c8ca2..8686a262 100644 --- a/README +++ b/README @@ -49,6 +49,7 @@ Requirements for the C library: - libgpib (optional, used by some drivers) - libieee1284 (optional, used by some drivers) - libgio >= 2.32.0 (optional, used by some drivers) + - nettle (optional, used by some drivers) - check >= 0.9.4 (optional, only needed to run unit tests) - doxygen (optional, only needed for the C API docs) - graphviz (optional, only needed for the C API docs) @@ -63,7 +64,7 @@ Requirements for the C++ bindings: - doxygen (required for building the bindings, not only for C++ API docs!) - graphviz (optional, only needed for the C++ API docs) - Python (2 or 3) executable (development files are not needed) - - glibmm-2.4 (>= 2.32.0) + - glibmm-2.4 (>= 2.32.0) or glibmm-2.68 (>= 2.68.0) Requirements for the Python bindings: diff --git a/README.devices b/README.devices index 226ec06b..7563a57c 100644 --- a/README.devices +++ b/README.devices @@ -63,6 +63,10 @@ The following drivers/devices require a firmware upload upon connection: These can be extracted from the vendor's Windows drivers using a tool from our 'sigrok-util' repository/project. + - kingst-la2016: The Kingst LA series of logic analyzers needs MCU firmware + and FPGA netlists. The 'sigrok-util' repository contains a script to + extract these files from the vendor software. + - lecroy-logicstudio: The LeCroy LogicStudio requires FPGA bitstream files. These can be extracted from the vendor's Windows software using a tool from our 'sigrok-util' repository/project. @@ -197,7 +201,7 @@ Formal syntax for serial communication: path may contain slashes path and serno are "greedy" (span to the end of the spec) - Bluetooth Classic and Bluetooth Low Energy (BLE): - conn=bt// + conn=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 @@ -205,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. @@ -288,6 +294,19 @@ rules shipped by the system will be broken. Please consult the udev docs for details. +Assigning drivers to devices (Windows, Zadig) +--------------------------------------------- + +On Windows systems it may be necessary to assign drivers to devices +before libusb based applications can access them. It may be necessary +to re-run this driver assignment after firmware upload in case the +device changes its USB identification as a consequence of loading the +firmware image. + +The https://sigrok.org/wiki/Windows wiki page discusses this subject, +and other platform specific aspects. + + Non-default drivers for commodity chips --------------------------------------- @@ -347,19 +366,27 @@ Examples (sigrok-cli): $ sigrok-cli --driver uni-t-ut61e-ser:conn=/dev/ttyUSB0 ... $ sigrok-cli --driver voltcraft-vc820-ser:conn=/dev/ttyS0 ... - -When using any of the UT-D04 USB/HID cables you have to use the respective -driver _without_ the '-ser' drivername suffix (internally all of these models -are handled by the 'uni-t-dmm' driver). - -You also need to specify the USB vendor/device IDs of the cable. -Autodetection is not possible here, since various other products use the -USB VID/PID of those cables too, and there is no way to distinguish them. - -Since the UT-D04 cables are USB based (but don't use a USB-to-serial chip) -there is no need to specify a serial port via 'conn', of course. -However, the user running the frontend does also need to have permissions -to access the respective USB device (see above). + $ sigrok-cli --driver uni-t-ut61e-ser:conn=hid/cp2110 + +Using any of the UT-D04 et al USB/HID cables can be done in two different +ways: Use transparent serial over HID support in libsigrok, by giving the +-ser driver a conn=hid/... serial port spec. This re-uses the 'serial-dmm' +driver, results in better coverage of these code paths, and reduces +maintenance overhead. Or by running non-ser drivers and passing USB +specific connection details. When the driver _without_ the '-ser' suffix +is used, the models are handled by the 'uni-t-dmm' driver. These duplicate +drivers only exist for historical reasons, the redundancy may result in +differences of behaviour between the two implementations. When in doubt, +check if the '-ser' driver works for you. + +In the USB specific driver case you need to specify the cable's vendor +and product IDs. Autodetection is not possible here, since various other +products use the USB VID/PID of those cables too, and there is no way to +distinguish them. The sigrok software errs on the safe side, and won't +communicate to serial ports unless explicitly instructed by the user. + +The user running the frontend does also need to have permissions to +access the respective USB device (see above). Examples (sigrok-cli): @@ -390,6 +417,18 @@ See also: http://erste.de/UT61/index.html done +UNI-T UT-D04 cable issue on Windows +----------------------------------- + +There have been reports that CH9325 based cables are not detected on +Windows out of the box when they are assigned to libwdi drivers. Though +they may be usable in that case when the USB address is manually specified. +This can happen when some "USB to serial" driver is assigned which does not +provide a genuine COM port that enumerates naturally. Manually assigning a +"USB input device" driver can improve HIDAPI compatibility and make the +cable show up in sigrok's serial port enumeration. + + Enabling multimeter / data logger measurement output ---------------------------------------------------- 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 424b0002..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.])]) ]) ####################### @@ -630,9 +687,6 @@ AC_CHECK_FUNCS([ftdi_tciflush ftdi_tcoflush ftdi_tcioflush]) LIBS=$sr_save_libs CFLAGS=$sr_save_cflags -AM_COND_IF([NEED_USB], [AS_CASE([$sr_have_libusb_os_handle:$host_os], [no:mingw*], - [AC_MSG_ERROR([Windows builds require the event-abstraction branch of libusb])])]) - sr_glib_version=`$PKG_CONFIG --modversion glib-2.0 2>&AS_MESSAGE_LOG_FD` sr_libzip_version=`$PKG_CONFIG --modversion libzip 2>&AS_MESSAGE_LOG_FD` sr_zlib_version=`$PKG_CONFIG --modversion zlib 2>&AS_MESSAGE_LOG_FD` @@ -660,6 +714,7 @@ cat >&AS_MESSAGE_FD <<_EOF libsigrok configuration summary: - Package version................. $SR_PACKAGE_VERSION + - Version string suffix .......... $SR_PACKAGE_VERSION_STRING_SUFFIX - Library ABI version............. $SR_LIB_VERSION - Prefix.......................... $prefix - Building on..................... $build diff --git a/contrib/60-libsigrok.rules b/contrib/60-libsigrok.rules index f6ae259f..b6ac3ba0 100644 --- a/contrib/60-libsigrok.rules +++ b/contrib/60-libsigrok.rules @@ -49,7 +49,9 @@ ATTRS{idVendor}=="0957", ATTRS{idProduct}=="1735", ENV{ID_SIGROK}="1" # ASIX SIGMA # ASIX SIGMA2 +# ASIX OMEGA ATTRS{idVendor}=="a600", ATTRS{idProduct}=="a000", ENV{ID_SIGROK}="1" +ATTRS{idVendor}=="a600", ATTRS{idProduct}=="a004", ENV{ID_SIGROK}="1" # Braintechnology USB-LPS ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0498", ENV{ID_SIGROK}="1" @@ -111,6 +113,10 @@ ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0020", ENV{ID_SIGROK}="1" # DreamSourceLab DSLogic Basic ATTRS{idVendor}=="2a0e", ATTRS{idProduct}=="0021", ENV{ID_SIGROK}="1" +# Great Scott Gadgets +# GreatFET One +ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="60e6", ENV{ID_SIGROK}="1" + # GW-Instek GDM-9061 (USBTMC mode) ATTRS{idVendor}=="2184", ATTRS{idProduct}=="0059", ENV{ID_SIGROK}="1" @@ -248,6 +254,9 @@ ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0117", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0118", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0119", ENV{ID_SIGROK}="1" +# Rohde&Schwarz HMC series power supply (previously branded Hameg) VCP/USBTMC mode +ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0135", ENV{ID_SIGROK}="1" + # Sainsmart DDS120 / Rocktech BM102, renumerates as 1d50:608e "sigrok fx2lafw", Serial: Sainsmart DDS120 ATTRS{idVendor}=="8102", ATTRS{idProduct}=="8102", ENV{ID_SIGROK}="1" @@ -263,13 +272,22 @@ ATTRS{idVendor}=="0925", ATTRS{idProduct}=="3881", ENV{ID_SIGROK}="1" # Saleae Logic16 ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1001", ENV{ID_SIGROK}="1" +ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1003", ENV{ID_SIGROK}="1" + +# Saleae Logic 8, currently unsupported by libsigrok +#ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1004", ENV{ID_SIGROK}="1" + +# Saleae Logic Pro 8 +ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1005", ENV{ID_SIGROK}="1" + # Saleae Logic Pro 16 ATTRS{idVendor}=="21a9", ATTRS{idProduct}=="1006", ENV{ID_SIGROK}="1" # Siglent USBTMC devices. # f4ec:ee3a: E.g. SDS1052DL+ scope -# f4ec:ee38: E.g. SDS1104X-E scope +# f4ec:ee38: E.g. SDS1104X-E scope or SDM3055 Multimeter # f4ed:ee3a: E.g. SDS1202X-E scope or SDG1010 waveform generator +ATTRS{idVendor}=="f4ec", ATTRS{idProduct}=="ee38", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="f4ec", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1" ATTRS{idVendor}=="f4ed", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1" @@ -324,6 +342,9 @@ ATTRS{idVendor}=="04fc", ATTRS{idProduct}=="0201", ENV{ID_SIGROK}="1" # Victor 86C ATTRS{idVendor}=="1244", ATTRS{idProduct}=="d237", ENV{ID_SIGROK}="1" +# Voltcraft DSO2020, renumerates as 1d50:608e "sigrok fx2lafw", Serial: Hantek 6022BE +ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="2020", ENV{ID_SIGROK}="1" + # YiXingDianZi MDSO ATTRS{idVendor}=="d4a2", ATTRS{idProduct}=="5660", ENV{ID_SIGROK}="1" diff --git a/include/libsigrok/libsigrok.h b/include/libsigrok/libsigrok.h index 81a831b3..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; }; @@ -784,6 +810,26 @@ enum sr_configkey { */ SR_CONF_FORCE_DETECT, + /** + * Override builtin probe names from user specs. + * + * Users may want to override the names which are assigned to + * probes during scan (these usually match the vendor's labels + * on the device). This avoids the interactive tedium of + * changing channel names after device creation and before + * protocol decoder attachment. Think of IEEE488 recorders or + * parallel computer bus loggers. The scan option eliminates + * the issue of looking up previously assigned names before + * renaming a channel (see sigrok-cli -C), which depends on + * the device as well as the application, and is undesirable. + * The scan option is limited to those drivers which implement + * support for it, but works identically across those drivers. + * + * The value is a string, either a comma separated list of + * probe names, or an alias for a typical set of names. + */ + SR_CONF_PROBE_NAMES, + /* Update sr_key_info_config[] (hwdriver.c) upon changes! */ /*--- Device (or channel group) configuration -----------------------*/ @@ -1083,6 +1129,14 @@ enum sr_configkey { */ SR_CONF_RESISTANCE_TARGET, + /** + * Over-current protection (OCP) delay + * @arg type: double (time) + * @arg get: get current delay + * @arg set: set new delay + */ + SR_CONF_OVER_CURRENT_PROTECTION_DELAY, + /* Update sr_key_info_config[] (hwdriver.c) upon changes! */ /*--- Special stuff -------------------------------------------------*/ diff --git a/include/libsigrok/proto.h b/include/libsigrok/proto.h index 41d9cfcc..2c3b2559 100644 --- a/include/libsigrok/proto.h +++ b/include/libsigrok/proto.h @@ -166,7 +166,7 @@ SR_API const char *sr_input_name_get(const struct sr_input_module *imod); SR_API const char *sr_input_description_get(const struct sr_input_module *imod); SR_API const char *const *sr_input_extensions_get( const struct sr_input_module *imod); -SR_API const struct sr_input_module *sr_input_find(char *id); +SR_API const struct sr_input_module *sr_input_find(const char *id); SR_API const struct sr_option **sr_input_options_get(const struct sr_input_module *imod); SR_API void sr_input_options_free(const struct sr_option **options); SR_API struct sr_input *sr_input_new(const struct sr_input_module *imod, @@ -253,6 +253,10 @@ SR_API uint64_t sr_parse_timestring(const char *timestring); SR_API gboolean sr_parse_boolstring(const char *boolstring); SR_API int sr_parse_period(const char *periodstr, uint64_t *p, uint64_t *q); SR_API int sr_parse_voltage(const char *voltstr, uint64_t *p, uint64_t *q); +SR_API char **sr_parse_probe_names(const char *spec, + const char **dflt_names, size_t dflt_count, + size_t max_count, size_t *ret_count); +SR_API void sr_free_probe_names(char **names); SR_API int sr_sprintf_ascii(char *buf, const char *format, ...); SR_API int sr_vsprintf_ascii(char *buf, const char *format, va_list args); SR_API int sr_snprintf_ascii(char *buf, size_t buf_size, @@ -260,6 +264,11 @@ SR_API int sr_snprintf_ascii(char *buf, size_t buf_size, SR_API int sr_vsnprintf_ascii(char *buf, size_t buf_size, const char *format, va_list args); SR_API int sr_parse_rational(const char *str, struct sr_rational *ret); +SR_API char *sr_text_trim_spaces(char *s); +SR_API char *sr_text_next_line(char *s, size_t l, char **next, size_t *taken); +SR_API char *sr_text_next_word(char *s, char **next); + +SR_API int sr_next_power_of_two(size_t value, size_t *bits, size_t *power); /*--- version.c -------------------------------------------------------------*/ 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/device.c b/src/device.c index 5a144d97..5c681378 100644 --- a/src/device.c +++ b/src/device.c @@ -67,7 +67,7 @@ SR_PRIV struct sr_channel *sr_channel_new(struct sr_dev_inst *sdi, ch->index = index; ch->type = type; ch->enabled = enabled; - if (name) + if (name && *name) ch->name = g_strdup(name); sdi->channels = g_slist_append(sdi->channels, ch); @@ -120,6 +120,8 @@ SR_API int sr_dev_channel_name_set(struct sr_channel *channel, { if (!channel) return SR_ERR_ARG; + if (!name || !*name) + return SR_ERR_ARG; g_free(channel->name); channel->name = g_strdup(name); @@ -255,6 +257,62 @@ SR_PRIV gboolean sr_channel_lists_differ(GSList *l1, GSList *l2) return FALSE; } +/** + * Allocate and initialize a new channel group, and add it to sdi. + * + * @param[in] sdi The device instance the channel group is connected to. + * Optional, can be NULL. + * @param[in] name @copydoc sr_channel_group::name + * @param[in] priv @copydoc sr_channel_group::priv + * + * @return A pointer to a new struct sr_channel_group, NULL upon error. + * + * @private + */ +SR_PRIV struct sr_channel_group *sr_channel_group_new(struct sr_dev_inst *sdi, + const char *name, void *priv) +{ + struct sr_channel_group *cg; + + cg = g_malloc0(sizeof(*cg)); + if (name && *name) + cg->name = g_strdup(name); + cg->priv = priv; + + if (sdi) + sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + + return cg; +} + +/** + * Release a previously allocated struct sr_channel_group. + * + * @param[in] cg Pointer to struct sr_channel_group. + * + * @private + */ +SR_PRIV void sr_channel_group_free(struct sr_channel_group *cg) +{ + if (!cg) + return; + + g_free(cg->name); + g_slist_free(cg->channels); + g_free(cg->priv); + g_free(cg); +} + +/** + * Wrapper around sr_channel_group_free(), suitable for glib iterators. + * + * @private + */ +SR_PRIV void sr_channel_group_free_cb(void *cg) +{ + return sr_channel_group_free(cg); +} + /** * Determine whether the specified device instance has the specified * capability. @@ -433,7 +491,8 @@ SR_API int sr_dev_inst_channel_add(struct sr_dev_inst *sdi, int index, int type, if (!sdi || sdi->inst_type != SR_INST_USER || index < 0) return SR_ERR_ARG; - sr_channel_new(sdi, index, type, TRUE, name); + if (!sr_channel_new(sdi, index, type, TRUE, name)) + return SR_ERR_DATA; return SR_OK; } @@ -448,7 +507,6 @@ SR_API int sr_dev_inst_channel_add(struct sr_dev_inst *sdi, int index, int type, SR_PRIV void sr_dev_inst_free(struct sr_dev_inst *sdi) { struct sr_channel *ch; - struct sr_channel_group *cg; GSList *l; if (!sdi) @@ -459,15 +517,7 @@ SR_PRIV void sr_dev_inst_free(struct sr_dev_inst *sdi) sr_channel_free(ch); } g_slist_free(sdi->channels); - - for (l = sdi->channel_groups; l; l = l->next) { - cg = l->data; - g_free(cg->name); - g_slist_free(cg->channels); - g_free(cg->priv); - g_free(cg); - } - g_slist_free(sdi->channel_groups); + g_slist_free_full(sdi->channel_groups, sr_channel_group_free_cb); if (sdi->session) sr_session_dev_remove(sdi->session, sdi); @@ -519,6 +569,15 @@ SR_PRIV void sr_usb_dev_inst_free(struct sr_usb_dev_inst *usb) g_free(usb); } +/** + * Wrapper for g_slist_free_full() convenience. + * + * @private + */ +SR_PRIV void sr_usb_dev_inst_free_cb(gpointer p) +{ + sr_usb_dev_inst_free(p); +} #endif #ifdef HAVE_SERIAL_COMM diff --git a/src/dmm/bm52x.c b/src/dmm/bm52x.c index 89c393a9..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, @@ -1362,7 +1363,7 @@ SR_PRIV int brymen_bm52x_config_get(void *st, uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct brymen_bm52x_state *state; - char text[20]; + char text[32]; state = st; 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 b38c3123..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 @@ -59,6 +60,22 @@ SR_PRIV gboolean sr_brymen_bm86x_packet_valid(const uint8_t *buf) if (buf[19] != 0x86) return FALSE; + /* + * 2021-05 update: The devices that we have seen in the field do + * provide four bytes which we can synchronize to. Which happens + * to match the bm52x and bm82x devices' protocol. This condition + * is not documented by the vendor, but improves reliability of + * the re-synchronization for slight offsets, which were seen + * in the field, and which would have kept failing in an earlier + * implementation. + */ + if (buf[16] != 0x86) + return FALSE; + if (buf[17] != 0x86) + return FALSE; + if (buf[18] != 0x86) + return FALSE; + return TRUE; } @@ -286,6 +303,8 @@ static void brymen_bm86x_parse(const uint8_t *buf, float *floatval, NULL, &temp_unit, NULL, 0x80); ret = brymen_bm86x_parse_digits(&buf[9], 4, txtbuf, floatval, NULL, &digits, 0x10); + if (ret != SR_OK) + return; /* SI unit. */ if (buf[14] & 0x08) { diff --git a/src/dmm/eev121gw.c b/src/dmm/eev121gw.c index 7dfba8d0..ac762207 100644 --- a/src/dmm/eev121gw.c +++ b/src/dmm/eev121gw.c @@ -762,6 +762,8 @@ static int sr_eev121gw_parse(const uint8_t *buf, float *floatval, ser_mon = FIELD_NL(raw_serial, SERIAL_MONTH); ser_nr = FIELD_NL(raw_serial, SERIAL_NUMBER); sr_spew("Packet: Y-M %x-%x, nr %x.", ser_year, ser_mon, ser_nr); + } else { + (void)raw_serial; /* Silence compiler warning. */ } switch (display) { 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 30d6308a..6dc2c55c 100644 --- a/src/drivers.c +++ b/src/drivers.c @@ -25,11 +25,12 @@ #include "libsigrok-internal.h" /* - * sr_driver_list is a special section contains pointers to all the hardware - * drivers built into the library. The __start and __stop symbols are - * created from driver_list_start.c and driver_list_stop.c, and point to the - * start and end of the section. They are used to iterate over the list of - * all drivers. + * The special __sr_driver_list section contains pointers to all hardware + * drivers which were built into the library according to its configuration + * (will depend on the availability of dependencies, as well as user provided + * specs). The __start and __stop symbols point to the start and end of the + * section. They are used to iterate over the list of all drivers which were + * included in the library. */ SR_PRIV extern const struct sr_dev_driver *sr_driver_list__start[]; SR_PRIV extern const struct sr_dev_driver *sr_driver_list__stop[]; @@ -51,6 +52,5 @@ SR_API void sr_drivers_init(struct sr_context *ctx) drivers < sr_driver_list__stop; drivers++) g_array_append_val(array, *drivers); #endif - ctx->driver_list = (struct sr_dev_driver **)array->data; - g_array_free(array, FALSE); + ctx->driver_list = (struct sr_dev_driver **)g_array_free(array, FALSE); } 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/arachnid-labs-re-load-pro/api.c b/src/hardware/arachnid-labs-re-load-pro/api.c index fb6f8272..c14c20d4 100644 --- a/src/hardware/arachnid-labs-re-load-pro/api.c +++ b/src/hardware/arachnid-labs-re-load-pro/api.c @@ -146,9 +146,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->inst_type = SR_INST_SERIAL; sdi->conn = serial; - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup("1"); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + cg = sr_channel_group_new(sdi, "1", NULL); ch = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V"); cg->channels = g_slist_append(cg->channels, ch); diff --git a/src/hardware/asix-omega-rtm-cli/api.c b/src/hardware/asix-omega-rtm-cli/api.c index 71e92254..60c3bfea 100644 --- a/src/hardware/asix-omega-rtm-cli/api.c +++ b/src/hardware/asix-omega-rtm-cli/api.c @@ -126,9 +126,10 @@ static const uint32_t devopts[] = { static GSList *scan(struct sr_dev_driver *di, GSList *options) { - const char *conn, *serno, *exe; - GSList *devices; - size_t argc, chidx; + const char *conn, *probe_names, *serno, *exe; + GSList *devices, *l; + struct sr_config *src; + size_t argc, chmax, chidx; gchar **argv, *output, *vers_text, *eol; GSpawnFlags flags; GError *error; @@ -139,9 +140,18 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) /* Extract optional serial number from conn= spec. */ conn = NULL; + probe_names = NULL; (void)sr_serial_extract_options(options, &conn, NULL); if (!conn || !*conn) conn = NULL; + for (l = options; l; l = l->next) { + src = l->data; + switch (src->key) { + case SR_CONF_PROBE_NAMES: + probe_names = g_variant_get_string(src->data, NULL); + break; + } + } serno = NULL; if (conn) { if (!g_str_has_prefix(conn, "sn=")) { @@ -242,13 +252,16 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->serial_num = g_strdup(serno); if (conn) sdi->connection_id = g_strdup(conn); - for (chidx = 0; chidx < ARRAY_SIZE(channel_names); chidx++) { + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; + devc->channel_names = sr_parse_probe_names(probe_names, + channel_names, ARRAY_SIZE(channel_names), + ARRAY_SIZE(channel_names), &chmax); + for (chidx = 0; chidx < chmax; chidx++) { sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC, - TRUE, channel_names[chidx]); + TRUE, devc->channel_names[chidx]); } - devc = g_malloc0(sizeof(*devc)); - sdi->priv = devc; sr_sw_limits_init(&devc->limits); argc = 1; g_free(argv[argc]); 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/asix-omega-rtm-cli/protocol.h b/src/hardware/asix-omega-rtm-cli/protocol.h index 2f4641c8..b55f5f18 100644 --- a/src/hardware/asix-omega-rtm-cli/protocol.h +++ b/src/hardware/asix-omega-rtm-cli/protocol.h @@ -32,6 +32,7 @@ #define FEED_QUEUE_DEPTH (256 * 1024) struct dev_context { + char **channel_names; struct sr_sw_limits limits; struct { gchar **argv; diff --git a/src/hardware/asix-sigma/api.c b/src/hardware/asix-sigma/api.c index 4fdafec9..f1b96bb7 100644 --- a/src/hardware/asix-sigma/api.c +++ b/src/hardware/asix-sigma/api.c @@ -35,6 +35,7 @@ static const char *channel_names[] = { static const uint32_t scanopts[] = { SR_CONF_CONN, + SR_CONF_PROBE_NAMES, }; static const uint32_t drvopts[] = { @@ -109,6 +110,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) struct drv_context *drvc; libusb_context *usbctx; const char *conn; + const char *probe_names; GSList *l, *conn_devices; struct sr_config *src; GSList *devices; @@ -126,18 +128,23 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) struct sr_dev_inst *sdi; struct dev_context *devc; size_t devidx, chidx; + size_t count; drvc = di->context; usbctx = drvc->sr_ctx->libusb_ctx; /* Find all devices which match an (optional) conn= spec. */ conn = NULL; + probe_names = NULL; for (l = options; l; l = l->next) { src = l->data; switch (src->key) { case SR_CONF_CONN: conn = g_variant_get_string(src->data, NULL); break; + case SR_CONF_PROBE_NAMES: + probe_names = g_variant_get_string(src->data, NULL); + break; } } conn_devices = NULL; @@ -234,12 +241,14 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->model = g_strdup(dev_text); sdi->serial_num = g_strdup(serno_txt); sdi->connection_id = g_strdup(conn_id); - for (chidx = 0; chidx < ARRAY_SIZE(channel_names); chidx++) - sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC, - TRUE, channel_names[chidx]); - devc = g_malloc0(sizeof(*devc)); sdi->priv = devc; + devc->channel_names = sr_parse_probe_names(probe_names, + channel_names, ARRAY_SIZE(channel_names), + ARRAY_SIZE(channel_names), &count); + for (chidx = 0; chidx < count; chidx++) + sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC, + TRUE, devc->channel_names[chidx]); devc->id.vid = des.idVendor; devc->id.pid = des.idProduct; devc->id.serno = serno_num; @@ -304,7 +313,7 @@ static int config_get(uint32_t key, GVariant **data, *data = g_variant_new_boolean(devc->clock.use_ext_clock); break; case SR_CONF_EXTERNAL_CLOCK_SOURCE: - clock_text = channel_names[devc->clock.clock_pin]; + clock_text = devc->channel_names[devc->clock.clock_pin]; *data = g_variant_new_string(clock_text); break; case SR_CONF_CLOCK_EDGE: @@ -330,6 +339,8 @@ static int config_set(uint32_t key, GVariant *data, struct dev_context *devc; int ret; uint64_t want_rate, have_rate; + const char **names; + size_t count; int idx; (void)cg; @@ -357,7 +368,9 @@ static int config_set(uint32_t key, GVariant *data, devc->clock.use_ext_clock = g_variant_get_boolean(data); break; case SR_CONF_EXTERNAL_CLOCK_SOURCE: - idx = std_str_idx(data, ARRAY_AND_SIZE(channel_names)); + names = (const char **)devc->channel_names; + count = g_strv_length(devc->channel_names); + idx = std_str_idx(data, names, count); if (idx < 0) return SR_ERR_ARG; devc->clock.clock_pin = idx; @@ -384,6 +397,11 @@ static int config_set(uint32_t key, GVariant *data, static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { + struct dev_context *devc; + const char **names; + size_t count; + + devc = sdi ? sdi->priv : NULL; switch (key) { case SR_CONF_SCAN_OPTIONS: case SR_CONF_DEVICE_OPTIONS: @@ -395,7 +413,11 @@ static int config_list(uint32_t key, GVariant **data, *data = sigma_get_samplerates_list(); break; case SR_CONF_EXTERNAL_CLOCK_SOURCE: - *data = g_variant_new_strv(ARRAY_AND_SIZE(channel_names)); + if (!devc) + return SR_ERR_ARG; + names = (const char **)devc->channel_names; + count = g_strv_length(devc->channel_names); + *data = g_variant_new_strv(names, count); break; case SR_CONF_CLOCK_EDGE: *data = g_variant_new_strv(ARRAY_AND_SIZE(ext_clock_edges)); diff --git a/src/hardware/asix-sigma/protocol.h b/src/hardware/asix-sigma/protocol.h index 26370170..a35bcb0a 100644 --- a/src/hardware/asix-sigma/protocol.h +++ b/src/hardware/asix-sigma/protocol.h @@ -330,6 +330,7 @@ struct dev_context { uint16_t prefix; enum asix_device_type type; } id; + char **channel_names; struct { struct ftdi_context ctx; gboolean is_open, must_close; diff --git a/src/hardware/atorch/api.c b/src/hardware/atorch/api.c new file mode 100644 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/atten-pps3xxx/api.c b/src/hardware/atten-pps3xxx/api.c index dc6e5201..28b22015 100644 --- a/src/hardware/atten-pps3xxx/api.c +++ b/src/hardware/atten-pps3xxx/api.c @@ -157,11 +157,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options, int modelid) for (i = 0; i < MAX_CHANNELS; i++) { snprintf(channel, 10, "CH%d", i + 1); ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel); - cg = g_malloc(sizeof(struct sr_channel_group)); - cg->name = g_strdup(channel); + cg = sr_channel_group_new(sdi, channel, NULL); cg->channels = g_slist_append(NULL, ch); - cg->priv = NULL; - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } devc = g_malloc0(sizeof(struct dev_context)); diff --git a/src/hardware/baylibre-acme/protocol.c b/src/hardware/baylibre-acme/protocol.c index 89391d54..8d23b9e2 100644 --- a/src/hardware/baylibre-acme/protocol.c +++ b/src/hardware/baylibre-acme/protocol.c @@ -332,9 +332,8 @@ SR_PRIV gboolean bl_acme_register_probe(struct sr_dev_inst *sdi, int type, if (hwmon < 0) return FALSE; - cg = g_malloc0(sizeof(struct sr_channel_group)); cgp = g_malloc0(sizeof(struct channel_group_priv)); - cg->priv = cgp; + cg = sr_channel_group_new(sdi, NULL, cgp); /* * See if we can read the EEPROM contents. If not, assume it's @@ -380,8 +379,6 @@ SR_PRIV gboolean bl_acme_register_probe(struct sr_dev_inst *sdi, int type, sr_err("Invalid probe type: %d.", type); } - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); - return TRUE; } 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/dcttech-usbrelay/api.c b/src/hardware/dcttech-usbrelay/api.c index 137277d0..deeb234a 100644 --- a/src/hardware/dcttech-usbrelay/api.c +++ b/src/hardware/dcttech-usbrelay/api.c @@ -63,6 +63,7 @@ static struct sr_dev_inst *probe_device_common(const char *path, struct channel_group_context *cgc; size_t idx, nr; struct sr_channel_group *cg; + char cg_name[24]; /* * Get relay count from product string. Weak condition, @@ -153,12 +154,11 @@ static struct sr_dev_inst *probe_device_common(const char *path, devc->relay_mask = (1U << relay_count) - 1; for (idx = 0; idx < devc->relay_count; idx++) { nr = idx + 1; - cg = g_malloc0(sizeof(*cg)); - cg->name = g_strdup_printf("R%zu", nr); + snprintf(cg_name, sizeof(cg_name), "R%zu", nr); cgc = g_malloc0(sizeof(*cgc)); - cg->priv = cgc; cgc->number = nr; - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + cg = sr_channel_group_new(sdi, cg_name, cgc); + (void)cg; } return sdi; diff --git a/src/hardware/demo/api.c b/src/hardware/demo/api.c index ba317fbe..bea35d06 100644 --- a/src/hardware/demo/api.c +++ b/src/hardware/demo/api.c @@ -151,14 +151,12 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) if (num_logic_channels > 0) { /* Logic channels, all in one channel group. */ - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup("Logic"); + cg = sr_channel_group_new(sdi, "Logic", NULL); for (i = 0; i < num_logic_channels; i++) { sprintf(channel_name, "D%d", i); ch = sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name); cg->channels = g_slist_append(cg->channels, ch); } - sdi->channel_groups = g_slist_append(NULL, cg); } /* Analog channels, channel groups and pattern generators. */ @@ -173,9 +171,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) pattern = 0; /* An "Analog" channel group with all analog channels in it. */ - acg = g_malloc0(sizeof(struct sr_channel_group)); - acg->name = g_strdup("Analog"); - sdi->channel_groups = g_slist_append(sdi->channel_groups, acg); + acg = sr_channel_group_new(sdi, "Analog", NULL); for (i = 0; i < num_analog_channels; i++) { snprintf(channel_name, 16, "A%d", i); @@ -184,10 +180,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) acg->channels = g_slist_append(acg->channels, ch); /* Every analog channel gets its own channel group as well. */ - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup(channel_name); + cg = sr_channel_group_new(sdi, channel_name, NULL); cg->channels = g_slist_append(NULL, ch); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); /* Every channel gets a generator struct. */ ag = g_malloc(sizeof(struct analog_gen)); diff --git a/src/hardware/devantech-eth008/api.c b/src/hardware/devantech-eth008/api.c new file mode 100644 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/dreamsourcelab-dslogic/api.c b/src/hardware/dreamsourcelab-dslogic/api.c index 98f4acdb..6d5b7fc7 100644 --- a/src/hardware/dreamsourcelab-dslogic/api.c +++ b/src/hardware/dreamsourcelab-dslogic/api.c @@ -240,15 +240,13 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->connection_id = g_strdup(connection_id); /* Logic channels, all in one channel group. */ - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup("Logic"); + cg = sr_channel_group_new(sdi, "Logic", NULL); for (j = 0; j < NUM_CHANNELS; j++) { sprintf(channel_name, "%d", j); ch = sr_channel_new(sdi, j, SR_CHANNEL_LOGIC, TRUE, channel_name); cg->channels = g_slist_append(cg->channels, ch); } - sdi->channel_groups = g_slist_append(NULL, cg); devc = dslogic_dev_new(); devc->profile = prof; 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/fx2lafw/api.c b/src/hardware/fx2lafw/api.c index 5acb0016..255788bf 100644 --- a/src/hardware/fx2lafw/api.c +++ b/src/hardware/fx2lafw/api.c @@ -117,6 +117,7 @@ static const struct fx2lafw_profile supported_fx2[] = { static const uint32_t scanopts[] = { SR_CONF_CONN, + SR_CONF_PROBE_NAMES, }; static const uint32_t drvopts[] = { @@ -161,6 +162,15 @@ static const uint64_t samplerates[] = { SR_MHZ(48), }; +static const char *channel_names_logic[] = { + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", + "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15", +}; + +static const char *channel_names_analog[] = { + "A0", "A1", "A2", "A3", +}; + static gboolean is_plausible(const struct libusb_device_descriptor *des) { int i; @@ -190,21 +200,27 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) struct libusb_device_descriptor des; libusb_device **devlist; struct libusb_device_handle *hdl; - int ret, i, j; - int num_logic_channels = 0, num_analog_channels = 0; + int ret, i; + size_t j, num_logic_channels, num_analog_channels; const char *conn; + const char *probe_names; char manufacturer[64], product[64], serial_num[64], connection_id[64]; - char channel_name[16]; + size_t ch_max, ch_idx; + const char *channel_name; drvc = di->context; conn = NULL; + probe_names = NULL; for (l = options; l; l = l->next) { src = l->data; switch (src->key) { case SR_CONF_CONN: conn = g_variant_get_string(src->data, NULL); break; + case SR_CONF_PROBE_NAMES: + probe_names = g_variant_get_string(src->data, NULL); + break; } } if (conn) @@ -301,38 +317,56 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->serial_num = g_strdup(serial_num); sdi->connection_id = g_strdup(connection_id); + devc = fx2lafw_dev_new(); + devc->profile = prof; + sdi->priv = devc; + devices = g_slist_append(devices, sdi); + /* Fill in channellist according to this device's profile. */ num_logic_channels = prof->dev_caps & DEV_CAPS_16BIT ? 16 : 8; + if (num_logic_channels > ARRAY_SIZE(channel_names_logic)) + num_logic_channels = ARRAY_SIZE(channel_names_logic); num_analog_channels = prof->dev_caps & DEV_CAPS_AX_ANALOG ? 1 : 0; + if (num_analog_channels > ARRAY_SIZE(channel_names_analog)) + num_analog_channels = ARRAY_SIZE(channel_names_analog); + + /* + * Allow user specs to override the builtin probe names. + * + * Implementor's note: Because the device's number of + * logic channels is not known at compile time, and thus + * the location of the analog channel names is not known + * at compile time, and the construction of a list with + * default names at runtime is not done here, and we + * don't want to keep several default lists around, this + * implementation only supports to override the names of + * logic probes. The use case which motivated the config + * key is protocol decoders, which are logic only. + */ + ch_max = num_logic_channels; + devc->channel_names = sr_parse_probe_names(probe_names, + channel_names_logic, ch_max, ch_max, &ch_max); + ch_idx = 0; /* Logic channels, all in one channel group. */ - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup("Logic"); + cg = sr_channel_group_new(sdi, "Logic", NULL); for (j = 0; j < num_logic_channels; j++) { - sprintf(channel_name, "D%d", j); - ch = sr_channel_new(sdi, j, SR_CHANNEL_LOGIC, - TRUE, channel_name); + channel_name = devc->channel_names[j]; + ch = sr_channel_new(sdi, ch_idx++, SR_CHANNEL_LOGIC, + TRUE, channel_name); cg->channels = g_slist_append(cg->channels, ch); } - sdi->channel_groups = g_slist_append(NULL, cg); for (j = 0; j < num_analog_channels; j++) { - snprintf(channel_name, 16, "A%d", j); - ch = sr_channel_new(sdi, j + num_logic_channels, - SR_CHANNEL_ANALOG, TRUE, channel_name); + channel_name = channel_names_analog[j]; + ch = sr_channel_new(sdi, ch_idx++, SR_CHANNEL_ANALOG, + TRUE, channel_name); /* Every analog channel gets its own channel group. */ - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup(channel_name); + cg = sr_channel_group_new(sdi, channel_name, NULL); cg->channels = g_slist_append(NULL, ch); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } - devc = fx2lafw_dev_new(); - devc->profile = prof; - sdi->priv = devc; - devices = g_slist_append(devices, sdi); - devc->samplerates = samplerates; devc->num_samplerates = ARRAY_SIZE(samplerates); has_firmware = usb_match_manuf_prod(devlist[i], diff --git a/src/hardware/fx2lafw/protocol.c b/src/hardware/fx2lafw/protocol.c index 963d0336..8854f5a6 100644 --- a/src/hardware/fx2lafw/protocol.c +++ b/src/hardware/fx2lafw/protocol.c @@ -255,6 +255,7 @@ SR_PRIV struct dev_context *fx2lafw_dev_new(void) devc = g_malloc0(sizeof(struct dev_context)); devc->profile = NULL; + devc->channel_names = NULL; devc->fw_updated = 0; devc->cur_samplerate = 0; devc->limit_frames = 1; diff --git a/src/hardware/fx2lafw/protocol.h b/src/hardware/fx2lafw/protocol.h index 2c56b02c..91f0e9fb 100644 --- a/src/hardware/fx2lafw/protocol.h +++ b/src/hardware/fx2lafw/protocol.h @@ -89,6 +89,7 @@ struct fx2lafw_profile { struct dev_context { const struct fx2lafw_profile *profile; + char **channel_names; GSList *enabled_analog_channels; /* * Since we can't keep track of an fx2lafw device after upgrading diff --git a/src/hardware/greatfet/api.c b/src/hardware/greatfet/api.c new file mode 100644 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/gwinstek-gds-800/api.c b/src/hardware/gwinstek-gds-800/api.c index 22ac40e6..6e9b85bb 100644 --- a/src/hardware/gwinstek-gds-800/api.c +++ b/src/hardware/gwinstek-gds-800/api.c @@ -76,12 +76,9 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "CH1"); sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "CH2"); - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup(""); + cg = sr_channel_group_new(sdi, "", NULL); cg->channels = g_slist_append(cg->channels, g_slist_nth_data(sdi->channels, 0)); cg->channels = g_slist_append(cg->channels, g_slist_nth_data(sdi->channels, 1)); - cg->priv = NULL; - sdi->channel_groups = g_slist_append(NULL, cg); return sdi; } diff --git a/src/hardware/gwinstek-gpd/api.c b/src/hardware/gwinstek-gpd/api.c index 2b685202..a85be2ed 100644 --- a/src/hardware/gwinstek-gpd/api.c +++ b/src/hardware/gwinstek-gpd/api.c @@ -194,11 +194,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) for (i = 0; i < model->num_channels; i++) { snprintf(channel, sizeof(channel), "CH%d", i + 1); ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel); - cg = g_malloc(sizeof(struct sr_channel_group)); - cg->name = g_strdup(channel); + cg = sr_channel_group_new(sdi, channel, NULL); cg->channels = g_slist_append(NULL, ch); - cg->priv = NULL; - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } devc = g_malloc0(sizeof(struct dev_context)); diff --git a/src/hardware/hameg-hmo/protocol.c b/src/hardware/hameg-hmo/protocol.c index 26dac0cb..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", @@ -330,7 +347,17 @@ static const uint64_t vdivs[][2] = { { 2, 1 }, { 5, 1 }, { 10, 1 }, + { 20, 1 }, + { 50, 1 }, }; +/* + * It feels a little hacky to use a single table yet use different item + * count values here. But it simplifies maintenance, reduces redundancy + * by avoiding several vdivs[] table versions of mostly identical content, + * still references which declare models' capabilities remain readable. + */ +#define VDIVS_COUNT_UPTO_10V (ARRAY_SIZE(vdivs) - 2) +#define VDIVS_COUNT_UPTO_50V (ARRAY_SIZE(vdivs)) static const char *scope_analog_channel_names[] = { "CH1", "CH2", "CH3", "CH4", @@ -377,7 +404,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases_hmo_compact), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_10V, .num_ydivs = 8, @@ -418,7 +445,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_10V, .num_ydivs = 8, @@ -459,7 +486,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_10V, .num_ydivs = 8, @@ -500,7 +527,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases_hmo_compact), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_10V, .num_ydivs = 8, @@ -540,7 +567,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_10V, .num_ydivs = 8, @@ -580,7 +607,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_50V, .num_ydivs = 8, @@ -620,7 +647,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_50V, .num_ydivs = 8, @@ -660,7 +687,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_10V, .num_ydivs = 8, @@ -700,7 +727,7 @@ static struct scope_config scope_models[] = { .num_timebases = ARRAY_SIZE(timebases), .vdivs = &vdivs, - .num_vdivs = ARRAY_SIZE(vdivs), + .num_vdivs = VDIVS_COUNT_UPTO_10V, .num_ydivs = 8, @@ -739,6 +766,86 @@ static struct scope_config scope_models[] = { .timebases = &timebases, .num_timebases = ARRAY_SIZE(timebases), + .vdivs = &vdivs, + .num_vdivs = VDIVS_COUNT_UPTO_10V, + + .num_ydivs = 8, + + .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect, + }, + { + .name = {"RTH1002", NULL}, + .analog_channels = 2, + .digital_channels = 8, + + .analog_names = &scope_analog_channel_names, + .digital_names = &scope_digital_channel_names, + + .devopts = &devopts, + .num_devopts = ARRAY_SIZE(devopts), + + .devopts_cg_analog = &devopts_cg_analog, + .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog), + + .devopts_cg_digital = &devopts_cg_digital, + .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital), + + .coupling_options = &coupling_options_rth100x, + .num_coupling_options = ARRAY_SIZE(coupling_options_rth100x), + + .logic_threshold = &logic_threshold, + .num_logic_threshold = ARRAY_SIZE(logic_threshold), + .logic_threshold_for_pod = TRUE, + + .trigger_sources = &an2_dig8_isol_trigger_sources, + .num_trigger_sources = ARRAY_SIZE(an2_dig8_isol_trigger_sources), + + .trigger_slopes = &scope_trigger_slopes, + .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes), + + .timebases = &timebases, + .num_timebases = ARRAY_SIZE(timebases), + + .vdivs = &vdivs, + .num_vdivs = ARRAY_SIZE(vdivs), + + .num_ydivs = 8, + + .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect, + }, + { + .name = {"RTH1004", NULL}, + .analog_channels = 4, + .digital_channels = 8, + + .analog_names = &scope_analog_channel_names, + .digital_names = &scope_digital_channel_names, + + .devopts = &devopts, + .num_devopts = ARRAY_SIZE(devopts), + + .devopts_cg_analog = &devopts_cg_analog, + .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog), + + .devopts_cg_digital = &devopts_cg_digital, + .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital), + + .coupling_options = &coupling_options_rth100x, + .num_coupling_options = ARRAY_SIZE(coupling_options_rth100x), + + .logic_threshold = &logic_threshold, + .num_logic_threshold = ARRAY_SIZE(logic_threshold), + .logic_threshold_for_pod = TRUE, + + .trigger_sources = &an4_dig8_isol_trigger_sources, + .num_trigger_sources = ARRAY_SIZE(an4_dig8_isol_trigger_sources), + + .trigger_slopes = &scope_trigger_slopes, + .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes), + + .timebases = &timebases, + .num_timebases = ARRAY_SIZE(timebases), + .vdivs = &vdivs, .num_vdivs = ARRAY_SIZE(vdivs), @@ -896,7 +1003,7 @@ static int analog_channel_state_get(struct sr_dev_inst *sdi, if (sr_scpi_get_string(scpi, command, &tmp_str) != SR_OK) return SR_ERR; - if (array_float_get(tmp_str, ARRAY_AND_SIZE(vdivs), &j) != SR_OK) { + if (array_float_get(tmp_str, *(config->vdivs), config->num_vdivs, &j) != SR_OK) { g_free(tmp_str); sr_err("Could not determine array index for vertical div scale."); return SR_ERR; @@ -1192,6 +1299,7 @@ SR_PRIV int hmo_init_device(struct sr_dev_inst *sdi) unsigned int i, j, group; struct sr_channel *ch; struct dev_context *devc; + const char *cg_name; int ret; devc = sdi->priv; @@ -1232,27 +1340,20 @@ SR_PRIV int hmo_init_device(struct sr_dev_inst *sdi) ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, (*scope_models[model_index].analog_names)[i]); - devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); - - devc->analog_groups[i]->name = g_strdup( - (char *)(*scope_models[model_index].analog_names)[i]); + cg_name = (*scope_models[model_index].analog_names)[i]; + devc->analog_groups[i] = sr_channel_group_new(sdi, cg_name, NULL); devc->analog_groups[i]->channels = g_slist_append(NULL, ch); - - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->analog_groups[i]); } /* Add digital channel groups. */ ret = SR_OK; for (i = 0; i < scope_models[model_index].digital_pods; i++) { - devc->digital_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); + devc->digital_groups[i] = sr_channel_group_new(sdi, NULL, NULL); if (!devc->digital_groups[i]) { ret = SR_ERR_MALLOC; break; } devc->digital_groups[i]->name = g_strdup_printf("POD%d", i + 1); - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->digital_groups[i]); } if (ret != SR_OK) return ret; diff --git a/src/hardware/hantek-4032l/api.c b/src/hardware/hantek-4032l/api.c index 26a9315e..7e823f8c 100644 --- a/src/hardware/hantek-4032l/api.c +++ b/src/hardware/hantek-4032l/api.c @@ -233,10 +233,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) struct sr_channel_group *channel_groups[2]; for (int j = 0; j < 2; j++) { - cg = g_malloc0(sizeof(struct sr_channel_group)); + cg = sr_channel_group_new(sdi, NULL, NULL); cg->name = g_strdup_printf("%c", 'A' + j); channel_groups[j] = cg; - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } /* Assemble channel list and add channel to channel groups. */ diff --git a/src/hardware/hantek-6xxx/api.c b/src/hardware/hantek-6xxx/api.c index 408d1fcd..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", @@ -140,10 +146,8 @@ static struct sr_dev_inst *hantek_6xxx_dev_new(const struct hantek_6xxx_profile for (i = 0; i < ARRAY_SIZE(channel_names); i++) { ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_names[i]); - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup(channel_names[i]); + cg = sr_channel_group_new(sdi, channel_names[i], NULL); cg->channels = g_slist_append(cg->channels, ch); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } devc = g_malloc0(sizeof(struct dev_context)); diff --git a/src/hardware/hantek-dso/api.c b/src/hardware/hantek-dso/api.c index a3cc8a28..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[] = { @@ -199,10 +198,8 @@ static struct sr_dev_inst *dso_dev_new(const struct dso_profile *prof) */ for (i = 0; i < ARRAY_SIZE(channel_names); i++) { ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_names[i]); - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup(channel_names[i]); + cg = sr_channel_group_new(sdi, channel_names[i], NULL); cg->channels = g_slist_append(cg->channels, ch); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } devc = g_malloc0(sizeof(struct dev_context)); @@ -221,7 +218,7 @@ static struct sr_dev_inst *dso_dev_new(const struct dso_profile *prof) devc->voffset_trigger = DEFAULT_VERT_TRIGGERPOS; devc->framesize = DEFAULT_FRAMESIZE; devc->triggerslope = SLOPE_POSITIVE; - devc->triggersource = g_strdup(DEFAULT_TRIGGER_SOURCE); + devc->triggersource = NULL; devc->capture_ratio = DEFAULT_CAPTURE_RATIO; sdi->priv = devc; @@ -468,6 +465,8 @@ static int config_get(uint32_t key, GVariant **data, *data = g_variant_new_uint64(devc->framesize); break; case SR_CONF_TRIGGER_SOURCE: + if (!devc->triggersource) + return SR_ERR_NA; *data = g_variant_new_string(devc->triggersource); break; case SR_CONF_TRIGGER_SLOPE: @@ -558,6 +557,7 @@ static int config_set(uint32_t key, GVariant *data, case SR_CONF_TRIGGER_SOURCE: if ((idx = std_str_idx(data, ARRAY_AND_SIZE(trigger_sources))) < 0) return SR_ERR_ARG; + g_free(devc->triggersource); devc->triggersource = g_strdup(trigger_sources[idx]); break; default: @@ -839,8 +839,10 @@ static int handle_event(int fd, int revents, void *cb_data) return TRUE; if (dso_enable_trigger(sdi) != SR_OK) return TRUE; -// if (dso_force_trigger(sdi) != SR_OK) -// return TRUE; + if (!devc->triggersource) { + if (dso_force_trigger(sdi) != SR_OK) + return TRUE; + } sr_dbg("Successfully requested next chunk."); devc->dev_state = CAPTURE; return TRUE; @@ -861,8 +863,10 @@ static int handle_event(int fd, int revents, void *cb_data) break; if (dso_enable_trigger(sdi) != SR_OK) break; -// if (dso_force_trigger(sdi) != SR_OK) -// break; + if (!devc->triggersource) { + if (dso_force_trigger(sdi) != SR_OK) + break; + } sr_dbg("Successfully requested next chunk."); } break; 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-3457a/api.c b/src/hardware/hp-3457a/api.c index 4cfc2d75..9f006df5 100644 --- a/src/hardware/hp-3457a/api.c +++ b/src/hardware/hp-3457a/api.c @@ -52,12 +52,9 @@ static int create_front_channel(struct sr_dev_inst *sdi, int chan_idx) TRUE, "Front"); channel->priv = chanc; - front = g_malloc0(sizeof(*front)); - front->name = g_strdup("Front"); + front = sr_channel_group_new(sdi, "Front", NULL); front->channels = g_slist_append(front->channels, channel); - sdi->channel_groups = g_slist_append(sdi->channel_groups, front); - return chan_idx; } @@ -74,10 +71,7 @@ static int create_rear_channels(struct sr_dev_inst *sdi, int chan_idx, if (!card) return chan_idx; - group = g_malloc0(sizeof(*group)); - group->priv = NULL; - group->name = g_strdup(card->cg_name); - sdi->channel_groups = g_slist_append(sdi->channel_groups, group); + group = sr_channel_group_new(sdi, card->cg_name, NULL); for (i = 0; i < card->num_channels; i++) { diff --git a/src/hardware/hp-3478a/api.c b/src/hardware/hp-3478a/api.c index 0c461bb1..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", }; @@ -192,7 +192,9 @@ static int config_get(uint32_t key, GVariant **data, (void)cg; - devc = sdi ? sdi->priv : NULL; + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; switch (key) { case SR_CONF_LIMIT_SAMPLES: @@ -225,7 +227,7 @@ static int config_get(uint32_t key, GVariant **data, ret = hp_3478a_get_status_bytes(sdi); if (ret != SR_OK) return ret; - *data = g_variant_new_string(digits_map[devc->spec_digits]); + *data = g_variant_new_string(digits_map[devc->digits]); break; default: return SR_ERR_NA; @@ -247,7 +249,9 @@ static int config_set(uint32_t key, GVariant *data, (void)cg; - devc = sdi ? sdi->priv : NULL; + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; switch (key) { case SR_CONF_LIMIT_SAMPLES: @@ -294,7 +298,11 @@ static int config_list(uint32_t key, GVariant **data, GVariant *gvar, *arr[2]; GVariantBuilder gvb; - devc = sdi ? sdi->priv : NULL; + /* Only handle standard keys when no device instance is given. */ + if (!sdi) + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + + devc = sdi->priv; switch (key) { case SR_CONF_SCAN_OPTIONS: diff --git a/src/hardware/hp-3478a/protocol.c b/src/hardware/hp-3478a/protocol.c index 2e9130b0..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); } @@ -489,7 +489,7 @@ SR_PRIV int hp_3478a_receive_data(int fd, int revents, void *cb_data) */ if (sr_scpi_gpib_spoll(scpi, &status_register) != SR_OK) return FALSE; - if (!(((uint8_t)status_register) & 0x01)) + if (!(((uint8_t)status_register) & SRQ_BUS_AVAIL)) return TRUE; /* Get a reading from the DMM. */ 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/hp-59306a/api.c b/src/hardware/hp-59306a/api.c index c7a0afca..d2b84004 100644 --- a/src/hardware/hp-59306a/api.c +++ b/src/hardware/hp-59306a/api.c @@ -47,6 +47,7 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) struct channel_group_context *cgc; size_t idx, nr; struct sr_channel_group *cg; + char cg_name[24]; /* * The device cannot get identified by means of SCPI queries. @@ -73,15 +74,11 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) devc->channel_count = 6; for (idx = 0; idx < devc->channel_count; idx++) { nr = idx + 1; - - cg = g_malloc0(sizeof(*cg)); - cg->name = g_strdup_printf("R%zu", nr); - + snprintf(cg_name, sizeof(cg_name), "R%zu", nr); cgc = g_malloc0(sizeof(*cgc)); cgc->number = nr; - cg->priv = cgc; - - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + cg = sr_channel_group_new(sdi, cg_name, cgc); + (void)cg; } return sdi; diff --git a/src/hardware/hung-chang-dso-2100/api.c b/src/hardware/hung-chang-dso-2100/api.c index f3d94d20..88423357 100644 --- a/src/hardware/hung-chang-dso-2100/api.c +++ b/src/hardware/hung-chang-dso-2100/api.c @@ -138,11 +138,9 @@ static GSList *scan_port(GSList *devices, struct parport *port) ieee1284_ref(port); for (i = 0; i < NUM_CHANNELS; i++) { - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup(trigger_sources[i]); + cg = sr_channel_group_new(sdi, trigger_sources[i], NULL); ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, FALSE, trigger_sources[i]); cg->channels = g_slist_append(cg->channels, ch); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } devc = g_malloc0(sizeof(struct dev_context)); diff --git a/src/hardware/icstation-usbrelay/api.c b/src/hardware/icstation-usbrelay/api.c new file mode 100644 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/itech-it8500/api.c b/src/hardware/itech-it8500/api.c index a720af52..f6156f76 100644 --- a/src/hardware/itech-it8500/api.c +++ b/src/hardware/itech-it8500/api.c @@ -270,9 +270,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->version = g_strdup_printf("%x.%02x", fw_major, fw_minor); sdi->serial_num = unit_serial; - cg = g_malloc0(sizeof(*cg)); - cg->name = g_strdup("1"); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + cg = sr_channel_group_new(sdi, "1", NULL); ch = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V1"); cg->channels = g_slist_append(cg->channels, ch); ch = sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I1"); diff --git a/src/hardware/juntek-jds6600/api.c b/src/hardware/juntek-jds6600/api.c new file mode 100644 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 216869ed..5a4ee6fa 100644 --- a/src/hardware/kingst-la2016/api.c +++ b/src/hardware/kingst-la2016/api.c @@ -1,6 +1,7 @@ /* * This file is part of the libsigrok project. * + * Copyright (C) 2022 Gerhard Sittig * Copyright (C) 2020 Florian Schmidt * Copyright (C) 2013 Marcus Comstedt * Copyright (C) 2013 Bert Vermeulen @@ -20,36 +21,52 @@ * along with this program. If not, see . */ -/* mostly stolen from src/hardware/saleae-logic16/ */ +/* + * This driver implementation initially was derived from the + * src/hardware/saleae-logic16/ source code. + */ #include -#include -#include -#include -#include -#include + #include +#include + #include "libsigrok-internal.h" #include "protocol.h" static const uint32_t scanopts[] = { SR_CONF_CONN, + SR_CONF_PROBE_NAMES, }; static const uint32_t drvopts[] = { SR_CONF_LOGIC_ANALYZER, + SR_CONF_SIGNAL_GENERATOR, }; static const uint32_t devopts[] = { - /* TODO: SR_CONF_CONTINUOUS, */ SR_CONF_CONN | SR_CONF_GET, SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, - SR_CONF_LIMIT_SAMPLES | SR_CONF_SET | SR_CONF_GET | SR_CONF_LIST, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, +#if WITH_THRESHOLD_DEVCFG SR_CONF_VOLTAGE_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, - SR_CONF_LOGIC_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, - SR_CONF_LOGIC_THRESHOLD_CUSTOM | SR_CONF_GET | SR_CONF_SET, +#endif SR_CONF_TRIGGER_MATCH | SR_CONF_LIST, SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, + SR_CONF_CONTINUOUS | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg_logic[] = { +#if !WITH_THRESHOLD_DEVCFG + SR_CONF_VOLTAGE_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +#endif +}; + +static const uint32_t devopts_cg_pwm[] = { + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_SET, + SR_CONF_DUTY_CYCLE | SR_CONF_GET | SR_CONF_SET, }; static const int32_t trigger_matches[] = { @@ -59,12 +76,35 @@ static const int32_t trigger_matches[] = { SR_TRIGGER_FALLING, }; -static const char *channel_names[] = { - "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", "10", "11", "12", "13", "14", "15", +static const char *channel_names_logic[] = { + "CH0", "CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", + "CH8", "CH9", "CH10", "CH11", "CH12", "CH13", "CH14", "CH15", + "CH16", "CH17", "CH18", "CH19", "CH20", "CH21", "CH22", "CH23", + "CH24", "CH25", "CH26", "CH27", "CH28", "CH29", "CH30", "CH31", +}; + +static const char *channel_names_pwm[] = { + "PWM1", "PWM2", }; -static const uint64_t samplerates_la2016[] = { +/* + * The devices have an upper samplerate limit of 100/200/500 MHz each. + * But their hardware uses different base clocks (100/200/800MHz, this + * is _not_ a typo) and a 16bit divider. Which results in per-model ranges + * of supported rates which not only differ in the upper boundary, but + * also at the lower boundary. It's assumed that the 10kHz rate is not + * useful enough to provide by all means. Starting at 20kHz for all models + * simplfies the implementation of the config API routines, and eliminates + * redundancy in these samplerates tables. + * + * Streaming mode is constrained by the channel count and samplerate + * product (the bits per second which need to travel the USB connection + * while the acquisition is executing). Because streaming mode does not + * compress the capture data, a later implementation may desire a finer + * resolution. For now let's just stick with the 1/2/5 steps. + */ + +static const uint64_t rates_500mhz[] = { SR_KHZ(20), SR_KHZ(50), SR_KHZ(100), @@ -72,17 +112,16 @@ static const uint64_t samplerates_la2016[] = { SR_KHZ(500), SR_MHZ(1), SR_MHZ(2), - SR_MHZ(4), SR_MHZ(5), - SR_MHZ(8), SR_MHZ(10), SR_MHZ(20), SR_MHZ(50), SR_MHZ(100), SR_MHZ(200), + SR_MHZ(500), }; -static const uint64_t samplerates_la1016[] = { +static const uint64_t rates_200mhz[] = { SR_KHZ(20), SR_KHZ(50), SR_KHZ(100), @@ -90,291 +129,560 @@ static const uint64_t samplerates_la1016[] = { SR_KHZ(500), SR_MHZ(1), SR_MHZ(2), - SR_MHZ(4), SR_MHZ(5), - SR_MHZ(8), SR_MHZ(10), SR_MHZ(20), SR_MHZ(50), SR_MHZ(100), + SR_MHZ(200), }; -static const float logic_threshold_value[] = { - 1.58, - 2.5, - 1.165, - 1.5, - 1.25, - 0.9, - 0.75, - 0.60, - 0.45, +static const uint64_t rates_100mhz[] = { + SR_KHZ(20), + SR_KHZ(50), + SR_KHZ(100), + SR_KHZ(200), + SR_KHZ(500), + SR_MHZ(1), + SR_MHZ(2), + SR_MHZ(5), + SR_MHZ(10), + SR_MHZ(20), + SR_MHZ(50), + SR_MHZ(100), }; -static const char *logic_threshold[] = { - "TTL 5V", - "CMOS 5V", - "CMOS 3.3V", - "CMOS 3.0V", - "CMOS 2.5V", - "CMOS 1.8V", - "CMOS 1.5V", - "CMOS 1.2V", - "CMOS 0.9V", - "USER", +/* + * Only list a few discrete voltages, to form a useful set which covers + * most logic families. Too many choices can make some applications use + * a slider again. Which may lack a scale for the current value, and + * leave users without feedback what the currently used value might be. + */ +static const double threshold_ranges[][2] = { + { 0.4, 0.4, }, + { 0.6, 0.6, }, + { 0.9, 0.9, }, + { 1.2, 1.2, }, + { 1.4, 1.4, }, /* Default, 1.4V, index 4. */ + { 2.0, 2.0, }, + { 2.5, 2.5, }, + { 4.0, 4.0, }, }; +#define LOGIC_THRESHOLD_IDX_DFLT 4 + +static double threshold_voltage(const struct sr_dev_inst *sdi, double *high) +{ + struct dev_context *devc; + size_t idx; + double voltage; + + devc = sdi->priv; + idx = devc->threshold_voltage_idx; + voltage = threshold_ranges[idx][0]; + if (high) + *high = threshold_ranges[idx][1]; -#define MAX_NUM_LOGIC_THRESHOLD_ENTRIES ARRAY_SIZE(logic_threshold) + return voltage; +} + +/* Convenience. Release an allocated devc from error paths. */ +static void kingst_la2016_free_devc(struct dev_context *devc) +{ + if (!devc) + return; + g_free(devc->mcu_firmware); + g_free(devc->fpga_bitstream); + g_free(devc); +} + +/* Convenience. Release an allocated sdi from error paths. */ +static void kingst_la2016_free_sdi(struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb; + struct dev_context *devc; + + if (!sdi) + return; + + usb = sdi->conn; + if (usb && usb->devhdl) + sr_usb_close(usb); + sr_usb_dev_inst_free(usb); + + devc = sdi->priv; + kingst_la2016_free_devc(devc); + + sr_dev_inst_free(sdi); +} + +/* Convenience. Open a USB device (including claiming an interface). */ +static int la2016_open_usb(struct sr_usb_dev_inst *usb, + libusb_device *dev, gboolean show_message) +{ + int ret; + + ret = libusb_open(dev, &usb->devhdl); + if (ret != 0) { + if (show_message) { + sr_err("Cannot open device: %s.", + libusb_error_name(ret)); + } + return SR_ERR_IO; + } + + if (usb->address == 0xff) { + /* + * First encounter after firmware upload. + * Grab current address after enumeration. + */ + usb->address = libusb_get_device_address(dev); + } + + ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE); + if (ret == LIBUSB_ERROR_BUSY) { + sr_err("Cannot claim USB interface. Another program or driver using it?"); + return SR_ERR_IO; + } else if (ret == LIBUSB_ERROR_NO_DEVICE) { + sr_err("Device has been disconnected."); + return SR_ERR_IO; + } else if (ret != 0) { + sr_err("Cannot claim USB interface: %s.", + libusb_error_name(ret)); + return SR_ERR_IO; + } + + return SR_OK; +} + +/* Convenience. Close an opened USB device (and release the interface). */ +static void la2016_close_usb(struct sr_usb_dev_inst *usb) +{ + + if (!usb) + return; + + if (usb->devhdl) { + libusb_release_interface(usb->devhdl, USB_INTERFACE); + libusb_close(usb->devhdl); + usb->devhdl = NULL; + } +} + +/* Communicate to an USB device to identify the Kingst LA model. */ +static int la2016_identify_read(struct sr_dev_inst *sdi, + struct sr_usb_dev_inst *usb, libusb_device *dev, + gboolean show_message) +{ + int ret; + + ret = la2016_open_usb(usb, dev, show_message); + if (ret != SR_OK) { + if (show_message) + sr_err("Cannot communicate to MCU firmware."); + return ret; + } + + /* + * Also complete the hardware configuration (FPGA bitstream) + * when MCU firmware communication became operational. Either + * failure is considered fatal when probing for the device. + */ + ret = la2016_identify_device(sdi, show_message); + if (ret == SR_OK) { + ret = la2016_init_hardware(sdi); + } + + la2016_close_usb(usb); + + return ret; +} + +/* Find given conn_id in another USB enum. Identify Kingst LA model. */ +static int la2016_identify_enum(struct sr_dev_inst *sdi) +{ + struct sr_dev_driver *di; + struct drv_context *drvc; + struct sr_context *ctx; + libusb_device **devlist, *dev; + struct libusb_device_descriptor des; + int ret, id_ret; + size_t device_count, dev_idx; + char conn_id[64]; + + di = sdi->driver; + drvc = di->context; + ctx = drvc->sr_ctx;; + + ret = libusb_get_device_list(ctx->libusb_ctx, &devlist); + if (ret < 0) + return SR_ERR_IO; + device_count = ret; + if (!device_count) + return SR_ERR_IO; + id_ret = SR_ERR_IO; + for (dev_idx = 0; dev_idx < device_count; dev_idx++) { + dev = devlist[dev_idx]; + libusb_get_device_descriptor(dev, &des); + if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID) + continue; + if (des.iProduct != LA2016_IPRODUCT_INDEX) + continue; + ret = usb_get_port_path(dev, conn_id, sizeof(conn_id)); + if (ret < 0) + continue; + if (strcmp(sdi->connection_id, conn_id) != 0) + continue; + id_ret = la2016_identify_read(sdi, sdi->conn, dev, FALSE); + break; + } + libusb_free_device_list(devlist, 1); + + return id_ret; +} + +/* Wait for a device to re-appear after firmware upload. */ +static int la2016_identify_wait(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + uint64_t reset_done, now, elapsed_ms; + int ret; + + devc = sdi->priv; + + sr_info("Waiting for device to reset after firmware upload."); + now = g_get_monotonic_time(); + reset_done = devc->fw_uploaded + RENUM_GONE_DELAY_MS * 1000; + if (now < reset_done) + g_usleep(reset_done - now); + do { + now = g_get_monotonic_time(); + elapsed_ms = (now - devc->fw_uploaded) / 1000; + sr_spew("Waited %" PRIu64 "ms.", elapsed_ms); + ret = la2016_identify_enum(sdi); + if (ret == SR_OK) { + devc->fw_uploaded = 0; + break; + } + g_usleep(RENUM_POLL_INTERVAL_MS * 1000); + } while (elapsed_ms < RENUM_CHECK_PERIOD_MS); + if (ret != SR_OK) { + sr_err("Device failed to re-enumerate."); + return ret; + } + sr_info("Device came back after %" PRIi64 "ms.", elapsed_ms); + + return SR_OK; +} + +/* + * Open given conn_id from another USB enum. Used by dev_open(). Similar + * to, and should be kept in sync with la2016_identify_enum(). + */ +static int la2016_open_enum(struct sr_dev_inst *sdi) +{ + struct sr_dev_driver *di; + struct drv_context *drvc; + struct sr_context *ctx; + libusb_device **devlist, *dev; + struct libusb_device_descriptor des; + int ret, open_ret; + size_t device_count, dev_idx; + char conn_id[64]; + + di = sdi->driver; + drvc = di->context; + ctx = drvc->sr_ctx;; + + ret = libusb_get_device_list(ctx->libusb_ctx, &devlist); + if (ret < 0) + return SR_ERR_IO; + device_count = ret; + if (!device_count) + return SR_ERR_IO; + open_ret = SR_ERR_IO; + for (dev_idx = 0; dev_idx < device_count; dev_idx++) { + dev = devlist[dev_idx]; + libusb_get_device_descriptor(dev, &des); + if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID) + continue; + if (des.iProduct != LA2016_IPRODUCT_INDEX) + continue; + ret = usb_get_port_path(dev, conn_id, sizeof(conn_id)); + if (ret < 0) + continue; + if (strcmp(sdi->connection_id, conn_id) != 0) + continue; + open_ret = la2016_open_usb(sdi->conn, dev, TRUE); + break; + } + libusb_free_device_list(devlist, 1); + + return open_ret; +} static GSList *scan(struct sr_dev_driver *di, GSList *options) { struct drv_context *drvc; + struct sr_context *ctx; struct dev_context *devc; struct sr_dev_inst *sdi; struct sr_usb_dev_inst *usb; struct sr_config *src; GSList *l; - GSList *devices; + GSList *devices, *found_devices, *renum_devices; GSList *conn_devices; struct libusb_device_descriptor des; - libusb_device **devlist; - unsigned int i, j; + libusb_device **devlist, *dev; + size_t dev_count, dev_idx, ch_idx; + uint8_t bus, addr; + uint16_t pid; const char *conn; - char connection_id[64]; - int64_t fw_updated; - unsigned int dev_addr; + const char *probe_names; + char conn_id[64]; + int ret; + size_t ch_off, ch_max; + struct sr_channel *ch; + struct sr_channel_group *cg; drvc = di->context; + ctx = drvc->sr_ctx;; conn = NULL; + conn_devices = NULL; + probe_names = NULL; for (l = options; l; l = l->next) { src = l->data; switch (src->key) { case SR_CONF_CONN: conn = g_variant_get_string(src->data, NULL); break; + case SR_CONF_PROBE_NAMES: + probe_names = g_variant_get_string(src->data, NULL); + break; } } if (conn) - conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn); - else - conn_devices = NULL; + conn_devices = sr_usb_find(ctx->libusb_ctx, conn); + if (conn && !conn_devices) { + sr_err("Cannot find the specified connection '%s'.", conn); + return NULL; + } - /* Find all LA2016 devices and upload firmware to them. */ + /* + * Find all LA2016 devices, optionally upload firmware to them. + * Defer completion of sdi/devc creation until all (selected) + * devices were found in a usable state, and their models got + * identified which affect their feature set. It appears that + * we cannot communicate to the device within the same USB enum + * cycle, needs another USB enumeration after firmware upload. + */ devices = NULL; - libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); - for (i = 0; devlist[i]; i++) { - if (conn) { - usb = NULL; - for (l = conn_devices; l; l = l->next) { - usb = l->data; - if (usb->bus == libusb_get_bus_number(devlist[i]) && - usb->address == libusb_get_device_address(devlist[i])) - break; - } - if (!l) { - /* This device matched none of the ones that - * matched the conn specification. */ - continue; - } + found_devices = NULL; + renum_devices = NULL; + ret = libusb_get_device_list(ctx->libusb_ctx, &devlist); + if (ret < 0) { + sr_err("Cannot get device list: %s.", libusb_error_name(ret)); + return devices; + } + dev_count = ret; + for (dev_idx = 0; dev_idx < dev_count; dev_idx++) { + dev = devlist[dev_idx]; + bus = libusb_get_bus_number(dev); + addr = libusb_get_device_address(dev); + + /* Filter by connection when externally specified. */ + for (l = conn_devices; l; l = l->next) { + usb = l->data; + if (usb->bus == bus && usb->address == addr) + break; } - - libusb_get_device_descriptor(devlist[i], &des); - - if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0) + if (conn_devices && !l) { + sr_spew("Bus %hhu, addr %hhu do not match specified filter.", + bus, addr); continue; + } + /* Check USB VID:PID. Get the connection string. */ + libusb_get_device_descriptor(dev, &des); if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID) continue; + pid = des.idProduct; + ret = usb_get_port_path(dev, conn_id, sizeof(conn_id)); + if (ret < 0) + continue; + sr_dbg("USB enum found %04x:%04x at path %s, %d.%d.", + des.idVendor, des.idProduct, conn_id, bus, addr); + usb = sr_usb_dev_inst_new(bus, addr, NULL); - /* Already has the firmware */ - sr_dbg("Found a LA2016 device."); - sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi = g_malloc0(sizeof(*sdi)); + sdi->driver = di; sdi->status = SR_ST_INITIALIZING; - sdi->connection_id = g_strdup(connection_id); + sdi->inst_type = SR_INST_USB; + sdi->connection_id = g_strdup(conn_id); + sdi->conn = usb; - fw_updated = 0; - dev_addr = libusb_get_device_address(devlist[i]); - if (des.iProduct != 2) { - sr_info("device at '%s' has no firmware loaded!", connection_id); + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; - if (la2016_upload_firmware(drvc->sr_ctx, devlist[i], des.idProduct) != SR_OK) { - sr_err("uC firmware upload failed!"); - g_free(sdi->connection_id); - g_free(sdi); + /* + * Load MCU firmware if it is currently missing. Which + * makes the device disappear and renumerate in USB. + * We need to come back another time to communicate to + * this device. + */ + devc->fw_uploaded = 0; + devc->usb_pid = pid; + if (des.iProduct != LA2016_IPRODUCT_INDEX) { + sr_info("Uploading MCU firmware to '%s'.", conn_id); + ret = la2016_upload_firmware(sdi, ctx, dev, FALSE); + if (ret != SR_OK) { + sr_err("MCU firmware upload failed."); + kingst_la2016_free_sdi(sdi); + continue; + } + devc->fw_uploaded = g_get_monotonic_time(); + usb->address = 0xff; + renum_devices = g_slist_append(renum_devices, sdi); + continue; + } else { + ret = la2016_upload_firmware(sdi, NULL, NULL, TRUE); + if (ret != SR_OK) { + sr_err("MCU firmware filename check failed."); + kingst_la2016_free_sdi(sdi); continue; } - fw_updated = g_get_monotonic_time(); - dev_addr = 0xff; /* to mark that we don't know address yet... ugly */ } - sdi->vendor = g_strdup("Kingst"); - sdi->model = g_strdup("LA2016"); - - for (j = 0; j < ARRAY_SIZE(channel_names); j++) - sr_channel_new(sdi, j, SR_CHANNEL_LOGIC, TRUE, channel_names[j]); - - devices = g_slist_append(devices, sdi); - - devc = g_malloc0(sizeof(struct dev_context)); - sdi->priv = devc; - devc->fw_updated = fw_updated; - devc->threshold_voltage_idx = 0; - devc->threshold_voltage = logic_threshold_value[devc->threshold_voltage_idx]; - - sdi->status = SR_ST_INACTIVE; - sdi->inst_type = SR_INST_USB; - - sdi->conn = sr_usb_dev_inst_new( - libusb_get_bus_number(devlist[i]), - dev_addr, NULL); + /* + * Communicate to the MCU firmware to access EEPROM data + * which lets us identify the device type. Then stop, to + * share remaining sdi/devc creation with those devices + * which had their MCU firmware uploaded above and which + * get revisited later. + */ + ret = la2016_identify_read(sdi, usb, dev, TRUE); + if (ret != SR_OK || !devc->model) { + sr_err("Unknown or unsupported device type."); + kingst_la2016_free_sdi(sdi); + continue; + } + found_devices = g_slist_append(found_devices, sdi); } libusb_free_device_list(devlist, 1); - g_slist_free_full(conn_devices, (GDestroyNotify)sr_usb_dev_inst_free); - - return std_scan_complete(di, devices); -} - -static int la2016_dev_open(struct sr_dev_inst *sdi) -{ - struct sr_dev_driver *di; - libusb_device **devlist; - struct sr_usb_dev_inst *usb; - struct libusb_device_descriptor des; - struct drv_context *drvc; - int ret, i, device_count; - char connection_id[64]; - - di = sdi->driver; - drvc = di->context; - usb = sdi->conn; - ret = SR_ERR; - - device_count = libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); - if (device_count < 0) { - sr_err("Failed to get device list: %s.", libusb_error_name(device_count)); - return SR_ERR; - } - - for (i = 0; i < device_count; i++) { - libusb_get_device_descriptor(devlist[i], &des); + g_slist_free_full(conn_devices, sr_usb_dev_inst_free_cb); - if (des.idVendor != LA2016_VID || des.idProduct != LA2016_PID || des.iProduct != 2) + /* + * Wait for devices to re-appear after firmware upload. Append + * the yet unidentified device to the list of found devices, or + * release the previously allocated sdi/devc. + */ + for (l = renum_devices; l; l = l->next) { + sdi = l->data; + devc = sdi->priv; + ret = la2016_identify_wait(sdi); + if (ret != SR_OK || !devc->model) { + sr_dbg("Skipping unusable '%s'.", sdi->connection_id); + kingst_la2016_free_sdi(sdi); continue; + } + found_devices = g_slist_append(found_devices, sdi); + } + g_slist_free(renum_devices); - if ((sdi->status == SR_ST_INITIALIZING) || (sdi->status == SR_ST_INACTIVE)) { - /* - * Check device by its physical USB bus/port address. - */ - if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0) - continue; + /* + * All found devices got identified, their type is known here. + * Complete the sdi/devc creation. Assign default settings + * because the vendor firmware would not let us read back the + * previously written configuration. + */ + for (l = found_devices; l; l = l->next) { + sdi = l->data; + devc = sdi->priv; - if (strcmp(sdi->connection_id, connection_id)) - /* This is not the one. */ - continue; + sdi->vendor = g_strdup("Kingst"); + sdi->model = g_strdup(devc->model->name); + ch_off = 0; + + /* Create the "Logic" channel group. */ + ch_max = ARRAY_SIZE(channel_names_logic); + if (ch_max > devc->model->channel_count) + ch_max = devc->model->channel_count; + devc->channel_names_logic = sr_parse_probe_names(probe_names, + channel_names_logic, ch_max, ch_max, &ch_max); + cg = sr_channel_group_new(sdi, "Logic", NULL); + devc->cg_logic = cg; + for (ch_idx = 0; ch_idx < ch_max; ch_idx++) { + ch = sr_channel_new(sdi, ch_off, + SR_CHANNEL_LOGIC, TRUE, + devc->channel_names_logic[ch_idx]); + ch_off++; + cg->channels = g_slist_append(cg->channels, ch); } - if (!(ret = libusb_open(devlist[i], &usb->devhdl))) { - if (usb->address == 0xff) - /* - * First time we touch this device after FW - * upload, so we don't know the address yet. - */ - usb->address = libusb_get_device_address(devlist[i]); - } else { - sr_err("Failed to open device: %s.", libusb_error_name(ret)); - ret = SR_ERR; - break; + /* Create the "PWMx" channel groups. */ + ch_max = ARRAY_SIZE(channel_names_pwm); + for (ch_idx = 0; ch_idx < ch_max; ch_idx++) { + const char *name; + name = channel_names_pwm[ch_idx]; + cg = sr_channel_group_new(sdi, name, NULL); + if (!devc->cg_pwm) + devc->cg_pwm = cg; + ch = sr_channel_new(sdi, ch_off, + SR_CHANNEL_ANALOG, FALSE, name); + ch_off++; + cg->channels = g_slist_append(cg->channels, ch); } - ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE); - if (ret == LIBUSB_ERROR_BUSY) { - sr_err("Unable to claim USB interface. Another " - "program or driver has already claimed it."); - ret = SR_ERR; - break; - } else if (ret == LIBUSB_ERROR_NO_DEVICE) { - sr_err("Device has been disconnected."); - ret = SR_ERR; - break; - } else if (ret != 0) { - sr_err("Unable to claim interface: %s.", libusb_error_name(ret)); - ret = SR_ERR; - break; + /* + * Ideally we'd get the previous configuration from the + * hardware, but this device is write-only. So we have + * to assign a fixed set of initial configuration values. + */ + sr_sw_limits_init(&devc->sw_limits); + devc->sw_limits.limit_samples = 0; + devc->capture_ratio = 50; + devc->samplerate = devc->model->samplerate; + if (!devc->model->memory_bits) + devc->continuous = TRUE; + devc->threshold_voltage_idx = LOGIC_THRESHOLD_IDX_DFLT; + if (ARRAY_SIZE(devc->pwm_setting) >= 1) { + devc->pwm_setting[0].enabled = FALSE; + devc->pwm_setting[0].freq = SR_KHZ(1); + devc->pwm_setting[0].duty = 50; } - - if ((ret = la2016_init_device(sdi)) != SR_OK) { - sr_err("Failed to init device."); - break; + if (ARRAY_SIZE(devc->pwm_setting) >= 2) { + devc->pwm_setting[1].enabled = FALSE; + devc->pwm_setting[1].freq = SR_KHZ(100); + devc->pwm_setting[1].duty = 50; } - sr_info("Opened device on %d.%d (logical) / %s (physical), interface %d.", - usb->bus, usb->address, sdi->connection_id, USB_INTERFACE); - - ret = SR_OK; - - break; - } - - libusb_free_device_list(devlist, 1); - - if (ret != SR_OK) { - if (usb->devhdl) { - libusb_release_interface(usb->devhdl, USB_INTERFACE); - libusb_close(usb->devhdl); - usb->devhdl = NULL; - } - return SR_ERR; + sdi->status = SR_ST_INACTIVE; + devices = g_slist_append(devices, sdi); } + g_slist_free(found_devices); - return SR_OK; + return std_scan_complete(di, devices); } static int dev_open(struct sr_dev_inst *sdi) { struct dev_context *devc; - int64_t timediff_us, timediff_ms; - uint64_t reset_done; - uint64_t now; int ret; + size_t ch; devc = sdi->priv; - /* - * If the firmware was recently uploaded, wait up to MAX_RENUM_DELAY_MS - * milliseconds for the FX2 to renumerate. - */ - ret = SR_ERR; - if (devc->fw_updated > 0) { - sr_info("Waiting for device to reset after firmware upload."); - /* Takes >= 2000ms for the uC to be gone from the USB bus. */ - reset_done = devc->fw_updated + 18 * (uint64_t)1e5; /* 1.8 seconds */ - now = g_get_monotonic_time(); - if (reset_done > now) - g_usleep(reset_done - now); - timediff_ms = 0; - while (timediff_ms < MAX_RENUM_DELAY_MS) { - g_usleep(200 * 1000); - - timediff_us = g_get_monotonic_time() - devc->fw_updated; - timediff_ms = timediff_us / 1000; - - if ((ret = la2016_dev_open(sdi)) == SR_OK) - break; - sr_spew("Waited %" PRIi64 "ms.", timediff_ms); - } - if (ret != SR_OK) { - sr_err("Device failed to re-enumerate."); - return SR_ERR; - } - sr_info("Device came back after %" PRIi64 "ms.", timediff_ms); - } else { - ret = la2016_dev_open(sdi); + ret = la2016_open_enum(sdi); + if (ret != SR_OK) { + sr_err("Cannot open device."); + return ret; } - if (ret != SR_OK) { - sr_err("Unable to open device."); - return SR_ERR; + /* Send most recent PWM configuration to the device. */ + for (ch = 0; ch < ARRAY_SIZE(devc->pwm_setting); ch++) { + ret = la2016_write_pwm_config(sdi, ch); + if (ret != SR_OK) + return ret; } return SR_OK; @@ -389,62 +697,151 @@ static int dev_close(struct sr_dev_inst *sdi) if (!usb->devhdl) return SR_ERR_BUG; - la2016_deinit_device(sdi); + la2016_release_resources(sdi); + + if (WITH_DEINIT_IN_CLOSE) + la2016_deinit_hardware(sdi); sr_info("Closing device on %d.%d (logical) / %s (physical) interface %d.", usb->bus, usb->address, sdi->connection_id, USB_INTERFACE); - libusb_release_interface(usb->devhdl, USB_INTERFACE); - libusb_close(usb->devhdl); - usb->devhdl = NULL; + la2016_close_usb(sdi->conn); return SR_OK; } +/* Config API helper. Get type and index of a channel group. */ +static int get_cg_index(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, + int *type, size_t *logic, size_t *analog) +{ + struct dev_context *devc; + GSList *l; + size_t idx; + + /* Preset return values. */ + if (type) + *type = 0; + if (logic) + *logic = 0; + if (analog) + *analog = 0; + + /* Start categorizing the received cg. */ + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!cg) + return SR_OK; + l = sdi->channel_groups; + + /* First sdi->channelgroups item is "Logic". */ + if (!l) + return SR_ERR_BUG; + if (cg == l->data) { + if (type) + *type = SR_CHANNEL_LOGIC; + if (logic) + *logic = 0; + return SR_OK; + } + l = l->next; + + /* Next sdi->channelgroups items are "PWMx". */ + idx = 0; + while (l && l->data != cg) { + idx++; + l = l->next; + } + if (l && idx < ARRAY_SIZE(devc->pwm_setting)) { + if (type) + *type = SR_CHANNEL_ANALOG; + if (analog) + *analog = idx; + return SR_OK; + } + + return SR_ERR_ARG; +} + static int config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; + int ret, cg_type; + size_t logic_idx, analog_idx; + struct pwm_setting *pwm; struct sr_usb_dev_inst *usb; - double rounded; + double voltage, rounded; - (void)cg; + (void)rounded; + (void)voltage; if (!sdi) return SR_ERR_ARG; devc = sdi->priv; + /* Check for types (and index) of channel groups. */ + ret = get_cg_index(sdi, cg, &cg_type, &logic_idx, &analog_idx); + if (cg && ret != SR_OK) + return SR_ERR_ARG; + + /* Handle requests for the "Logic" channel group. */ + if (cg && cg_type == SR_CHANNEL_LOGIC) { + switch (key) { +#if !WITH_THRESHOLD_DEVCFG + case SR_CONF_VOLTAGE_THRESHOLD: + voltage = threshold_voltage(sdi, NULL); + *data = std_gvar_tuple_double(voltage, voltage); + break; +#endif /* WITH_THRESHOLD_DEVCFG */ + default: + return SR_ERR_NA; + } + return SR_OK; + } + + /* Handle requests for the "PWMx" channel groups. */ + if (cg && cg_type == SR_CHANNEL_ANALOG) { + pwm = &devc->pwm_setting[analog_idx]; + switch (key) { + case SR_CONF_ENABLED: + *data = g_variant_new_boolean(pwm->enabled); + break; + case SR_CONF_OUTPUT_FREQUENCY: + *data = g_variant_new_double(pwm->freq); + break; + case SR_CONF_DUTY_CYCLE: + *data = g_variant_new_double(pwm->duty); + break; + default: + return SR_ERR_NA; + } + return SR_OK; + } + switch (key) { case SR_CONF_CONN: - if (!sdi->conn) - return SR_ERR_ARG; usb = sdi->conn; - if (usb->address == 255) { - /* Device still needs to re-enumerate after firmware - * upload, so we don't know its (future) address. */ - return SR_ERR; - } *data = g_variant_new_printf("%d.%d", usb->bus, usb->address); break; case SR_CONF_SAMPLERATE: - *data = g_variant_new_uint64(devc->cur_samplerate); + *data = g_variant_new_uint64(devc->samplerate); break; case SR_CONF_LIMIT_SAMPLES: - *data = g_variant_new_uint64(devc->limit_samples); - break; + case SR_CONF_LIMIT_MSEC: + return sr_sw_limits_config_get(&devc->sw_limits, key, data); case SR_CONF_CAPTURE_RATIO: *data = g_variant_new_uint64(devc->capture_ratio); break; +#if WITH_THRESHOLD_DEVCFG case SR_CONF_VOLTAGE_THRESHOLD: - rounded = (int)(devc->threshold_voltage / 0.1) * 0.1; - *data = std_gvar_tuple_double(rounded, rounded + 0.1); - return SR_OK; - case SR_CONF_LOGIC_THRESHOLD: - *data = g_variant_new_string(logic_threshold[devc->threshold_voltage_idx]); + voltage = threshold_voltage(sdi, NULL); + *data = std_gvar_tuple_double(voltage, voltage); break; - case SR_CONF_LOGIC_THRESHOLD_CUSTOM: - *data = g_variant_new_double(devc->threshold_voltage); +#endif /* WITH_THRESHOLD_DEVCFG */ + case SR_CONF_CONTINUOUS: + *data = g_variant_new_boolean(devc->continuous); break; - default: return SR_ERR_NA; } @@ -456,41 +853,96 @@ static int config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; - double low, high; + int ret, cg_type; + size_t logic_idx, analog_idx; + struct pwm_setting *pwm; + double value_f; int idx; - - (void)cg; + gboolean on; devc = sdi->priv; + /* Check for types (and index) of channel groups. */ + ret = get_cg_index(sdi, cg, &cg_type, &logic_idx, &analog_idx); + if (cg && ret != SR_OK) + return SR_ERR_ARG; + + /* Handle requests for the "Logic" channel group. */ + if (cg && cg_type == SR_CHANNEL_LOGIC) { + switch (key) { +#if !WITH_THRESHOLD_DEVCFG + case SR_CONF_LOGIC_THRESHOLD: + idx = std_double_tuple_idx(data, + ARRAY_AND_SIZE(threshold_ranges)); + if (idx < 0) + return SR_ERR_ARG; + devc->threshold_voltage_idx = idx; + break; +#endif /* WITH_THRESHOLD_DEVCFG */ + default: + return SR_ERR_NA; + } + return SR_OK; + } + + /* Handle requests for the "PWMx" channel groups. */ + if (cg && cg_type == SR_CHANNEL_ANALOG) { + pwm = &devc->pwm_setting[analog_idx]; + switch (key) { + case SR_CONF_ENABLED: + pwm->enabled = g_variant_get_boolean(data); + ret = la2016_write_pwm_config(sdi, analog_idx); + if (ret != SR_OK) + return ret; + break; + case SR_CONF_OUTPUT_FREQUENCY: + value_f = g_variant_get_double(data); + if (value_f <= 0.0 || value_f > MAX_PWM_FREQ) + return SR_ERR_ARG; + pwm->freq = value_f; + ret = la2016_write_pwm_config(sdi, analog_idx); + if (ret != SR_OK) + return ret; + break; + case SR_CONF_DUTY_CYCLE: + value_f = g_variant_get_double(data); + if (value_f <= 0.0 || value_f > 100.0) + return SR_ERR_ARG; + pwm->duty = value_f; + ret = la2016_write_pwm_config(sdi, analog_idx); + if (ret != SR_OK) + return ret; + break; + default: + return SR_ERR_NA; + } + return SR_OK; + } + switch (key) { case SR_CONF_SAMPLERATE: - devc->cur_samplerate = g_variant_get_uint64(data); + devc->samplerate = g_variant_get_uint64(data); break; case SR_CONF_LIMIT_SAMPLES: - devc->limit_samples = g_variant_get_uint64(data); - break; + case SR_CONF_LIMIT_MSEC: + return sr_sw_limits_config_set(&devc->sw_limits, key, data); case SR_CONF_CAPTURE_RATIO: devc->capture_ratio = g_variant_get_uint64(data); break; +#if WITH_THRESHOLD_DEVCFG case SR_CONF_VOLTAGE_THRESHOLD: - g_variant_get(data, "(dd)", &low, &high); - devc->threshold_voltage = (low + high) / 2.0; - devc->threshold_voltage_idx = MAX_NUM_LOGIC_THRESHOLD_ENTRIES - 1; /* USER */ - break; - case SR_CONF_LOGIC_THRESHOLD: { - if ((idx = std_str_idx(data, logic_threshold, MAX_NUM_LOGIC_THRESHOLD_ENTRIES)) < 0) + idx = std_double_tuple_idx(data, + ARRAY_AND_SIZE(threshold_ranges)); + if (idx < 0) return SR_ERR_ARG; - if (idx == MAX_NUM_LOGIC_THRESHOLD_ENTRIES - 1) { - /* user threshold */ - } else { - devc->threshold_voltage = logic_threshold_value[idx]; - } devc->threshold_voltage_idx = idx; break; - } - case SR_CONF_LOGIC_THRESHOLD_CUSTOM: - devc->threshold_voltage = g_variant_get_double(data); +#endif /* WITH_THRESHOLD_DEVCFG */ + case SR_CONF_CONTINUOUS: + on = g_variant_get_boolean(data); + if (!devc->model->memory_bits && !on) + return SR_ERR_ARG; + devc->continuous = on; break; default: return SR_ERR_NA; @@ -503,36 +955,79 @@ static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; + int ret, cg_type; + size_t logic_idx, analog_idx; + + devc = sdi ? sdi->priv : NULL; + + /* Check for types (and index) of channel groups. */ + ret = get_cg_index(sdi, cg, &cg_type, &logic_idx, &analog_idx); + if (cg && ret != SR_OK) + return SR_ERR_ARG; + + /* Handle requests for the "Logic" channel group. */ + if (cg && cg_type == SR_CHANNEL_LOGIC) { + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + if (ARRAY_SIZE(devopts_cg_logic) == 0) + return SR_ERR_NA; + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + devopts_cg_logic, ARRAY_SIZE(devopts_cg_logic), + sizeof(devopts_cg_logic[0])); + break; +#if !WITH_THRESHOLD_DEVCFG + case SR_CONF_VOLTAGE_THRESHOLD: + *data = std_gvar_thresholds(ARRAY_AND_SIZE(threshold_ranges)); + break; +#endif /* WITH_THRESHOLD_DEVCFG */ + default: + return SR_ERR_NA; + } + return SR_OK; + } + + /* Handle requests for the "PWMx" channel groups. */ + if (cg && cg_type == SR_CHANNEL_ANALOG) { + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + devopts_cg_pwm, ARRAY_SIZE(devopts_cg_pwm), + sizeof(devopts_cg_pwm[0])); + break; + default: + return SR_ERR_NA; + } + return SR_OK; + } switch (key) { case SR_CONF_SCAN_OPTIONS: case SR_CONF_DEVICE_OPTIONS: - return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); case SR_CONF_SAMPLERATE: if (!sdi) return SR_ERR_ARG; - devc = sdi->priv; - if (devc->max_samplerate == SR_MHZ(200)) { - *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates_la2016)); - } - else { - *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates_la1016)); - } + if (devc->model->samplerate == SR_MHZ(500)) + *data = std_gvar_samplerates(ARRAY_AND_SIZE(rates_500mhz)); + else if (devc->model->samplerate == SR_MHZ(200)) + *data = std_gvar_samplerates(ARRAY_AND_SIZE(rates_200mhz)); + else if (devc->model->samplerate == SR_MHZ(100)) + *data = std_gvar_samplerates(ARRAY_AND_SIZE(rates_100mhz)); + else + return SR_ERR_BUG; break; case SR_CONF_LIMIT_SAMPLES: - *data = std_gvar_tuple_u64(LA2016_NUM_SAMPLES_MIN, LA2016_NUM_SAMPLES_MAX); + *data = std_gvar_tuple_u64(0, LA2016_NUM_SAMPLES_MAX); break; +#if WITH_THRESHOLD_DEVCFG case SR_CONF_VOLTAGE_THRESHOLD: - *data = std_gvar_min_max_step_thresholds( - LA2016_THR_VOLTAGE_MIN, - LA2016_THR_VOLTAGE_MAX, 0.1); + *data = std_gvar_thresholds(ARRAY_AND_SIZE(threshold_ranges)); break; +#endif /* WITH_THRESHOLD_DEVCFG */ case SR_CONF_TRIGGER_MATCH: *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches)); break; - case SR_CONF_LOGIC_THRESHOLD: - *data = g_variant_new_strv(logic_threshold, MAX_NUM_LOGIC_THRESHOLD_ENTRIES); - break; default: return SR_ERR_NA; } @@ -540,247 +1035,74 @@ static int config_list(uint32_t key, GVariant **data, return SR_OK; } -static void send_chunk(struct sr_dev_inst *sdi, - const uint8_t *packets, unsigned int num_tfers) -{ - struct dev_context *devc; - struct sr_datafeed_logic logic; - struct sr_datafeed_packet sr_packet; - unsigned int max_samples, n_samples, total_samples, free_n_samples; - unsigned int i, j, k; - int do_signal_trigger; - uint16_t *wp; - const uint8_t *rp; - uint16_t state; - uint8_t repetitions; - - devc = sdi->priv; - - logic.unitsize = 2; - logic.data = devc->convbuffer; - - sr_packet.type = SR_DF_LOGIC; - sr_packet.payload = &logic; - - max_samples = devc->convbuffer_size / 2; - n_samples = 0; - wp = (uint16_t *)devc->convbuffer; - total_samples = 0; - do_signal_trigger = 0; - - if (devc->had_triggers_configured && devc->reading_behind_trigger == 0 && devc->info.n_rep_packets_before_trigger == 0) { - std_session_send_df_trigger(sdi); - devc->reading_behind_trigger = 1; - } - - rp = packets; - for (i = 0; i < num_tfers; i++) { - for (k = 0; k < NUM_PACKETS_IN_CHUNK; k++) { - free_n_samples = max_samples - n_samples; - if (free_n_samples < 256 || do_signal_trigger) { - logic.length = n_samples * 2; - sr_session_send(sdi, &sr_packet); - n_samples = 0; - wp = (uint16_t *)devc->convbuffer; - if (do_signal_trigger) { - std_session_send_df_trigger(sdi); - do_signal_trigger = 0; - } - } - - state = read_u16le_inc(&rp); - repetitions = read_u8_inc(&rp); - for (j = 0; j < repetitions; j++) - *wp++ = state; - - n_samples += repetitions; - total_samples += repetitions; - devc->total_samples += repetitions; - if (!devc->reading_behind_trigger) { - devc->n_reps_until_trigger--; - if (devc->n_reps_until_trigger == 0) { - devc->reading_behind_trigger = 1; - do_signal_trigger = 1; - sr_dbg(" here is trigger position after %" PRIu64 " samples, %.6fms", - devc->total_samples, - (double)devc->total_samples / devc->cur_samplerate * 1e3); - } - } - } - (void)read_u8_inc(&rp); /* Skip sequence number. */ - } - if (n_samples) { - logic.length = n_samples * 2; - sr_session_send(sdi, &sr_packet); - if (do_signal_trigger) { - std_session_send_df_trigger(sdi); - } - } - sr_dbg("send_chunk done after %d samples", total_samples); -} - -static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) -{ - struct sr_dev_inst *sdi; - struct dev_context *devc; - struct sr_usb_dev_inst *usb; - int ret; - - sdi = transfer->user_data; - devc = sdi->priv; - usb = sdi->conn; - - sr_dbg("receive_transfer(): status %s received %d bytes.", - libusb_error_name(transfer->status), transfer->actual_length); - - if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { - sr_err("bulk transfer timeout!"); - devc->transfer_finished = 1; - } - send_chunk(sdi, transfer->buffer, transfer->actual_length / TRANSFER_PACKET_LENGTH); - - devc->n_bytes_to_read -= transfer->actual_length; - if (devc->n_bytes_to_read) { - uint32_t to_read = devc->n_bytes_to_read; - /* determine read size for the next usb transfer */ - if (to_read >= LA2016_USB_BUFSZ) - to_read = LA2016_USB_BUFSZ; - else /* last transfer, make read size some multiple of LA2016_EP6_PKTSZ */ - to_read = (to_read + (LA2016_EP6_PKTSZ-1)) & ~(LA2016_EP6_PKTSZ-1); - libusb_fill_bulk_transfer( - transfer, usb->devhdl, - 0x86, transfer->buffer, to_read, - receive_transfer, (void *)sdi, DEFAULT_TIMEOUT_MS); - - if ((ret = libusb_submit_transfer(transfer)) == 0) - return; - sr_err("Failed to submit further transfer: %s.", libusb_error_name(ret)); - } - - g_free(transfer->buffer); - libusb_free_transfer(transfer); - devc->transfer_finished = 1; -} - -static int handle_event(int fd, int revents, void *cb_data) -{ - const struct sr_dev_inst *sdi; - struct dev_context *devc; - struct drv_context *drvc; - struct timeval tv; - - (void)fd; - (void)revents; - - sdi = cb_data; - devc = sdi->priv; - drvc = sdi->driver->context; - - if (devc->have_trigger == 0) { - if (la2016_has_triggered(sdi) == 0) { - /* not yet ready for download */ - return TRUE; - } - devc->have_trigger = 1; - devc->transfer_finished = 0; - devc->reading_behind_trigger = 0; - devc->total_samples = 0; - /* we can start retrieving data! */ - if (la2016_start_retrieval(sdi, receive_transfer) != SR_OK) { - sr_err("failed to start retrieval!"); - return FALSE; - } - sr_dbg("retrieval is started..."); - std_session_send_df_frame_begin(sdi); - - return TRUE; - } - - tv.tv_sec = tv.tv_usec = 0; - libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv); - - if (devc->transfer_finished) { - sr_dbg("transfer is finished!"); - std_session_send_df_frame_end(sdi); - - usb_source_remove(sdi->session, drvc->sr_ctx); - std_session_send_df_end(sdi); - - la2016_stop_acquisition(sdi); - - g_free(devc->convbuffer); - devc->convbuffer = NULL; - - devc->transfer = NULL; - - sr_dbg("transfer is now finished"); - } - - return TRUE; -} - -static void abort_acquisition(struct dev_context *devc) -{ - if (devc->transfer) - libusb_cancel_transfer(devc->transfer); -} - -static int configure_channels(const struct sr_dev_inst *sdi) -{ - struct dev_context *devc; - - devc = sdi->priv; - devc->cur_channels = 0; - devc->num_channels = 0; - - for (GSList *l = sdi->channels; l; l = l->next) { - struct sr_channel *ch = (struct sr_channel*)l->data; - if (ch->enabled == FALSE) - continue; - devc->cur_channels |= 1 << ch->index; - devc->num_channels++; - } - - return SR_OK; -} - static int dev_acquisition_start(const struct sr_dev_inst *sdi) { struct sr_dev_driver *di; struct drv_context *drvc; + struct sr_context *ctx; struct dev_context *devc; + size_t unitsize, xfersize, repsize, seqsize; + double voltage; int ret; di = sdi->driver; drvc = di->context; + ctx = drvc->sr_ctx;; devc = sdi->priv; - if (configure_channels(sdi) != SR_OK) { - sr_err("Failed to configure channels."); - return SR_ERR; + if (!devc->feed_queue) { + /* + * TODO + * Move this into protocol.c which concentrates the + * wire format. The api.c source should not bother. + */ + if (devc->model->channel_count == 32) { + unitsize = sizeof(uint32_t); + repsize = sizeof(uint8_t); + seqsize = 2 * sizeof(uint8_t); + xfersize = 32; + } else if (devc->model->channel_count == 16) { + unitsize = sizeof(uint16_t); + repsize = sizeof(uint8_t); + seqsize = 1 * sizeof(uint8_t); + xfersize = 16; + } else { + return SR_ERR_ARG; + } + devc->feed_queue = feed_queue_logic_alloc(sdi, + LA2016_CONVBUFFER_SIZE, unitsize); + if (!devc->feed_queue) { + sr_err("Cannot allocate buffer for session feed."); + return SR_ERR_MALLOC; + } + devc->transfer_size = xfersize; + devc->sequence_size = seqsize; + devc->packets_per_chunk = xfersize; + devc->packets_per_chunk -= seqsize; + devc->packets_per_chunk /= unitsize + repsize; } - devc->convbuffer_size = 4 * 1024 * 1024; - if (!(devc->convbuffer = g_try_malloc(devc->convbuffer_size))) { - sr_err("Conversion buffer malloc failed."); - return SR_ERR_MALLOC; - } + sr_sw_limits_acquisition_start(&devc->sw_limits); - if ((ret = la2016_setup_acquisition(sdi)) != SR_OK) { - g_free(devc->convbuffer); - devc->convbuffer = NULL; + voltage = threshold_voltage(sdi, NULL); + ret = la2016_setup_acquisition(sdi, voltage); + if (ret != SR_OK) { + feed_queue_logic_free(devc->feed_queue); + devc->feed_queue = NULL; return ret; } - devc->ctx = drvc->sr_ctx; - - if ((ret = la2016_start_acquisition(sdi)) != SR_OK) { - abort_acquisition(devc); + ret = la2016_start_acquisition(sdi); + if (ret != SR_OK) { + la2016_abort_acquisition(sdi); + feed_queue_logic_free(devc->feed_queue); + devc->feed_queue = NULL; return ret; } - devc->have_trigger = 0; - usb_source_add(sdi->session, drvc->sr_ctx, 50, handle_event, (void *)sdi); + devc->completion_seen = FALSE; + usb_source_add(sdi->session, ctx, 50, + la2016_receive_data, (void *)sdi); std_session_send_df_header(sdi); @@ -792,7 +1114,6 @@ static int dev_acquisition_stop(struct sr_dev_inst *sdi) int ret; ret = la2016_abort_acquisition(sdi); - abort_acquisition(sdi->priv); return ret; } diff --git a/src/hardware/kingst-la2016/protocol.c b/src/hardware/kingst-la2016/protocol.c index ecb21e3f..dd806eaa 100644 --- a/src/hardware/kingst-la2016/protocol.c +++ b/src/hardware/kingst-la2016/protocol.c @@ -1,6 +1,7 @@ /* * This file is part of the libsigrok project. * + * Copyright (C) 2022 Gerhard Sittig * Copyright (C) 2020 Florian Schmidt * Copyright (C) 2013 Marcus Comstedt * Copyright (C) 2013 Bert Vermeulen @@ -21,149 +22,321 @@ */ #include -#include -#include -#include -#include -#include -#include -#include -#include + #include +#include + #include "libsigrok-internal.h" #include "protocol.h" -#define UC_FIRMWARE "kingst-la-%04x.fw" -#define FPGA_FW_LA2016 "kingst-la2016-fpga.bitstream" -#define FPGA_FW_LA2016A "kingst-la2016a1-fpga.bitstream" -#define FPGA_FW_LA1016 "kingst-la1016-fpga.bitstream" -#define FPGA_FW_LA1016A "kingst-la1016a1-fpga.bitstream" +/* USB PID dependent MCU firmware. Model dependent FPGA bitstream. */ +#define MCU_FWFILE_FMT "kingst-la-%04x.fw" +#define FPGA_FWFILE_FMT "kingst-%s-fpga.bitstream" -#define MAX_SAMPLE_RATE_LA2016 SR_MHZ(200) -#define MAX_SAMPLE_RATE_LA1016 SR_MHZ(100) -#define MAX_SAMPLE_DEPTH 10e9 -#define MAX_PWM_FREQ SR_MHZ(20) -#define PWM_CLOCK SR_MHZ(200) /* this is 200MHz for both the LA2016 and LA1016 */ - -/* usb vendor class control requests to the cypress FX2 microcontroller */ +/* + * List of known devices and their features. See @ref kingst_model + * for the fields' type and meaning. Table is sorted by EEPROM magic. + * More specific items need to go first (additional byte[2/6]). Not + * all devices are covered by this driver implementation, but telling + * users what was detected is considered useful. + * + * TODO Verify the identification of models that were not tested before. + */ +static const struct kingst_model models[] = { + { 0x02, 0x01, "LA2016", "la2016a1", SR_MHZ(200), 16, 1, 0, }, + { 0x02, 0x00, "LA2016", "la2016", SR_MHZ(200), 16, 1, 0, }, + { 0x03, 0x01, "LA1016", "la1016a1", SR_MHZ(100), 16, 1, 0, }, + { 0x03, 0x00, "LA1016", "la1016", SR_MHZ(100), 16, 1, 0, }, + { 0x04, 0x00, "LA1010", "la1010a0", SR_MHZ(100), 16, 0, SR_MHZ(800), }, + { 0x05, 0x00, "LA5016", "la5016a1", SR_MHZ(500), 16, 2, SR_MHZ(800), }, + { 0x06, 0x00, "LA5032", "la5032a0", SR_MHZ(500), 32, 4, SR_MHZ(800), }, + { 0x07, 0x00, "LA1010", "la1010a1", SR_MHZ(100), 16, 0, SR_MHZ(800), }, + { 0x08, 0x00, "LA2016", "la2016a1", SR_MHZ(200), 16, 1, 0, }, + { 0x09, 0x00, "LA1016", "la1016a1", SR_MHZ(100), 16, 1, 0, }, + { 0x0a, 0x00, "LA1010", "la1010a2", SR_MHZ(100), 16, 0, SR_MHZ(800), }, + { 0x0b, 0x10, "LA2016", "la2016a2", SR_MHZ(200), 16, 1, 0, }, + { 0x0c, 0x10, "LA5016", "la5016a2", SR_MHZ(500), 16, 2, SR_MHZ(800), }, + { 0x0c, 0x00, "LA5016", "la5016a2", SR_MHZ(500), 16, 2, SR_MHZ(800), }, + { 0x41, 0x00, "LA5016", "la5016a1", SR_MHZ(500), 16, 2, SR_MHZ(800), }, +}; + +/* USB vendor class control requests, executed by the Cypress FX2 MCU. */ #define CMD_FPGA_ENABLE 0x10 -#define CMD_FPGA_SPI 0x20 /* access registers in the FPGA over SPI bus, ctrl_in reads, ctrl_out writes */ -#define CMD_BULK_START 0x30 /* begin transfer of capture data via usb endpoint 6 IN */ -#define CMD_BULK_RESET 0x38 /* flush FX2 usb endpoint 6 IN fifos */ -#define CMD_FPGA_INIT 0x50 /* used before and after FPGA bitstream loading */ -#define CMD_KAUTH 0x60 /* communicate with authentication ic U10, not used */ -#define CMD_EEPROM 0xa2 /* ctrl_in reads, ctrl_out writes */ +#define CMD_FPGA_SPI 0x20 /* R/W access to FPGA registers via SPI. */ +#define CMD_BULK_START 0x30 /* Start sample data download via USB EP6 IN. */ +#define CMD_BULK_RESET 0x38 /* Flush FIFO of FX2 USB EP6 IN. */ +#define CMD_FPGA_INIT 0x50 /* Used before and after FPGA bitstream upload. */ +#define CMD_KAUTH 0x60 /* Communicate to auth IC (U10). Not used. */ +#define CMD_EEPROM 0xa2 /* R/W access to EEPROM content. */ /* - * fpga spi register addresses for control request CMD_FPGA_SPI: - * There are around 60 byte-wide registers within the fpga and - * these are the base addresses used for accessing them. - * On the spi bus, the msb of the address byte is set for read - * and cleared for write, but that is handled by the fx2 mcu - * as appropriate. In this driver code just use IN transactions - * to read, OUT to write. + * FPGA register addresses (base addresses when registers span multiple + * bytes, in that case data is kept in little endian format). Passed to + * CMD_FPGA_SPI requests. The FX2 MCU transparently handles the detail + * of SPI transfers encoding the read (1) or write (0) direction in the + * MSB of the address field. There are some 60 byte-wide FPGA registers. + * + * Unfortunately the FPGA registers change their meaning between the + * read and write directions of access, or exclusively provide one of + * these directions and not the other. This is an arbitrary vendor's + * choice, there is nothing which the sigrok driver could do about it. + * Values written to registers typically cannot get read back, neither + * verified after writing a configuration, nor queried upon startup for + * automatic detection of the current configuration. Neither appear to + * be there echo registers for presence and communication checks, nor + * version identifying registers, as far as we know. */ -#define REG_RUN 0x00 /* read capture status, write capture start */ -#define REG_PWM_EN 0x02 /* user pwm channels on/off */ -#define REG_CAPT_MODE 0x03 /* set to 0x00 for capture to sdram, 0x01 bypass sdram for streaming */ -#define REG_BULK 0x08 /* write start address and number of bytes for capture data bulk upload */ -#define REG_SAMPLING 0x10 /* write capture config, read capture data location in sdram */ -#define REG_TRIGGER 0x20 /* write level and edge trigger config */ -#define REG_THRESHOLD 0x68 /* write two pwm configs to control input threshold dac */ -#define REG_PWM1 0x70 /* write config for user pwm1 */ -#define REG_PWM2 0x78 /* write config for user pwm2 */ +#define REG_RUN 0x00 /* Read capture status, write start capture. */ +#define REG_PWM_EN 0x02 /* User PWM channels on/off. */ +#define REG_CAPT_MODE 0x03 /* Write 0x00 capture to SDRAM, 0x01 streaming. */ +#define REG_PIN_STATE 0x04 /* Read current pin state (real time display). */ +#define REG_BULK 0x08 /* Write start addr, byte count to download samples. */ +#define REG_SAMPLING 0x10 /* Write capture config, read capture SDRAM location. */ +#define REG_TRIGGER 0x20 /* Write level and edge trigger config. */ +#define REG_UNKNOWN_30 0x30 +#define REG_THRESHOLD 0x68 /* Write PWM config to setup input threshold DAC. */ +#define REG_PWM1 0x70 /* Write config for user PWM1. */ +#define REG_PWM2 0x78 /* Write config for user PWM2. */ + +/* Bit patterns to write to REG_CAPT_MODE. */ +#define CAPTMODE_TO_RAM 0x00 +#define CAPTMODE_STREAM 0x01 + +/* Bit patterns to write to REG_RUN, setup run mode. */ +#define RUNMODE_HALT 0x00 +#define RUNMODE_RUN 0x03 + +/* Bit patterns when reading from REG_RUN, get run state. */ +#define RUNSTATE_IDLE_BIT (1UL << 0) +#define RUNSTATE_DRAM_BIT (1UL << 1) +#define RUNSTATE_TRGD_BIT (1UL << 2) +#define RUNSTATE_POST_BIT (1UL << 3) static int ctrl_in(const struct sr_dev_inst *sdi, - uint8_t bRequest, uint16_t wValue, uint16_t wIndex, - void *data, uint16_t wLength) + uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + void *data, uint16_t wLength) { struct sr_usb_dev_inst *usb; int ret; usb = sdi->conn; - if ((ret = libusb_control_transfer( - usb->devhdl, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, - bRequest, wValue, wIndex, (unsigned char *)data, wLength, - DEFAULT_TIMEOUT_MS)) != wLength) { - sr_err("failed to read %d bytes via ctrl-in %d %#x, %d: %s.", - wLength, bRequest, wValue, wIndex, - libusb_error_name(ret)); - return SR_ERR; + ret = libusb_control_transfer(usb->devhdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + bRequest, wValue, wIndex, data, wLength, + DEFAULT_TIMEOUT_MS); + if (ret != wLength) { + sr_dbg("USB ctrl in: %d bytes, req %d val %#x idx %d: %s.", + wLength, bRequest, wValue, wIndex, + libusb_error_name(ret)); + sr_err("Cannot read %d bytes from USB: %s.", + wLength, libusb_error_name(ret)); + return SR_ERR_IO; } return SR_OK; } static int ctrl_out(const struct sr_dev_inst *sdi, - uint8_t bRequest, uint16_t wValue, uint16_t wIndex, - void *data, uint16_t wLength) + uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + void *data, uint16_t wLength) { struct sr_usb_dev_inst *usb; int ret; usb = sdi->conn; - if ((ret = libusb_control_transfer( - usb->devhdl, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, - bRequest, wValue, wIndex, (unsigned char*)data, wLength, - DEFAULT_TIMEOUT_MS)) != wLength) { - sr_err("failed to write %d bytes via ctrl-out %d %#x, %d: %s.", - wLength, bRequest, wValue, wIndex, - libusb_error_name(ret)); - return SR_ERR; + ret = libusb_control_transfer(usb->devhdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, + bRequest, wValue, wIndex, data, wLength, + DEFAULT_TIMEOUT_MS); + if (ret != wLength) { + sr_dbg("USB ctrl out: %d bytes, req %d val %#x idx %d: %s.", + wLength, bRequest, wValue, wIndex, + libusb_error_name(ret)); + sr_err("Cannot write %d bytes to USB: %s.", + wLength, libusb_error_name(ret)); + return SR_ERR_IO; } return SR_OK; } -static int upload_fpga_bitstream(const struct sr_dev_inst *sdi, const char *bitstream_fname) +/* HACK Experiment to spot FPGA registers of interest. */ +static void la2016_dump_fpga_registers(const struct sr_dev_inst *sdi, + const char *caption, size_t reg_lower, size_t reg_upper) +{ + static const size_t dump_chunk_len = 16; + + size_t rdlen; + uint8_t rdbuf[0x80 - 0x00]; /* Span all FPGA registers. */ + const uint8_t *rdptr; + int ret; + size_t dump_addr, indent, dump_len; + GString *txt; + + if (sr_log_loglevel_get() < SR_LOG_SPEW) + return; + + if (!reg_lower && !reg_upper) { + reg_lower = 0; + reg_upper = sizeof(rdbuf); + } + if (reg_upper - reg_lower > sizeof(rdbuf)) + reg_upper = sizeof(rdbuf) - reg_lower; + + rdlen = reg_upper - reg_lower; + ret = ctrl_in(sdi, CMD_FPGA_SPI, reg_lower, 0, rdbuf, rdlen); + if (ret != SR_OK) { + sr_err("Cannot get registers space."); + return; + } + rdptr = rdbuf; + + sr_spew("FPGA registers dump: %s", caption ? : "for fun"); + dump_addr = reg_lower; + while (rdlen) { + dump_len = rdlen; + indent = dump_addr % dump_chunk_len; + if (dump_len > dump_chunk_len) + dump_len = dump_chunk_len; + if (dump_len + indent > dump_chunk_len) + dump_len = dump_chunk_len - indent; + txt = sr_hexdump_new(rdptr, dump_len); + sr_spew(" %04zx %*s%s", + dump_addr, (int)(3 * indent), "", txt->str); + sr_hexdump_free(txt); + dump_addr += dump_len; + rdptr += dump_len; + rdlen -= dump_len; + } +} + +/* + * Check the necessity for FPGA bitstream upload, because another upload + * would take some 600ms which is undesirable after program startup. Try + * to access some FPGA registers and check the values' plausibility. The + * check should fail on the safe side, request another upload when in + * doubt. A positive response (the request to continue operation with the + * currently active bitstream) should be conservative. Accessing multiple + * registers is considered cheap compared to the cost of bitstream upload. + * + * It helps though that both the vendor software and the sigrok driver + * use the same bundle of MCU firmware and FPGA bitstream for any of the + * supported models. We don't expect to successfully communicate to the + * device yet disagree on its protocol. Ideally we would access version + * identifying registers for improved robustness, but are not aware of + * any. A bitstream reload can always be forced by a power cycle. + */ +static int check_fpga_bitstream(const struct sr_dev_inst *sdi) +{ + uint8_t init_rsp; + uint8_t buff[REG_PWM_EN - REG_RUN]; /* Larger of REG_RUN, REG_PWM_EN. */ + int ret; + uint16_t run_state; + uint8_t pwm_en; + size_t read_len; + const uint8_t *rdptr; + + sr_dbg("Checking operation of the FPGA bitstream."); + la2016_dump_fpga_registers(sdi, "bitstream check", 0, 0); + + init_rsp = ~0; + ret = ctrl_in(sdi, CMD_FPGA_INIT, 0x00, 0, &init_rsp, sizeof(init_rsp)); + if (ret != SR_OK || init_rsp != 0) { + sr_dbg("FPGA init query failed, or unexpected response."); + return SR_ERR_IO; + } + + read_len = sizeof(run_state); + ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_RUN, 0, buff, read_len); + if (ret != SR_OK) { + sr_dbg("FPGA register access failed (run state)."); + return SR_ERR_IO; + } + rdptr = buff; + run_state = read_u16le_inc(&rdptr); + sr_spew("FPGA register: run state 0x%04x.", run_state); + if (run_state && (run_state & 0x3) != 0x1) { + sr_dbg("Unexpected FPGA register content (run state)."); + return SR_ERR_DATA; + } + if (run_state && (run_state & ~0xf) != 0x85e0) { + sr_dbg("Unexpected FPGA register content (run state)."); + return SR_ERR_DATA; + } + + read_len = sizeof(pwm_en); + ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0, buff, read_len); + if (ret != SR_OK) { + sr_dbg("FPGA register access failed (PWM enable)."); + return SR_ERR_IO; + } + rdptr = buff; + pwm_en = read_u8_inc(&rdptr); + sr_spew("FPGA register: PWM enable 0x%02x.", pwm_en); + if ((pwm_en & 0x3) != 0x0) { + sr_dbg("Unexpected FPGA register content (PWM enable)."); + return SR_ERR_DATA; + } + + sr_info("Could re-use current FPGA bitstream. No upload required."); + return SR_OK; +} + +static int upload_fpga_bitstream(const struct sr_dev_inst *sdi, + const char *bitstream_fname) { - struct dev_context *devc; struct drv_context *drvc; struct sr_usb_dev_inst *usb; struct sr_resource bitstream; + uint32_t bitstream_size; uint8_t buffer[sizeof(uint32_t)]; uint8_t *wrptr; - uint8_t cmd_resp; uint8_t block[4096]; int len, act_len; unsigned int pos; int ret; - unsigned int zero_pad_to = 0x2c000; + unsigned int zero_pad_to; - devc = sdi->priv; drvc = sdi->driver->context; usb = sdi->conn; sr_info("Uploading FPGA bitstream '%s'.", bitstream_fname); - ret = sr_resource_open(drvc->sr_ctx, &bitstream, SR_RESOURCE_FIRMWARE, bitstream_fname); + ret = sr_resource_open(drvc->sr_ctx, &bitstream, + SR_RESOURCE_FIRMWARE, bitstream_fname); if (ret != SR_OK) { - sr_err("could not find fpga firmware %s!", bitstream_fname); + sr_err("Cannot find FPGA bitstream %s.", bitstream_fname); return ret; } - devc->bitstream_size = (uint32_t)bitstream.size; + bitstream_size = (uint32_t)bitstream.size; wrptr = buffer; - write_u32le_inc(&wrptr, devc->bitstream_size); - if ((ret = ctrl_out(sdi, CMD_FPGA_INIT, 0x00, 0, buffer, wrptr - buffer)) != SR_OK) { - sr_err("failed to give upload init command"); + write_u32le_inc(&wrptr, bitstream_size); + ret = ctrl_out(sdi, CMD_FPGA_INIT, 0x00, 0, buffer, wrptr - buffer); + if (ret != SR_OK) { + sr_err("Cannot initiate FPGA bitstream upload."); sr_resource_close(drvc->sr_ctx, &bitstream); return ret; } + zero_pad_to = bitstream_size; + zero_pad_to += LA2016_EP2_PADDING - 1; + zero_pad_to /= LA2016_EP2_PADDING; + zero_pad_to *= LA2016_EP2_PADDING; pos = 0; while (1) { if (pos < bitstream.size) { - len = (int)sr_resource_read(drvc->sr_ctx, &bitstream, &block, sizeof(block)); + len = (int)sr_resource_read(drvc->sr_ctx, &bitstream, + block, sizeof(block)); if (len < 0) { - sr_err("failed to read from fpga bitstream!"); + sr_err("Cannot read FPGA bitstream."); sr_resource_close(drvc->sr_ctx, &bitstream); - return SR_ERR; + return SR_ERR_IO; } } else { - // fill with zero's until zero_pad_to + /* Zero-pad until 'zero_pad_to'. */ len = zero_pad_to - pos; if ((unsigned)len > sizeof(block)) len = sizeof(block); @@ -172,101 +345,103 @@ static int upload_fpga_bitstream(const struct sr_dev_inst *sdi, const char *bits if (len == 0) break; - ret = libusb_bulk_transfer(usb->devhdl, 2, (unsigned char*)&block[0], len, &act_len, DEFAULT_TIMEOUT_MS); + ret = libusb_bulk_transfer(usb->devhdl, USB_EP_FPGA_BITSTREAM, + &block[0], len, &act_len, DEFAULT_TIMEOUT_MS); if (ret != 0) { - sr_dbg("failed to write fpga bitstream block at %#x len %d: %s.", pos, (int)len, libusb_error_name(ret)); - ret = SR_ERR; + sr_dbg("Cannot write FPGA bitstream, block %#x len %d: %s.", + pos, (int)len, libusb_error_name(ret)); + ret = SR_ERR_IO; break; } if (act_len != len) { - sr_dbg("failed to write fpga bitstream block at %#x len %d: act_len is %d.", pos, (int)len, act_len); - ret = SR_ERR; + sr_dbg("Short write for FPGA bitstream, block %#x len %d: got %d.", + pos, (int)len, act_len); + ret = SR_ERR_IO; break; } pos += len; } sr_resource_close(drvc->sr_ctx, &bitstream); - if (ret != 0) + if (ret != SR_OK) return ret; - sr_info("FPGA bitstream upload (%" PRIu64 " bytes) done.", bitstream.size); + sr_info("FPGA bitstream upload (%" PRIu64 " bytes) done.", + bitstream.size); + + return SR_OK; +} - if ((ret = ctrl_in(sdi, CMD_FPGA_INIT, 0x00, 0, &cmd_resp, sizeof(cmd_resp))) != SR_OK) { - sr_err("failed to read response after FPGA bitstream upload"); +static int enable_fpga_bitstream(const struct sr_dev_inst *sdi) +{ + int ret; + uint8_t resp; + + ret = ctrl_in(sdi, CMD_FPGA_INIT, 0x00, 0, &resp, sizeof(resp)); + if (ret != SR_OK) { + sr_err("Cannot read response after FPGA bitstream upload."); return ret; } - if (cmd_resp != 0) { - sr_err("after fpga bitstream upload command response is 0x%02x, expect 0!", cmd_resp); - return SR_ERR; + if (resp != 0) { + sr_err("Unexpected FPGA bitstream upload response, got 0x%02x, want 0.", + resp); + return SR_ERR_DATA; } + g_usleep(30 * 1000); - g_usleep(30000); - - if ((ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x01, 0, NULL, 0)) != SR_OK) { - sr_err("failed enable fpga"); + ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x01, 0, NULL, 0); + if (ret != SR_OK) { + sr_err("Cannot enable FPGA after bitstream upload."); return ret; } + g_usleep(40 * 1000); - g_usleep(40000); return SR_OK; } static int set_threshold_voltage(const struct sr_dev_inst *sdi, float voltage) { - struct dev_context *devc; int ret; - - devc = sdi->priv; - - uint16_t duty_R79,duty_R56; - uint8_t buf[2 * sizeof(uint16_t)]; + uint16_t duty_R79, duty_R56; + uint8_t buf[REG_PWM1 - REG_THRESHOLD]; /* Width of REG_THRESHOLD. */ uint8_t *wrptr; - /* clamp threshold setting within valid range for LA2016 */ - if (voltage > 4.0) { - voltage = 4.0; - } - else if (voltage < -4.0) { - voltage = -4.0; + /* Clamp threshold setting to valid range for LA2016. */ + if (voltage > LA2016_THR_VOLTAGE_MAX) { + voltage = LA2016_THR_VOLTAGE_MAX; + } else if (voltage < -LA2016_THR_VOLTAGE_MAX) { + voltage = -LA2016_THR_VOLTAGE_MAX; } /* - * The fpga has two programmable pwm outputs which feed a dac that - * is used to adjust input offset. The dac changes the input - * swing around the fixed fpga input threshold. - * The two pwm outputs can be seen on R79 and R56 respectvely. - * Frequency is fixed at 100kHz and duty is varied. - * The R79 pwm uses just three settings. - * The R56 pwm varies with required threshold and its behaviour - * also changes depending on the setting of R79 PWM. - */ - - /* - * calculate required pwm duty register values from requested threshold voltage - * see last page of schematic (on wiki) for an explanation of these numbers + * Two PWM output channels feed one DAC which generates a bias + * voltage, which offsets the input probe's voltage level, and + * in combination with the FPGA pins' fixed threshold result in + * a programmable input threshold from the user's perspective. + * The PWM outputs can be seen on R79 and R56 respectively, the + * frequency is 100kHz and the duty cycle varies. The R79 PWM + * uses three discrete settings. The R56 PWM varies with desired + * thresholds and depends on the R79 PWM configuration. See the + * schematics comments which discuss the formulae. */ if (voltage >= 2.9) { - duty_R79 = 0; /* this pwm is off (0V)*/ + duty_R79 = 0; /* PWM off (0V). */ duty_R56 = (uint16_t)(302 * voltage - 363); - } - else if (voltage <= -0.4) { - duty_R79 = 0x02D7; /* 72% duty */ - duty_R56 = (uint16_t)(302 * voltage + 1090); - } - else { - duty_R79 = 0x00f2; /* 25% duty */ + } else if (voltage > -0.4) { + duty_R79 = 0x00f2; /* 25% duty cycle. */ duty_R56 = (uint16_t)(302 * voltage + 121); + } else { + duty_R79 = 0x02d7; /* 72% duty cycle. */ + duty_R56 = (uint16_t)(302 * voltage + 1090); } - /* clamp duty register values at sensible limits */ + /* Clamp duty register values to sensible limits. */ if (duty_R56 < 10) { duty_R56 = 10; - } - else if (duty_R56 > 1100) { + } else if (duty_R56 > 1100) { duty_R56 = 1100; } - sr_dbg("set threshold voltage %.2fV", voltage); - sr_dbg("duty_R56=0x%04x, duty_R79=0x%04x", duty_R56, duty_R79); + sr_dbg("Set threshold voltage %.2fV.", voltage); + sr_dbg("Duty cycle values: R56 0x%04x, R79 0x%04x.", duty_R56, duty_R79); wrptr = buf; write_u16le_inc(&wrptr, duty_R56); @@ -274,149 +449,190 @@ static int set_threshold_voltage(const struct sr_dev_inst *sdi, float voltage) ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_THRESHOLD, 0, buf, wrptr - buf); if (ret != SR_OK) { - sr_err("error setting new threshold voltage of %.2fV", voltage); + sr_err("Cannot set threshold voltage %.2fV.", voltage); return ret; } - devc->threshold_voltage = voltage; return SR_OK; } -static int enable_pwm(const struct sr_dev_inst *sdi, uint8_t p1, uint8_t p2) +/* + * Communicates a channel's configuration to the device after the + * parameters may have changed. Configuration of one channel may + * interfere with other channels since they share FPGA registers. + */ +static int set_pwm_config(const struct sr_dev_inst *sdi, size_t idx) { + static uint8_t reg_bases[] = { REG_PWM1, REG_PWM2, }; + struct dev_context *devc; - uint8_t cfg; + struct pwm_setting *params; + uint8_t reg_base; + double val_f; + uint32_t val_u; + uint32_t period, duty; + size_t ch; int ret; + uint8_t enable_all, enable_cfg, reg_val; + uint8_t buf[REG_PWM2 - REG_PWM1]; /* Width of one REG_PWMx. */ + uint8_t *wrptr; devc = sdi->priv; - cfg = 0; + if (idx >= ARRAY_SIZE(devc->pwm_setting)) + return SR_ERR_ARG; + params = &devc->pwm_setting[idx]; + if (idx >= ARRAY_SIZE(reg_bases)) + return SR_ERR_ARG; + reg_base = reg_bases[idx]; - if (p1) cfg |= 1 << 0; - if (p2) cfg |= 1 << 1; + /* + * Map application's specs to hardware register values. Do math + * in floating point initially, but convert to u32 eventually. + */ + sr_dbg("PWM config, app spec, ch %zu, en %d, freq %.1f, duty %.1f.", + idx, params->enabled ? 1 : 0, params->freq, params->duty); + val_f = PWM_CLOCK; + val_f /= params->freq; + val_u = val_f; + period = val_u; + val_f = period; + val_f *= params->duty; + val_f /= 100.0; + val_f += 0.5; + val_u = val_f; + duty = val_u; + sr_dbg("PWM config, reg 0x%04x, freq %u, duty %u.", + (unsigned)reg_base, (unsigned)period, (unsigned)duty); + + /* Get the "enabled" state of all supported PWM channels. */ + enable_all = 0; + for (ch = 0; ch < ARRAY_SIZE(devc->pwm_setting); ch++) { + if (!devc->pwm_setting[ch].enabled) + continue; + enable_all |= 1U << ch; + } + enable_cfg = 1U << idx; + sr_spew("PWM config, enable all 0x%02hhx, cfg 0x%02hhx.", + enable_all, enable_cfg); - sr_dbg("set pwm enable %d %d", p1, p2); - ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0, &cfg, sizeof(cfg)); + /* + * Disable the to-get-configured channel before its parameters + * will change. Or disable and exit when the channel is supposed + * to get turned off. + */ + sr_spew("PWM config, disabling before param change."); + reg_val = enable_all & ~enable_cfg; + ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0, + ®_val, sizeof(reg_val)); if (ret != SR_OK) { - sr_err("error setting new pwm enable 0x%02x", cfg); + sr_err("Cannot adjust PWM enabled state."); return ret; } - devc->pwm_setting[0].enabled = (p1) ? 1 : 0; - devc->pwm_setting[1].enabled = (p2) ? 1 : 0; + if (!params->enabled) + return SR_OK; - return SR_OK; -} - -static int set_pwm(const struct sr_dev_inst *sdi, uint8_t which, float freq, float duty) -{ - int CTRL_PWM[] = { REG_PWM1, REG_PWM2 }; - struct dev_context *devc; - pwm_setting_dev_t cfg; - pwm_setting_t *setting; - int ret; - uint8_t buf[2 * sizeof(uint32_t)]; - uint8_t *wrptr; - - devc = sdi->priv; - - if (which < 1 || which > 2) { - sr_err("invalid pwm channel: %d", which); - return SR_ERR; - } - if (freq > MAX_PWM_FREQ) { - sr_err("pwm frequency too high: %.1f", freq); - return SR_ERR; - } - if (duty > 100 || duty < 0) { - sr_err("invalid pwm percentage: %f", duty); - return SR_ERR; + /* Write register values to device. */ + sr_spew("PWM config, sending new parameters."); + wrptr = buf; + write_u32le_inc(&wrptr, period); + write_u32le_inc(&wrptr, duty); + ret = ctrl_out(sdi, CMD_FPGA_SPI, reg_base, 0, buf, wrptr - buf); + if (ret != SR_OK) { + sr_err("Cannot change PWM parameters."); + return ret; } - cfg.period = (uint32_t)(PWM_CLOCK / freq); - cfg.duty = (uint32_t)(0.5f + (cfg.period * duty / 100.)); - sr_dbg("set pwm%d period %d, duty %d", which, cfg.period, cfg.duty); - - wrptr = buf; - write_u32le_inc(&wrptr, cfg.period); - write_u32le_inc(&wrptr, cfg.duty); - ret = ctrl_out(sdi, CMD_FPGA_SPI, CTRL_PWM[which - 1], 0, buf, wrptr - buf); + /* Enable configured channel after write completion. */ + sr_spew("PWM config, enabling after param change."); + reg_val = enable_all | enable_cfg; + ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_PWM_EN, 0, + ®_val, sizeof(reg_val)); if (ret != SR_OK) { - sr_err("error setting new pwm%d config %d %d", which, cfg.period, cfg.duty); + sr_err("Cannot adjust PWM enabled state."); return ret; } - setting = &devc->pwm_setting[which - 1]; - setting->freq = freq; - setting->duty = duty; return SR_OK; } -static int set_defaults(const struct sr_dev_inst *sdi) +/* + * Determine the number of enabled channels as well as their bitmask + * representation. Derive data here which later simplifies processing + * of raw capture data memory content in streaming mode. + */ +static void la2016_prepare_stream(const struct sr_dev_inst *sdi) { struct dev_context *devc; - int ret; + struct stream_state_t *stream; + size_t channel_mask; + GSList *l; + struct sr_channel *ch; devc = sdi->priv; - - devc->capture_ratio = 5; /* percent */ - devc->cur_channels = 0xffff; - devc->limit_samples = 5000000; - devc->cur_samplerate = SR_MHZ(100); - - ret = set_threshold_voltage(sdi, devc->threshold_voltage); - if (ret) - return ret; - - ret = enable_pwm(sdi, 0, 0); - if (ret) - return ret; - - ret = set_pwm(sdi, 1, 1e3, 50); - if (ret) - return ret; - - ret = set_pwm(sdi, 2, 100e3, 50); - if (ret) - return ret; - - ret = enable_pwm(sdi, 1, 1); - if (ret) - return ret; - - return SR_OK; + stream = &devc->stream; + memset(stream, 0, sizeof(*stream)); + + stream->enabled_count = 0; + for (l = sdi->channels; l; l = l->next) { + ch = l->data; + if (ch->type != SR_CHANNEL_LOGIC) + continue; + if (!ch->enabled) + continue; + channel_mask = 1UL << ch->index; + stream->enabled_mask |= channel_mask; + stream->channel_masks[stream->enabled_count++] = channel_mask; + } + stream->channel_index = 0; } +/* + * This routine configures the set of enabled channels, as well as the + * trigger condition (if one was specified). Also prepares the capture + * data processing in stream mode, where the memory layout dramatically + * differs from normal mode. + */ static int set_trigger_config(const struct sr_dev_inst *sdi) { struct dev_context *devc; struct sr_trigger *trigger; - trigger_cfg_t cfg; + struct trigger_cfg { + uint32_t channels; /* Actually: Enabled channels? */ + uint32_t enabled; /* Actually: Triggering channels? */ + uint32_t level; + uint32_t high_or_falling; + } cfg; GSList *stages; GSList *channel; struct sr_trigger_stage *stage1; struct sr_trigger_match *match; - uint16_t ch_mask; + uint32_t ch_mask; int ret; - uint8_t buf[4 * sizeof(uint32_t)]; + uint8_t buf[REG_UNKNOWN_30 - REG_TRIGGER]; /* Width of REG_TRIGGER. */ uint8_t *wrptr; devc = sdi->priv; - trigger = sr_session_trigger_get(sdi->session); - - memset(&cfg, 0, sizeof(cfg)); - cfg.channels = devc->cur_channels; + la2016_prepare_stream(sdi); + memset(&cfg, 0, sizeof(cfg)); + cfg.channels = devc->stream.enabled_mask; + if (!cfg.channels) { + sr_err("Need at least one enabled logic channel."); + return SR_ERR_ARG; + } + trigger = sr_session_trigger_get(sdi->session); if (trigger && trigger->stages) { stages = trigger->stages; stage1 = stages->data; if (stages->next) { sr_err("Only one trigger stage supported for now."); - return SR_ERR; + return SR_ERR_ARG; } channel = stage1->matches; while (channel) { match = channel->data; - ch_mask = 1 << match->channel->index; + ch_mask = 1UL << match->channel->index; switch (match->match) { case SR_TRIGGER_ZERO: @@ -429,162 +645,318 @@ static int set_trigger_config(const struct sr_dev_inst *sdi) break; case SR_TRIGGER_RISING: if ((cfg.enabled & ~cfg.level)) { - sr_err("Only one trigger signal with falling-/rising-edge allowed."); - return SR_ERR; + sr_err("Device only supports one edge trigger."); + return SR_ERR_ARG; } cfg.level &= ~ch_mask; cfg.high_or_falling &= ~ch_mask; break; case SR_TRIGGER_FALLING: if ((cfg.enabled & ~cfg.level)) { - sr_err("Only one trigger signal with falling-/rising-edge allowed."); - return SR_ERR; + sr_err("Device only supports one edge trigger."); + return SR_ERR_ARG; } cfg.level &= ~ch_mask; cfg.high_or_falling |= ch_mask; break; default: - sr_err("Unknown trigger value."); - return SR_ERR; + sr_err("Unknown trigger condition."); + return SR_ERR_ARG; } cfg.enabled |= ch_mask; channel = channel->next; } } - sr_dbg("set trigger configuration channels: 0x%04x, " - "trigger-enabled 0x%04x, level-triggered 0x%04x, " - "high/falling 0x%04x", cfg.channels, cfg.enabled, cfg.level, - cfg.high_or_falling); + sr_dbg("Set trigger config: " + "enabled-channels 0x%04x, triggering-channels 0x%04x, " + "level-triggered 0x%04x, high/falling 0x%04x.", + cfg.channels, cfg.enabled, cfg.level, cfg.high_or_falling); - devc->had_triggers_configured = cfg.enabled != 0; + /* + * Don't configure hardware trigger parameters in streaming mode + * or when the device lacks local memory. Yet the above dump of + * derived parameters from user specs is considered valueable. + * + * TODO Add support for soft triggers when hardware triggers in + * the device are not used or are not available at all. + */ + if (!devc->model->memory_bits || devc->continuous) { + if (!devc->model->memory_bits) + sr_dbg("Device without memory. No hardware triggers."); + else if (devc->continuous) + sr_dbg("Streaming mode. No hardware triggers."); + cfg.enabled = 0; + cfg.level = 0; + cfg.high_or_falling = 0; + } + + devc->trigger_involved = cfg.enabled != 0; wrptr = buf; write_u32le_inc(&wrptr, cfg.channels); write_u32le_inc(&wrptr, cfg.enabled); write_u32le_inc(&wrptr, cfg.level); write_u32le_inc(&wrptr, cfg.high_or_falling); + /* TODO + * Comment on this literal 16. Origin, meaning? Cannot be the + * register offset, nor the transfer length. Is it a channels + * count that is relevant for 16 and 32 channel models? Is it + * an obsolete experiment? + */ ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_TRIGGER, 16, buf, wrptr - buf); if (ret != SR_OK) { - sr_err("error setting trigger config!"); + sr_err("Cannot setup trigger configuration."); return ret; } return SR_OK; } +/* + * This routine communicates the sample configuration to the device: + * Total samples count and samplerate, pre-trigger configuration. + */ static int set_sample_config(const struct sr_dev_inst *sdi) { struct dev_context *devc; - double clock_divisor; - uint64_t total; - int ret; - uint16_t divisor; - uint8_t buf[2 * sizeof(uint32_t) + 48 / 8 + sizeof(uint16_t)]; + uint64_t baseclock; + uint64_t min_samplerate, eff_samplerate; + uint64_t stream_bandwidth; + uint16_t divider_u16; + uint64_t limit_samples; + uint64_t pre_trigger_samples; + uint64_t pre_trigger_memory; + uint8_t buf[REG_TRIGGER - REG_SAMPLING]; /* Width of REG_SAMPLING. */ uint8_t *wrptr; + int ret; devc = sdi->priv; - total = 128 * 1024 * 1024; - if (devc->cur_samplerate > devc->max_samplerate) { - sr_err("too high sample rate: %" PRIu64, devc->cur_samplerate); - return SR_ERR; + /* + * The base clock need not be identical to the maximum samplerate, + * and differs between models. The 500MHz devices even use a base + * clock of 800MHz, and communicate divider 1 to the hardware to + * configure the 500MHz samplerate. This allows them to operate at + * a 200MHz samplerate which uses divider 4. + */ + if (devc->samplerate > devc->model->samplerate) { + sr_err("Too high a sample rate: %" PRIu64 ".", + devc->samplerate); + return SR_ERR_ARG; } - - clock_divisor = devc->max_samplerate / (double)devc->cur_samplerate; - if (clock_divisor > 0xffff) - clock_divisor = 0xffff; - divisor = (uint16_t)(clock_divisor + 0.5); - devc->cur_samplerate = devc->max_samplerate / divisor; - - if (devc->limit_samples > MAX_SAMPLE_DEPTH) { - sr_err("too high sample depth: %" PRIu64, devc->limit_samples); - return SR_ERR; + baseclock = devc->model->baseclock; + if (!baseclock) + baseclock = devc->model->samplerate; + min_samplerate = baseclock; + min_samplerate /= 65536; + if (devc->samplerate < min_samplerate) { + sr_err("Too low a sample rate: %" PRIu64 ".", + devc->samplerate); + return SR_ERR_ARG; } + divider_u16 = baseclock / devc->samplerate; + eff_samplerate = baseclock / divider_u16; + if (eff_samplerate > devc->model->samplerate) + eff_samplerate = devc->model->samplerate; - devc->pre_trigger_size = (devc->capture_ratio * devc->limit_samples) / 100; + ret = sr_sw_limits_get_remain(&devc->sw_limits, + &limit_samples, NULL, NULL, NULL); + if (ret != SR_OK) { + sr_err("Cannot get acquisition limits."); + return ret; + } + if (limit_samples > LA2016_NUM_SAMPLES_MAX) { + sr_warn("Too high a sample depth: %" PRIu64 ", capping.", + limit_samples); + limit_samples = LA2016_NUM_SAMPLES_MAX; + } + if (limit_samples == 0) { + limit_samples = LA2016_NUM_SAMPLES_MAX; + sr_dbg("Passing %" PRIu64 " to HW for unlimited samples.", + limit_samples); + } - sr_dbg("set sampling configuration %.0fkHz, %d samples, trigger-pos %d%%", - devc->cur_samplerate / 1e3, (unsigned int)devc->limit_samples, (unsigned int)devc->capture_ratio); + /* + * The acquisition configuration communicates "pre-trigger" + * specs in several formats. sigrok users provide a percentage + * (0-100%), which translates to a pre-trigger samples count + * (assuming that a total samples count limit was specified). + * The device supports hardware compression, which depends on + * slowly changing input data to be effective. Fast changing + * input data may occupy more space in sample memory than its + * uncompressed form would. This is why a third parameter can + * limit the amount of sample memory to use for pre-trigger + * data. Only the upper 24 bits of that memory size spec get + * communicated to the device (written to its FPGA register). + */ + if (!devc->model->memory_bits) { + sr_dbg("Memory-less device, skipping pre-trigger config."); + pre_trigger_samples = 0; + pre_trigger_memory = 0; + } else if (devc->trigger_involved) { + pre_trigger_samples = limit_samples; + pre_trigger_samples *= devc->capture_ratio; + pre_trigger_samples /= 100; + pre_trigger_memory = devc->model->memory_bits; + pre_trigger_memory *= UINT64_C(1024 * 1024 * 1024); + pre_trigger_memory /= 8; /* devc->model->channel_count ? */ + pre_trigger_memory *= devc->capture_ratio; + pre_trigger_memory /= 100; + } else { + sr_dbg("No trigger setup, skipping pre-trigger config."); + pre_trigger_samples = 0; + pre_trigger_memory = 0; + } + /* Ensure non-zero value after LSB shift out in HW reg. */ + if (pre_trigger_memory < 0x100) + pre_trigger_memory = 0x100; + + sr_dbg("Set sample config: %" PRIu64 "kHz (div %" PRIu16 "), %" PRIu64 " samples.", + eff_samplerate / SR_KHZ(1), divider_u16, limit_samples); + sr_dbg("Capture ratio %" PRIu64 "%%, count %" PRIu64 ", mem %" PRIu64 ".", + devc->capture_ratio, pre_trigger_samples, pre_trigger_memory); + + if (devc->continuous) { + stream_bandwidth = eff_samplerate; + stream_bandwidth *= devc->stream.enabled_count; + sr_dbg("Streaming: channel count %zu, product %" PRIu64 ".", + devc->stream.enabled_count, stream_bandwidth); + stream_bandwidth /= 1000 * 1000; + if (stream_bandwidth >= LA2016_STREAM_MBPS_MAX) { + sr_warn("High USB stream bandwidth: %" PRIu64 "Mbps.", + stream_bandwidth); + } + if (stream_bandwidth < LA2016_STREAM_PUSH_THR) { + sr_dbg("Streaming: low Mbps, suggest periodic flush."); + devc->stream.flush_period_ms = LA2016_STREAM_PUSH_IVAL; + } + } + /* + * The acquisition configuration occupies a total of 16 bytes: + * - A 34bit total samples count limit (up to 10 billions) that + * is kept in a 40bit register. + * - A 34bit pre-trigger samples count limit (up to 10 billions) + * in another 40bit register. + * - A 32bit pre-trigger memory space limit (in bytes) of which + * the upper 24bits are kept in an FPGA register. + * - A 16bit clock divider which gets applied to the maximum + * samplerate of the device. + * - An 8bit register of unknown meaning. Currently always 0. + */ wrptr = buf; - write_u32le_inc(&wrptr, devc->limit_samples); + write_u40le_inc(&wrptr, limit_samples); + write_u40le_inc(&wrptr, pre_trigger_samples); + write_u24le_inc(&wrptr, pre_trigger_memory >> 8); + write_u16le_inc(&wrptr, divider_u16); write_u8_inc(&wrptr, 0); - write_u32le_inc(&wrptr, devc->pre_trigger_size); - write_u32le_inc(&wrptr, ((total * devc->capture_ratio) / 100) & 0xFFFFFF00); - write_u16le_inc(&wrptr, divisor); - write_u8_inc(&wrptr, 0); - ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_SAMPLING, 0, buf, wrptr - buf); if (ret != SR_OK) { - sr_err("error setting sample config!"); + sr_err("Cannot setup acquisition configuration."); return ret; } return SR_OK; } -/* The run state is read from FPGA registers 1[hi-byte] and 0[lo-byte] - * and the bits are interpreted as follows: - * - * register 0: - * bit0 1= idle - * bit1 1= writing to sdram - * bit2 0= waiting_for_trigger 1=been_triggered - * bit3 0= pretrigger_sampling 1=posttrigger_sampling - * ...unknown... - * register 1: - * meaning of bits unknown (but vendor software reads this, so just do the same) +/* + * FPGA register REG_RUN holds the run state (u16le format). Bit fields + * of interest: + * bit 0: value 1 = idle + * bit 1: value 1 = writing to SDRAM + * bit 2: value 0 = waiting for trigger, 1 = trigger seen + * bit 3: value 0 = pretrigger sampling, 1 = posttrigger sampling + * The meaning of other bit fields is unknown. * - * The run state values occur in this order: - * 0x85E2: pre-sampling (for samples before trigger position, capture ratio > 0%) - * 0x85EA: pre-sampling complete, now waiting for trigger (whilst sampling continuously) - * 0x85EE: running - * 0x85ED: idle + * Typical values in order of appearance during execution: + * 0x85e1: idle, no acquisition pending + * IDLE set, TRGD don't care, POST don't care; DRAM don't care + * "In idle state." Takes precedence over all others. + * 0x85e2: pre-sampling, samples before the trigger position, + * when capture ratio > 0% + * IDLE clear, TRGD clear, POST clear; DRAM don't care + * "Not idle any more, no post yet, not triggered yet." + * 0x85ea: pre-sampling complete, now waiting for the trigger + * (whilst sampling continuously) + * IDLE clear, TRGD clear, POST set; DRAM don't care + * "Post set thus after pre, not triggered yet" + * 0x85ee: trigger seen, capturing post-trigger samples, running + * IDLE clear, TRGD set, POST set; DRAM don't care + * "Triggered and in post, not idle yet." + * 0x85ed: idle + * IDLE set, TRGD don't care, POST don't care; DRAM don't care + * "In idle state." TRGD/POST don't care, same meaning as above. */ +static const uint16_t runstate_mask_idle = RUNSTATE_IDLE_BIT; +static const uint16_t runstate_patt_idle = RUNSTATE_IDLE_BIT; +static const uint16_t runstate_mask_step = + RUNSTATE_IDLE_BIT | RUNSTATE_TRGD_BIT | RUNSTATE_POST_BIT; +static const uint16_t runstate_patt_pre_trig = 0; +static const uint16_t runstate_patt_wait_trig = RUNSTATE_POST_BIT; +static const uint16_t runstate_patt_post_trig = + RUNSTATE_TRGD_BIT | RUNSTATE_POST_BIT; + static uint16_t run_state(const struct sr_dev_inst *sdi) { - uint16_t state; - static uint16_t previous_state = 0; + static uint16_t previous_state; + int ret; + uint16_t state; + uint8_t buff[REG_PWM_EN - REG_RUN]; /* Width of REG_RUN. */ + const uint8_t *rdptr; + const char *label; - if ((ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_RUN, 0, &state, sizeof(state))) != SR_OK) { - sr_err("failed to read run state!"); + ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_RUN, 0, buff, sizeof(state)); + if (ret != SR_OK) { + sr_err("Cannot read run state."); return ret; } + rdptr = buff; + state = read_u16le_inc(&rdptr); - /* This function is called about every 50ms. - * To avoid filling the log file with redundant information during long captures, - * just print a log message if status has changed. + /* + * Avoid flooding the log, only dump values as they change. + * The routine is called about every 50ms. */ - - if (state != previous_state) { - previous_state = state; - if ((state & 0x0003) == 0x01) { - sr_dbg("run_state: 0x%04x (%s)", state, "idle"); - } - else if ((state & 0x000f) == 0x02) { - sr_dbg("run_state: 0x%04x (%s)", state, "pre-trigger sampling"); - } - else if ((state & 0x000f) == 0x0a) { - sr_dbg("run_state: 0x%04x (%s)", state, "sampling, waiting for trigger"); - } - else if ((state & 0x000f) == 0x0e) { - sr_dbg("run_state: 0x%04x (%s)", state, "post-trigger sampling"); - } - else { - sr_dbg("run_state: 0x%04x", state); - } - } + if (state == previous_state) + return state; + + previous_state = state; + label = NULL; + if ((state & runstate_mask_idle) == runstate_patt_idle) + label = "idle"; + if ((state & runstate_mask_step) == runstate_patt_pre_trig) + label = "pre-trigger sampling"; + if ((state & runstate_mask_step) == runstate_patt_wait_trig) + label = "sampling, waiting for trigger"; + if ((state & runstate_mask_step) == runstate_patt_post_trig) + label = "post-trigger sampling"; + if (label && *label) + sr_dbg("Run state: 0x%04x (%s).", state, label); + else + sr_dbg("Run state: 0x%04x.", state); return state; } -static int set_run_mode(const struct sr_dev_inst *sdi, uint8_t fast_blinking) +static gboolean la2016_is_idle(const struct sr_dev_inst *sdi) +{ + uint16_t state; + + state = run_state(sdi); + if ((state & runstate_mask_idle) == runstate_patt_idle) + return TRUE; + + return FALSE; +} + +static int set_run_mode(const struct sr_dev_inst *sdi, uint8_t mode) { int ret; - if ((ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_RUN, 0, &fast_blinking, sizeof(fast_blinking))) != SR_OK) { - sr_err("failed to send set-run-mode command %d", fast_blinking); + ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_RUN, 0, &mode, sizeof(mode)); + if (ret != SR_OK) { + sr_err("Cannot configure run mode %d.", mode); return ret; } @@ -595,13 +967,14 @@ static int get_capture_info(const struct sr_dev_inst *sdi) { struct dev_context *devc; int ret; - uint8_t buf[3 * sizeof(uint32_t)]; + uint8_t buf[REG_TRIGGER - REG_SAMPLING]; /* Width of REG_SAMPLING. */ const uint8_t *rdptr; devc = sdi->priv; - if ((ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_SAMPLING, 0, buf, sizeof(buf))) != SR_OK) { - sr_err("failed to read capture info!"); + ret = ctrl_in(sdi, CMD_FPGA_SPI, REG_SAMPLING, 0, buf, sizeof(buf)); + if (ret != SR_OK) { + sr_err("Cannot read capture info."); return ret; } @@ -610,25 +983,199 @@ static int get_capture_info(const struct sr_dev_inst *sdi) devc->info.n_rep_packets_before_trigger = read_u32le_inc(&rdptr); devc->info.write_pos = read_u32le_inc(&rdptr); - sr_dbg("capture info: n_rep_packets: 0x%08x/%d, before_trigger: 0x%08x/%d, write_pos: 0x%08x%d", - devc->info.n_rep_packets, devc->info.n_rep_packets, - devc->info.n_rep_packets_before_trigger, devc->info.n_rep_packets_before_trigger, - devc->info.write_pos, devc->info.write_pos); + sr_dbg("Capture info: n_rep_packets: 0x%08x/%d, before_trigger: 0x%08x/%d, write_pos: 0x%08x/%d.", + devc->info.n_rep_packets, devc->info.n_rep_packets, + devc->info.n_rep_packets_before_trigger, + devc->info.n_rep_packets_before_trigger, + devc->info.write_pos, devc->info.write_pos); + + if (devc->info.n_rep_packets % devc->packets_per_chunk) { + sr_warn("Unexpected packets count %lu, not a multiple of %lu.", + (unsigned long)devc->info.n_rep_packets, + (unsigned long)devc->packets_per_chunk); + } + + return SR_OK; +} + +SR_PRIV int la2016_upload_firmware(const struct sr_dev_inst *sdi, + struct sr_context *sr_ctx, libusb_device *dev, gboolean skip_upload) +{ + struct dev_context *devc; + uint16_t pid; + char *fw; + int ret; + + devc = sdi ? sdi->priv : NULL; + if (!devc || !devc->usb_pid) + return SR_ERR_ARG; + pid = devc->usb_pid; + + fw = g_strdup_printf(MCU_FWFILE_FMT, pid); + sr_info("USB PID %04hx, MCU firmware '%s'.", pid, fw); + devc->mcu_firmware = g_strdup(fw); + + if (skip_upload) + ret = SR_OK; + else + ret = ezusb_upload_firmware(sr_ctx, dev, USB_CONFIGURATION, fw); + g_free(fw); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +static void LIBUSB_CALL receive_transfer(struct libusb_transfer *xfer); + +static void la2016_usbxfer_release_cb(gpointer p) +{ + struct libusb_transfer *xfer; + + xfer = p; + g_free(xfer->buffer); + libusb_free_transfer(xfer); +} + +static int la2016_usbxfer_release(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + devc = sdi ? sdi->priv : NULL; + if (!devc) + return SR_ERR_ARG; + + /* Release all USB transfers. */ + g_slist_free_full(devc->transfers, la2016_usbxfer_release_cb); + devc->transfers = NULL; + + return SR_OK; +} + +static int la2016_usbxfer_allocate(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + size_t bufsize, xfercount; + uint8_t *buffer; + struct libusb_transfer *xfer; + + devc = sdi ? sdi->priv : NULL; + if (!devc) + return SR_ERR_ARG; + + /* Transfers were already allocated before? */ + if (devc->transfers) + return SR_OK; - if (devc->info.n_rep_packets % 5) - sr_warn("number of packets is not as expected multiples of 5: %d", devc->info.n_rep_packets); + /* + * Allocate all USB transfers and their buffers. Arrange for a + * buffer size which is within the device's capabilities, and + * is a multiple of the USB endpoint's size, to make use of the + * RAW_IO performance feature. + * + * Implementation detail: The LA2016_USB_BUFSZ value happens + * to match all those constraints. No additional arithmetics is + * required in this location. + */ + bufsize = LA2016_USB_BUFSZ; + xfercount = LA2016_USB_XFER_COUNT; + while (xfercount--) { + buffer = g_try_malloc(bufsize); + if (!buffer) { + sr_err("Cannot allocate USB transfer buffer."); + return SR_ERR_MALLOC; + } + xfer = libusb_alloc_transfer(0); + if (!xfer) { + sr_err("Cannot allocate USB transfer."); + g_free(buffer); + return SR_ERR_MALLOC; + } + xfer->buffer = buffer; + devc->transfers = g_slist_append(devc->transfers, xfer); + } + devc->transfer_bufsize = bufsize; return SR_OK; } -SR_PRIV int la2016_upload_firmware(struct sr_context *sr_ctx, libusb_device *dev, uint16_t product_id) +static int la2016_usbxfer_cancel_all(const struct sr_dev_inst *sdi) { - char fw_file[1024]; - snprintf(fw_file, sizeof(fw_file) - 1, UC_FIRMWARE, product_id); - return ezusb_upload_firmware(sr_ctx, dev, USB_CONFIGURATION, fw_file); + struct dev_context *devc; + GSList *l; + struct libusb_transfer *xfer; + + devc = sdi ? sdi->priv : NULL; + if (!devc) + return SR_ERR_ARG; + + /* Unconditionally cancel the transfer. Ignore errors. */ + for (l = devc->transfers; l; l = l->next) { + xfer = l->data; + if (!xfer) + continue; + libusb_cancel_transfer(xfer); + } + + return SR_OK; } -SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi) +static int la2016_usbxfer_resubmit(const struct sr_dev_inst *sdi, + struct libusb_transfer *xfer) +{ + struct dev_context *devc; + struct sr_usb_dev_inst *usb; + libusb_transfer_cb_fn cb; + int ret; + + devc = sdi ? sdi->priv : NULL; + usb = sdi ? sdi->conn : NULL; + if (!devc || !usb) + return SR_ERR_ARG; + + if (!xfer) + return SR_ERR_ARG; + + cb = receive_transfer; + libusb_fill_bulk_transfer(xfer, usb->devhdl, + USB_EP_CAPTURE_DATA | LIBUSB_ENDPOINT_IN, + xfer->buffer, devc->transfer_bufsize, + cb, (void *)sdi, CAPTURE_TIMEOUT_MS); + ret = libusb_submit_transfer(xfer); + if (ret != 0) { + sr_err("Cannot submit USB transfer: %s.", + libusb_error_name(ret)); + return SR_ERR_IO; + } + + return SR_OK; +} + +static int la2016_usbxfer_submit_all(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + GSList *l; + struct libusb_transfer *xfer; + int ret; + + devc = sdi ? sdi->priv : NULL; + if (!devc) + return SR_ERR_ARG; + + for (l = devc->transfers; l; l = l->next) { + xfer = l->data; + if (!xfer) + return SR_ERR_ARG; + ret = la2016_usbxfer_resubmit(sdi, xfer); + if (ret != SR_OK) + return ret; + } + + return SR_OK; +} + +SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi, + double voltage) { struct dev_context *devc; int ret; @@ -636,13 +1183,14 @@ SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi) devc = sdi->priv; - ret = set_threshold_voltage(sdi, devc->threshold_voltage); + ret = set_threshold_voltage(sdi, voltage); if (ret != SR_OK) return ret; - cmd = 0; - if ((ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_CAPT_MODE, 0, &cmd, sizeof(cmd))) != SR_OK) { - sr_err("failed to send stop sampling command"); + cmd = devc->continuous ? CAPTMODE_STREAM : CAPTMODE_TO_RAM; + ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_CAPT_MODE, 0, &cmd, sizeof(cmd)); + if (ret != SR_OK) { + sr_err("Cannot send command to stop sampling."); return ret; } @@ -659,225 +1207,723 @@ SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi) SR_PRIV int la2016_start_acquisition(const struct sr_dev_inst *sdi) { - return set_run_mode(sdi, 3); + struct dev_context *devc; + int ret; + + devc = sdi->priv; + + ret = la2016_usbxfer_allocate(sdi); + if (ret != SR_OK) + return ret; + + if (devc->continuous) { + ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0); + if (ret != SR_OK) + return ret; + + ret = la2016_usbxfer_submit_all(sdi); + if (ret != SR_OK) + return ret; + + /* + * Periodic receive callback will set runmode. This + * activity MUST be close to data reception, a pause + * between these steps breaks the stream's operation. + */ + } else { + ret = set_run_mode(sdi, RUNMODE_RUN); + if (ret != SR_OK) + return ret; + } + + return SR_OK; } -SR_PRIV int la2016_stop_acquisition(const struct sr_dev_inst *sdi) +static int la2016_stop_acquisition(const struct sr_dev_inst *sdi) { - return set_run_mode(sdi, 0); + struct dev_context *devc; + int ret; + + ret = set_run_mode(sdi, RUNMODE_HALT); + if (ret != SR_OK) + return ret; + + devc = sdi->priv; + if (devc->continuous) + devc->download_finished = TRUE; + + return SR_OK; } SR_PRIV int la2016_abort_acquisition(const struct sr_dev_inst *sdi) { - return la2016_stop_acquisition(sdi); -} + int ret; -SR_PRIV int la2016_has_triggered(const struct sr_dev_inst *sdi) -{ - uint16_t state; + ret = la2016_stop_acquisition(sdi); + if (ret != SR_OK) + return ret; - state = run_state(sdi); + (void)la2016_usbxfer_cancel_all(sdi); - return (state & 0x3) == 1; + return SR_OK; } -SR_PRIV int la2016_start_retrieval(const struct sr_dev_inst *sdi, libusb_transfer_cb_fn cb) +static int la2016_start_download(const struct sr_dev_inst *sdi) { struct dev_context *devc; - struct sr_usb_dev_inst *usb; int ret; - uint8_t wrbuf[2 * sizeof(uint32_t)]; + uint8_t wrbuf[REG_SAMPLING - REG_BULK]; /* Width of REG_BULK. */ uint8_t *wrptr; - uint32_t to_read; - uint8_t *buffer; devc = sdi->priv; - usb = sdi->conn; - if ((ret = get_capture_info(sdi)) != SR_OK) + ret = get_capture_info(sdi); + if (ret != SR_OK) return ret; - devc->n_transfer_packets_to_read = devc->info.n_rep_packets / NUM_PACKETS_IN_CHUNK; - devc->n_bytes_to_read = devc->n_transfer_packets_to_read * TRANSFER_PACKET_LENGTH; + devc->n_transfer_packets_to_read = devc->info.n_rep_packets; + devc->n_transfer_packets_to_read /= devc->packets_per_chunk; + devc->n_bytes_to_read = devc->n_transfer_packets_to_read; + devc->n_bytes_to_read *= devc->transfer_size; devc->read_pos = devc->info.write_pos - devc->n_bytes_to_read; devc->n_reps_until_trigger = devc->info.n_rep_packets_before_trigger; - sr_dbg("want to read %d tfer-packets starting from pos %d", - devc->n_transfer_packets_to_read, devc->read_pos); + sr_dbg("Want to read %u xfer-packets starting from pos %" PRIu32 ".", + devc->n_transfer_packets_to_read, devc->read_pos); - if ((ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0)) != SR_OK) { - sr_err("failed to reset bulk state"); + ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0); + if (ret != SR_OK) { + sr_err("Cannot reset USB bulk state."); return ret; } - sr_dbg("will read from 0x%08x, 0x%08x bytes", devc->read_pos, devc->n_bytes_to_read); + sr_dbg("Will read from 0x%08lx, 0x%08x bytes.", + (unsigned long)devc->read_pos, devc->n_bytes_to_read); wrptr = wrbuf; write_u32le_inc(&wrptr, devc->read_pos); write_u32le_inc(&wrptr, devc->n_bytes_to_read); - if ((ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_BULK, 0, wrbuf, wrptr - wrbuf)) != SR_OK) { - sr_err("failed to send bulk config"); + ret = ctrl_out(sdi, CMD_FPGA_SPI, REG_BULK, 0, wrbuf, wrptr - wrbuf); + if (ret != SR_OK) { + sr_err("Cannot send USB bulk config."); return ret; } - if ((ret = ctrl_out(sdi, CMD_BULK_START, 0x00, 0, NULL, 0)) != SR_OK) { - sr_err("failed to unblock bulk transfers"); + + ret = la2016_usbxfer_submit_all(sdi); + if (ret != SR_OK) { + sr_err("Cannot submit USB bulk transfers."); return ret; } - to_read = devc->n_bytes_to_read; - /* choose a buffer size for all of the usb transfers */ - if (to_read >= LA2016_USB_BUFSZ) - to_read = LA2016_USB_BUFSZ; /* multiple transfers */ - else /* one transfer, make buffer size some multiple of LA2016_EP6_PKTSZ */ - to_read = (to_read + (LA2016_EP6_PKTSZ-1)) & ~(LA2016_EP6_PKTSZ-1); - buffer = g_try_malloc(to_read); - if (!buffer) { - sr_err("Failed to allocate %d bytes for bulk transfer", to_read); - return SR_ERR_MALLOC; + ret = ctrl_out(sdi, CMD_BULK_START, 0x00, 0, NULL, 0); + if (ret != SR_OK) { + sr_err("Cannot start USB bulk transfers."); + return ret; } - devc->transfer = libusb_alloc_transfer(0); - libusb_fill_bulk_transfer( - devc->transfer, usb->devhdl, - 0x86, buffer, to_read, - cb, (void *)sdi, DEFAULT_TIMEOUT_MS); + return SR_OK; +} + +/* + * A chunk of sample memory was received via USB. These chunks contain + * transfers of 16 or 32 bytes each (model dependent size and layout). + * Transfers contain a number of packets (5 or 6 per transfer), which + * contain a number of samples (16 or 32 sampled pin values, and an + * 8bit repeat count for these sampled pin values). A sequence number + * follows the packets within the transfer, allows to detect missing or + * out of order reception. + * + * Memory layout for 16-channel models: + * - 16 bytes per transfer + * - 5x (u16 pins, and u8 count) + * - 1x u8 sequence number + * + * Memory layout for 32-channel models: + * - 32 bytes per transfer + * - 6x (u32 pins, and u8 count) + * - 2x u8 sequence number (inverted, and normal) + * + * This implementation silently ignores the (weak) sequence number. + */ +static void send_chunk(struct sr_dev_inst *sdi, + const uint8_t *data_buffer, size_t data_length) +{ + struct dev_context *devc; + size_t num_xfers, num_pkts, num_seqs; + const uint8_t *rp; + uint32_t sample_value; + size_t repetitions; + uint8_t sample_buff[sizeof(sample_value)]; + + devc = sdi->priv; + + /* Ignore incoming USB data after complete sample data download. */ + if (devc->download_finished) + return; - if ((ret = libusb_submit_transfer(devc->transfer)) != 0) { - sr_err("Failed to submit transfer: %s.", libusb_error_name(ret)); - libusb_free_transfer(devc->transfer); - devc->transfer = NULL; - g_free(buffer); - return SR_ERR; + if (devc->trigger_involved && !devc->trigger_marked && devc->info.n_rep_packets_before_trigger == 0) { + feed_queue_logic_send_trigger(devc->feed_queue); + devc->trigger_marked = TRUE; } - return SR_OK; + /* + * Adjust the number of remaining bytes to read from the device + * before the processing of the currently received chunk affects + * the variable which holds the number of received bytes. + */ + if (data_length > devc->n_bytes_to_read) + devc->n_bytes_to_read = 0; + else + devc->n_bytes_to_read -= data_length; + + /* Process the received chunk of capture data. */ + sample_value = 0; + rp = data_buffer; + num_xfers = data_length / devc->transfer_size; + while (num_xfers--) { + num_pkts = devc->packets_per_chunk; + while (num_pkts--) { + + if (devc->model->channel_count == 32) + sample_value = read_u32le_inc(&rp); + else if (devc->model->channel_count == 16) + sample_value = read_u16le_inc(&rp); + repetitions = read_u8_inc(&rp); + + devc->total_samples += repetitions; + + write_u32le(sample_buff, sample_value); + feed_queue_logic_submit_one(devc->feed_queue, + sample_buff, repetitions); + sr_sw_limits_update_samples_read(&devc->sw_limits, + repetitions); + + if (devc->trigger_involved && !devc->trigger_marked) { + if (!--devc->n_reps_until_trigger) { + feed_queue_logic_send_trigger(devc->feed_queue); + devc->trigger_marked = TRUE; + sr_dbg("Trigger position after %" PRIu64 " samples, %.6fms.", + devc->total_samples, + (double)devc->total_samples / devc->samplerate * 1e3); + } + } + } + /* Skip the sequence number bytes. */ + num_seqs = devc->sequence_size; + while (num_seqs--) + (void)read_u8_inc(&rp); + } + + /* + * Check for several conditions which shall terminate the + * capture data download: When the amount of capture data in + * the device is exhausted. When the user specified samples + * count limit is reached. + */ + if (!devc->n_bytes_to_read) { + devc->download_finished = TRUE; + } else { + sr_dbg("%" PRIu32 " more bytes to download from the device.", + devc->n_bytes_to_read); + } + if (!devc->download_finished && sr_sw_limits_check(&devc->sw_limits)) { + sr_dbg("Acquisition limit reached."); + devc->download_finished = TRUE; + } + if (devc->download_finished) { + sr_dbg("Download finished, flushing session feed queue."); + feed_queue_logic_flush(devc->feed_queue); + } + sr_dbg("Total samples after chunk: %" PRIu64 ".", devc->total_samples); } -SR_PRIV int la2016_init_device(const struct sr_dev_inst *sdi) +/* + * Process a chunk of capture data in streaming mode. The memory layout + * is rather different from "normal mode" (see the send_chunk() routine + * above). In streaming mode data is not compressed, and memory cells + * neither contain raw sampled pin values at a given point in time. The + * memory content needs transformation. + * + * All enabled channels get iterated over. Disabled channels will not + * occupy space in the streamed sample data. Per channel chunk there is + * one 16bit entity which carries samples that were taken at different + * times. The least significant bit was sampled first, higher bits were + * sampled later. After all 16bit entities for all enabled channels + * were seen, the first enabled channel's next chunk follows. + * + * Implementor's note: This routine is inspired by convert_sample_data() + * in the https://github.com/AlexUg/sigrok implementation. Which in turn + * appears to have been derived from the saleae-logic16 sigrok driver. + * The code is phrased conservatively to verify the layout as discussed + * above, performance was not a priority. Operation was verified with an + * LA2016 device. The LA5032 reportedly shares the 16 samples per channel + * layout, just round-robins through a potentially larger set of enabled + * channels before returning to the first of the channels. + */ +static void stream_data(struct sr_dev_inst *sdi, + const uint8_t *data_buffer, size_t data_length) { struct dev_context *devc; - uint16_t state; - uint8_t buf[8]; - int16_t purchase_date_bcd[2]; - uint8_t magic; + struct stream_state_t *stream; + size_t bit_count; + const uint8_t *rp; + uint32_t sample_value; + uint8_t sample_buff[sizeof(sample_value)]; + size_t bit_idx; + uint32_t ch_mask; + + devc = sdi->priv; + stream = &devc->stream; + + /* Ignore incoming USB data after complete sample data download. */ + if (devc->download_finished) + return; + sr_dbg("Stream mode, got another chunk: %p, length %zu.", + data_buffer, data_length); + + /* TODO Add soft trigger support when in stream mode? */ + + /* All channels' chunks carry 16 samples for one channel. */ + bit_count = 16; + data_length /= sizeof(uint16_t); + + rp = data_buffer; + sample_value = 0; + while (data_length--) { + /* Get another entity. */ + sample_value = read_u16le_inc(&rp); + + /* Map the entity's bits to a channel's samples. */ + ch_mask = stream->channel_masks[stream->channel_index]; + for (bit_idx = 0; bit_idx < bit_count; bit_idx++) { + if (sample_value & (1UL << bit_idx)) + stream->sample_data[bit_idx] |= ch_mask; + } + + /* + * Advance to the next channel. Submit a block of + * samples when all channels' data was seen. + */ + stream->channel_index++; + if (stream->channel_index != stream->enabled_count) + continue; + for (bit_idx = 0; bit_idx < bit_count; bit_idx++) { + sample_value = stream->sample_data[bit_idx]; + write_u32le(sample_buff, sample_value); + feed_queue_logic_submit_one(devc->feed_queue, + sample_buff, 1); + } + sr_sw_limits_update_samples_read(&devc->sw_limits, bit_count); + devc->total_samples += bit_count; + memset(stream->sample_data, 0, sizeof(stream->sample_data)); + stream->channel_index = 0; + } + + /* + * Need we count empty or failed USB transfers? This version + * doesn't, assumes that timeouts are perfectly legal because + * transfers are started early, and slow samplerates or trigger + * support in hardware are plausible causes for empty transfers. + * + * TODO Maybe a good condition would be (rather large) a timeout + * after a previous capture data chunk was seen? So that stalled + * streaming gets detected which _is_ an exceptional condition. + * We have observed these when "runmode" is set early but bulk + * transfers start late with a pause after setting the runmode. + */ + if (sr_sw_limits_check(&devc->sw_limits)) { + sr_dbg("Acquisition end reached (sw limits)."); + devc->download_finished = TRUE; + } + if (devc->download_finished) { + sr_dbg("Stream receive done, flushing session feed queue."); + feed_queue_logic_flush(devc->feed_queue); + } + sr_dbg("Total samples after chunk: %" PRIu64 ".", devc->total_samples); +} + +static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + gboolean was_cancelled, device_gone; + int ret; + + sdi = transfer->user_data; + devc = sdi->priv; + + was_cancelled = transfer->status == LIBUSB_TRANSFER_CANCELLED; + device_gone = transfer->status == LIBUSB_TRANSFER_NO_DEVICE; + sr_dbg("receive_transfer(): status %s received %d bytes.", + libusb_error_name(transfer->status), transfer->actual_length); + if (device_gone) { + sr_warn("Lost communication to USB device."); + devc->download_finished = TRUE; + return; + } + + /* + * Implementation detail: A USB transfer timeout is not fatal + * here. We just process whatever was received, empty input is + * perfectly acceptable. Reaching (or exceeding) the sw limits + * or exhausting the device's captured data will complete the + * sample data download. + */ + if (devc->continuous) + stream_data(sdi, transfer->buffer, transfer->actual_length); + else + send_chunk(sdi, transfer->buffer, transfer->actual_length); + + /* + * Re-submit completed transfers (regardless of timeout or + * data reception), unless the transfer was cancelled when + * the acquisition was terminated or has completed. + */ + if (!was_cancelled && !devc->download_finished) { + ret = la2016_usbxfer_resubmit(sdi, transfer); + if (ret == SR_OK) + return; + devc->download_finished = TRUE; + } +} + +SR_PRIV int la2016_receive_data(int fd, int revents, void *cb_data) +{ + const struct sr_dev_inst *sdi; + struct dev_context *devc; + struct drv_context *drvc; + struct timeval tv; int ret; + (void)fd; + (void)revents; + + sdi = cb_data; devc = sdi->priv; + drvc = sdi->driver->context; + + /* Arrange for the start of stream mode when requested. */ + if (devc->continuous && !devc->frame_begin_sent) { + sr_dbg("First receive callback in stream mode."); + devc->download_finished = FALSE; + devc->trigger_marked = FALSE; + devc->total_samples = 0; + + std_session_send_df_frame_begin(sdi); + devc->frame_begin_sent = TRUE; + + ret = set_run_mode(sdi, RUNMODE_RUN); + if (ret != SR_OK) { + sr_err("Cannot set 'runmode' to 'run'."); + return FALSE; + } + + ret = ctrl_out(sdi, CMD_BULK_START, 0x00, 0, NULL, 0); + if (ret != SR_OK) { + sr_err("Cannot start USB bulk transfers."); + return FALSE; + } + sr_dbg("Stream data reception initiated."); + } + + /* + * Wait for the acquisition to complete in hardware. + * Periodically check a potentially configured msecs timeout. + */ + if (!devc->continuous && !devc->completion_seen) { + if (!la2016_is_idle(sdi)) { + if (sr_sw_limits_check(&devc->sw_limits)) { + devc->sw_limits.limit_msec = 0; + sr_dbg("Limit reached. Stopping acquisition."); + la2016_stop_acquisition(sdi); + } + /* Not yet ready for sample data download. */ + return TRUE; + } + sr_dbg("Acquisition completion seen (hardware)."); + devc->sw_limits.limit_msec = 0; + devc->completion_seen = TRUE; + devc->download_finished = FALSE; + devc->trigger_marked = FALSE; + devc->total_samples = 0; + + la2016_dump_fpga_registers(sdi, "acquisition complete", 0, 0); + + /* Initiate the download of acquired sample data. */ + std_session_send_df_frame_begin(sdi); + devc->frame_begin_sent = TRUE; + ret = la2016_start_download(sdi); + if (ret != SR_OK) { + sr_err("Cannot start acquisition data download."); + return FALSE; + } + sr_dbg("Acquisition data download started."); + + return TRUE; + } - /* Four bytes of eeprom at 0x20 are purchase year & month in BCD format, with 16bit - * complemented checksum; e.g. 2004DFFB = 2020-April. - * This helps to identify the age of devices if unknown magic numbers occur. + /* Handle USB reception. Drives sample data download. */ + memset(&tv, 0, sizeof(tv)); + libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv); + + /* + * Periodically flush acquisition data in streaming mode. + * Without this nudge, previously received and accumulated data + * keeps sitting in queues and is not seen by applications. */ - if ((ret = ctrl_in(sdi, CMD_EEPROM, 0x20, 0, purchase_date_bcd, sizeof(purchase_date_bcd))) != SR_OK) { - sr_err("failed to read eeprom purchase_date_bcd"); + if (devc->continuous && devc->stream.flush_period_ms) { + uint64_t now, elapsed; + now = g_get_monotonic_time(); + if (!devc->stream.last_flushed) + devc->stream.last_flushed = now; + elapsed = now - devc->stream.last_flushed; + elapsed /= 1000; + if (elapsed >= devc->stream.flush_period_ms) { + sr_dbg("Stream mode, flushing."); + feed_queue_logic_flush(devc->feed_queue); + devc->stream.last_flushed = now; + } + } + + /* Postprocess completion of sample data download. */ + if (devc->download_finished) { + sr_dbg("Download finished, post processing."); + + la2016_stop_acquisition(sdi); + usb_source_remove(sdi->session, drvc->sr_ctx); + + la2016_usbxfer_cancel_all(sdi); + memset(&tv, 0, sizeof(tv)); + libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv); + + feed_queue_logic_flush(devc->feed_queue); + feed_queue_logic_free(devc->feed_queue); + devc->feed_queue = NULL; + if (devc->frame_begin_sent) { + std_session_send_df_frame_end(sdi); + devc->frame_begin_sent = FALSE; + } + std_session_send_df_end(sdi); + + sr_dbg("Download finished, done post processing."); } - else { - sr_dbg("purchase date: 20%02hx-%02hx", (purchase_date_bcd[0]) & 0x00ff, (purchase_date_bcd[0] >> 8) & 0x00ff); - if (purchase_date_bcd[0] != (0x0ffff & ~purchase_date_bcd[1])) { - sr_err("purchase date: checksum failure"); + + return TRUE; +} + +SR_PRIV int la2016_identify_device(const struct sr_dev_inst *sdi, + gboolean show_message) +{ + struct dev_context *devc; + uint8_t buf[8]; /* Larger size of manuf date and device type magic. */ + size_t rdoff, rdlen; + const uint8_t *rdptr; + uint8_t date_yy, date_mm; + uint8_t dinv_yy, dinv_mm; + uint8_t magic, magic2; + size_t model_idx; + const struct kingst_model *model; + int ret; + + devc = sdi->priv; + + /* + * Four EEPROM bytes at offset 0x20 are the manufacturing date, + * year and month in BCD format, followed by inverted values for + * consistency checks. For example bytes 20 04 df fb translate + * to 2020-04. This information can help identify the vintage of + * devices when unknown magic numbers are seen. + */ + rdoff = 0x20; + rdlen = 4 * sizeof(uint8_t); + ret = ctrl_in(sdi, CMD_EEPROM, rdoff, 0, buf, rdlen); + if (ret != SR_OK && !show_message) { + /* Non-fatal weak attempt during probe. Not worth logging. */ + sr_dbg("Cannot access EEPROM."); + return SR_ERR_IO; + } else if (ret != SR_OK) { + /* Failed attempt in regular use. Non-fatal. Worth logging. */ + sr_err("Cannot read manufacture date in EEPROM."); + } else { + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + GString *txt; + txt = sr_hexdump_new(buf, rdlen); + sr_spew("Manufacture date bytes %s.", txt->str); + sr_hexdump_free(txt); } + rdptr = &buf[0]; + date_yy = read_u8_inc(&rdptr); + date_mm = read_u8_inc(&rdptr); + dinv_yy = read_u8_inc(&rdptr); + dinv_mm = read_u8_inc(&rdptr); + sr_info("Manufacture date: 20%02hx-%02hx.", date_yy, date_mm); + if ((date_mm ^ dinv_mm) != 0xff || (date_yy ^ dinv_yy) != 0xff) + sr_warn("Manufacture date fails checksum test."); } /* - * There are four known kingst logic analyser devices which use this same usb vid and pid: - * LA2016, LA1016 and the older revision of each of these. They all use the same hardware - * and the same FX2 mcu firmware but each requires a different fpga bitstream. They are - * differentiated by a 'magic' byte within the 8 bytes of EEPROM from address 0x08. - * For example; + * Several Kingst logic analyzer devices share the same USB VID + * and PID. The product ID determines which MCU firmware to load. + * The MCU firmware provides access to EEPROM content which then + * allows to identify the device model. Which in turn determines + * which FPGA bitstream to load. Eight bytes at offset 0x08 are + * to get inspected. * - * magic=0x08 - * | ~magic=0xf7 - * | | - * 08F7000008F710EF - * | | - * | ~magic-backup - * magic-backup + * EEPROM content for model identification is kept redundantly + * in memory. The values are stored in verbatim and in inverted + * form, multiple copies are kept at different offsets. Example + * data: * - * It seems that only these magic bytes are used, other bytes shown above are 'don't care'. - * Changing the magic byte on newer device to older magic causes OEM software to load - * the older fpga bitstream. The device then functions but has channels out of order. - * It's likely the bitstreams were changed to move input channel pins due to PCB changes. + * magic 0x08 + * | ~magic 0xf7 + * | | + * 08f7000008f710ef + * | | + * | ~magic backup + * magic backup * - * magic 9 == LA1016a using "kingst-la1016a1-fpga.bitstream" (latest v1.3.0 PCB, perhaps others) - * magic 8 == LA2016a using "kingst-la2016a1-fpga.bitstream" (latest v1.3.0 PCB, perhaps others) - * magic 3 == LA1016 using "kingst-la1016-fpga.bitstream" - * magic 2 == LA2016 using "kingst-la2016-fpga.bitstream" + * Exclusively inspecting the magic byte appears to be sufficient, + * other fields seem to be 'don't care'. * - * This was all determined by altering the eeprom contents of an LA2016 and LA1016 and observing - * the vendor software actions, either raising errors or loading specific bitstreams. + * magic 2 == LA2016 using "kingst-la2016-fpga.bitstream" + * magic 3 == LA1016 using "kingst-la1016-fpga.bitstream" + * magic 8 == LA2016a using "kingst-la2016a1-fpga.bitstream" + * (latest v1.3.0 PCB, perhaps others) + * magic 9 == LA1016a using "kingst-la1016a1-fpga.bitstream" + * (latest v1.3.0 PCB, perhaps others) * - * Note: - * An LA1016 cannot be converted to an LA2016 by changing the magic number - the bitstream - * will not authenticate with ic U10, which has different security coding for each device type. + * When EEPROM content does not match the hardware configuration + * (the board layout), the software may load but yield incorrect + * results (like swapped channels). The FPGA bitstream itself + * will authenticate with IC U10 and fail when its capabilities + * do not match the hardware model. An LA1016 won't become a + * LA2016 by faking its EEPROM content. */ - - if ((ret = ctrl_in(sdi, CMD_EEPROM, 0x08, 0, &buf, sizeof(buf))) != SR_OK) { - sr_err("failed to read eeprom device identifier bytes"); + devc->identify_magic = 0; + rdoff = 0x08; + rdlen = 8 * sizeof(uint8_t); + ret = ctrl_in(sdi, CMD_EEPROM, rdoff, 0, &buf, rdlen); + if (ret != SR_OK) { + sr_err("Cannot read EEPROM device identifier bytes."); return ret; } - + if (sr_log_loglevel_get() >= SR_LOG_SPEW) { + GString *txt; + txt = sr_hexdump_new(buf, rdlen); + sr_spew("EEPROM magic bytes %s.", txt->str); + sr_hexdump_free(txt); + } magic = 0; - if (buf[0] == (0x0ff & ~buf[1])) { - /* primary copy of magic passes complement check */ + magic2 = 0; + if ((buf[0] ^ buf[1]) == 0xff && (buf[2] ^ buf[3]) == 0xff) { + /* Primary copy of magic passes complement check (4 bytes). */ magic = buf[0]; - } - else if (buf[4] == (0x0ff & ~buf[5])) { - /* backup copy of magic passes complement check */ - sr_dbg("device_type: using backup copy of magic number"); + magic2 = buf[2]; + sr_dbg("Using primary magic 0x%hhx (0x%hhx).", magic, magic2); + } else if ((buf[4] ^ buf[5]) == 0xff && (buf[6] ^ buf[7]) == 0xff) { + /* Backup copy of magic passes complement check (4 bytes). */ + magic = buf[4]; + magic2 = buf[6]; + sr_dbg("Using secondary magic 0x%hhx (0x%hhx).", magic, magic2); + } else if ((buf[0] ^ buf[1]) == 0xff) { + /* Primary copy of magic passes complement check (2 bytes). */ + magic = buf[0]; + sr_dbg("Using primary magic 0x%hhx.", magic); + } else if ((buf[4] ^ buf[5]) == 0xff) { + /* Backup copy of magic passes complement check (2 bytes). */ magic = buf[4]; + sr_dbg("Using secondary magic 0x%hhx.", magic); + } else { + sr_err("Cannot find consistent device type identification."); } - - sr_dbg("device_type: magic number is %hhu", magic); - - /* select the correct fpga bitstream for this device */ - switch (magic) { - case 2: - ret = upload_fpga_bitstream(sdi, FPGA_FW_LA2016); - devc->max_samplerate = MAX_SAMPLE_RATE_LA2016; - break; - case 3: - ret = upload_fpga_bitstream(sdi, FPGA_FW_LA1016); - devc->max_samplerate = MAX_SAMPLE_RATE_LA1016; - break; - case 8: - ret = upload_fpga_bitstream(sdi, FPGA_FW_LA2016A); - devc->max_samplerate = MAX_SAMPLE_RATE_LA2016; - break; - case 9: - ret = upload_fpga_bitstream(sdi, FPGA_FW_LA1016A); - devc->max_samplerate = MAX_SAMPLE_RATE_LA1016; + devc->identify_magic = magic; + devc->identify_magic2 = magic2; + + devc->model = NULL; + for (model_idx = 0; model_idx < ARRAY_SIZE(models); model_idx++) { + model = &models[model_idx]; + if (model->magic != magic) + continue; + if (model->magic2 && model->magic2 != magic2) + continue; + devc->model = model; + sr_info("Model '%s', %zu channels, max %" PRIu64 "MHz.", + model->name, model->channel_count, + model->samplerate / SR_MHZ(1)); + devc->fpga_bitstream = g_strdup_printf(FPGA_FWFILE_FMT, + model->fpga_stem); + sr_info("FPGA bitstream file '%s'.", devc->fpga_bitstream); + if (!model->channel_count) { + sr_warn("Device lacks logic channels. Not supported."); + devc->model = NULL; + } break; - default: - sr_err("device_type: device not supported; magic number indicates this is not a LA2016 or LA1016"); - return SR_ERR; } + if (!devc->model) { + sr_err("Cannot identify as one of the supported models."); + return SR_ERR_DATA; + } + + return SR_OK; +} + +SR_PRIV int la2016_init_hardware(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + const char *bitstream_fn; + int ret; + uint16_t state; + + devc = sdi->priv; + bitstream_fn = devc ? devc->fpga_bitstream : ""; + ret = check_fpga_bitstream(sdi); + if (ret != SR_OK) { + ret = upload_fpga_bitstream(sdi, bitstream_fn); + if (ret != SR_OK) { + sr_err("Cannot upload FPGA bitstream."); + return ret; + } + } + ret = enable_fpga_bitstream(sdi); if (ret != SR_OK) { - sr_err("failed to upload fpga bitstream"); + sr_err("Cannot enable FPGA bitstream after upload."); return ret; } state = run_state(sdi); - if (state != 0x85e9) { - sr_warn("expect run state to be 0x85e9, but it reads 0x%04x", state); + if ((state & 0xfff0) != 0x85e0) { + sr_warn("Unexpected run state, want 0x85eX, got 0x%04x.", state); } - if ((ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0)) != SR_OK) { - sr_err("failed to send CMD_BULK_RESET"); + ret = ctrl_out(sdi, CMD_BULK_RESET, 0x00, 0, NULL, 0); + if (ret != SR_OK) { + sr_err("Cannot reset USB bulk transfer."); return ret; } - sr_dbg("device should be initialized"); + sr_dbg("Device should be initialized."); - return set_defaults(sdi); + return SR_OK; } -SR_PRIV int la2016_deinit_device(const struct sr_dev_inst *sdi) +SR_PRIV int la2016_deinit_hardware(const struct sr_dev_inst *sdi) { int ret; - if ((ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x00, 0, NULL, 0)) != SR_OK) { - sr_err("failed to send deinit command"); + ret = ctrl_out(sdi, CMD_FPGA_ENABLE, 0x00, 0, NULL, 0); + if (ret != SR_OK) { + sr_err("Cannot deinitialize device's FPGA."); return ret; } return SR_OK; } + +SR_PRIV void la2016_release_resources(const struct sr_dev_inst *sdi) +{ + (void)la2016_usbxfer_release(sdi); +} + +SR_PRIV int la2016_write_pwm_config(const struct sr_dev_inst *sdi, size_t idx) +{ + return set_pwm_config(sdi, idx); +} diff --git a/src/hardware/kingst-la2016/protocol.h b/src/hardware/kingst-la2016/protocol.h index 09e12be6..371a3ccd 100644 --- a/src/hardware/kingst-la2016/protocol.h +++ b/src/hardware/kingst-la2016/protocol.h @@ -1,6 +1,7 @@ /* * This file is part of the libsigrok project. * + * Copyright (C) 2022 Gerhard Sittig * Copyright (C) 2020 Florian Schmidt * Copyright (C) 2013 Marcus Comstedt * Copyright (C) 2013 Bert Vermeulen @@ -23,110 +24,164 @@ #ifndef LIBSIGROK_HARDWARE_KINGST_LA2016_PROTOCOL_H #define LIBSIGROK_HARDWARE_KINGST_LA2016_PROTOCOL_H -#include -#include #include -#include "libsigrok-internal.h" +#include -#define LOG_PREFIX "kingst-la2016" +#define LOG_PREFIX "kingst-la2016" #define LA2016_VID 0x77a1 #define LA2016_PID 0x01a2 +#define LA2016_IPRODUCT_INDEX 2 #define USB_INTERFACE 0 #define USB_CONFIGURATION 1 - -#define LA2016_BULK_MAX 8388608 +#define USB_EP_FPGA_BITSTREAM 2 +#define USB_EP_CAPTURE_DATA 6 /* * On Windows sigrok uses WinUSB RAW_IO policy which requires the * USB transfer buffer size to be a multiple of the endpoint max packet - * size, which is 512 bytes in this case. Also, the maximum allowed size of - * the transfer buffer is normally read from WinUSB_GetPipePolicy API but - * libusb does not expose this function. Typically, max size is 2MB. + * size, which is 512 bytes in this case. Also, the maximum allowed size + * of the transfer buffer is normally read from WinUSB_GetPipePolicy API + * but libusb does not expose this function. Typically, max size is 2MB. + */ +#define LA2016_EP6_PKTSZ 512 /* Max packet size of USB endpoint 6. */ +#define LA2016_USB_BUFSZ (512 * 1024) /* 512KiB buffer. */ +#define LA2016_USB_XFER_COUNT 8 /* Size of USB bulk transfers pool. */ + +/* USB communication timeout during regular operation. */ +#define DEFAULT_TIMEOUT_MS 200 +#define CAPTURE_TIMEOUT_MS 500 + +/* + * Check for MCU firmware to take effect after upload. Check the device + * presence for a maximum period of time, delay between checks in that + * phase. Allow for the device to vanish after upload and before checks, + * to not mistake its earlier incarnation for the successful operation + * of the most recently loaded firmware. */ -#define LA2016_EP6_PKTSZ 512 /* endpoint 6 max packet size */ -#define LA2016_USB_BUFSZ (256 * 2 * LA2016_EP6_PKTSZ) /* 256KB buffer */ +#define RENUM_CHECK_PERIOD_MS 3000 +#define RENUM_GONE_DELAY_MS 1800 +#define RENUM_POLL_INTERVAL_MS 200 -#define MAX_RENUM_DELAY_MS 3000 -#define DEFAULT_TIMEOUT_MS 200 +/* + * The device expects some zero padding to follow the content of the + * file which contains the FPGA bitstream. Specify the chunk size here. + */ +#define LA2016_EP2_PADDING 4096 -#define LA2016_THR_VOLTAGE_MIN 0.40 -#define LA2016_THR_VOLTAGE_MAX 4.00 +/* + * Whether the logic input threshold voltage is a config item of the + * "Logic" channel group or a global config item of the device. Ideally + * it would be the former (being strictly related to the Logic channels) + * but mainline applications work better with the latter, and many other + * device drivers implement it that way, too. + */ +#define WITH_THRESHOLD_DEVCFG 1 -#define LA2016_NUM_SAMPLES_MIN 256 -#define LA2016_NUM_SAMPLES_MAX (10UL * 1000 * 1000 * 1000) +#define LA2016_THR_VOLTAGE_MIN 0.40 +#define LA2016_THR_VOLTAGE_MAX 4.00 -typedef struct pwm_setting_dev { - uint32_t period; - uint32_t duty; -} pwm_setting_dev_t; +#define LA2016_NUM_SAMPLES_MAX (UINT64_C(10 * 1000 * 1000 * 1000)) -typedef struct trigger_cfg { - uint32_t channels; - uint32_t enabled; - uint32_t level; - uint32_t high_or_falling; -} trigger_cfg_t; +/* Maximum device capabilities. May differ between models. */ +#define MAX_PWM_FREQ SR_MHZ(20) +#define PWM_CLOCK SR_MHZ(200) /* 200MHz for both LA2016 and LA1016 */ -typedef struct capture_info { - uint32_t n_rep_packets; - uint32_t n_rep_packets_before_trigger; - uint32_t write_pos; -} capture_info_t; +#define LA2016_NUM_PWMCH_MAX 2 -#define NUM_PACKETS_IN_CHUNK 5 -#define TRANSFER_PACKET_LENGTH 16 +/* Streaming mode related thresholds. Not enforced, used for warnings. */ +#define LA2016_STREAM_MBPS_MAX 200 /* In units of Mbps. */ +#define LA2016_STREAM_PUSH_THR 16 /* In units of Mbps. */ +#define LA2016_STREAM_PUSH_IVAL 250 /* In units of ms. */ -typedef struct pwm_setting { - uint8_t enabled; - float freq; - float duty; -} pwm_setting_t; +/* + * Whether to de-initialize the device hardware in the driver's close + * callback. It is desirable to e.g. configure PWM channels and leave + * the generator running after the application shuts down. Users can + * always disable channels on their way out if they want to. + */ +#define WITH_DEINIT_IN_CLOSE 0 + +#define LA2016_CONVBUFFER_SIZE (4 * 1024 * 1024) + +struct kingst_model { + uint8_t magic, magic2; /* EEPROM magic byte values. */ + const char *name; /* User perceived model name. */ + const char *fpga_stem; /* Bitstream filename stem. */ + uint64_t samplerate; /* Max samplerate in Hz. */ + size_t channel_count; /* Max channel count (16, 32). */ + uint64_t memory_bits; /* RAM capacity in Gbit (1, 2, 4). */ + uint64_t baseclock; /* Base clock to derive samplerate from. */ +}; struct dev_context { - struct sr_context *ctx; - - int64_t fw_updated; - pwm_setting_t pwm_setting[2]; - unsigned int threshold_voltage_idx; - float threshold_voltage; - uint64_t max_samplerate; - uint64_t cur_samplerate; - uint64_t limit_samples; + uint16_t usb_pid; + char *mcu_firmware; + char *fpga_bitstream; + uint64_t fw_uploaded; /* Timestamp of most recent FW upload. */ + uint8_t identify_magic, identify_magic2; + const struct kingst_model *model; + char **channel_names_logic; + struct sr_channel_group *cg_logic, *cg_pwm; + + /* User specified parameters. */ + struct pwm_setting { + gboolean enabled; + float freq; + float duty; + } pwm_setting[LA2016_NUM_PWMCH_MAX]; + size_t threshold_voltage_idx; + uint64_t samplerate; + struct sr_sw_limits sw_limits; uint64_t capture_ratio; - uint16_t cur_channels; - int num_channels; - - uint32_t bitstream_size; - - /* derived stuff */ - uint64_t pre_trigger_size; - - /* state after sampling */ - int had_triggers_configured; - int have_trigger; - int transfer_finished; - capture_info_t info; - unsigned int n_transfer_packets_to_read; /* each with 5 acq packets */ - unsigned int n_bytes_to_read; - unsigned int n_reps_until_trigger; - unsigned int reading_behind_trigger; + gboolean continuous; + + /* Internal acquisition and download state. */ + gboolean trigger_involved; + gboolean frame_begin_sent; + gboolean completion_seen; + gboolean download_finished; + size_t transfer_size; + size_t sequence_size; + uint32_t packets_per_chunk; + struct capture_info { + uint32_t n_rep_packets; + uint32_t n_rep_packets_before_trigger; + uint32_t write_pos; + } info; + uint32_t n_transfer_packets_to_read; /* each with 5 acq packets */ + uint32_t n_bytes_to_read; + uint32_t n_reps_until_trigger; + gboolean trigger_marked; uint64_t total_samples; uint32_t read_pos; - unsigned int convbuffer_size; - uint8_t *convbuffer; - struct libusb_transfer *transfer; + struct feed_queue_logic *feed_queue; + GSList *transfers; + size_t transfer_bufsize; + struct stream_state_t { + size_t enabled_count; + uint32_t enabled_mask; + uint32_t channel_masks[32]; + size_t channel_index; + uint32_t sample_data[32]; + uint64_t flush_period_ms; + uint64_t last_flushed; + } stream; }; -SR_PRIV int la2016_upload_firmware(struct sr_context *sr_ctx, libusb_device *dev, uint16_t product_id); -SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi); +SR_PRIV int la2016_upload_firmware(const struct sr_dev_inst *sdi, + struct sr_context *sr_ctx, libusb_device *dev, gboolean skip_upload); +SR_PRIV int la2016_identify_device(const struct sr_dev_inst *sdi, + gboolean show_message); +SR_PRIV int la2016_init_hardware(const struct sr_dev_inst *sdi); +SR_PRIV int la2016_deinit_hardware(const struct sr_dev_inst *sdi); +SR_PRIV int la2016_write_pwm_config(const struct sr_dev_inst *sdi, size_t idx); +SR_PRIV int la2016_setup_acquisition(const struct sr_dev_inst *sdi, + double voltage); SR_PRIV int la2016_start_acquisition(const struct sr_dev_inst *sdi); -SR_PRIV int la2016_stop_acquisition(const struct sr_dev_inst *sdi); SR_PRIV int la2016_abort_acquisition(const struct sr_dev_inst *sdi); -SR_PRIV int la2016_has_triggered(const struct sr_dev_inst *sdi); -SR_PRIV int la2016_start_retrieval(const struct sr_dev_inst *sdi, libusb_transfer_cb_fn cb); -SR_PRIV int la2016_init_device(const struct sr_dev_inst *sdi); -SR_PRIV int la2016_deinit_device(const struct sr_dev_inst *sdi); +SR_PRIV int la2016_receive_data(int fd, int revents, void *cb_data); +SR_PRIV void la2016_release_resources(const struct sr_dev_inst *sdi); #endif diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c index d5237441..82e43797 100644 --- a/src/hardware/korad-kaxxxxp/api.c +++ b/src/hardware/korad-kaxxxxp/api.c @@ -19,6 +19,9 @@ */ #include + +#include + #include "protocol.h" static const uint32_t scanopts[] = { @@ -46,55 +49,179 @@ static const uint32_t devopts[] = { SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED | SR_CONF_GET | SR_CONF_SET, }; +/* Voltage and current ranges. Values are: Min, max, step. */ +static const double volts_30[] = { 0, 31, 0.01, }; +static const double volts_60[] = { 0, 61, 0.01, }; +static const double amps_3[] = { 0, 3.1, 0.001, }; +static const double amps_5[] = { 0, 5.1, 0.001, }; + static const struct korad_kaxxxxp_model models[] = { - /* Device enum, vendor, model, ID reply, channels, voltage, current */ - {VELLEMAN_PS3005D, "Velleman", "PS3005D", - "VELLEMANPS3005DV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {VELLEMAN_LABPS3005D, "Velleman", "LABPS3005D", - "VELLEMANLABPS3005DV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {KORAD_KA3005P, "Korad", "KA3005P", - "KORADKA3005PV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - /* Sometimes the KA3005P has an extra 0x01 after the ID. */ - {KORAD_KA3005P_0X01, "Korad", "KA3005P", - "KORADKA3005PV2.0\x01", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - /* Sometimes the KA3005P has an extra 0xBC after the ID. */ - {KORAD_KA3005P_0XBC, "Korad", "KA3005P", - "KORADKA3005PV2.0\xBC", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {KORAD_KA3005P_V42, "Korad", "KA3005P", - "KORAD KA3005P V4.2", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {KORAD_KA3005P_V55, "Korad", "KA3005P", - "KORAD KA3005P V5.5", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {KORAD_KD3005P, "Korad", "KD3005P", - "KORAD KD3005P V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {KORAD_KD3005P_V20_NOSP, "Korad", "KD3005P", - "KORADKD3005PV2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {KORAD_KD3005P_V21_NOSP, "Korad", "KD3005P", - "KORADKD3005PV2.1", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {RND_320_KD3005P, "RND", "KD3005P", - "RND 320-KD3005P V4.2", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {RND_320_KA3005P, "RND", "KA3005P", - "RND 320-KA3005P V5.5", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {RND_320K30PV, "RND", "KA3005P", - "RND 320-KA3005P V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {TENMA_72_2550_V2, "Tenma", "72-2550", - "TENMA72-2550V2.0", 1, {0, 61, 0.01}, {0, 3.1, 0.001}}, - {TENMA_72_2540_V20, "Tenma", "72-2540", - "TENMA72-2540V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {TENMA_72_2540_V21, "Tenma", "72-2540", - "TENMA 72-2540 V2.1", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {TENMA_72_2540_V52, "Tenma", "72-2540", - "TENMA 72-2540 V5.2", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {TENMA_72_2535_V21, "Tenma", "72-2535", - "TENMA 72-2535 V2.1", 1, {0, 31, 0.01}, {0, 3.1, 0.001}}, - {TENMA_72_2710_V66, "Tenma", "72-2710", - "TENMA 72-2710 V6.6", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {STAMOS_SLS31_V20, "Stamos Soldering", "S-LS-31", - "S-LS-31 V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}}, - {KORAD_KD6005P, "Korad", "KD6005P", - "KORAD KD6005P V2.2", 1, {0, 61, 0.01}, {0, 5.1, 0.001}}, + /* Vendor, model name, ID reply, channels, voltage, current, quirks. */ + {"Korad", "KA3005P", "", 1, volts_30, amps_5, + KORAD_QUIRK_ID_TRAILING}, + {"Korad", "KD3005P", "", 1, volts_30, amps_5, 0}, + {"Korad", "KD6005P", "", 1, volts_60, amps_5, 0}, + {"RND", "KA3005P", "RND 320-KA3005P", 1, volts_30, amps_5, + KORAD_QUIRK_ID_OPT_VERSION}, + {"RND", "KD3005P", "RND 320-KD3005P", 1, volts_30, amps_5, + KORAD_QUIRK_ID_OPT_VERSION}, + {"Stamos Soldering", "S-LS-31", "", 1, volts_30, amps_5, + KORAD_QUIRK_ID_NO_VENDOR}, + {"Tenma", "72-2535", "", 1, volts_30, amps_3, 0}, + {"Tenma", "72-2540", "", 1, volts_30, amps_5, 0}, + {"Tenma", "72-2550", "", 1, volts_60, amps_3, 0}, + {"Tenma", "72-2705", "", 1, volts_30, amps_3, 0}, + {"Tenma", "72-2710", "", 1, volts_30, amps_5, 0}, + {"Velleman", "LABPS3005D", "", 1, volts_30, amps_5, + KORAD_QUIRK_LABPS_OVP_EN}, + {"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5, + KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING}, + {"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0}, ALL_ZERO }; +/* + * Bump this when adding new models[] above. Make sure the text buffer + * for the ID response can hold the longest sequence that we expect in + * the field which consists of: vendor + model [ + version ][ + serno ]. + * Don't be too generous here, the maximum receive buffer size affects + * the timeout within which the first response character is expected. + */ +static const size_t id_text_buffer_size = 48; + +/* + * Check whether the device's "*IDN?" response matches a supported model. + * The caller already stripped off the optional serial number. + */ +static gboolean model_matches(const struct korad_kaxxxxp_model *model, + const char *id_text) +{ + gboolean matches; + gboolean opt_version, skip_vendor, accept_trail; + const char *want; + + if (!model) + return FALSE; + + /* + * When the models[] entry contains a specific response text, + * then expect to see this very text in literal form. This + * lets the driver map weird and untypical responses to a + * specific set of display texts for vendor and model names. + * Accept an optionally trailing version if models[] says so. + */ + if (model->id && model->id[0]) { + opt_version = model->quirks & KORAD_QUIRK_ID_OPT_VERSION; + if (!opt_version) { + matches = g_strcmp0(id_text, model->id) == 0; + if (!matches) + return FALSE; + sr_dbg("Matches expected ID text: '%s'.", model->id); + return TRUE; + } + matches = g_str_has_prefix(id_text, model->id); + if (!matches) + return FALSE; + id_text += strlen(model->id); + while (isspace((int)*id_text)) + id_text++; + if (*id_text == 'V') { + id_text++; + while (*id_text == '.' || isdigit((int)*id_text)) + id_text++; + while (isspace((int)*id_text)) + id_text++; + } + if (*id_text) + return FALSE; + sr_dbg("Matches expected ID text [vers]: '%s'.", model->id); + return TRUE; + } + + /* + * A more generic approach which covers most devices: Check + * for the very vendor and model names which also are shown + * to users (the display texts). Weakened to match responses + * more widely: Case insensitive checks, optional whitespace + * in responses, optional version details. Optional trailing + * garbage. Optional omission of the vendor name. Shall match + * all the devices which were individually listed in earlier + * implementations of the driver, and shall also match firmware + * versions that were not listed before. + */ + skip_vendor = model->quirks & KORAD_QUIRK_ID_NO_VENDOR; + accept_trail = model->quirks & KORAD_QUIRK_ID_TRAILING; + if (!skip_vendor) { + want = model->vendor; + matches = g_ascii_strncasecmp(id_text, want, strlen(want)) == 0; + if (!matches) + return FALSE; + id_text += strlen(want); + while (isspace((int)*id_text)) + id_text++; + } + want = model->name; + matches = g_ascii_strncasecmp(id_text, want, strlen(want)) == 0; + if (!matches) + return FALSE; + id_text += strlen(want); + while (isspace((int)*id_text)) + id_text++; + if (*id_text == 'V') { + /* TODO Isolate and (also) return version details? */ + id_text++; + while (*id_text == '.' || isdigit((int)*id_text)) + id_text++; + while (isspace((int)*id_text)) + id_text++; + } + if (accept_trail) { + /* + * TODO Determine how many non-printables to accept here + * and how strict to check for "known" garbage variants. + */ + switch (*id_text) { + case '\x01': + case '\xbc': + id_text++; + break; + case '\x00': + /* EMPTY */ + break; + default: + return FALSE; + } + } + if (*id_text) + return FALSE; + sr_dbg("Matches generic '[vendor] model [vers] [trail]' pattern."); + return TRUE; +} + +/* Lookup a model from an ID response text. */ +static const struct korad_kaxxxxp_model *model_lookup(const char *id_text) +{ + size_t idx; + const struct korad_kaxxxxp_model *check; + + if (!id_text || !*id_text) + return NULL; + sr_dbg("Looking up: [%s].", id_text); + + for (idx = 0; idx < ARRAY_SIZE(models); idx++) { + check = &models[idx]; + if (!check->name || !check->name[0]) + continue; + if (!model_matches(check, id_text)) + continue; + sr_dbg("Found: [%s] [%s]", check->vendor, check->name); + return check; + } + sr_dbg("Not found"); + + return NULL; +} + static GSList *scan(struct sr_dev_driver *di, GSList *options) { static const char *serno_prefix = " SN:"; @@ -107,7 +234,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) const char *force_detect; struct sr_serial_dev_inst *serial; char reply[50]; - int ret, i, model_id; + int ret; + const struct korad_kaxxxxp_model *model; size_t len; char *serno; @@ -144,17 +272,8 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) if (serial_open(serial, SERIAL_RDWR) != SR_OK) return NULL; - /* - * Prepare a receive buffer for the identification response that - * is large enough to hold the longest known model name, and an - * optional serial number. Communicate the identification request. - */ - len = 0; - for (i = 0; models[i].id; i++) { - if (len < strlen(models[i].id)) - len = strlen(models[i].id); - } - len += strlen(serno_prefix) + 12; + /* Communicate the identification request. */ + len = id_text_buffer_size; if (len > sizeof(reply) - 1) len = sizeof(reply) - 1; sr_dbg("Want max %zu bytes.", len); @@ -178,35 +297,25 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) serno += strlen(serno_prefix); } - model_id = -1; - for (i = 0; models[i].id; i++) { - if (g_strcmp0(models[i].id, reply) != 0) - continue; - model_id = i; - break; - } - if (model_id < 0 && force_detect) { - sr_warn("Found model ID '%s' is unknown, trying '%s' spec.", + model = model_lookup(reply); + if (!model && force_detect) { + sr_warn("Could not find model ID '%s', trying '%s'.", reply, force_detect); - for (i = 0; models[i].id; i++) { - if (strcmp(models[i].id, force_detect) != 0) - continue; + model = model_lookup(force_detect); + if (model) sr_info("Found replacement, using it instead."); - model_id = i; - break; - } } - if (model_id < 0) { - sr_err("Unknown model ID '%s' detected, aborting.", reply); + if (!model) { + sr_err("Unsupported model ID '%s', aborting.", reply); return NULL; } - sr_dbg("Found: %s %s (idx %d, ID '%s').", models[model_id].vendor, - models[model_id].name, model_id, models[model_id].id); + sr_dbg("Found: %s %s (idx %zu).", model->vendor, model->name, + model - &models[0]); - sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi = g_malloc0(sizeof(*sdi)); sdi->status = SR_ST_INACTIVE; - sdi->vendor = g_strdup(models[model_id].vendor); - sdi->model = g_strdup(models[model_id].name); + sdi->vendor = g_strdup(model->vendor); + sdi->model = g_strdup(model->name); if (serno) sdi->serial_num = g_strdup(serno); sdi->inst_type = SR_INST_SERIAL; @@ -216,11 +325,11 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V"); sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I"); - devc = g_malloc0(sizeof(struct dev_context)); + devc = g_malloc0(sizeof(*devc)); sr_sw_limits_init(&devc->limits); g_mutex_init(&devc->rw_mutex); - devc->model = &models[model_id]; - devc->req_sent_at = 0; + devc->model = model; + devc->next_req_time = 0; devc->cc_mode_1_changed = FALSE; devc->cc_mode_2_changed = FALSE; devc->output_enabled_changed = FALSE; @@ -409,7 +518,7 @@ static int dev_acquisition_start(const struct sr_dev_inst *sdi) sr_sw_limits_acquisition_start(&devc->limits); std_session_send_df_header(sdi); - devc->req_sent_at = 0; + devc->next_req_time = 0; serial = sdi->conn; serial_source_add(sdi->session, serial, G_IO_IN, KAXXXXP_POLL_INTERVAL_MS, diff --git a/src/hardware/korad-kaxxxxp/protocol.c b/src/hardware/korad-kaxxxxp/protocol.c index 1407d51a..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,12 +269,13 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial, } SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, - int target, struct dev_context *devc) + int target, struct dev_context *devc) { int ret, count; char reply[6]; float *value; char status_byte; + gboolean needs_ovp_quirk; gboolean prev_status; g_mutex_lock(&devc->rw_mutex); @@ -297,11 +315,14 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, break; default: sr_err("Don't know how to query %d.", target); + ret = SR_ERR; + } + if (ret < 0) { g_mutex_unlock(&devc->rw_mutex); - return SR_ERR; + return ret; } - devc->req_sent_at = g_get_monotonic_time(); + devc->next_req_time = next_req_time(devc, FALSE, target); if ((ret = korad_kaxxxxp_read_chars(serial, count, reply)) < 0) { g_mutex_unlock(&devc->rw_mutex); @@ -342,9 +363,8 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, devc->output_enabled_changed = devc->output_enabled != prev_status; /* OVP enabled, special handling for Velleman LABPS3005 quirk. */ - if ((devc->model->model_id == VELLEMAN_LABPS3005D && devc->output_enabled) || - devc->model->model_id != VELLEMAN_LABPS3005D) { - + needs_ovp_quirk = devc->model->quirks & KORAD_QUIRK_LABPS_OVP_EN; + if (!needs_ovp_quirk || devc->output_enabled) { prev_status = devc->ovp_enabled; devc->ovp_enabled = status_byte & (1 << 7); devc->ovp_enabled_changed = devc->ovp_enabled != prev_status; @@ -374,7 +394,7 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, } SR_PRIV int korad_kaxxxxp_get_all_values(struct sr_serial_dev_inst *serial, - struct dev_context *devc) + struct dev_context *devc) { int ret, target; diff --git a/src/hardware/korad-kaxxxxp/protocol.h b/src/hardware/korad-kaxxxxp/protocol.h index 315961b1..005d94ab 100644 --- a/src/hardware/korad-kaxxxxp/protocol.h +++ b/src/hardware/korad-kaxxxxp/protocol.h @@ -31,40 +31,25 @@ #define KAXXXXP_POLL_INTERVAL_MS 80 -enum { - VELLEMAN_PS3005D, - VELLEMAN_LABPS3005D, - KORAD_KA3005P, - KORAD_KA3005P_0X01, - KORAD_KA3005P_0XBC, - KORAD_KA3005P_V42, - KORAD_KA3005P_V55, - KORAD_KD3005P, - KORAD_KD3005P_V20_NOSP, - KORAD_KD3005P_V21_NOSP, - RND_320_KD3005P, - RND_320_KA3005P, - RND_320K30PV, - TENMA_72_2550_V2, - TENMA_72_2540_V20, - TENMA_72_2540_V21, - TENMA_72_2540_V52, - TENMA_72_2535_V21, - TENMA_72_2710_V66, - STAMOS_SLS31_V20, - KORAD_KD6005P, - /* Support for future devices with this protocol. */ +enum korad_quirks_flag { + KORAD_QUIRK_NONE = 0, + KORAD_QUIRK_LABPS_OVP_EN = 1UL << 0, + KORAD_QUIRK_ID_NO_VENDOR = 1UL << 1, + KORAD_QUIRK_ID_TRAILING = 1UL << 2, + KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3, + KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4, + KORAD_QUIRK_ALL = (1UL << 5) - 1, }; /* Information on single model */ struct korad_kaxxxxp_model { - int model_id; /**< Model info */ const char *vendor; /**< Vendor name */ const char *name; /**< Model name */ const char *id; /**< Model ID, as delivered by interface */ int channels; /**< Number of channels */ - double voltage[3]; /**< Min, max, step */ - double current[3]; /**< Min, max, step */ + const double *voltage; /**< References: Min, max, step */ + const double *current; /**< References: Min, max, step */ + enum korad_quirks_flag quirks; }; /* Reply targets */ @@ -86,7 +71,7 @@ struct dev_context { const struct korad_kaxxxxp_model *model; /**< Model information. */ struct sr_sw_limits limits; - int64_t req_sent_at; + int64_t next_req_time; GMutex rw_mutex; float current; /**< Last current value [A] read from device. */ @@ -118,15 +103,15 @@ struct dev_context { }; SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial, - const char *cmd); + const char *cmd); SR_PRIV int korad_kaxxxxp_read_chars(struct sr_serial_dev_inst *serial, - size_t count, char *buf); + size_t count, char *buf); SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial, - int target, struct dev_context *devc); + int target, struct dev_context *devc); SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial, - int target, struct dev_context *devc); + int target, struct dev_context *devc); SR_PRIV int korad_kaxxxxp_get_all_values(struct sr_serial_dev_inst *serial, - struct dev_context *devc); + struct dev_context *devc); SR_PRIV int korad_kaxxxxp_receive_data(int fd, int revents, void *cb_data); #endif diff --git a/src/hardware/lecroy-xstream/protocol.c b/src/hardware/lecroy-xstream/protocol.c index a05ff618..8cc2bbd3 100644 --- a/src/hardware/lecroy-xstream/protocol.c +++ b/src/hardware/lecroy-xstream/protocol.c @@ -527,14 +527,9 @@ SR_PRIV int lecroy_xstream_init_device(struct sr_dev_inst *sdi) ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, channel_enabled, (*scope_models[model_index].analog_names)[i]); - devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); - - devc->analog_groups[i]->name = g_strdup( - (char *)(*scope_models[model_index].analog_names)[i]); + devc->analog_groups[i] = sr_channel_group_new(sdi, + (*scope_models[model_index].analog_names)[i], NULL); devc->analog_groups[i]->channels = g_slist_append(NULL, ch); - - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->analog_groups[i]); } devc->model_config = &scope_models[model_index]; diff --git a/src/hardware/maynuo-m97/api.c b/src/hardware/maynuo-m97/api.c index 54444d62..20a922fa 100644 --- a/src/hardware/maynuo-m97/api.c +++ b/src/hardware/maynuo-m97/api.c @@ -145,9 +145,7 @@ static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus) sdi->driver = &maynuo_m97_driver_info; sdi->inst_type = SR_INST_MODBUS; - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup("1"); - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + cg = sr_channel_group_new(sdi, "1", NULL); ch = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V1"); cg->channels = g_slist_append(cg->channels, ch); diff --git a/src/hardware/microchip-pickit2/api.c b/src/hardware/microchip-pickit2/api.c index 4eb755d2..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,17 +164,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sdi->conn = usb; sdi->connection_id = g_strdup(conn); - /* Create the logic channels group. */ - cg = g_malloc0(sizeof(*cg)); - sdi->channel_groups = g_slist_append(NULL, cg); - cg->name = g_strdup("Logic"); - ch_count = ARRAY_SIZE(channel_names); - for (ch_idx = 0; ch_idx < ch_count; ch_idx++) { - ch = sr_channel_new(sdi, ch_idx, SR_CHANNEL_LOGIC, - TRUE, channel_names[ch_idx]); - cg->channels = g_slist_append(cg->channels, ch); - } - /* * Create the device context. Pre-select the highest * samplerate and the deepest sample count available. @@ -182,6 +177,17 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) devc->num_captureratios = ARRAY_SIZE(captureratios); devc->curr_captureratio_idx = 0; devc->sw_limits.limit_samples = PICKIT2_SAMPLE_COUNT; + devc->channel_names = sr_parse_probe_names(probe_names, + channel_names, ARRAY_SIZE(channel_names), + ARRAY_SIZE(channel_names), &ch_count); + + /* Create the logic channels group. */ + cg = sr_channel_group_new(sdi, "Logic", NULL); + for (ch_idx = 0; ch_idx < ch_count; ch_idx++) { + ch = sr_channel_new(sdi, ch_idx, SR_CHANNEL_LOGIC, + TRUE, devc->channel_names[ch_idx]); + cg->channels = g_slist_append(cg->channels, ch); + } } return std_scan_complete(di, devices); @@ -269,21 +275,27 @@ static int config_get(uint32_t key, GVariant **data, (void)cg; devc = sdi ? sdi->priv : NULL; + usb = sdi ? sdi->conn : NULL; switch (key) { case SR_CONF_CONN: - if (!sdi->conn) + if (!usb) return SR_ERR_ARG; - usb = sdi->conn; *data = g_variant_new_printf("%d.%d", usb->bus, usb->address); return SR_OK; case SR_CONF_SAMPLERATE: + if (!devc) + return SR_ERR_ARG; rate = devc->samplerates[devc->curr_samplerate_idx]; *data = g_variant_new_uint64(rate); return SR_OK; case SR_CONF_LIMIT_SAMPLES: + if (!devc) + return SR_ERR_ARG; return sr_sw_limits_config_get(&devc->sw_limits, key, data); case SR_CONF_CAPTURE_RATIO: + if (!devc) + return SR_ERR_ARG; ratio = devc->captureratios[devc->curr_captureratio_idx]; *data = g_variant_new_uint64(ratio); return SR_OK; 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/motech-lps-30x/api.c b/src/hardware/motech-lps-30x/api.c index 31bbfa43..b29346ca 100644 --- a/src/hardware/motech-lps-30x/api.c +++ b/src/hardware/motech-lps-30x/api.c @@ -438,13 +438,9 @@ static GSList *do_scan(lps_modelid modelid, struct sr_dev_driver *drv, GSList *o devc->channel_status[cnt].info = g_slist_append(NULL, ch); - cg = g_malloc(sizeof(struct sr_channel_group)); snprintf(channel, sizeof(channel), "CG%d", cnt + 1); - cg->name = g_strdup(channel); - cg->priv = NULL; + cg = sr_channel_group_new(sdi, channel, NULL); cg->channels = g_slist_append(NULL, ch); - - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } /* Query status */ diff --git a/src/hardware/openbench-logic-sniffer/api.c b/src/hardware/openbench-logic-sniffer/api.c index 788d4b80..09ef0659 100644 --- a/src/hardware/openbench-logic-sniffer/api.c +++ b/src/hardware/openbench-logic-sniffer/api.c @@ -25,6 +25,7 @@ static const uint32_t scanopts[] = { SR_CONF_CONN, SR_CONF_SERIALCOMM, + SR_CONF_PROBE_NAMES, }; static const uint32_t drvopts[] = { @@ -32,6 +33,7 @@ static const uint32_t drvopts[] = { }; static const uint32_t devopts[] = { + SR_CONF_CONN | SR_CONF_GET, SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, SR_CONF_TRIGGER_MATCH | SR_CONF_LIST, @@ -99,10 +101,13 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) GSList *l; int num_read; unsigned int i; - const char *conn, *serialcomm; + const char *conn, *serialcomm, *probe_names; char buf[4] = { 0, 0, 0, 0 }; + struct dev_context *devc; + size_t ch_max; conn = serialcomm = NULL; + probe_names = NULL; for (l = options; l; l = l->next) { src = l->data; switch (src->key) { @@ -112,6 +117,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) case SR_CONF_SERIALCOMM: serialcomm = g_variant_get_string(src->data, NULL); break; + case SR_CONF_PROBE_NAMES: + probe_names = g_variant_get_string(src->data, NULL); + break; } } if (!conn) @@ -160,37 +168,59 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) sr_hexdump_free(id); return NULL; + } else { + sr_dbg("Successful detection, got '%c%c%c%c' (0x%02x 0x%02x 0x%02x 0x%02x).", + buf[0], buf[1], buf[2], buf[3], + buf[0], buf[1], buf[2], buf[3]); } - /* Definitely using the OLS protocol, check if it supports - * the metadata command. + /* + * Create common data structures (sdi, devc) here in the common + * code path. These further get filled in either from metadata + * which is gathered from the device, or from open coded generic + * fallback data which is kept in the driver source code. + */ + sdi = g_malloc0(sizeof(*sdi)); + sdi->status = SR_ST_INACTIVE; + sdi->inst_type = SR_INST_SERIAL; + sdi->conn = serial; + sdi->connection_id = g_strdup(serial->port); + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; + devc->trigger_at_smpl = OLS_NO_TRIGGER; + devc->channel_names = sr_parse_probe_names(probe_names, + ols_channel_names, ARRAY_SIZE(ols_channel_names), + ARRAY_SIZE(ols_channel_names), &ch_max); + + /* + * Definitely using the OLS protocol, check if it supports + * the metadata command. Otherwise assign generic values. + * Create as many sigrok channels as was determined when + * the device was probed. */ send_shortcommand(serial, CMD_METADATA); - g_usleep(RESPONSE_DELAY_US); - if (serial_has_receive_data(serial) != 0) { /* Got metadata. */ - sdi = get_metadata(serial); + (void)ols_get_metadata(sdi); } else { - /* Not an OLS -- some other board that uses the sump protocol. */ + /* Not an OLS -- some other board using the SUMP protocol. */ sr_info("Device does not support metadata."); - sdi = g_malloc0(sizeof(struct sr_dev_inst)); - sdi->status = SR_ST_INACTIVE; sdi->vendor = g_strdup("Sump"); sdi->model = g_strdup("Logic Analyzer"); sdi->version = g_strdup("v1.0"); - for (i = 0; i < ARRAY_SIZE(ols_channel_names); i++) - sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, - ols_channel_names[i]); - sdi->priv = ols_dev_new(); + devc->max_channels = ch_max; + } + if (devc->max_channels && ch_max > devc->max_channels) + ch_max = devc->max_channels; + for (i = 0; i < ch_max; i++) { + sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, + devc->channel_names[i]); } /* Configure samplerate and divider. */ if (ols_set_samplerate(sdi, DEFAULT_SAMPLERATE) != SR_OK) sr_dbg("Failed to set default samplerate (%" PRIu64 ").", DEFAULT_SAMPLERATE); - sdi->inst_type = SR_INST_SERIAL; - sdi->conn = serial; serial_close(serial); @@ -211,6 +241,11 @@ static int config_get(uint32_t key, GVariant **data, devc = sdi->priv; switch (key) { + case SR_CONF_CONN: + if (!sdi->conn || !sdi->connection_id) + return SR_ERR_NA; + *data = g_variant_new_string(sdi->connection_id); + break; case SR_CONF_SAMPLERATE: *data = g_variant_new_uint64(devc->cur_samplerate); break; diff --git a/src/hardware/openbench-logic-sniffer/protocol.c b/src/hardware/openbench-logic-sniffer/protocol.c index c747753a..125c279d 100644 --- a/src/hardware/openbench-logic-sniffer/protocol.c +++ b/src/hardware/openbench-logic-sniffer/protocol.c @@ -139,28 +139,6 @@ static int convert_trigger(const struct sr_dev_inst *sdi, return SR_OK; } -SR_PRIV struct dev_context *ols_dev_new(void) -{ - struct dev_context *devc; - - devc = g_malloc0(sizeof(struct dev_context)); - devc->trigger_at_smpl = OLS_NO_TRIGGER; - - return devc; -} - -static void ols_channel_new(struct sr_dev_inst *sdi, int num_chan) -{ - struct dev_context *devc = sdi->priv; - int i; - - for (i = 0; i < num_chan; i++) - sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, - ols_channel_names[i]); - - devc->max_channels = num_chan; -} - static void ols_metadata_quirks(struct sr_dev_inst *sdi) { struct dev_context *devc; @@ -175,7 +153,7 @@ static void ols_metadata_quirks(struct sr_dev_inst *sdi) is_shrimp = sdi->model && strcmp(sdi->model, "Shrimp1.0") == 0; if (is_shrimp) { if (!devc->max_channels) - ols_channel_new(sdi, 4); + devc->max_channels = 4; if (!devc->max_samples) devc->max_samples = 256 * 1024; if (!devc->max_samplerate) @@ -186,9 +164,9 @@ static void ols_metadata_quirks(struct sr_dev_inst *sdi) devc->device_flags |= DEVICE_FLAG_IS_DEMON_CORE; } -SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial) +SR_PRIV int ols_get_metadata(struct sr_dev_inst *sdi) { - struct sr_dev_inst *sdi; + struct sr_serial_dev_inst *serial; struct dev_context *devc; uint32_t tmp_int; uint8_t key, type; @@ -196,10 +174,8 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial) GString *tmp_str, *devname, *version; guchar tmp_c; - sdi = g_malloc0(sizeof(struct sr_dev_inst)); - sdi->status = SR_ST_INACTIVE; - devc = ols_dev_new(); - sdi->priv = devc; + serial = sdi->conn; + devc = sdi->priv; devname = g_string_new(""); version = g_string_new(""); @@ -264,7 +240,7 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial) switch (key) { case METADATA_TOKEN_NUM_PROBES_LONG: /* Number of usable channels */ - ols_channel_new(sdi, tmp_int); + devc->max_channels = tmp_int; break; case METADATA_TOKEN_SAMPLE_MEMORY_BYTES: /* Amount of sample memory available (bytes) */ @@ -298,7 +274,7 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial) switch (key) { case METADATA_TOKEN_NUM_PROBES_SHORT: /* Number of usable channels */ - ols_channel_new(sdi, tmp_c); + devc->max_channels = tmp_c; break; case METADATA_TOKEN_PROTOCOL_VERSION_SHORT: /* protocol version */ @@ -316,15 +292,13 @@ SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial) } } - sdi->model = devname->str; - sdi->version = version->str; - g_string_free(devname, FALSE); - g_string_free(version, FALSE); + sdi->model = g_string_free(devname, FALSE); + sdi->version = g_string_free(version, FALSE); /* Optionally amend received metadata, model specific quirks. */ ols_metadata_quirks(sdi); - return sdi; + return SR_OK; } SR_PRIV int ols_set_samplerate(const struct sr_dev_inst *sdi, diff --git a/src/hardware/openbench-logic-sniffer/protocol.h b/src/hardware/openbench-logic-sniffer/protocol.h index 01a38213..00ed35be 100644 --- a/src/hardware/openbench-logic-sniffer/protocol.h +++ b/src/hardware/openbench-logic-sniffer/protocol.h @@ -99,8 +99,10 @@ #define OLS_NO_TRIGGER (-1) struct dev_context { + char **channel_names; + /* constant device properties: */ - int max_channels; + size_t max_channels; uint32_t max_samples; uint32_t max_samplerate; uint32_t protocol_version; @@ -135,8 +137,7 @@ SR_PRIV int send_longcommand(struct sr_serial_dev_inst *serial, uint8_t command, SR_PRIV int ols_send_reset(struct sr_serial_dev_inst *serial); SR_PRIV int ols_prepare_acquisition(const struct sr_dev_inst *sdi); SR_PRIV uint32_t ols_channel_mask(const struct sr_dev_inst *sdi); -SR_PRIV struct dev_context *ols_dev_new(void); -SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial); +SR_PRIV int ols_get_metadata(struct sr_dev_inst *sdi); SR_PRIV int ols_set_samplerate(const struct sr_dev_inst *sdi, uint64_t samplerate); SR_PRIV void abort_acquisition(const struct sr_dev_inst *sdi); 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 9ce2eb66..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. @@ -132,7 +141,7 @@ static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi, modbus = sdi->conn; wrptr = (void *)registers; - write_u16le(wrptr, value); + write_u16be(wrptr, value); g_mutex_lock(&devc->rw_mutex); ret = sr_modbus_write_multiple_registers(modbus, address, @@ -187,8 +196,8 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, if (ret != SR_OK) return ret; rdptr = (void *)registers; - *model = read_u16le_inc(&rdptr); - *version = read_u16le_inc(&rdptr); + *model = read_u16be_inc(&rdptr); + *version = read_u16be_inc(&rdptr); *serno = 0; sr_info("RDTech DPS/DPH model: %u version: %u", *model, *version); @@ -200,7 +209,7 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, if (ret != SR_OK) return ret; rdptr = (void *)registers; - *model = read_u16be_inc(&rdptr) / 10; + *model = read_u16be_inc(&rdptr); *serno = read_u32be_inc(&rdptr); *version = read_u16be_inc(&rdptr); sr_info("RDTech RD model: %u version: %u, serno %u", @@ -213,6 +222,49 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, /* UNREACH */ } +SR_PRIV void rdtech_dps_update_multipliers(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + const struct rdtech_dps_range *range; + + devc = sdi->priv; + range = &devc->model->ranges[devc->curr_range]; + devc->current_multiplier = pow(10.0, range->current_digits); + devc->voltage_multiplier = pow(10.0, range->voltage_digits); +} + +/* + * Determine range of connected device. Don't do anything once + * acquisition has started (since the range will then be tracked). + */ +SR_PRIV int rdtech_dps_update_range(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + uint16_t range; + int ret; + + devc = sdi->priv; + + /* + * Only update range if there are multiple ranges and data + * acquisition hasn't started. + */ + if (devc->model->n_ranges <= 1 || devc->acquisition_started) + return SR_OK; + if (devc->model->model_type != MODEL_RD) + return SR_ERR; + + ret = rdtech_dps_read_holding_registers(sdi->conn, + REG_RD_RANGE, 1, &range); + if (ret != SR_OK) + return ret; + range = range ? 1 : 0; + devc->curr_range = range; + rdtech_dps_update_multipliers(sdi); + + return SR_OK; +} + /* Send a measured value to the session feed. */ static int send_value(const struct sr_dev_inst *sdi, struct sr_channel *ch, float value, @@ -259,13 +311,15 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, struct dev_context *devc; struct sr_modbus_dev_inst *modbus; gboolean get_config, get_init_state, get_curr_meas; - uint16_t registers[12]; + uint16_t registers[14]; int ret; const uint8_t *rdptr; uint16_t uset_raw, iset_raw, uout_raw, iout_raw, power_raw; uint16_t reg_val, reg_state, out_state, ovpset_raw, ocpset_raw; gboolean is_lock, is_out_enabled, is_reg_cc; gboolean uses_ovp, uses_ocp; + gboolean have_range; + uint16_t range; float volt_target, curr_limit; float ovp_threshold, ocp_threshold; float curr_voltage, curr_current, curr_power; @@ -308,6 +362,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, (void)get_init_state; (void)get_curr_meas; + have_range = devc->model->n_ranges > 1; + if (!have_range) + range = 0; + switch (devc->model->model_type) { case MODEL_DPS: /* @@ -320,32 +378,33 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, */ g_mutex_lock(&devc->rw_mutex); ret = rdtech_dps_read_holding_registers(modbus, - REG_DPS_USET, 10, registers); + REG_DPS_USET, REG_DPS_ENABLE - REG_DPS_USET + 1, + registers); g_mutex_unlock(&devc->rw_mutex); if (ret != SR_OK) return ret; /* Interpret the registers' values. */ rdptr = (const void *)registers; - uset_raw = read_u16le_inc(&rdptr); + uset_raw = read_u16be_inc(&rdptr); volt_target = uset_raw / devc->voltage_multiplier; - iset_raw = read_u16le_inc(&rdptr); + iset_raw = read_u16be_inc(&rdptr); curr_limit = iset_raw / devc->current_multiplier; - uout_raw = read_u16le_inc(&rdptr); + uout_raw = read_u16be_inc(&rdptr); curr_voltage = uout_raw / devc->voltage_multiplier; - iout_raw = read_u16le_inc(&rdptr); + iout_raw = read_u16be_inc(&rdptr); curr_current = iout_raw / devc->current_multiplier; - power_raw = read_u16le_inc(&rdptr); + power_raw = read_u16be_inc(&rdptr); curr_power = power_raw / 100.0f; - (void)read_u16le_inc(&rdptr); /* UIN */ - reg_val = read_u16le_inc(&rdptr); /* LOCK */ + (void)read_u16be_inc(&rdptr); /* UIN */ + reg_val = read_u16be_inc(&rdptr); /* LOCK */ is_lock = reg_val != 0; - reg_val = read_u16le_inc(&rdptr); /* PROTECT */ + reg_val = read_u16be_inc(&rdptr); /* PROTECT */ uses_ovp = reg_val == STATE_OVP; uses_ocp = reg_val == STATE_OCP; - reg_state = read_u16le_inc(&rdptr); /* CV_CC */ + reg_state = read_u16be_inc(&rdptr); /* CV_CC */ is_reg_cc = reg_state == MODE_CC; - out_state = read_u16le_inc(&rdptr); /* ENABLE */ + out_state = read_u16be_inc(&rdptr); /* ENABLE */ is_out_enabled = out_state != 0; /* Transfer another chunk of registers in a single call. */ @@ -358,9 +417,9 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, /* Interpret the second registers chunk's values. */ rdptr = (const void *)registers; - ovpset_raw = read_u16le_inc(&rdptr); /* PRE OVPSET */ + ovpset_raw = read_u16be_inc(&rdptr); /* PRE OVPSET */ ovp_threshold = ovpset_raw * devc->voltage_multiplier; - ocpset_raw = read_u16le_inc(&rdptr); /* PRE OCPSET */ + ocpset_raw = read_u16be_inc(&rdptr); /* PRE OCPSET */ ocp_threshold = ocpset_raw * devc->current_multiplier; break; @@ -369,7 +428,11 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, /* Retrieve a set of adjacent registers. */ g_mutex_lock(&devc->rw_mutex); ret = rdtech_dps_read_holding_registers(modbus, - REG_RD_VOLT_TGT, 11, registers); + REG_RD_VOLT_TGT, + devc->model->n_ranges > 1 + ? REG_RD_RANGE - REG_RD_VOLT_TGT + 1 + : REG_RD_ENABLE - REG_RD_VOLT_TGT + 1, + registers); g_mutex_unlock(&devc->rw_mutex); if (ret != SR_OK) return ret; @@ -396,6 +459,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, is_reg_cc = reg_state == MODE_CC; out_state = read_u16be_inc(&rdptr); /* ENABLE */ is_out_enabled = out_state != 0; + if (have_range) { + (void)read_u16be_inc(&rdptr); /* PRESET */ + range = read_u16be_inc(&rdptr) ? 1 : 0; /* RANGE */ + } /* Retrieve a set of adjacent registers. */ g_mutex_lock(&devc->rw_mutex); @@ -456,6 +523,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, state->mask |= STATE_CURRENT; state->power = curr_power; state->mask |= STATE_POWER; + if (have_range) { + state->range = range; + state->mask |= STATE_RANGE; + } return SR_OK; } @@ -575,6 +646,42 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi, return SR_ERR_ARG; } } + if (state->mask & STATE_RANGE) { + reg_value = state->range; + switch (devc->model->model_type) { + case MODEL_DPS: + /* DPS models don't support current ranges at all. */ + if (reg_value > 0) + return SR_ERR_ARG; + break; + case MODEL_RD: + /* + * Reject unsupported range indices. + * Need not set the range when the device only + * supports a single fixed range. + */ + if (reg_value >= devc->model->n_ranges) + return SR_ERR_NA; + if (devc->model->n_ranges <= 1) + return SR_OK; + ret = rdtech_rd_set_reg(sdi, REG_RD_RANGE, reg_value); + if (ret != SR_OK) + return ret; + /* + * Immediately update internal state outside of + * an acquisition. Assume that in-acquisition + * activity will update internal state. This is + * essential for meta package emission. + */ + if (!devc->acquisition_started) { + devc->curr_range = reg_value; + rdtech_dps_update_multipliers(sdi); + } + break; + default: + return SR_ERR_ARG; + } + } return SR_OK; } @@ -602,6 +709,10 @@ SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi) devc->curr_cc_state = state.regulation_cc; if (state.mask & STATE_OUTPUT_ENABLED) devc->curr_out_state = state.output_enabled; + if (state.mask & STATE_RANGE) { + devc->curr_range = state.range; + rdtech_dps_update_multipliers(sdi); + } return SR_OK; } @@ -614,7 +725,7 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) struct rdtech_dps_state state; int ret; struct sr_channel *ch; - const char *regulation_text; + const char *regulation_text, *range_text; (void)fd; (void)revents; @@ -635,11 +746,11 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) ch = g_slist_nth_data(sdi->channels, 0); send_value(sdi, ch, state.voltage, SR_MQ_VOLTAGE, SR_MQFLAG_DC, SR_UNIT_VOLT, - devc->model->voltage_digits); + devc->model->ranges[devc->curr_range].voltage_digits); ch = g_slist_nth_data(sdi->channels, 1); send_value(sdi, ch, state.current, SR_MQ_CURRENT, SR_MQFLAG_DC, SR_UNIT_AMPERE, - devc->model->current_digits); + devc->model->ranges[devc->curr_range].current_digits); ch = g_slist_nth_data(sdi->channels, 2); send_value(sdi, ch, state.power, SR_MQ_POWER, 0, SR_UNIT_WATT, 2); @@ -669,6 +780,13 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) g_variant_new_boolean(state.output_enabled)); devc->curr_out_state = state.output_enabled; } + if (devc->curr_range != state.range) { + range_text = devc->model->ranges[state.range].range_str; + (void)sr_session_send_meta(sdi, SR_CONF_RANGE, + g_variant_new_string(range_text)); + devc->curr_range = state.range; + rdtech_dps_update_multipliers(sdi); + } /* Check optional acquisition limits. */ sr_sw_limits_update_samples_read(&devc->limits, 1); 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-dg/api.c b/src/hardware/rigol-dg/api.c index e8a3aadc..a1732432 100644 --- a/src/hardware/rigol-dg/api.c +++ b/src/hardware/rigol-dg/api.c @@ -380,12 +380,9 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) for (i = 0; i < device->num_channels; i++) { ch = sr_channel_new(sdi, ch_idx++, SR_CHANNEL_ANALOG, TRUE, device->channels[i].name); - cg = g_malloc0(sizeof(*cg)); snprintf(tmp, sizeof(tmp), "%u", i + 1); - cg->name = g_strdup(tmp); + cg = sr_channel_group_new(sdi, tmp, NULL); cg->channels = g_slist_append(cg->channels, ch); - - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } /* Create channels for the frequency counter output. */ diff --git a/src/hardware/rigol-ds/api.c b/src/hardware/rigol-ds/api.c index 632c9596..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}, @@ -395,17 +396,13 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) channel_name = g_strdup_printf("CH%d", i + 1); ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_name); - devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); - - devc->analog_groups[i]->name = channel_name; + devc->analog_groups[i] = sr_channel_group_new(sdi, + channel_name, NULL); devc->analog_groups[i]->channels = g_slist_append(NULL, ch); - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->analog_groups[i]); } if (devc->model->has_digital) { - devc->digital_group = g_malloc0(sizeof(struct sr_channel_group)); - + devc->digital_group = sr_channel_group_new(sdi, "LA", NULL); for (i = 0; i < ARRAY_SIZE(devc->digital_channels); i++) { channel_name = g_strdup_printf("D%d", i); ch = sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name); @@ -413,9 +410,6 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) devc->digital_group->channels = g_slist_append( devc->digital_group->channels, ch); } - devc->digital_group->name = g_strdup("LA"); - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->digital_group); } for (i = 0; i < ARRAY_SIZE(timebases); i++) { diff --git a/src/hardware/scpi-dmm/api.c b/src/hardware/scpi-dmm/api.c index 520dc706..d0a448d7 100644 --- a/src/hardware/scpi-dmm/api.c +++ b/src/hardware/scpi-dmm/api.c @@ -50,6 +50,7 @@ static const uint32_t devopts_generic_range[] = { static const struct scpi_command cmdset_agilent[] = { { DMM_CMD_SETUP_REMOTE, "\n", }, + { DMM_CMD_SETUP_LOCAL, "SYST:LOC", }, { DMM_CMD_SETUP_FUNC, "CONF:%s", }, { DMM_CMD_QUERY_FUNC, "CONF?", }, { DMM_CMD_START_ACQ, "INIT", }, @@ -198,6 +199,20 @@ static const struct mqopt_item mqopts_owon_xdm2041[] = { { SR_MQ_CAPACITANCE, 0, "CAP", "CAP", NO_DFLT_PREC, FLAGS_NONE, }, }; +static const struct mqopt_item mqopts_siglent_sdm3055[] = { + { SR_MQ_VOLTAGE, SR_MQFLAG_DC, "VOLT:DC", "VOLT ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_VOLTAGE, SR_MQFLAG_AC, "VOLT:AC", "VOLT:AC ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CURRENT, SR_MQFLAG_DC, "CURR:DC", "CURR ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CURRENT, SR_MQFLAG_AC, "CURR:AC", "CURR:AC ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_RESISTANCE, 0, "RES", "RES ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, "FRES", "FRES ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, FLAGS_NONE, }, + { SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, FLAGS_NONE, }, + { SR_MQ_FREQUENCY, 0, "FREQ", "FREQ ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_TIME, 0, "PER", "PER ", NO_DFLT_PREC, FLAGS_NONE, }, + { SR_MQ_CAPACITANCE, 0, "CAP", "CAP", NO_DFLT_PREC, FLAGS_NONE, }, +}; + SR_PRIV const struct scpi_dmm_model models[] = { { "Agilent", "34405A", @@ -272,6 +287,14 @@ SR_PRIV const struct scpi_dmm_model models[] = { 0, 0, 10 * 1000, 0, FALSE, scpi_dmm_get_range_text, scpi_dmm_set_range_from_text, NULL, }, + { + "OWON", "XDM1041", + 1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm2041), + scpi_dmm_get_meas_gwinstek, + ARRAY_AND_SIZE(devopts_generic), + 0, 0, 0, 1e9, TRUE, + NULL, NULL, NULL, + }, { "OWON", "XDM2041", 1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm2041), @@ -280,6 +303,14 @@ SR_PRIV const struct scpi_dmm_model models[] = { 0, 0, 0, 1e9, TRUE, NULL, NULL, NULL, }, + { + "Siglent", "SDM3055", + 1, 5, cmdset_hp, ARRAY_AND_SIZE(mqopts_siglent_sdm3055), + scpi_dmm_get_meas_agilent, + ARRAY_AND_SIZE(devopts_generic), + 0, 0, 0, 0, FALSE, + NULL, NULL, NULL, + }, }; static const struct scpi_dmm_model *is_compatible(const char *vendor, const char *model) diff --git a/src/hardware/scpi-dmm/protocol.c b/src/hardware/scpi-dmm/protocol.c index f413fcd7..a13db841 100644 --- a/src/hardware/scpi-dmm/protocol.c +++ b/src/hardware/scpi-dmm/protocol.c @@ -396,7 +396,7 @@ SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch) if (ret != SR_OK) return ret; g_strstrip(response); - use_double = devc->model->digits > 6; + use_double = devc->model->digits >= 6; ret = sr_atod_ascii(response, &info->d_value); if (ret != SR_OK) { g_free(response); diff --git a/src/hardware/scpi-pps/api.c b/src/hardware/scpi-pps/api.c index aa5b945a..116a5d46 100644 --- a/src/hardware/scpi-pps/api.c +++ b/src/hardware/scpi-pps/api.c @@ -145,8 +145,7 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi, for (i = 0; i < num_channel_groups; i++) { cgs = &channel_groups[i]; - cg = g_malloc0(sizeof(struct sr_channel_group)); - cg->name = g_strdup(cgs->name); + cg = sr_channel_group_new(sdi, cgs->name, NULL); for (j = 0, mask = 1; j < 64; j++, mask <<= 1) { if (cgs->channel_index_mask & mask) { for (l = sdi->channels; l; l = l->next) { @@ -167,7 +166,6 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi, pcg = g_malloc0(sizeof(struct pps_channel_group)); pcg->features = cgs->features; cg->priv = pcg; - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } sr_scpi_hw_info_free(hw_info); @@ -439,6 +437,10 @@ static int config_get(uint32_t key, GVariant **data, gvtype = G_VARIANT_TYPE_DOUBLE; cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_THRESHOLD; break; + case SR_CONF_OVER_CURRENT_PROTECTION_DELAY: + gvtype = G_VARIANT_TYPE_DOUBLE; + cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_DELAY; + break; case SR_CONF_OVER_TEMPERATURE_PROTECTION: if (devc->device->dialect == SCPI_DIALECT_HMP) { /* OTP is always enabled. */ @@ -700,6 +702,12 @@ static int config_set(uint32_t key, GVariant *data, channel_group_cmd, channel_group_name, SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD, d); break; + case SR_CONF_OVER_CURRENT_PROTECTION_DELAY: + d = g_variant_get_double(data); + ret = sr_scpi_cmd(sdi, devc->device->commands, + channel_group_cmd, channel_group_name, + SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DELAY, d); + break; case SR_CONF_OVER_TEMPERATURE_PROTECTION: if (g_variant_get_boolean(data)) ret = sr_scpi_cmd(sdi, devc->device->commands, @@ -796,6 +804,9 @@ static int config_list(uint32_t key, GVariant **data, case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD: *data = std_gvar_min_max_step_array(ch_spec->ocp); break; + case SR_CONF_OVER_CURRENT_PROTECTION_DELAY: + *data = std_gvar_min_max_step_array(ch_spec->ocp_delay); + break; default: return SR_ERR_NA; } 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/serial-dmm/protocol.c b/src/hardware/serial-dmm/protocol.c index 483bb009..4cbec267 100644 --- a/src/hardware/serial-dmm/protocol.c +++ b/src/hardware/serial-dmm/protocol.c @@ -199,7 +199,7 @@ static void handle_new_data(struct sr_dev_inst *sdi, void *info) pkt_size = dmm->packet_size; } - /* Process the package. */ + /* Process the packet. */ sr_dbg("Valid packet, size %zu, processing", pkt_size); handle_packet(sdi, check_ptr, pkt_size, info); check_pos += pkt_size; diff --git a/src/hardware/siglent-sds/api.c b/src/hardware/siglent-sds/api.c index 846399b6..76955e6d 100644 --- a/src/hardware/siglent-sds/api.c +++ b/src/hardware/siglent-sds/api.c @@ -305,17 +305,13 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) channel_name = g_strdup_printf("CH%d", i + 1); ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_name); - devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); - - devc->analog_groups[i]->name = channel_name; + devc->analog_groups[i] = sr_channel_group_new(sdi, + channel_name, NULL); devc->analog_groups[i]->channels = g_slist_append(NULL, ch); - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->analog_groups[i]); } if (devc->model->has_digital) { - devc->digital_group = g_malloc0(sizeof(struct sr_channel_group)); - + devc->digital_group = sr_channel_group_new(sdi, "LA", NULL); for (i = 0; i < ARRAY_SIZE(devc->digital_channels); i++) { channel_name = g_strdup_printf("D%d", i); ch = sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name); @@ -323,9 +319,6 @@ static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) devc->digital_group->channels = g_slist_append( devc->digital_group->channels, ch); } - devc->digital_group->name = g_strdup("LA"); - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->digital_group); } for (i = 0; i < ARRAY_SIZE(timebases); i++) { 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/uni-t-dmm/api.c b/src/hardware/uni-t-dmm/api.c index 150be803..972a8e9f 100644 --- a/src/hardware/uni-t-dmm/api.c +++ b/src/hardware/uni-t-dmm/api.c @@ -177,6 +177,33 @@ static int dev_acquisition_stop(struct sr_dev_inst *sdi) }).di SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers, + /* {{{ es519xx */ + DMM( + "tenma-72-7750", es519xx, + /* The baudrate is actually 19230, see "Note 1" below. */ + "Tenma", "72-7750", 19200, + ES519XX_11B_PACKET_SIZE, + sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse, + NULL + ), + DMM( + "uni-t-ut60g", es519xx, + /* The baudrate is actually 19230, see "Note 1" below. */ + "UNI-T", "UT60G", 19200, + ES519XX_11B_PACKET_SIZE, + sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse, + NULL + ), + DMM( + "uni-t-ut61e", es519xx, + /* The baudrate is actually 19230, see "Note 1" below. */ + "UNI-T", "UT61E", 19200, + ES519XX_14B_PACKET_SIZE, + sr_es519xx_19200_14b_packet_valid, sr_es519xx_19200_14b_parse, + NULL + ), + /* }}} */ + /* {{{ fs9721 */ DMM( "tecpel-dmm-8061", fs9721, "Tecpel", "DMM-8061", 2400, @@ -185,11 +212,11 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers, sr_fs9721_00_temp_c ), DMM( - "uni-t-ut372", ut372, - "UNI-T", "UT372", 2400, - UT372_PACKET_SIZE, - sr_ut372_packet_valid, sr_ut372_parse, - NULL + "tenma-72-7745", fs9721, + "Tenma", "72-7745", 2400, + FS9721_PACKET_SIZE, + sr_fs9721_packet_valid, sr_fs9721_parse, + sr_fs9721_00_temp_c ), DMM( "uni-t-ut60a", fs9721, @@ -206,13 +233,21 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers, sr_fs9721_00_temp_c ), DMM( - "uni-t-ut60g", es519xx, - /* The baudrate is actually 19230, see "Note 1" below. */ - "UNI-T", "UT60G", 19200, - ES519XX_11B_PACKET_SIZE, - sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse, + "voltcraft-vc820", fs9721, + "Voltcraft", "VC-820", 2400, + FS9721_PACKET_SIZE, + sr_fs9721_packet_valid, sr_fs9721_parse, NULL ), + DMM( + "voltcraft-vc840", fs9721, + "Voltcraft", "VC-840", 2400, + FS9721_PACKET_SIZE, + sr_fs9721_packet_valid, sr_fs9721_parse, + sr_fs9721_00_temp_c + ), + /* }}} */ + /* {{{ fs9922 */ DMM( "uni-t-ut61b", fs9922, "UNI-T", "UT61B", 2400, @@ -235,13 +270,46 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers, NULL ), DMM( - "uni-t-ut61e", es519xx, - /* The baudrate is actually 19230, see "Note 1" below. */ - "UNI-T", "UT61E", 19200, - ES519XX_14B_PACKET_SIZE, - sr_es519xx_19200_14b_packet_valid, sr_es519xx_19200_14b_parse, + "voltcraft-vc830", fs9922, + /* + * Note: The VC830 doesn't set the 'volt' and 'diode' bits of + * the FS9922 protocol. Instead, it only sets the user-defined + * bit "z1" to indicate "diode mode" and "voltage". + */ + "Voltcraft", "VC-830", 2400, + FS9922_PACKET_SIZE, + sr_fs9922_packet_valid, sr_fs9922_parse, + &sr_fs9922_z1_diode + ), + /* }}} */ + /* {{{ ut372 */ + DMM( + "uni-t-ut372", ut372, + "UNI-T", "UT372", 2400, + UT372_PACKET_SIZE, + sr_ut372_packet_valid, sr_ut372_parse, NULL ), + /* }}} */ + /* {{{ ut71x */ + DMM( + "tenma-72-7730", ut71x, + "Tenma", "72-7730", 2400, + UT71X_PACKET_SIZE, + sr_ut71x_packet_valid, sr_ut71x_parse, NULL + ), + DMM( + "tenma-72-7732", ut71x, + "Tenma", "72-7732", 2400, + UT71X_PACKET_SIZE, + sr_ut71x_packet_valid, sr_ut71x_parse, NULL + ), + DMM( + "tenma-72-9380a", ut71x, + "Tenma", "72-9380A", 2400, + UT71X_PACKET_SIZE, + sr_ut71x_packet_valid, sr_ut71x_parse, NULL + ), DMM( "uni-t-ut71a", ut71x, "UNI-T", "UT71A", 2400, UT71X_PACKET_SIZE, @@ -272,37 +340,6 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers, "UNI-T", "UT804", 2400, UT71X_PACKET_SIZE, sr_ut71x_packet_valid, sr_ut71x_parse, NULL ), - DMM( - "voltcraft-vc820", fs9721, - "Voltcraft", "VC-820", 2400, - FS9721_PACKET_SIZE, - sr_fs9721_packet_valid, sr_fs9721_parse, - NULL - ), - DMM( - "voltcraft-vc830", fs9922, - /* - * Note: The VC830 doesn't set the 'volt' and 'diode' bits of - * the FS9922 protocol. Instead, it only sets the user-defined - * bit "z1" to indicate "diode mode" and "voltage". - */ - "Voltcraft", "VC-830", 2400, - FS9922_PACKET_SIZE, - sr_fs9922_packet_valid, sr_fs9922_parse, - &sr_fs9922_z1_diode - ), - DMM( - "voltcraft-vc840", fs9721, - "Voltcraft", "VC-840", 2400, - FS9721_PACKET_SIZE, - sr_fs9721_packet_valid, sr_fs9721_parse, - sr_fs9721_00_temp_c - ), - DMM( - "voltcraft-vc870", vc870, - "Voltcraft", "VC-870", 9600, VC870_PACKET_SIZE, - sr_vc870_packet_valid, sr_vc870_parse, NULL - ), DMM( "voltcraft-vc920", ut71x, "Voltcraft", "VC-920", 2400, UT71X_PACKET_SIZE, @@ -318,37 +355,12 @@ SR_REGISTER_DEV_DRIVER_LIST(uni_t_dmm_drivers, "Voltcraft", "VC-960", 2400, UT71X_PACKET_SIZE, sr_ut71x_packet_valid, sr_ut71x_parse, NULL ), + /* }}} */ + /* {{{ vc870 */ DMM( - "tenma-72-7730", ut71x, - "Tenma", "72-7730", 2400, - UT71X_PACKET_SIZE, - sr_ut71x_packet_valid, sr_ut71x_parse, NULL - ), - DMM( - "tenma-72-7732", ut71x, - "Tenma", "72-7732", 2400, - UT71X_PACKET_SIZE, - sr_ut71x_packet_valid, sr_ut71x_parse, NULL - ), - DMM( - "tenma-72-9380a", ut71x, - "Tenma", "72-9380A", 2400, - UT71X_PACKET_SIZE, - sr_ut71x_packet_valid, sr_ut71x_parse, NULL - ), - DMM( - "tenma-72-7745", fs9721, - "Tenma", "72-7745", 2400, - FS9721_PACKET_SIZE, - sr_fs9721_packet_valid, sr_fs9721_parse, - sr_fs9721_00_temp_c - ), - DMM( - "tenma-72-7750", es519xx, - /* The baudrate is actually 19230, see "Note 1" below. */ - "Tenma", "72-7750", 19200, - ES519XX_11B_PACKET_SIZE, - sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse, - NULL + "voltcraft-vc870", vc870, + "Voltcraft", "VC-870", 9600, VC870_PACKET_SIZE, + sr_vc870_packet_valid, sr_vc870_parse, NULL ), + /* }}} */ ); 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/yokogawa-dlm/protocol.c b/src/hardware/yokogawa-dlm/protocol.c index d2c51cda..06dae50f 100644 --- a/src/hardware/yokogawa-dlm/protocol.c +++ b/src/hardware/yokogawa-dlm/protocol.c @@ -790,24 +790,17 @@ SR_PRIV int dlm_device_init(struct sr_dev_inst *sdi, int model_index) ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, (*scope_models[model_index].analog_names)[i]); - devc->analog_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); - - devc->analog_groups[i]->name = g_strdup( - (char *)(*scope_models[model_index].analog_names)[i]); + devc->analog_groups[i] = sr_channel_group_new(sdi, + (*scope_models[model_index].analog_names)[i], NULL); devc->analog_groups[i]->channels = g_slist_append(NULL, ch); - - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->analog_groups[i]); } /* Add digital channel groups. */ for (i = 0; i < scope_models[model_index].pods; i++) { - devc->digital_groups[i] = g_malloc0(sizeof(struct sr_channel_group)); + devc->digital_groups[i] = sr_channel_group_new(sdi, NULL, NULL); if (!devc->digital_groups[i]) return SR_ERR_MALLOC; devc->digital_groups[i]->name = g_strdup_printf("POD%d", i); - sdi->channel_groups = g_slist_append(sdi->channel_groups, - devc->digital_groups[i]); } /* Add digital channels. */ 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 2850946f..068a215a 100644 --- a/src/hwdriver.c +++ b/src/hwdriver.c @@ -74,6 +74,8 @@ static struct sr_key_info sr_key_info_config[] = { "Modbus slave address", NULL}, {SR_CONF_FORCE_DETECT, SR_T_STRING, "force_detect", "Forced detection", NULL}, + {SR_CONF_PROBE_NAMES, SR_T_STRING, "probe_names", + "Names of device's probes", NULL}, /* Device (or channel group) configuration */ {SR_CONF_SAMPLERATE, SR_T_UINT64, "samplerate", @@ -212,6 +214,8 @@ static struct sr_key_info sr_key_info_config[] = { "Power Target", NULL}, {SR_CONF_RESISTANCE_TARGET, SR_T_FLOAT, "resistance_target", "Resistance Target", NULL}, + {SR_CONF_OVER_CURRENT_PROTECTION_DELAY, SR_T_FLOAT, "ocp_delay", + "Over-current protection delay", NULL}, /* Special stuff */ {SR_CONF_SESSIONFILE, SR_T_STRING, "sessionfile", @@ -329,6 +333,8 @@ SR_PRIV const GVariantType *sr_variant_type_get(int datatype) switch (datatype) { case SR_T_INT32: return G_VARIANT_TYPE_INT32; + case SR_T_UINT32: + return G_VARIANT_TYPE_UINT32; case SR_T_UINT64: return G_VARIANT_TYPE_UINT64; case SR_T_STRING: 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 b5de532b..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, }; @@ -173,7 +175,7 @@ SR_API const char *const *sr_input_extensions_get( * * @since 0.4.0 */ -SR_API const struct sr_input_module *sr_input_find(char *id) +SR_API const struct sr_input_module *sr_input_find(const char *id) { int i; diff --git a/src/input/logicport.c b/src/input/logicport.c index 354330ab..ea4cb39b 100644 --- a/src/input/logicport.c +++ b/src/input/logicport.c @@ -60,10 +60,6 @@ #include #include "libsigrok-internal.h" -/* TODO: Move these helpers to some library API routine group. */ -struct sr_channel_group *sr_channel_group_new(const char *name, void *priv); -void sr_channel_group_free(struct sr_channel_group *cg); - #define LOG_PREFIX "input/logicport" #define MAX_CHANNELS 34 @@ -155,26 +151,6 @@ static void free_signal_group(struct signal_group_desc *desc) g_free(desc); } -struct sr_channel_group *sr_channel_group_new(const char *name, void *priv) -{ - struct sr_channel_group *cg; - - cg = g_malloc0(sizeof(*cg)); - if (name && *name) - cg->name = g_strdup(name); - cg->priv = priv; - - return cg; -} - -void sr_channel_group_free(struct sr_channel_group *cg) -{ - if (!cg) - return; - g_free(cg->name); - g_slist_free(cg->channels); -} - /* Wrapper for GDestroyNotify compatibility. */ static void sg_free(void *p) { @@ -802,10 +778,9 @@ static int create_channels_groups(struct sr_input *in) sdi = in->sdi; for (l = inc->signal_groups; l; l = l->next) { desc = l->data; - cg = sr_channel_group_new(desc->name, NULL); + cg = sr_channel_group_new(sdi, desc->name, NULL); if (!cg) return SR_ERR_MALLOC; - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); mask = UINT64_C(1); for (idx = 0; idx < inc->channel_count; idx++, mask <<= 1) { if (!(desc->mask & mask)) diff --git a/src/input/protocoldata.c b/src/input/protocoldata.c new file mode 100644 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 41371230..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; @@ -205,15 +200,6 @@ static void free_channel(void *data) g_free(vcd_ch); } -/* TODO Drop the local decl when this has become a common helper. */ -void sr_channel_group_free(struct sr_channel_group *cg); - -/* Wrapper for GDestroyNotify compatibility. */ -static void cg_free(void *p) -{ - sr_channel_group_free(p); -} - /* * Another timestamp delta was observed, update statistics: Update the * sorted list of minimum values, and increment the occurance counter. @@ -466,6 +452,11 @@ static void check_remove_bom(GString *buf) /* * Reads a single VCD section from input file and parses it to name/contents. * e.g. $timescale 1ps $end => "timescale" "1ps" + * + * The section (its content and its opening/closing markers) can span + * multiple text lines. This routine must not modify the caller's input + * buffer. Executes potentially multiple times on the same input data, + * and executes outside of the processing of the file's data section. */ static gboolean parse_section(GString *buf, char **name, char **contents) { @@ -530,142 +521,6 @@ static gboolean parse_section(GString *buf, char **name, char **contents) return status; } -/* - * The glib routine which splits an input text into a list of words also - * "provides empty strings" which application code then needs to remove. - * And copies of the input text get allocated for all words. - * - * The repeated memory allocation is acceptable for small workloads like - * parsing the header sections. But the heavy lifting for sample data is - * done by DIY code to speedup execution. The use of glib routines would - * severely hurt throughput. Allocated memory gets re-used while a strict - * ping-pong pattern is assumed (each text line of input data enters and - * leaves in a strict symmetrical manner, due to the organization of the - * receive() routine and parse calls). - */ - -/* Remove empty parts from an array returned by g_strsplit(). */ -static void remove_empty_parts(gchar **parts) -{ - gchar **src, **dest; - - src = dest = parts; - while (*src) { - if (!**src) { - g_free(*src); - } else { - if (dest != src) - *dest = *src; - dest++; - } - src++; - } - *dest = NULL; -} - -static char **split_text_line(struct context *inc, char *text, size_t *count) -{ - struct split_state *state; - size_t counted, alloced, wanted; - char **words, *p, **new_words; - - state = &inc->split; - - if (count) - *count = 0; - - if (state->in_use) { - sr_dbg("coding error, split() called while \"in use\"."); - return NULL; - } - - /* - * Seed allocation when invoked for the first time. Assume - * simple logic data, start with a few words per line. Will - * automatically adjust with subsequent use. - */ - if (!state->alloced) { - alloced = 20; - words = g_malloc(sizeof(words[0]) * alloced); - if (!words) - return NULL; - state->alloced = alloced; - state->words = words; - } - - /* Start with most recently allocated word list space. */ - alloced = state->alloced; - words = state->words; - counted = 0; - - /* As long as more input text remains ... */ - p = text; - while (*p) { - /* Resize word list if needed. Just double the size. */ - if (counted + 1 >= alloced) { - wanted = 2 * alloced; - new_words = g_realloc(words, sizeof(words[0]) * wanted); - if (!new_words) { - return NULL; - } - words = new_words; - alloced = wanted; - state->words = words; - state->alloced = alloced; - } - - /* Skip leading spaces. */ - while (g_ascii_isspace(*p)) - p++; - if (!*p) - break; - - /* Add found word to word list. */ - words[counted++] = p; - - /* Find end of the word. Terminate loop upon EOS. */ - while (*p && !g_ascii_isspace(*p)) - p++; - if (!*p) - break; - - /* More text follows. Terminate the word. */ - *p++ = '\0'; - } - - /* - * NULL terminate the word list. Provide its length so that - * calling code need not re-iterate the list to get the count. - */ - words[counted] = NULL; - if (count) - *count = counted; - state->in_use = TRUE; - - return words; -} - -static void free_text_split(struct context *inc, char **words) -{ - struct split_state *state; - - state = &inc->split; - - if (words && words != state->words) { - sr_dbg("coding error, free() arg differs from split() result."); - } - - /* "Double free" finally releases the memory. */ - if (!state->in_use) { - g_free(state->words); - state->words = NULL; - state->alloced = 0; - } - - /* Mark as no longer in use. */ - state->in_use = FALSE; -} - static gboolean have_header(GString *buf) { static const char *enddef_txt = "$enddefinitions"; @@ -679,7 +534,14 @@ static gboolean have_header(GString *buf) return FALSE; p += strlen(enddef_txt); - /* Search for end of section (content expected to be empty). */ + /* + * Search for end of section (content expected to be empty). + * Uses DIY logic to scan for the literals' presence including + * empty space between keywords. MUST NOT modify the caller's + * input data, potentially executes several times on the same + * receive buffer, and executes outside of the processing the + * file's data section. + */ p_stop = &buf->str[buf->len]; p_stop -= strlen(end_txt); while (p < p_stop && g_ascii_isspace(*p)) @@ -742,8 +604,7 @@ static int parse_timescale(struct context *inc, char *contents) */ static int parse_scope(struct context *inc, char *contents, gboolean is_up) { - char *sep_pos, *name_pos; - char **parts; + char *sep_pos, *name_pos, *type_pos; size_t length; /* @@ -783,15 +644,17 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up) * was emitted by libsigrok's VCD output module. */ sr_spew("$scope, got: \"%s\"", contents); - parts = g_strsplit_set(contents, " \r\n\t", 0); - remove_empty_parts(parts); - length = g_strv_length(parts); - if (length != 2) { - sr_err("Unsupported 'scope' syntax: %s", contents); - g_strfreev(parts); + type_pos = sr_text_next_word(contents, &contents); + if (!type_pos) { + sr_err("Cannot parse 'scope' directive"); + return SR_ERR_DATA; + } + name_pos = sr_text_next_word(contents, &contents); + if (!name_pos || contents) { + sr_err("Cannot parse 'scope' directive"); return SR_ERR_DATA; } - name_pos = parts[1]; + if (strcmp(name_pos, PACKAGE_NAME) == 0) { sr_info("Skipping scope with application's package name: %s", name_pos); @@ -803,7 +666,6 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up) g_string_append_printf(inc->scope_prefix, "%s%c%c", name_pos, SCOPE_SEP, '\0'); } - g_strfreev(parts); sr_dbg("$scope, prefix now: \"%s\"", inc->scope_prefix->str); return SR_OK; @@ -817,8 +679,6 @@ static int parse_scope(struct context *inc, char *contents, gboolean is_up) */ static int parse_header_var(struct context *inc, char *contents) { - char **parts; - size_t length; char *type, *size_txt, *id, *ref, *idx; gboolean is_reg, is_wire, is_real, is_int; gboolean is_str; @@ -830,22 +690,18 @@ static int parse_header_var(struct context *inc, char *contents) * Format of $var or $reg header specs: * $var type size identifier reference [opt-index] $end */ - parts = g_strsplit_set(contents, " \r\n\t", 0); - remove_empty_parts(parts); - length = g_strv_length(parts); - if (length != 4 && length != 5) { + type = sr_text_next_word(contents, &contents); + size_txt = sr_text_next_word(contents, &contents); + id = sr_text_next_word(contents, &contents); + ref = sr_text_next_word(contents, &contents); + idx = sr_text_next_word(contents, &contents); + if (idx && !*idx) + idx = NULL; + if (!type || !size_txt || !id || !ref || contents) { sr_warn("$var section should have 4 or 5 items"); - g_strfreev(parts); return SR_ERR_DATA; } - type = parts[0]; - size_txt = parts[1]; - id = parts[2]; - ref = parts[3]; - idx = parts[4]; - if (idx && !*idx) - idx = NULL; is_reg = g_strcmp0(type, "reg") == 0; is_wire = g_strcmp0(type, "wire") == 0; is_real = g_strcmp0(type, "real") == 0; @@ -861,11 +717,9 @@ static int parse_header_var(struct context *inc, char *contents) id, ref, idx ? idx : "", type); inc->ignored_signals = g_slist_append(inc->ignored_signals, g_strdup(id)); - g_strfreev(parts); return SR_OK; } else { sr_err("Unsupported signal type: '%s'", type); - g_strfreev(parts); return SR_ERR_DATA; } @@ -891,7 +745,6 @@ static int parse_header_var(struct context *inc, char *contents) } if (!size) { sr_warn("Unsupported signal size: '%s'", size_txt); - g_strfreev(parts); return SR_ERR_DATA; } if (inc->conv_bits.max_bits < size) @@ -902,7 +755,6 @@ static int parse_header_var(struct context *inc, char *contents) ref, idx ? idx : "", inc->options.maxchannels); inc->ignored_signals = g_slist_append(inc->ignored_signals, g_strdup(id)); - g_strfreev(parts); return SR_OK; } @@ -931,7 +783,6 @@ static int parse_header_var(struct context *inc, char *contents) vcd_ch->type == SR_CHANNEL_ANALOG ? "A" : "L", vcd_ch->array_index); inc->channels = g_slist_append(inc->channels, vcd_ch); - g_strfreev(parts); return SR_OK; } @@ -1074,10 +925,8 @@ static void create_channels(const struct sr_input *in, if (vcd_ch->type != ch_type) continue; cg = NULL; - if (vcd_ch->size != 1) { - cg = g_malloc0(sizeof(*cg)); - cg->name = g_strdup(vcd_ch->name); - } + if (vcd_ch->size != 1) + cg = sr_channel_group_new(sdi, vcd_ch->name, NULL); for (size_idx = 0; size_idx < vcd_ch->size; size_idx++) { ch_name = get_channel_name(vcd_ch, size_idx); sr_dbg("sigrok channel idx %zu, name %s, type %s, en %d.", @@ -1089,8 +938,6 @@ static void create_channels(const struct sr_input *in, if (cg) cg->channels = g_slist_append(cg->channels, ch); } - if (cg) - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); } } @@ -1135,7 +982,7 @@ static void keep_header_for_reread(const struct sr_input *in) inc = in->priv; - g_slist_free_full(inc->prev.sr_groups, cg_free); + g_slist_free_full(inc->prev.sr_groups, sr_channel_group_free_cb); inc->prev.sr_groups = in->sdi->channel_groups; in->sdi->channel_groups = NULL; @@ -1174,7 +1021,7 @@ static gboolean check_header_in_reread(const struct sr_input *in) return FALSE; } - g_slist_free_full(in->sdi->channel_groups, cg_free); + g_slist_free_full(in->sdi->channel_groups, sr_channel_group_free_cb); in->sdi->channel_groups = inc->prev.sr_groups; inc->prev.sr_groups = NULL; @@ -1189,7 +1036,7 @@ static gboolean check_header_in_reread(const struct sr_input *in) static int parse_header(const struct sr_input *in, GString *buf) { struct context *inc; - gboolean status; + gboolean enddef_seen, header_valid; char *name, *contents; size_t size; int ret; @@ -1197,34 +1044,35 @@ static int parse_header(const struct sr_input *in, GString *buf) inc = in->priv; /* Parse sections until complete header was seen. */ - status = FALSE; + enddef_seen = FALSE; + header_valid = TRUE; name = contents = NULL; inc->conv_bits.max_bits = 1; while (parse_section(buf, &name, &contents)) { sr_dbg("Section '%s', contents '%s'.", name, contents); if (g_strcmp0(name, "enddefinitions") == 0) { - status = TRUE; + enddef_seen = TRUE; goto done_section; } if (g_strcmp0(name, "timescale") == 0) { if (parse_timescale(inc, contents) != SR_OK) - status = FALSE; + header_valid = FALSE; goto done_section; } if (g_strcmp0(name, "scope") == 0) { if (parse_scope(inc, contents, FALSE) != SR_OK) - status = FALSE; + header_valid = FALSE; goto done_section; } if (g_strcmp0(name, "upscope") == 0) { if (parse_scope(inc, NULL, TRUE) != SR_OK) - status = FALSE; + header_valid = FALSE; goto done_section; } if (g_strcmp0(name, "var") == 0) { if (parse_header_var(inc, contents) != SR_OK) - status = FALSE; + header_valid = FALSE; goto done_section; } @@ -1234,14 +1082,14 @@ done_section: g_free(contents); contents = NULL; - if (status) + if (enddef_seen) break; } g_free(name); g_free(contents); - inc->got_header = status; - if (!status) + inc->got_header = enddef_seen && header_valid; + if (!inc->got_header) return SR_ERR_DATA; /* Create sigrok channels here, late, logic before analog. */ @@ -1304,7 +1152,7 @@ static void add_samples(const struct sr_input *in, size_t count, gboolean flush) inc = in->priv; if (inc->logic_count) { - feed_queue_logic_submit(inc->feed_logic, + feed_queue_logic_submit_one(inc->feed_logic, inc->current_logic, count); if (flush) feed_queue_logic_flush(inc->feed_logic); @@ -1317,7 +1165,7 @@ static void add_samples(const struct sr_input *in, size_t count, gboolean flush) if (!q) continue; value = inc->current_floats[vcd_ch->array_index]; - feed_queue_analog_submit(q, value, count); + feed_queue_analog_submit_one(q, value, count); if (flush) feed_queue_analog_flush(q); } @@ -1609,13 +1457,11 @@ static gboolean vcd_string_valid(const char *s) } /* Parse one text line of the data section. */ -static int parse_textline(const struct sr_input *in, char *lines) +static int parse_textline(const struct sr_input *in, char *line) { struct context *inc; int ret; - char **words; - size_t word_count, word_idx; - char *curr_word, *next_word, curr_first; + char *curr_word, curr_first; gboolean is_timestamp, is_section; gboolean is_real, is_multibit, is_singlebit, is_string; uint64_t timestamp; @@ -1625,30 +1471,33 @@ static int parse_textline(const struct sr_input *in, char *lines) inc = in->priv; /* - * Split the caller's text lines into a list of space separated - * words. Note that some of the branches consume the very next - * words as well, and assume that both adjacent words will be - * available when the first word is seen. This constraint applies - * to bit vector data, multi-bit integers and real (float) data, - * as well as single-bit data with whitespace before its - * identifier (if that's valid in VCD, we'd accept it here). + * Consume space separated words from a caller's text line. Note + * that many words are self contained, but some require another + * word to follow. This implementation assumes that both words + * (when involved) become available in the same invocation, that + * is that both words reside on the same text line of the file. * The fact that callers always pass complete text lines should - * make this assumption acceptable. + * make this assumption acceptable. No generator is known to + * split two corresponding words across text lines. + * + * This constraint applies to bit vector data, multi-bit integer + * and real (float) values, text strings, as well as single-bit + * values with whitespace before their identifiers (if that is + * valid in VCD, we'd accept it here; if generators don't create + * such input, then support for it does not harm). */ ret = SR_OK; - words = split_text_line(inc, lines, &word_count); - for (word_idx = 0; word_idx < word_count; word_idx++) { + while (line) { /* - * Make the next two words available, to simpilify code - * paths below. The second word is optional here. + * Lookup one word here which is mandatory. Locations + * below conditionally lookup another word as needed. */ - curr_word = words[word_idx]; - if (!curr_word && !curr_word[0]) + curr_word = sr_text_next_word(line, &line); + if (!curr_word) + break; + if (!*curr_word) continue; curr_first = g_ascii_tolower(curr_word[0]); - next_word = words[word_idx + 1]; - if (next_word && !next_word[0]) - next_word = NULL; /* * Optionally skip some sections that can be interleaved @@ -1831,8 +1680,7 @@ static int parse_textline(const struct sr_input *in, char *lines) float real_val; real_text = &curr_word[1]; - identifier = next_word; - word_idx++; + identifier = sr_text_next_word(line, &line); if (!*real_text || !identifier || !*identifier) { sr_err("Unexpected real format."); ret = SR_ERR_DATA; @@ -1866,8 +1714,7 @@ static int parse_textline(const struct sr_input *in, char *lines) * we may never unify code paths at all here. */ bits_text = &curr_word[1]; - identifier = next_word; - word_idx++; + identifier = sr_text_next_word(line, &line); if (!*bits_text || !identifier || !*identifier) { sr_err("Unexpected integer/vector format."); @@ -1952,10 +1799,8 @@ static int parse_textline(const struct sr_input *in, char *lines) break; } identifier = ++bits_text; - if (!*identifier) { - identifier = next_word; - word_idx++; - } + if (!*identifier) + identifier = sr_text_next_word(line, &line); if (!identifier || !*identifier) { sr_err("Identifier missing."); ret = SR_ERR_DATA; @@ -1977,8 +1822,7 @@ static int parse_textline(const struct sr_input *in, char *lines) const char *str_value; str_value = &curr_word[1]; - identifier = next_word; - word_idx++; + identifier = sr_text_next_word(line, &line); if (!vcd_string_valid(str_value)) { sr_err("Invalid string data: %s", str_value); ret = SR_ERR_DATA; @@ -2005,7 +1849,6 @@ static int parse_textline(const struct sr_input *in, char *lines) ret = SR_ERR_DATA; break; } - free_text_split(inc, words); return ret; } @@ -2016,11 +1859,14 @@ static int process_buffer(struct sr_input *in, gboolean is_eof) uint64_t samplerate; GVariant *gvar; int ret; - char *rdptr, *endptr, *trimptr; - size_t rdlen; + char *rdptr, *line; + size_t taken, rdlen; inc = in->priv; + if (!inc->got_header) + return SR_ERR_DATA; + /* Send feed header and samplerate (once) before sample data. */ if (!inc->started) { std_session_send_df_header(in->sdi); @@ -2045,28 +1891,19 @@ static int process_buffer(struct sr_input *in, gboolean is_eof) /* Find and process complete text lines in the input data. */ ret = SR_OK; rdptr = in->buf->str; - while (TRUE) { + taken = 0; + while (rdptr) { rdlen = &in->buf->str[in->buf->len] - rdptr; - endptr = g_strstr_len(rdptr, rdlen, "\n"); - if (!endptr) + line = sr_text_next_line(rdptr, rdlen, &rdptr, &taken); + if (!line) break; - trimptr = endptr; - *endptr++ = '\0'; - while (g_ascii_isspace(*rdptr)) - rdptr++; - while (trimptr > rdptr && g_ascii_isspace(trimptr[-1])) - *(--trimptr) = '\0'; - if (!*rdptr) { - rdptr = endptr; + if (!*line) continue; - } - ret = parse_textline(in, rdptr); - rdptr = endptr; + ret = parse_textline(in, line); if (ret != SR_OK) break; } - rdlen = rdptr - in->buf->str; - g_string_erase(in->buf, 0, rdlen); + g_string_erase(in->buf, 0, taken); return ret; } @@ -2181,11 +2018,14 @@ static int end(struct sr_input *in) ret = SR_OK; /* Flush most recently queued sample data when EOF is seen. */ - count = inc->data_after_timestamp ? 1 : 0; - add_samples(in, count, TRUE); + if (inc->got_header && ret == SR_OK) { + count = inc->data_after_timestamp ? 1 : 0; + add_samples(in, count, TRUE); + } /* Optionally suggest downsampling after all input data was seen. */ - (void)ts_stats_post(inc, !inc->data_after_timestamp); + if (inc->got_header) + (void)ts_stats_post(inc, !inc->data_after_timestamp); /* Must send DF_END when DF_HEADER was sent before. */ if (inc->started) @@ -2216,7 +2056,6 @@ static void cleanup(struct sr_input *in) inc->scope_prefix = NULL; g_slist_free_full(inc->ignored_signals, g_free); inc->ignored_signals = NULL; - free_text_split(inc, NULL); } static int reset(struct sr_input *in) diff --git a/src/input/wav.c b/src/input/wav.c index a6369f4d..0675ec8f 100644 --- a/src/input/wav.c +++ b/src/input/wav.c @@ -51,7 +51,7 @@ struct context { int num_channels; int unitsize; gboolean found_data; - gboolean create_channels; + GSList *prev_sr_channels; }; static int parse_wav_header(GString *buf, struct context *inc) @@ -151,15 +151,10 @@ static int format_match(GHashTable *metadata, unsigned int *confidence) static int init(struct sr_input *in, GHashTable *options) { - struct context *inc; - (void)options; in->sdi = g_malloc0(sizeof(struct sr_dev_inst)); in->priv = g_malloc0(sizeof(struct context)); - inc = in->priv; - - inc->create_channels = TRUE; return SR_OK; } @@ -306,6 +301,44 @@ static int process_buffer(struct sr_input *in) return SR_OK; } +/* + * Check the channel list for consistency across file re-import. See + * the VCD input module for more details and motivation. + */ + +static void keep_header_for_reread(const struct sr_input *in) +{ + struct context *inc; + + inc = in->priv; + g_slist_free_full(inc->prev_sr_channels, sr_channel_free_cb); + inc->prev_sr_channels = in->sdi->channels; + in->sdi->channels = NULL; +} + +static int check_header_in_reread(const struct sr_input *in) +{ + struct context *inc; + + if (!in) + return FALSE; + inc = in->priv; + if (!inc) + return FALSE; + if (!inc->prev_sr_channels) + return TRUE; + + if (sr_channel_lists_differ(inc->prev_sr_channels, in->sdi->channels)) { + sr_err("Channel list change not supported for file re-read."); + return FALSE; + } + g_slist_free_full(in->sdi->channels, sr_channel_free_cb); + in->sdi->channels = inc->prev_sr_channels; + inc->prev_sr_channels = NULL; + + return TRUE; +} + static int receive(struct sr_input *in, GString *buf) { struct context *inc; @@ -330,14 +363,12 @@ static int receive(struct sr_input *in, GString *buf) else if (ret != SR_OK) return ret; - if (inc->create_channels) { - for (int i = 0; i < inc->num_channels; i++) { - snprintf(channelname, sizeof(channelname), "CH%d", i + 1); - sr_channel_new(in->sdi, i, SR_CHANNEL_ANALOG, TRUE, channelname); - } + for (int i = 0; i < inc->num_channels; i++) { + snprintf(channelname, sizeof(channelname), "CH%d", i + 1); + sr_channel_new(in->sdi, i, SR_CHANNEL_ANALOG, TRUE, channelname); } - - inc->create_channels = FALSE; + if (!check_header_in_reread(in)) + return SR_ERR_DATA; /* sdi is ready, notify frontend. */ in->sdi_ready = TRUE; @@ -368,12 +399,18 @@ static int end(struct sr_input *in) static int reset(struct sr_input *in) { - memset(in->priv, 0, sizeof(struct context)); + struct context *inc; + + inc = in->priv; + memset(inc, 0, sizeof(*inc)); /* - * We only want to create the sigrok channels once, so - * inc->create_channels won't be set to TRUE this time around. + * Create, and re-create channels for every iteration of file + * import. Other logic will enforce a consistent set of channels + * across re-import, or an appropriate error message when file + * properties should change. */ + keep_header_for_reread(in); g_string_truncate(in->buf, 0); diff --git a/src/libsigrok-internal.h b/src/libsigrok-internal.h index a2350e02..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 @@ -450,6 +467,19 @@ static inline void write_u16le(uint8_t *p, uint16_t x) } #define WL16(p, x) write_u16le((uint8_t *)(p), (uint16_t)(x)) +/** + * Write a 24 bits unsigned integer to memory stored as little endian. + * @param p a pointer to the output memory + * @param x the input unsigned integer + */ +static inline void write_u24le(uint8_t *p, uint32_t x) +{ + p[0] = x & 0xff; x >>= 8; + p[1] = x & 0xff; x >>= 8; + p[2] = x & 0xff; x >>= 8; +} +#define WL24(p, x) write_u24le((uint8_t *)(p), (uint32_t)(x)) + /** * Write a 32 bits unsigned integer to memory stored as big endian. * @param p a pointer to the output memory @@ -478,6 +508,21 @@ static inline void write_u32le(uint8_t *p, uint32_t x) } #define WL32(p, x) write_u32le((uint8_t *)(p), (uint32_t)(x)) +/** + * Write a 40 bits unsigned integer to memory stored as little endian. + * @param p a pointer to the output memory + * @param x the input unsigned integer + */ +static inline void write_u40le(uint8_t *p, uint64_t x) +{ + p[0] = x & 0xff; x >>= 8; + p[1] = x & 0xff; x >>= 8; + p[2] = x & 0xff; x >>= 8; + p[3] = x & 0xff; x >>= 8; + p[4] = x & 0xff; x >>= 8; +} +#define WL40(p, x) write_u40le((uint8_t *)(p), (uint64_t)(x)) + /** * Write a 48 bits unsigned integer to memory stored as little endian. * @param p a pointer to the output memory @@ -587,6 +632,30 @@ static inline uint8_t read_u8_inc(const uint8_t **p) return v; } +/** + * Read unsigned 8bit integer, check length, increment read position. + * @param[in, out] p Pointer into byte stream. + * @param[in, out] l Remaining input payload length. + * @return Retrieved integer value, unsigned. + */ +static inline uint8_t read_u8_inc_len(const uint8_t **p, size_t *l) +{ + uint8_t v; + + if (!p || !*p) + return 0; + if (l && *l < sizeof(v)) { + *l = 0; + return 0; + } + v = read_u8(*p); + *p += sizeof(v); + if (l) + *l -= sizeof(v); + + return v; +} + /** * Read signed 8bit integer from raw memory, increment read position. * @param[in, out] p Pointer into byte stream. @@ -638,6 +707,30 @@ static inline uint16_t read_u16le_inc(const uint8_t **p) return v; } +/** + * Read unsigned 16bit integer (LE format), check length, increment position. + * @param[in, out] p Pointer into byte stream. + * @param[in, out] l Remaining input payload length. + * @return Retrieved integer value, unsigned. + */ +static inline uint16_t read_u16le_inc_len(const uint8_t **p, size_t *l) +{ + uint16_t v; + + if (!p || !*p) + return 0; + if (l && *l < sizeof(v)) { + *l = 0; + return 0; + } + v = read_u16le(*p); + *p += sizeof(v); + if (l) + *l -= sizeof(v); + + return v; +} + /** * Read signed 16bit integer from raw memory (big endian format), increment read position. * @param[in, out] p Pointer into byte stream. @@ -898,6 +991,19 @@ static inline void write_u16le_inc(uint8_t **p, uint16_t x) *p += sizeof(x); } +/** + * Write unsigned 24bit liggle endian integer to raw memory, increment write position. + * @param[in, out] p Pointer into byte stream. + * @param[in] x Value to write. + */ +static inline void write_u24le_inc(uint8_t **p, uint32_t x) +{ + if (!p || !*p) + return; + write_u24le(*p, x); + *p += 3 * sizeof(uint8_t); +} + /** * Write unsigned 32bit big endian integer to raw memory, increment write position. * @param[in, out] p Pointer into byte stream. @@ -924,6 +1030,19 @@ static inline void write_u32le_inc(uint8_t **p, uint32_t x) *p += sizeof(x); } +/** + * Write unsigned 40bit little endian integer to raw memory, increment write position. + * @param[in, out] p Pointer into byte stream. + * @param[in] x Value to write. + */ +static inline void write_u40le_inc(uint8_t **p, uint64_t x) +{ + if (!p || !*p) + return; + write_u40le(*p, x); + *p += 5 * sizeof(uint8_t); +} + /** * Write unsigned 48bit little endian integer to raw memory, increment write position. * @param[in, out] p Pointer into byte stream. @@ -1469,6 +1588,13 @@ struct sr_usb_dev_inst { }; #endif +/** Raw TCP device instance. */ +struct sr_tcp_dev_inst { + char *host_addr; /**!< IP address or host name */ + char *tcp_port; /**!< TCP port number/name */ + int sock_fd; /**!< TCP socket's file descriptor */ +}; + struct sr_serial_dev_inst; #ifdef HAVE_SERIAL_COMM struct ser_lib_functions; @@ -1518,6 +1644,9 @@ struct sr_serial_dev_inst { SER_BT_CONN_BLE122, /**!< BLE, BLE122 module, indications */ SER_BT_CONN_NRF51, /**!< BLE, Nordic nRF51, notifications */ SER_BT_CONN_CC254x, /**!< BLE, TI CC254x, notifications */ + SER_BT_CONN_AC6328, /**!< BLE, JL AC6328B, notifications */ + SER_BT_CONN_DIALOG, /**!< BLE, dialog DA14580, notifications */ + SER_BT_CONN_NOTIFY, /**!< BLE, generic notifications */ SER_BT_CONN_MAX, /**!< sentinel */ } bt_conn_type; char *bt_addr_local; @@ -1527,9 +1656,11 @@ struct sr_serial_dev_inst { uint16_t bt_notify_handle_write; uint16_t bt_notify_handle_cccd; uint16_t bt_notify_value_cccd; + uint16_t bt_ble_mtu; struct sr_bt_desc *bt_desc; GSList *bt_source_args; #endif + struct sr_tcp_dev_inst *tcp_dev; }; #endif @@ -1547,17 +1678,20 @@ struct drv_context { /*--- log.c -----------------------------------------------------------------*/ +/* Provide a macro for other source code locations to re-use. */ #if defined(_WIN32) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) /* * On MinGW, we need to specify the gnu_printf format flavor or GCC * will assume non-standard Microsoft printf syntax. */ -SR_PRIV int sr_log(int loglevel, const char *format, ...) - __attribute__((__format__ (__gnu_printf__, 2, 3))); +#define ATTR_FMT_PRINTF(fmt_pos, arg_pos) \ + __attribute__((__format__ (__gnu_printf__, fmt_pos, arg_pos))) #else -SR_PRIV int sr_log(int loglevel, const char *format, ...) G_GNUC_PRINTF(2, 3); +#define ATTR_FMT_PRINTF(fmt_pos, arg_pos) G_GNUC_PRINTF(fmt_pos, arg_pos) #endif +SR_PRIV int sr_log(int loglevel, const char *format, ...) ATTR_FMT_PRINTF(2, 3); + /* Message logging helpers with subsystem-specific prefix string. */ #define sr_spew(...) sr_log(SR_LOG_SPEW, LOG_PREFIX ": " __VA_ARGS__) #define sr_dbg(...) sr_log(SR_LOG_DBG, LOG_PREFIX ": " __VA_ARGS__) @@ -1591,6 +1725,11 @@ SR_PRIV struct sr_channel *sr_next_enabled_channel(const struct sr_dev_inst *sdi SR_PRIV gboolean sr_channels_differ(struct sr_channel *ch1, struct sr_channel *ch2); SR_PRIV gboolean sr_channel_lists_differ(GSList *l1, GSList *l2); +SR_PRIV struct sr_channel_group *sr_channel_group_new(struct sr_dev_inst *sdi, + const char *name, void *priv); +SR_PRIV void sr_channel_group_free(struct sr_channel_group *cg); +SR_PRIV void sr_channel_group_free_cb(void *cg); + /** Device instance data */ struct sr_dev_inst { /** Device driver. */ @@ -1629,6 +1768,7 @@ SR_PRIV void sr_dev_inst_free(struct sr_dev_inst *sdi); SR_PRIV struct sr_usb_dev_inst *sr_usb_dev_inst_new(uint8_t bus, uint8_t address, struct libusb_device_handle *hdl); SR_PRIV void sr_usb_dev_inst_free(struct sr_usb_dev_inst *usb); +SR_PRIV void sr_usb_dev_inst_free_cb(gpointer p); /* Glib wrapper. */ #endif #ifdef HAVE_SERIAL_COMM @@ -1983,6 +2123,8 @@ SR_PRIV int ser_name_is_hid(struct sr_serial_dev_inst *serial); extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_hid; SR_PRIV int ser_name_is_bt(struct sr_serial_dev_inst *serial); extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_bt; +SR_PRIV int ser_name_is_tcpraw(struct sr_serial_dev_inst *serial); +extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_tcpraw; #ifdef HAVE_LIBHIDAPI struct vid_pid_item { @@ -2036,7 +2178,8 @@ SR_PRIV int sr_bt_config_addr_remote(struct sr_bt_desc *desc, const char *addr); SR_PRIV int sr_bt_config_rfcomm(struct sr_bt_desc *desc, size_t channel); SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc, uint16_t read_handle, uint16_t write_handle, - uint16_t cccd_handle, uint16_t cccd_value); + uint16_t cccd_handle, uint16_t cccd_value, + uint16_t ble_mtu); SR_PRIV int sr_bt_scan_le(struct sr_bt_desc *desc, int duration); SR_PRIV int sr_bt_scan_bt(struct sr_bt_desc *desc, int duration); @@ -2066,6 +2209,8 @@ SR_PRIV int ezusb_upload_firmware(struct sr_context *ctx, libusb_device *dev, /*--- usb.c -----------------------------------------------------------------*/ +SR_PRIV int sr_usb_split_conn(const char *conn, + uint16_t *vid, uint16_t *pid, uint8_t *bus, uint8_t *addr); #ifdef HAVE_LIBUSB_1_0 SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn); SR_PRIV int sr_usb_open(libusb_context *usb_ctx, struct sr_usb_dev_inst *usb); @@ -2078,6 +2223,27 @@ SR_PRIV gboolean usb_match_manuf_prod(libusb_device *dev, const char *manufacturer, const char *product); #endif +/*--- tcp.c -----------------------------------------------------------------*/ + +SR_PRIV gboolean sr_fd_is_readable(int fd); + +SR_PRIV struct sr_tcp_dev_inst *sr_tcp_dev_inst_new( + const char *host_addr, const char *tcp_port); +SR_PRIV void sr_tcp_dev_inst_free(struct sr_tcp_dev_inst *tcp); +SR_PRIV int sr_tcp_get_port_path(struct sr_tcp_dev_inst *tcp, + const char *prefix, char separator, char *path, size_t path_len); +SR_PRIV int sr_tcp_connect(struct sr_tcp_dev_inst *tcp); +SR_PRIV int sr_tcp_disconnect(struct sr_tcp_dev_inst *tcp); +SR_PRIV int sr_tcp_write_bytes(struct sr_tcp_dev_inst *tcp, + const uint8_t *data, size_t dlen); +SR_PRIV int sr_tcp_read_bytes(struct sr_tcp_dev_inst *tcp, + uint8_t *data, size_t dlen, gboolean nonblocking); +SR_PRIV int sr_tcp_source_add(struct sr_session *session, + struct sr_tcp_dev_inst *tcp, int events, int timeout, + sr_receive_data_callback cb, void *cb_data); +SR_PRIV int sr_tcp_source_remove(struct sr_session *session, + struct sr_tcp_dev_inst *tcp); + /*--- binary_helpers.c ------------------------------------------------------*/ /** Binary value type */ @@ -2085,68 +2251,47 @@ enum binary_value_type { BVT_INVALID, BVT_UINT8, - BVT_BE_UINT8 = BVT_UINT8, - BVT_LE_UINT8 = BVT_UINT8, BVT_BE_UINT16, + BVT_BE_UINT24, BVT_BE_UINT32, - BVT_BE_UINT64, - BVT_BE_FLOAT, BVT_LE_UINT16, + BVT_LE_UINT24, BVT_LE_UINT32, - BVT_LE_UINT64, - BVT_LE_FLOAT, }; /** Binary value specification */ struct binary_value_spec { - /** Offset into binary blob */ - size_t offset; - /** Data type to decode */ - enum binary_value_type type; - /** Scale factor to get native units */ - float scale; -}; - -/** Binary channel definition */ -struct binary_analog_channel { - /** Channel name */ - const char *name; - /** Binary value in data stream */ - struct binary_value_spec spec; - /** Significant digits */ - int digits; - /** Measured quantity */ - enum sr_mq mq; - /** Measured unit */ - enum sr_unit unit; + size_t offset; /**!< Offset into binary image */ + enum binary_value_type type; /**!< Data type to decode */ }; /** - * Read extract a value from a binary blob. + * Read extract a value from a binary data image, ensuring no out-of-bounds + * read happens. + * + * @param[out] out Pointer to output buffer (conversion result) + * @param[in] spec Binary value specification + * @param[in] data Pointer to binary input data + * @param[in] length Size of binary input data * - * @param out Pointer to output buffer. - * @param spec Binary value specification - * @param data Pointer to binary blob - * @param length Size of binary blob * @return SR_OK on success, SR_ERR_* error code on failure. */ -SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, const void *data, size_t length); +SR_PRIV int bv_get_value_len(float *out, const struct binary_value_spec *spec, + const uint8_t *data, size_t length); /** - * Send an analog channel packet based on a binary analog channel - * specification. + * Read extract a value from a binary data image, without bound check. + * + * @param[out] out Pointer to output buffer (conversion result) + * @param[in] spec Binary value specification + * @param[in] data Pointer to binary input data * - * @param sdi Device instance - * @param ch Sigrok channel - * @param spec Channel specification - * @param data Pointer to binary blob - * @param length Size of binary blob * @return SR_OK on success, SR_ERR_* error code on failure. */ -SR_PRIV int bv_send_analog_channel(const struct sr_dev_inst *sdi, struct sr_channel *ch, - const struct binary_analog_channel *spec, const void *data, size_t length); +SR_PRIV int bv_get_value(float *out, const struct binary_value_spec *spec, + const uint8_t *data); /*--- crc.c -----------------------------------------------------------------*/ @@ -2709,8 +2854,10 @@ struct feed_queue_analog; SR_API struct feed_queue_logic *feed_queue_logic_alloc( const struct sr_dev_inst *sdi, size_t sample_count, size_t unit_size); -SR_API int feed_queue_logic_submit(struct feed_queue_logic *q, - const uint8_t *data, size_t count); +SR_API int feed_queue_logic_submit_one(struct feed_queue_logic *q, + const uint8_t *data, size_t repeat_count); +SR_API int feed_queue_logic_submit_many(struct feed_queue_logic *q, + const uint8_t *data, size_t samples_count); SR_API int feed_queue_logic_flush(struct feed_queue_logic *q); SR_API int feed_queue_logic_send_trigger(struct feed_queue_logic *q); SR_API void feed_queue_logic_free(struct feed_queue_logic *q); @@ -2718,8 +2865,12 @@ SR_API void feed_queue_logic_free(struct feed_queue_logic *q); SR_API struct feed_queue_analog *feed_queue_analog_alloc( const struct sr_dev_inst *sdi, size_t sample_count, int digits, struct sr_channel *ch); -SR_API int feed_queue_analog_submit(struct feed_queue_analog *q, - float data, size_t count); +SR_API int feed_queue_analog_mq_unit(struct feed_queue_analog *q, + enum sr_mq mq, enum sr_mqflag mq_flag, enum sr_unit unit); +SR_API int feed_queue_analog_scale_offset(struct feed_queue_analog *q, + const struct sr_rational *scale, const struct sr_rational *offset); +SR_API int feed_queue_analog_submit_one(struct feed_queue_analog *q, + float data, size_t repeat_count); SR_API int feed_queue_analog_flush(struct feed_queue_analog *q); SR_API void feed_queue_analog_free(struct feed_queue_analog *q); 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/resource.c b/src/resource.c index a30b4b3f..3164b943 100644 --- a/src/resource.c +++ b/src/resource.c @@ -57,6 +57,19 @@ SR_API GSList *sr_resourcepaths_get(int res_type) env = g_getenv("SIGROK_FIRMWARE_DIR"); if (env) l = g_slist_append(l, g_strdup(env)); + + env = g_getenv("SIGROK_FIRMWARE_PATH"); + if (env) { + char **dir_list, **dir_iter, *dir_item; + dir_list = g_strsplit(env, G_SEARCHPATH_SEPARATOR_S, 0); + for (dir_iter = dir_list; *dir_iter; dir_iter++) { + dir_item = *dir_iter; + if (!dir_item || !*dir_item) + continue; + l = g_slist_append(l, g_strdup(dir_item)); + } + g_strfreev(dir_list); + } } l = g_slist_append(l, g_build_filename(g_get_user_data_dir(), subdir, NULL)); diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 0e74b329..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" }, }; /** @@ -1037,7 +1038,29 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, buf[0] = response->str[1]; buf[1] = '\0'; ret = sr_atol(buf, &llen); - if ((ret != SR_OK) || (llen == 0)) { + /* + * The form "#0..." is legal, and does not mean "empty response", + * but means that the number of data bytes is not known (or was + * not communicated) at this time. Instead the block ends at an + * "END MESSAGE" termination sequence. Which translates to active + * EOI while a text line termination is sent (CR or LF, and this + * text line termination is not part of the block's data value). + * Since this kind of #0... response is considered rare, and + * depends on specific support in physical transports underneath + * the SCPI layer, let's flag the condition and bail out with an + * error here, until it's found to be a genuine issue in the field. + * + * The SCPI 1999.0 specification (see page 220 and following in + * the "HCOPy" description) references IEEE 488.2, especially + * section 8.7.9 for DEFINITE LENGTH and section 8.7.10 for + * INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE DATA. The latter + * with a leading "#0" length and a trailing "NL^END" marker. + */ + if (ret == SR_OK && !llen) { + sr_err("unsupported INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE"); + ret = SR_ERR_NA; + } + if (ret != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); return ret; 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 9d7cfb2b..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; - char **fields, *field; + int ret_parse, ret; + size_t fields_count, field_idx; + char *endp; + unsigned long parm_val; if (conn_type) *conn_type = SER_BT_CONN_UNKNOWN; @@ -160,9 +219,8 @@ static int ser_bt_parse_conn_spec( *cccd_hdl = 0; if (cccd_val) *cccd_val = 0; - - type = SER_BT_CONN_UNKNOWN; - addr = NULL; + if (ble_mtu) + *ble_mtu = 0; if (!serial || !spec || !spec[0]) return SR_ERR_ARG; @@ -240,14 +298,126 @@ static int ser_bt_parse_conn_spec( if (cccd_val) *cccd_val = 0x0001; break; + case SER_BT_CONN_AC6328: + if (read_hdl) + *read_hdl = 12; + if (write_hdl) + *write_hdl = 15; + if (cccd_hdl) + *cccd_hdl = 13; + if (cccd_val) + *cccd_val = 0x0001; + break; + case SER_BT_CONN_DIALOG: + if (read_hdl) + *read_hdl = 23; + if (write_hdl) + *write_hdl = 18; + if (cccd_hdl) + *cccd_hdl = 0; + if (cccd_val) + *cccd_val = 0x0001; + if (ble_mtu) + *ble_mtu = 400; + break; + case SER_BT_CONN_NOTIFY: + /* All other values must be provided externally. */ + if (cccd_val) + *cccd_val = 0x0001; + break; default: return SR_ERR_ARG; } - /* TODO Evaluate optionally trailing fields, override defaults? */ + /* + * Preset a successful return value for the conn= parse call. + * Scan optional additional fields which specify more params. + * Update the defaults which were setup above. Pessimize the + * routine's return value in error paths. + */ + ret_parse = SR_OK; + fields_count = g_strv_length(fields); + for (field_idx = 3; field_idx < fields_count; field_idx++) { + field = fields[field_idx]; + if (!field || !*field) + continue; + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_CHANNEL)) { + field += strlen(SER_BT_PARAM_PREFIX_CHANNEL); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (rfcomm_channel) + *rfcomm_channel = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_RX)) { + field += strlen(SER_BT_PARAM_PREFIX_HDL_RX); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (read_hdl) + *read_hdl = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_TX)) { + field += strlen(SER_BT_PARAM_PREFIX_HDL_TX); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (write_hdl) + *write_hdl = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_HDL_CCCD)) { + field += strlen(SER_BT_PARAM_PREFIX_HDL_CCCD); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (cccd_hdl) + *cccd_hdl = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_VAL_CCCD)) { + field += strlen(SER_BT_PARAM_PREFIX_VAL_CCCD); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (cccd_val) + *cccd_val = parm_val; + continue; + } + if (g_str_has_prefix(field, SER_BT_PARAM_PREFIX_BLE_MTU)) { + field += strlen(SER_BT_PARAM_PREFIX_BLE_MTU); + endp = NULL; + ret = sr_atoul_base(field, &parm_val, &endp, 0); + if (ret != SR_OK || !endp || *endp != '\0') { + ret_parse = SR_ERR_ARG; + break; + } + if (ble_mtu) + *ble_mtu = parm_val; + continue; + } + return SR_ERR_DATA; + } g_strfreev(fields); - return SR_OK; + return ret_parse; } static void ser_bt_mask_databits(struct sr_serial_dev_inst *serial, @@ -274,6 +444,11 @@ static int ser_bt_data_cb(void *cb_data, uint8_t *data, size_t dlen) if (!serial) return -1; + if (!data && dlen) + return -1; + if (!data || !dlen) + return 0; + ser_bt_mask_databits(serial, data, dlen); sr_ser_queue_rx_data(serial, data, dlen); @@ -312,6 +487,7 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) const char *remote_addr; size_t rfcomm_channel; uint16_t read_hdl, write_hdl, cccd_hdl, cccd_val; + uint16_t ble_mtu; int rc; struct sr_bt_desc *desc; @@ -322,7 +498,8 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) &conn_type, &remote_addr, &rfcomm_channel, &read_hdl, &write_hdl, - &cccd_hdl, &cccd_val); + &cccd_hdl, &cccd_val, + &ble_mtu); if (rc != SR_OK) return SR_ERR_ARG; @@ -350,14 +527,19 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: rc = sr_bt_config_notify(desc, - read_hdl, write_hdl, cccd_hdl, cccd_val); + read_hdl, write_hdl, cccd_hdl, cccd_val, + ble_mtu); if (rc < 0) return SR_ERR; serial->bt_notify_handle_read = read_hdl; serial->bt_notify_handle_write = write_hdl; serial->bt_notify_handle_cccd = cccd_hdl; serial->bt_notify_value_cccd = cccd_val; + serial->bt_ble_mtu = ble_mtu; break; default: /* Unsupported type, or incomplete implementation. */ @@ -382,6 +564,9 @@ static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags) case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: rc = sr_bt_connect_ble(desc); if (rc < 0) return SR_ERR; @@ -458,6 +643,9 @@ static int ser_bt_write(struct sr_serial_dev_inst *serial, case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: /* * Assume that when applications call the serial layer's * write routine, then the BLE chip/module does support @@ -498,7 +686,6 @@ static int ser_bt_read(struct sr_serial_dev_inst *serial, * where to stop reception. */ deadline_us = 0; - now_us = 0; /* Silence a (false) compiler warning. */ if (timeout_ms) { now_us = g_get_monotonic_time(); deadline_us = now_us + timeout_ms * 1000; @@ -524,6 +711,9 @@ static int ser_bt_read(struct sr_serial_dev_inst *serial, case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: dlen = sr_ser_has_queued_data(serial); rc = sr_bt_check_notify(serial->bt_desc); if (rc < 0) @@ -621,6 +811,9 @@ static int bt_source_cb(int fd, int revents, void *cb_data) case SER_BT_CONN_BLE122: case SER_BT_CONN_NRF51: case SER_BT_CONN_CC254x: + case SER_BT_CONN_AC6328: + case SER_BT_CONN_DIALOG: + case SER_BT_CONN_NOTIFY: dlen = sr_ser_has_queued_data(serial); rc = sr_bt_check_notify(serial->bt_desc); if (rc < 0) @@ -698,23 +891,6 @@ static int ser_bt_setup_source_remove(struct sr_session *session, return SR_OK; } -static enum ser_bt_conn_t scan_is_supported(const char *name) -{ - size_t idx; - const struct scan_supported_item *item; - - for (idx = 0; idx < ARRAY_SIZE(scan_supported_items); idx++) { - item = &scan_supported_items[idx]; - if (!item->name) - break; - if (strcmp(name, item->name) != 0) - continue; - return item->type; - } - - return SER_BT_CONN_UNKNOWN; -} - struct bt_scan_args_t { GSList *port_list; sr_ser_list_append_t append; @@ -727,6 +903,7 @@ static void scan_cb(void *cb_args, const char *addr, const char *name) struct bt_scan_args_t *scan_args; GSList *l; char addr_text[20]; + const struct scan_supported_item *item; enum ser_bt_conn_t type; char *port_name, *port_desc; char *addr_copy; @@ -749,9 +926,11 @@ static void scan_cb(void *cb_args, const char *addr, const char *name) g_strcanon(addr_text, "0123456789abcdefABCDEF", '-'); /* Create a port name, and a description. */ - type = scan_is_supported(name); - port_name = g_strdup_printf("%s/%s/%s", - SER_BT_CONN_PREFIX, conn_name_text(type), addr_text); + item = scan_is_supported(name); + type = item ? item->type : SER_BT_CONN_UNKNOWN; + port_name = g_strdup_printf("%s/%s/%s%s", + SER_BT_CONN_PREFIX, conn_name_text(type), addr_text, + (item && item->add_params) ? item->add_params : ""); port_desc = g_strdup_printf("%s (%s)", name, scan_args->bt_type); scan_args->port_list = scan_args->append(scan_args->port_list, port_name, port_desc); @@ -765,7 +944,7 @@ static void scan_cb(void *cb_args, const char *addr, const char *name) static GSList *ser_bt_list(GSList *list, sr_ser_list_append_t append) { - static const int scan_duration = 2; + static const int scan_duration = 3; struct bt_scan_args_t scan_args; struct sr_bt_desc *desc; diff --git a/src/serial_hid.c b/src/serial_hid.c index eccdf0f2..759a58c4 100644 --- a/src/serial_hid.c +++ b/src/serial_hid.c @@ -181,9 +181,10 @@ static char *extract_hidapi_path(const char *copy) } /* - * The HIDAPI specific list() callback, invoked by common serial.c code. - * Enumerate all devices (no VID:PID is involved). - * Invoke an 'append' callback with "path" and "name". + * Enumerate all devices (no VID:PID is involved). Invoke an 'append' + * callback with "path" and "name". Exclusively list connections that + * involve supported chip types, because mice and keyboards etc are not + * too useful to communicate to measurement equipment. */ static GSList *ser_hid_hidapi_list(GSList *list, sr_ser_list_append_t append) { @@ -197,14 +198,14 @@ static GSList *ser_hid_hidapi_list(GSList *list, sr_ser_list_append_t append) devs = hid_enumerate(0x0000, 0x0000); for (curdev = devs; curdev; curdev = curdev->next) { /* - * Determine the chip name from VID:PID (if it's one of - * the supported types with an ID known to us). + * Determine the chip name from VID:PID. Exlusively list + * supported connection types (known chips). */ vid = curdev->vendor_id; pid = curdev->product_id; chipname = ser_hid_chip_find_name_vid_pid(vid, pid); if (!chipname) - chipname = ""; + continue; /* * Prefix port names such that open() calls with this @@ -250,19 +251,36 @@ static GSList *ser_hid_hidapi_list(GSList *list, sr_ser_list_append_t append) } /* - * The HIDAPI specific find_usb() callback, invoked by common serial.c code. - * Enumerate devices for the specified VID:PID pair. - * Invoke an "append" callback with 'path' for the device. + * Enumerate devices for the specified VID:PID pair. Invoke an "append" + * callback with 'path' for found devices. Exclusively finds supported + * chip types, skips unknown VID:PID pairs (even if caller specified). */ static GSList *ser_hid_hidapi_find_usb(GSList *list, sr_ser_find_append_t append, uint16_t vendor_id, uint16_t product_id) { + const char *caller_chip; + const char *dev_chip; struct hid_device_info *devs, *curdev; const char *name; + char *path; + + caller_chip = ser_hid_chip_find_name_vid_pid(vendor_id, product_id); devs = hid_enumerate(vendor_id, product_id); for (curdev = devs; curdev; curdev = curdev->next) { - name = curdev->path; + dev_chip = caller_chip; + if (!dev_chip) { + dev_chip = ser_hid_chip_find_name_vid_pid( + curdev->vendor_id, curdev->product_id); + } + if (!dev_chip) + continue; + path = get_hidapi_path_copy(curdev->path); + if (!path) + continue; + name = g_strdup_printf("%s/%s/%s", + SER_HID_CONN_PREFIX, dev_chip, path); + g_free(path); list = append(list, name); } hid_free_enumeration(devs); @@ -1025,6 +1043,7 @@ static int ser_hid_chip_search(enum ser_hid_chip_t *chip_ref, return SR_ERR_NA; have_chip = 1; } + (void)have_chip; if (chip_ref) *chip_ref = chip; diff --git a/src/serial_tcpraw.c b/src/serial_tcpraw.c new file mode 100644 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/std.c b/src/std.c index 81f269fa..959d0005 100644 --- a/src/std.c +++ b/src/std.c @@ -605,7 +605,8 @@ SR_PRIV GVariant *std_gvar_tuple_array(const uint64_t a[][2], unsigned int n) rational[1] = g_variant_new_uint64(a[i][1]); /* FIXME: Valgrind reports a memory leak here. */ - g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational, 2)); + g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational, + ARRAY_SIZE(rational))); } return g_variant_builder_end(&gvb); @@ -624,7 +625,8 @@ SR_PRIV GVariant *std_gvar_tuple_rational(const struct sr_rational *r, unsigned rational[1] = g_variant_new_uint64(r[i].q); /* FIXME: Valgrind reports a memory leak here. */ - g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational, 2)); + g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational, + ARRAY_SIZE(rational))); } return g_variant_builder_end(&gvb); @@ -687,17 +689,17 @@ SR_PRIV GVariant *std_gvar_min_max_step_thresholds(const double min, const doubl g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY); - for (d = min; d <= max; d += step) { + for (d = min; d <= max + step / 2.0; d += step) { /* * We will never see exactly 0.0 because of the error we're * accumulating, so catch the "zero" value and force it to be 0. */ - v = ((d > (-step / 2)) && (d < (step / 2))) ? 0 : d; + v = ((d > (-step / 2.0)) && (d < (step / 2.0))) ? 0 : d; range[0] = g_variant_new_double(v); range[1] = g_variant_new_double(v); - gvar = g_variant_new_tuple(range, 2); + gvar = g_variant_new_tuple(range, ARRAY_SIZE(range)); g_variant_builder_add_value(&gvb, gvar); } @@ -711,7 +713,7 @@ SR_PRIV GVariant *std_gvar_tuple_u64(uint64_t low, uint64_t high) range[0] = g_variant_new_uint64(low); range[1] = g_variant_new_uint64(high); - return g_variant_new_tuple(range, 2); + return g_variant_new_tuple(range, ARRAY_SIZE(range)); } SR_PRIV GVariant *std_gvar_tuple_double(double low, double high) @@ -721,7 +723,7 @@ SR_PRIV GVariant *std_gvar_tuple_double(double low, double high) range[0] = g_variant_new_double(low); range[1] = g_variant_new_double(high); - return g_variant_new_tuple(range, 2); + return g_variant_new_tuple(range, ARRAY_SIZE(range)); } SR_PRIV GVariant *std_gvar_array_i32(const int32_t a[], unsigned int n) @@ -770,7 +772,7 @@ SR_PRIV GVariant *std_gvar_thresholds(const double a[][2], unsigned int n) for (i = 0; i < n; i++) { range[0] = g_variant_new_double(a[i][0]); range[1] = g_variant_new_double(a[i][1]); - gvar = g_variant_new_tuple(range, 2); + gvar = g_variant_new_tuple(range, ARRAY_SIZE(range)); g_variant_builder_add_value(&gvb, gvar); } diff --git a/src/strutil.c b/src/strutil.c index c22c7175..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]); } @@ -828,78 +835,149 @@ SR_PRIV void sr_hexdump_free(GString *s) */ SR_API int sr_parse_rational(const char *str, struct sr_rational *ret) { - char *endptr = NULL; + const char *readptr; + char *endptr; + gboolean is_negative, empty_integral, empty_fractional, exp_negative; int64_t integral; - int64_t fractional = 0; - int64_t denominator = 1; - int32_t fractional_len = 0; - int32_t exponent = 0; - gboolean is_negative = FALSE; - gboolean no_integer, no_fractional; - - while (isspace(*str)) - str++; + int64_t fractional; + int64_t denominator; + uint32_t fractional_len; + int32_t exponent; - errno = 0; - integral = g_ascii_strtoll(str, &endptr, 10); - - if (str == endptr && (str[0] == '-' || str[0] == '+') && str[1] == '.') { - endptr += 1; - no_integer = TRUE; - } else if (str == endptr && str[0] == '.') { - no_integer = TRUE; - } else if (errno) { - return SR_ERR; - } else { - no_integer = FALSE; - } + /* + * Implementor's note: This routine tries hard to avoid calling + * glib's or the platform's conversion routines with input that + * cannot get converted *at all* (see bug #1093). It also takes + * care to return with non-zero errno values for any failed + * conversion attempt. It's assumed that correctness and robustness + * are more important than performance, which is why code paths + * are not optimized at all. Maintainability took priority. + */ + + readptr = str; + + /* Skip leading whitespace. */ + while (isspace(*readptr)) + readptr++; - if (integral < 0 || str[0] == '-') + /* Determine the sign, default to non-negative. */ + is_negative = FALSE; + if (*readptr == '-') { is_negative = TRUE; + readptr++; + } else if (*readptr == '+') { + is_negative = FALSE; + readptr++; + } + /* Get the (optional) integral part. */ + empty_integral = TRUE; + integral = 0; + endptr = (char *)readptr; errno = 0; - if (*endptr == '.') { - gboolean is_exp, is_eos; - const char *start = endptr + 1; - fractional = g_ascii_strtoll(start, &endptr, 10); - is_exp = *endptr == 'E' || *endptr == 'e'; - is_eos = *endptr == '\0'; - if (endptr == start && (is_exp || is_eos)) { - fractional = 0; - errno = 0; - } + if (isdigit(*readptr)) { + empty_integral = FALSE; + integral = g_ascii_strtoll(readptr, &endptr, 10); if (errno) return SR_ERR; - no_fractional = endptr == start; - if (no_integer && no_fractional) + if (endptr == str) { + errno = -EINVAL; return SR_ERR; - fractional_len = endptr - start; + } + readptr = endptr; } - errno = 0; - if ((*endptr == 'E') || (*endptr == 'e')) { - exponent = g_ascii_strtoll(endptr + 1, &endptr, 10); + /* Get the optional fractional part. */ + empty_fractional = TRUE; + fractional = 0; + fractional_len = 0; + if (*readptr == '.') { + readptr++; + endptr++; + errno = 0; + if (isdigit(*readptr)) { + empty_fractional = FALSE; + fractional = g_ascii_strtoll(readptr, &endptr, 10); + if (errno) + return SR_ERR; + if (endptr == readptr) { + errno = -EINVAL; + return SR_ERR; + } + fractional_len = endptr - readptr; + readptr = endptr; + } + } + + /* At least one of integral or fractional is required. */ + if (empty_integral && empty_fractional) { + errno = -EINVAL; + return SR_ERR; + } + + /* Get the (optional) exponent. */ + exponent = 0; + if ((*readptr == 'E') || (*readptr == 'e')) { + readptr++; + endptr++; + exp_negative = FALSE; + if (*readptr == '+') { + exp_negative = FALSE; + readptr++; + endptr++; + } else if (*readptr == '-') { + exp_negative = TRUE; + readptr++; + endptr++; + } + if (!isdigit(*readptr)) { + errno = -EINVAL; + return SR_ERR; + } + errno = 0; + exponent = g_ascii_strtoll(readptr, &endptr, 10); if (errno) return SR_ERR; + if (endptr == readptr) { + errno = -EINVAL; + return SR_ERR; + } + readptr = endptr; + if (exp_negative) + exponent = -exponent; } - if (*endptr != '\0') + /* Input must be exhausted. Unconverted remaining input is fatal. */ + if (*endptr != '\0') { + errno = -EINVAL; return SR_ERR; + } - for (int i = 0; i < fractional_len; i++) + /* + * Apply the sign to the integral (and fractional) part(s). + * Adjust exponent (decimal position) such that the above integral + * and fractional parts both fit into the (new) integral part. + */ + if (is_negative) + integral = -integral; + while (fractional_len-- > 0) { integral *= 10; - exponent -= fractional_len; - + exponent--; + } if (!is_negative) integral += fractional; else integral -= fractional; - while (exponent > 0) { integral *= 10; exponent--; } + /* + * When significant digits remain after the decimal, scale up the + * denominator such that we end up with two integer p/q numbers. + */ + denominator = 1; while (exponent < 0) { denominator *= 10; exponent++; @@ -1249,4 +1327,549 @@ SR_API int sr_parse_voltage(const char *voltstr, uint64_t *p, uint64_t *q) return SR_OK; } +/** + * Append another text item to a NULL terminated string vector. + * + * @param[in] table The previous string vector. + * @param[in,out] sz The previous and the resulting vector size + * (item count). + * @param[in] text The text string to append to the vector. + * Can be #NULL. + * + * @returns The new vector, its location can differ from 'table'. + * Or #NULL in case of failure. + * + * This implementation happens to work for the first invocation when + * 'table' is #NULL and 'sz' is 0, as well as subsequent append calls. + * The 'text' can be #NULL or can be a non-empty string. When 'sz' is + * not provided, then the 'table' must be a NULL terminated vector, + * so that the routine can auto-determine the vector's current length. + * + * This routine re-allocates the vector as needed. Callers must not + * rely on the memory address to remain the same across calls. + */ +static char **append_probe_name(char **table, size_t *sz, const char *text) +{ + size_t curr_size, alloc_size; + char **new_table; + + /* Get the table's previous size (item count). */ + if (sz) + curr_size = *sz; + else if (table) + curr_size = g_strv_length(table); + else + curr_size = 0; + + /* Extend storage to hold one more item, and the termination. */ + alloc_size = curr_size + (text ? 1 : 0) + 1; + alloc_size *= sizeof(table[0]); + new_table = g_realloc(table, alloc_size); + if (!new_table) { + g_strfreev(table); + if (sz) + *sz = 0; + return NULL; + } + + /* Append the item, NULL terminate. */ + if (text) { + new_table[curr_size] = g_strdup(text); + if (!new_table[curr_size]) { + g_strfreev(new_table); + if (sz) + *sz = 0; + return NULL; + } + curr_size++; + } + if (sz) + *sz = curr_size; + new_table[curr_size] = NULL; + + return new_table; +} + +static char **append_probe_names(char **table, size_t *sz, + const char **names) +{ + if (!names) + return table; + + while (names[0]) { + table = append_probe_name(table, sz, names[0]); + names++; + } + return table; +} + +static const struct { + const char *name; + const char **expands; +} probe_name_aliases[] = { + { + "ac97", (const char *[]){ + "sync", "clk", + "out", "in", "rst", + NULL, + }, + }, + { + "i2c", (const char *[]){ + "scl", "sda", NULL, + }, + }, + { + "jtag", (const char *[]){ + "tdi", "tdo", "tck", "tms", NULL, + }, + }, + { + "jtag-opt", (const char *[]){ + "tdi", "tdo", "tck", "tms", + "trst", "srst", "rtck", NULL, + }, + }, + { + "ieee488", (const char *[]){ + "dio1", "dio2", "dio3", "dio4", + "dio5", "dio6", "dio7", "dio8", + "eoi", "dav", "nrfd", "ndac", + "ifc", "srq", "atn", "ren", NULL, + }, + }, + { + "lpc", (const char *[]){ + "lframe", "lclk", + "lad0", "lad1", "lad2", "lad3", + NULL, + }, + }, + { + "lpc-opt", (const char *[]){ + "lframe", "lclk", + "lad0", "lad1", "lad2", "lad3", + "lreset", "ldrq", "serirq", "clkrun", + "lpme", "lpcpd", "lsmi", + NULL, + }, + }, + { + "mcs48", (const char *[]){ + "ale", "psen", + "d0", "d1", "d2", "d3", + "d4", "d5", "d6", "d7", + "a8", "a9", "a10", "a11", + "a12", "a13", + NULL, + }, + }, + { + "microwire", (const char *[]){ + "cs", "sk", "si", "so", NULL, + }, + }, + { + "sdcard_sd", (const char *[]){ + "cmd", "clk", + "dat0", "dat1", "dat2", "dat3", + NULL, + }, + }, + { + "seven_segment", (const char *[]){ + "a", "b", "c", "d", "e", "f", "g", + "dp", NULL, + }, + }, + { + "spi", (const char *[]){ + "clk", "miso", "mosi", "cs", NULL, + }, + }, + { + "swd", (const char *[]){ + "swclk", "swdio", NULL, + }, + }, + { + "uart", (const char *[]){ + "rx", "tx", NULL, + }, + }, + { + "usb", (const char *[]){ + "dp", "dm", NULL, + }, + }, + { + "z80", (const char *[]){ + "d0", "d1", "d2", "d3", + "d4", "d5", "d6", "d7", + "m1", "rd", "wr", + "mreq", "iorq", + "a0", "a1", "a2", "a3", + "a4", "a5", "a6", "a7", + "a8", "a9", "a10", "a11", + "a12", "a13", "a14", "a15", + NULL, + }, + }, +}; + +/* Case insensitive lookup of an alias name. */ +static const char **lookup_probe_alias(const char *name) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(probe_name_aliases); idx++) { + if (g_ascii_strcasecmp(probe_name_aliases[idx].name, name) != 0) + continue; + return probe_name_aliases[idx].expands; + } + return NULL; +} + +/** + * Parse a probe names specification, allocate a string vector. + * + * @param[in] spec The input spec, list of probes or aliases. + * @param[in] dflt_names The default probe names, a string array. + * @param[in] dflt_count The default probe names count. Either must + * match the unterminated array size, or can be 0 when the + * default names are NULL terminated. + * @param[in] max_count Optional resulting vector size limit. + * @param[out] ret_count Optional result vector size (return value). + * + * @returns A string vector with resulting probe names. Or #NULL + * in case of failure. + * + * The input spec is a comma separated list of probe names. Items can + * be aliases which expand to a corresponding set of signal names. + * The resulting names list optionally gets padded from the caller's + * builtin probe names, an empty input spec yields the original names + * as provided by the caller. Padding is omitted when the spec starts + * with '-', which may result in a device with fewer channels being + * created, enough to cover the user's spec, but none extra to maybe + * enable and use later on. An optional maximum length spec will trim + * the result set to that size. The resulting vector length optionally + * is returned to the caller, so that it need not re-get the length. + * + * Calling applications must release the allocated vector by means + * of @ref sr_free_probe_names(). + * + * @since 0.6.0 + */ +SR_API char **sr_parse_probe_names(const char *spec, + const char **dflt_names, size_t dflt_count, + size_t max_count, size_t *ret_count) +{ + char **result_names; + size_t result_count; + gboolean pad_from_dflt; + char **spec_names, *spec_name; + size_t spec_idx; + const char **alias_names; + + if (!spec || !*spec) + spec = NULL; + + /* + * Accept zero length spec for default input names. Determine + * the name table's length here. Cannot re-use g_strv_length() + * because of the 'const' decoration in application code. + */ + if (!dflt_count) { + while (dflt_names && dflt_names[dflt_count]) + dflt_count++; + } + if (!dflt_count) + return NULL; + + /* + * Start with an empty resulting names table. Will grow + * dynamically as more names get appended. + */ + result_names = NULL; + result_count = 0; + pad_from_dflt = TRUE; + + /* + * When an input spec exists, use its content. Lookup alias + * names, and append their corresponding signals. Or append + * the verbatim input name if it is not an alias. Recursion + * is not supported in this implementation. + * + * A leading '-' before the signal names list suppresses the + * padding of the resulting list from the device's default + * probe names. + */ + spec_names = NULL; + if (spec && *spec == '-') { + spec++; + pad_from_dflt = FALSE; + } + if (spec && *spec) + spec_names = g_strsplit(spec, ",", 0); + for (spec_idx = 0; spec_names && spec_names[spec_idx]; spec_idx++) { + spec_name = spec_names[spec_idx]; + if (!*spec_name) + continue; + alias_names = lookup_probe_alias(spec_name); + if (alias_names) { + result_names = append_probe_names(result_names, + &result_count, alias_names); + } else { + result_names = append_probe_name(result_names, + &result_count, spec_name); + } + } + g_strfreev(spec_names); + + /* + * By default pad the resulting names from the caller's + * probe names. Don't pad if the input spec started with + * '-', when the spec's exact length was requested. + */ + if (pad_from_dflt) do { + if (max_count && result_count >= max_count) + break; + if (result_count >= dflt_count) + break; + result_names = append_probe_name(result_names, &result_count, + dflt_names[result_count]); + } while (1); + + /* Optionally trim the result to the caller's length limit. */ + if (max_count) { + while (result_count > max_count) { + --result_count; + g_free(result_names[result_count]); + result_names[result_count] = NULL; + } + } + + if (ret_count) + *ret_count = result_count; + + return result_names; +} + +/** + * Release previously allocated probe names (string vector). + * + * @param[in] names The previously allocated string vector. + * + * @since 0.6.0 + */ +SR_API void sr_free_probe_names(char **names) +{ + g_strfreev(names); +} + +/** + * Trim leading and trailing whitespace off text. + * + * @param[in] s The input text. + * + * @return Start of trimmed input text. + * + * Manipulates the caller's input text in place. + * + * @since 0.6.0 + */ +SR_API char *sr_text_trim_spaces(char *s) +{ + char *p; + + if (!s || !*s) + return s; + + p = s + strlen(s); + while (p > s && isspace((int)p[-1])) + *(--p) = '\0'; + while (isspace((int)*s)) + s++; + + return s; +} + +/** + * Check for another complete text line, trim, return consumed char count. + * + * @param[in] s The input text, current read position. + * @param[in] l The input text, remaining available characters. + * @param[out] next Position after the current text line. + * @param[out] taken Count of consumed chars in current text line. + * + * @return Start of trimmed and NUL terminated text line. + * Or #NULL when no text line was found. + * + * Checks for the availability of another text line of input data. + * Manipulates the caller's input text in place. + * + * The end-of-line condition is the LF character ('\n'). Which covers + * LF-only as well as CR/LF input data. CR-only and LF/CR are considered + * unpopular and are not supported. LF/CR may appear to work at the + * caller's when leading whitespace gets trimmed (line boundaries will + * be incorrect, but content may get processed as expected). Support for + * all of the above combinations breaks the detection of empty lines (or + * becomes unmaintainably complex). + * + * The input buffer must be end-of-line terminated, lack of EOL results + * in failure to detect the text line. This is motivated by accumulating + * input in chunks, and the desire to not process incomplete lines before + * their reception has completed. Callers should enforce EOL if their + * source of input provides an EOF condition and is unreliable in terms + * of text line termination. + * + * When another text line is available, it gets NUL terminated and + * space gets trimmed of both ends. The start position of the trimmed + * text line is returned. Optionally the number of consumed characters + * is returned to the caller. Optionally 'next' points to after the + * returned text line, or #NULL when no other text is available in the + * input buffer. + * + * The 'taken' value is not preset by this routine, only gets updated. + * This is convenient for callers which expect to find multiple text + * lines in a received chunk, before finally discarding processed data + * from the input buffer (which can involve expensive memory move + * operations, and may be desirable to defer as much as possible). + * + * @since 0.6.0 + */ +SR_API char *sr_text_next_line(char *s, size_t l, char **next, size_t *taken) +{ + char *p; + + if (next) + *next = NULL; + if (!l) + l = strlen(s); + + /* Immediate reject incomplete input data. */ + if (!s || !*s || !l) + return NULL; + + /* Search for the next line termination. NUL terminate. */ + p = g_strstr_len(s, l, "\n"); + if (!p) + return NULL; + *p++ = '\0'; + if (taken) + *taken += p - s; + l -= p - s; + if (next) + *next = l ? p : NULL; + + /* Trim NUL terminated text line at both ends. */ + s = sr_text_trim_spaces(s); + return s; +} + +/** + * Isolates another space separated word in a text line. + * + * @param[in] s The input text, current read position. + * @param[out] next The position after the current word. + * + * @return The start of the current word. Or #NULL if there is none. + * + * Advances over leading whitespace. Isolates (NUL terminates) the next + * whitespace separated word. Optionally returns the position after the + * current word. Manipulates the caller's input text in place. + * + * @since 0.6.0 + */ +SR_API char *sr_text_next_word(char *s, char **next) +{ + char *word, *p; + + word = s; + if (next) + *next = NULL; + + /* Immediately reject incomplete input data. */ + if (!word || !*word) + return NULL; + + /* Advance over optional leading whitespace. */ + while (isspace((int)*word)) + word++; + if (!*word) + return NULL; + + /* + * Advance until whitespace or end of text. Quick return when + * end of input is seen. Otherwise advance over whitespace and + * return the position of trailing text. + */ + p = word; + while (*p && !isspace((int)*p)) + p++; + if (!*p) + return word; + *p++ = '\0'; + while (isspace((int)*p)) + p++; + if (!*p) + return word; + if (next) + *next = p; + return word; +} + +/** + * Get the number of necessary bits to hold a given value. Also gets + * the next power-of-two value at or above the caller provided value. + * + * @param[in] value The value that must get stored. + * @param[out] bits The required number of bits. + * @param[out] power The corresponding power-of-two. + * + * @return SR_OK upon success, SR_ERR* otherwise. + * + * TODO Move this routine to a more appropriate location, it is not + * strictly string related. + * + * @since 0.6.0 + */ +SR_API int sr_next_power_of_two(size_t value, size_t *bits, size_t *power) +{ + size_t need_bits; + size_t check_mask; + + if (bits) + *bits = 0; + if (power) + *power = 0; + + /* + * Handle the special case of input value 0 (needs 1 bit + * and results in "power of two" value 1) here. It is not + * covered by the generic logic below. + */ + if (!value) { + if (bits) + *bits = 1; + if (power) + *power = 1; + return SR_OK; + } + + need_bits = 0; + check_mask = 0; + do { + need_bits++; + check_mask <<= 1; + check_mask |= 1UL << 0; + } while (value & ~check_mask); + + if (bits) + *bits = need_bits; + if (power) + *power = ++check_mask; + return SR_OK; +} + /** @} */ 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/usb.c b/src/usb.c index 6bd9e818..feafe9d2 100644 --- a/src/usb.c +++ b/src/usb.c @@ -297,68 +297,116 @@ static GSource *usb_source_new(struct sr_session *session, } /** - * Find USB devices according to a connection string. + * Extract VID:PID or bus.addr from a connection string. * - * @param usb_ctx libusb context to use while scanning. - * @param conn Connection string specifying the device(s) to match. This - * can be of the form ".
", or ".". + * @param[in] conn Connection string. + * @param[out] vid Pointer to extracted vendor ID. Can be #NULL. + * @param[out] pid Pointer to extracted product ID. Can be #NULL. + * @param[out] bus Pointer to extracted bus number. Can be #NULL. + * @param[out] addr Pointer to extracted device number. Can be #NULL. * - * @return A GSList of struct sr_usb_dev_inst, with bus and address fields - * matching the device that matched the connection string. The GSList and - * its contents must be freed by the caller. + * @return SR_OK when parsing succeeded, SR_ERR* otherwise. + * + * @private + * + * The routine fills in the result variables, and returns the scan success + * in the return code. Callers can specify #NULL for variable references + * if they are not interested in specific aspects of the USB address. */ -SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn) +SR_PRIV int sr_usb_split_conn(const char *conn, + uint16_t *vid, uint16_t *pid, uint8_t *bus, uint8_t *addr) { - struct sr_usb_dev_inst *usb; - struct libusb_device **devlist; - struct libusb_device_descriptor des; - GSList *devices; + gboolean valid; GRegex *reg; GMatchInfo *match; - int vid, pid, bus, addr, b, a, ret, i; char *mstr; + uint32_t num; + + if (vid) *vid = 0; + if (pid) *pid = 0; + if (bus) *bus = 0; + if (addr) *addr = 0; - vid = pid = bus = addr = 0; + valid = TRUE; reg = g_regex_new(CONN_USB_VIDPID, 0, 0, NULL); if (g_regex_match(reg, conn, 0, &match)) { - if ((mstr = g_match_info_fetch(match, 1))) - vid = strtoul(mstr, NULL, 16); + /* Found a VID:PID style pattern. */ + if ((mstr = g_match_info_fetch(match, 1))) { + num = strtoul(mstr, NULL, 16); + if (num > 0xffff) + valid = FALSE; + if (vid) + *vid = num & 0xffff; + } g_free(mstr); - if ((mstr = g_match_info_fetch(match, 2))) - pid = strtoul(mstr, NULL, 16); + if ((mstr = g_match_info_fetch(match, 2))) { + num = strtoul(mstr, NULL, 16); + if (num > 0xffff) + valid = FALSE; + if (pid) + *pid = num & 0xffff; + } g_free(mstr); - /* Trying to find USB device via VID:PID. */ } else { g_match_info_unref(match); g_regex_unref(reg); reg = g_regex_new(CONN_USB_BUSADDR, 0, 0, NULL); if (g_regex_match(reg, conn, 0, &match)) { - if ((mstr = g_match_info_fetch(match, 1))) - bus = strtoul(mstr, NULL, 10); + /* Found a bus.address style pattern. */ + if ((mstr = g_match_info_fetch(match, 1))) { + num = strtoul(mstr, NULL, 10); + if (num > 255) + valid = FALSE; + if (bus) + *bus = num & 0xff; + } g_free(mstr); - if ((mstr = g_match_info_fetch(match, 2))) - addr = strtoul(mstr, NULL, 10); + if ((mstr = g_match_info_fetch(match, 2))) { + num = strtoul(mstr, NULL, 10); + if (num > 127) + valid = FALSE; + if (addr) + *addr = num & 0x7f; + } g_free(mstr); - /* Trying to find USB device via bus.address. */ } } g_match_info_unref(match); g_regex_unref(reg); - if (vid + pid + bus + addr == 0) { - sr_err("Neither VID:PID nor bus.address was specified."); - return NULL; - } + return valid ? SR_OK : SR_ERR_ARG; +} - if (bus > 255) { - sr_err("Invalid bus specified: %d.", bus); +/** + * Find USB devices according to a connection string. + * + * @param usb_ctx libusb context to use while scanning. + * @param conn Connection string specifying the device(s) to match. This + * can be of the form ".
", or ".". + * + * @return A GSList of struct sr_usb_dev_inst, with bus and address fields + * matching the device that matched the connection string. The GSList and + * its contents must be freed by the caller. + */ +SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn) +{ + struct sr_usb_dev_inst *usb; + struct libusb_device **devlist; + struct libusb_device_descriptor des; + GSList *devices; + uint16_t vid, pid; + uint8_t bus, addr; + int b, a, ret, i; + + ret = sr_usb_split_conn(conn, &vid, &pid, &bus, &addr); + if (ret != SR_OK) { + sr_err("Invalid input, or neither VID:PID nor bus.address specified."); return NULL; } - - if (addr > 127) { - sr_err("Invalid address specified: %d.", addr); + if (!(vid && pid) && !(bus && addr)) { + sr_err("Could neither determine VID:PID nor bus.address numbers."); return NULL; } @@ -372,12 +420,12 @@ SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn) continue; } - if (vid + pid && (des.idVendor != vid || des.idProduct != pid)) + if (vid && pid && (des.idVendor != vid || des.idProduct != pid)) continue; b = libusb_get_bus_number(devlist[i]); a = libusb_get_device_address(devlist[i]); - if (bus + addr && (b != bus || a != addr)) + if (bus && addr && (b != bus || a != addr)) continue; sr_dbg("Found USB device (VID:PID = %04x:%04x, bus.address = " 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/analog.c b/tests/analog.c index 8490bf9f..904f0cea 100644 --- a/tests/analog.c +++ b/tests/analog.c @@ -598,7 +598,7 @@ END_TEST START_TEST(test_mult_rational) { - const struct sr_rational r[][3] = { + static const struct sr_rational r[][3] = { /* a * b = c */ { { 1, 1 }, { 1, 1 }, { 1, 1 }}, { { 2, 1 }, { 3, 1 }, { 6, 1 }}, @@ -625,13 +625,15 @@ START_TEST(test_mult_rational) { { 10000*3, 4 }, { -80000*3, 1 }, { -200000000*9, 1 }}, }; - for (unsigned i = 0; i < ARRAY_SIZE(r); i++) { - struct sr_rational res; + size_t i; + struct sr_rational res; + int rc; - int rc = sr_rational_mult(&res, &r[i][0], &r[i][1]); + for (i = 0; i < ARRAY_SIZE(r); i++) { + rc = sr_rational_mult(&res, &r[i][0], &r[i][1]); fail_unless(rc == SR_OK); fail_unless(sr_rational_eq(&res, &r[i][2]) == 1, - "sr_rational_mult() failed: [%d] %ld/%lu != %ld/%lu.", + "sr_rational_mult() failed: [%zu] %" PRIi64 "/%" PRIu64 " != %" PRIi64 "/%" PRIu64 ".", i, res.p, res.q, r[i][2].p, r[i][2].q); } } @@ -639,7 +641,7 @@ END_TEST START_TEST(test_div_rational) { - const struct sr_rational r[][3] = { + static const struct sr_rational r[][3] = { /* a * b = c */ { { 1, 1 }, { 1, 1 }, { 1, 1 }}, { { 2, 1 }, { 1, 3 }, { 6, 1 }}, @@ -662,19 +664,20 @@ START_TEST(test_div_rational) { { 10000*3, 4 }, { -1, 80000*3 }, { -200000000*9, 1 }}, }; - for (unsigned i = 0; i < ARRAY_SIZE(r); i++) { - struct sr_rational res; + size_t i; + struct sr_rational res; + int rc; - int rc = sr_rational_div(&res, &r[i][0], &r[i][1]); + for (i = 0; i < ARRAY_SIZE(r); i++) { + rc = sr_rational_div(&res, &r[i][0], &r[i][1]); fail_unless(rc == SR_OK); fail_unless(sr_rational_eq(&res, &r[i][2]) == 1, - "sr_rational_mult() failed: [%d] %ld/%lu != %ld/%lu.", + "sr_rational_mult() failed: [%zu] %" PRIi64 "/%" PRIu64 " != %" PRIi64 "/%" PRIu64 ".", i, res.p, res.q, r[i][2].p, r[i][2].q); } { - struct sr_rational res; - int rc = sr_rational_div(&res, &r[0][0], &((struct sr_rational){ 0, 5 })); + rc = sr_rational_div(&res, &r[0][0], &((struct sr_rational){ 0, 5 })); fail_unless(rc == SR_ERR_ARG); } diff --git a/tests/conv.c b/tests/conv.c index 5ef0bab1..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; @@ -228,6 +258,13 @@ START_TEST(test_endian_write_inc) l = p - &buff[0]; fail_unless(l == 4 * 48 / 8 * sizeof(uint8_t)); fail_unless(memcmp(&buff[0], &buff1234large[0], l) == 0); + + p = &buff[0]; + write_u24le_inc(&p, 0xfe030201); + write_u40le_inc(&p, 0xdcba0807060504ul); + l = p - &buff[0]; + fail_unless(l == 24 / 8 + 40 / 8); + fail_unless(memcmp(&buff[0], &buff1234large[0], l) == 0); } END_TEST @@ -242,6 +279,7 @@ Suite *suite_conv(void) tcase_add_test(tc, test_endian_macro); tcase_add_test(tc, test_endian_read); tcase_add_test(tc, test_endian_read_inc); + tcase_add_test(tc, test_endian_read_inc_len); tcase_add_test(tc, test_endian_write); tcase_add_test(tc, test_endian_write_inc); suite_add_tcase(s, tc); diff --git a/tests/strutil.c b/tests/strutil.c index ec223332..3e43dbdc 100644 --- a/tests/strutil.c +++ b/tests/strutil.c @@ -121,7 +121,7 @@ static void test_rational(const char *input, struct sr_rational expected) fail_unless(ret == SR_OK, "Unexpected rc for '%s': %d, errno %d.", input, ret, errno); fail_unless((expected.p == rational.p) && (expected.q == rational.q), - "Invalid result for '%s': %ld/%ld'.", + "Invalid result for '%s': %" PRIi64 "/%" PRIu64 "'.", input, rational.p, rational.q); } @@ -430,6 +430,184 @@ START_TEST(test_exponent) } END_TEST +START_TEST(test_text_line) +{ + /* + * Covers text line splitting as used in input modules. Accepts + * input with differing end-of-line conventions, accepts leading + * and trailing whitespace. Isolates "the core" of a text line. + * Supports repeated calls which accumulate what later needs to + * get discarded after input data got processed in pieces. + */ +#define EOL "\n" + +#define TEXT_CORE_1 "Need to provide" +#define TEXT_CORE_2 "an input text" +#define TEXT_CORE_3 "" +#define TEXT_CORE_4 "with empty lines and funny spacing perhaps?" + +#define TEXT_LINE_1 TEXT_CORE_1 " \n" +#define TEXT_LINE_2 " " TEXT_CORE_2 "\n" +#define TEXT_LINE_3 TEXT_CORE_3 "\r\n" +#define TEXT_LINE_4 TEXT_CORE_4 "\n" + +#define TEXT_INPUT TEXT_LINE_1 TEXT_LINE_2 TEXT_LINE_3 TEXT_LINE_4 + + char *input_text, *read_pos, *next_pos, *line; + size_t input_len, taken; + + input_text = g_strdup(TEXT_INPUT); + read_pos = input_text; + input_len = strlen(input_text); + + /* Cover first line in tests. */ + taken = 0; + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_1) == 0, "Unexpected line content"); + fail_unless(next_pos, "No next line found"); + fail_unless(strncmp(next_pos, TEXT_LINE_2, strlen(TEXT_LINE_2)) == 0, + "Unexpected next line content"); + fail_unless(taken == strlen(TEXT_LINE_1), + "Unexpected consumed count"); + read_pos = next_pos; + input_len -= taken; + taken = 0; + + /* Cover second line in tests. DO NOT void 'taken' yet. */ + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_2) == 0, + "Unexpected text line content"); + fail_unless(next_pos, "No next line found"); + fail_unless(strncmp(next_pos, TEXT_LINE_3, strlen(TEXT_LINE_3)) == 0, + "Unexpected next line content"); + fail_unless(taken == strlen(TEXT_LINE_2), + "Unexpected consumed count"); + input_len -= next_pos - read_pos; + read_pos = next_pos; + + /* Cover third line in tests. Accumulates 'taken'. */ + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_3) == 0, "Unexpected line content"); + fail_unless(next_pos, "No next line found"); + fail_unless(strncmp(next_pos, TEXT_LINE_4, strlen(TEXT_LINE_4)) == 0, + "Unexpected next line content"); + fail_unless(taken == strlen(TEXT_LINE_2) + strlen(TEXT_LINE_3), + "Unexpected consumed count (totalled)"); + input_len -= next_pos - read_pos; + read_pos = next_pos; + taken = 0; + + /* Cover last line in tests. */ + line = sr_text_next_line(read_pos, input_len, &next_pos, &taken); + fail_unless(line, "Text line not found"); + fail_unless(strcmp(line, TEXT_CORE_4) == 0, + "Unexpected text line content"); + fail_unless(!next_pos, "Next line found, unexpected"); + fail_unless(taken == strlen(TEXT_LINE_4), + "Unexpected consumed count"); + input_len -= taken; + read_pos = next_pos; + + /* All input must have been consumed. */ + fail_unless(!read_pos); + fail_unless(!input_len); + + g_free(input_text); +} +END_TEST + +/* + * TODO Ideally this table of test cases should reside within the + * test_text_word() routine. But compilation fails when it's put there + * (initializers are said to not be constant, cause is yet uncertain). + */ +static const struct { + const char *line; + const char **words; +} word_cases[] = { + { "", (const char *[]){ NULL, }, }, + { " ", (const char *[]){ NULL, }, }, + { "one", (const char *[]){ "one", NULL, }, }, + { "one ", (const char *[]){ "one", NULL, }, }, + { " one ", (const char *[]){ "one", NULL, }, }, + { " one two ", (const char *[]){ "one", "two", NULL, }, }, + { "one two three ", + (const char *[]){ "one", "two", "three", NULL, }, + }, +}; + +START_TEST(test_text_word) +{ + size_t case_idx, word_idx; + char *line; + const char **words, *want; + char *read_pos, *next_pos, *have; + + for (case_idx = 0; case_idx < ARRAY_SIZE(word_cases); case_idx++) { + line = g_strdup(word_cases[case_idx].line); + words = word_cases[case_idx].words; + word_idx = 0; + + read_pos = line; + while (read_pos) { + want = words[word_idx]; + have = sr_text_next_word(read_pos, &next_pos); + if (!want) { + fail_unless(!have, "word found, unexpected"); + fail_unless(!next_pos, "next found after end"); + break; + } + word_idx++; + read_pos = next_pos; + fail_unless(have, "word not found"); + fail_unless(strcmp(have, want) == 0, + "unexpected word found"); + } + fail_unless(!words[word_idx], "missed expected words"); + + g_free(line); + } +} +END_TEST + +static const struct power_case_t { + size_t value; + size_t want_bits; + size_t want_power; +} power_cases[] = { + { 0, 1, 1, }, + { 1, 1, 2, }, + { 2, 2, 4, }, + { 3, 2, 4, }, + { 4, 3, 8, }, + { 5, 3, 8, }, + { 6, 3, 8, }, + { 7, 3, 8, }, + { 8, 4, 16, }, + { 15, 4, 16, }, + { 16, 5, 32, }, + { 31, 5, 32, }, +}; + +START_TEST(test_calc_power_of_two) +{ + size_t case_idx, bits, power; + const struct power_case_t *tcase; + int ret; + + for (case_idx = 0; case_idx < ARRAY_SIZE(power_cases); case_idx++) { + tcase = &power_cases[case_idx]; + ret = sr_next_power_of_two(tcase->value, &bits, &power); + fail_unless(ret == SR_OK, "bits count not found"); + fail_unless(bits == tcase->want_bits, "bits count differs"); + fail_unless(power == tcase->want_power, "power differs"); + } +} +END_TEST + Suite *suite_strutil(void) { Suite *s; @@ -452,5 +630,14 @@ Suite *suite_strutil(void) tcase_add_test(tc, test_exponent); suite_add_tcase(s, tc); + tc = tcase_create("text"); + tcase_add_test(tc, test_text_line); + tcase_add_test(tc, test_text_word); + suite_add_tcase(s, tc); + + tc = tcase_create("calc"); + tcase_add_test(tc, test_calc_power_of_two); + suite_add_tcase(s, tc); + return s; } 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