--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019-2023 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This input module reads data values from an input stream, and sends
+ * the corresponding samples to the sigrok session feed which form the
+ * respective waveform, pretending that a logic analyzer had captured
+ * wire traffic. This allows to feed data to protocol decoders which
+ * were recorded by different means (COM port redirection, pcap(3)
+ * recordings, 3rd party bus analyzers). It can also simplify the
+ * initial creation of protocol decoders by generating synthetic
+ * input data, before real world traffic captures become available.
+ *
+ * This input module "assumes ideal traffic" and absence of protocol
+ * errors. Does _not_ inject error conditions, instead generates valid
+ * bit patterns by naively filling blanks to decorate the payload data
+ * which the input file provides. To yield a stream of samples which
+ * successfully decodes at the recipient's, and upper layer decoders
+ * will see valid data which corresponds to the file's content. Edge
+ * positions and minute timnig details are not adjustable either in
+ * this module (no support for setup or hold times or slew rates etc).
+ * The goal is not to emulate a protocol with all its possibilities to
+ * the fullest detail. The module's purpose is to simplify the import
+ * of values while no capture of the wire traffic was available.
+ *
+ * There are several approaches to using the input module:
+ * - Input data can be a mere bytes sequence. While attributes can get
+ * specified by means of input module options. This is the fastest
+ * approach to accessing raw data that's externally made available.
+ * - An optional leading magic literal supports automatic file type
+ * detection, and obsoletes the -I input module selection. Unwanted
+ * automatic detection is possible but very unlikely. The magic text
+ * was chosen such that its occurance at the very start of payload
+ * data is extremely unlikely, and is easy to work around should the
+ * situation happen. Of course specifying input module options does
+ * necessitate the selection of the input module.
+ * - When the file type magic is present, an optional header section
+ * can follow, and can carry parameters which obsolete the necessity
+ * to specify input module options. The choice of header section
+ * boundaries again reduces the likelyhood of false detection. When
+ * input module options were specified, they take precedence over
+ * input stream content.
+ * - The payload of the input stream (the protocol values) can take
+ * the form of a mere bytes sequence where every byte is a value
+ * (this is the default). Or values can be represented in textual
+ * format when either an input module option or the header section
+ * specify that the input is text. Individual protocol handlers can
+ * also prefer one format over another, while file content and
+ * module options take precedence as usual. Some protocols may not
+ * usefully be described by values only, or may involve values and
+ * numbers larger than a byte, which essentially makes text format
+ * a non-option for these situations.
+ * - The text format supports coments which silently get discarded.
+ * As well as pseudo comments which can affect the interpretation
+ * of the input text, and/or can control properties of protocols
+ * that exceed the mere submission of values. Think chip-select or
+ * ACK/NAK slots or similar.
+ * - It's understood that the text format is more expensive to process,
+ * but is also more versatile. It's assumed that the 'protocoldata'
+ * input format is used for small or mid size capture lengths. The
+ * input module enables quick access to data that became available
+ * by other means. For higher fidelity of real world traffic and for
+ * long captures the native format should be preferred. For error
+ * injection the VCD format might be a better match.
+ * - It should be obvious that raw bytes or input data in text form,
+ * as well as header fields can either be the content of a file on
+ * disk, or can be part of a pipe input. Either the earlier process
+ * in the pipe which provides the values, or an intermediate filter
+ * in the pipe, can provide the decoration.
+ * $ ./gen-values.sh | sigrok-cli -i - ...
+ * $ ./gen-values.sh | cat header - | sigrok-cli -i - ...
+ * - Since the input format supports automatic detection as well as
+ * parameter specs by means of input module options as well as in
+ * file content, the format lends itself equally well to pipelined
+ * or scripted as well as interactive use in different applications.
+ * For pipelines, the header as well as the values (as well as any
+ * mix of these pieces) can be kept in separate locations. Generators
+ * need not provide all of the input stream in a single invocation.
+ * - As a matter of convenience, especially when targetting upper layer
+ * protocol decoders, users need not construct "correctly configured"
+ * from the lower protocol's perspective) waveforms on the wire.
+ * Instead "naive" waveforms which match the decoders' default options
+ * can be used, which eliminates the need to configure non-default
+ * options in decoders (and redundantly do the same thing in the
+ * input module, just to have them match again).
+ * $ ./gen-values.sh | sigrok-cli \
+ * -i - -I protocoldata:protocol=uart:bitrate=57600:frameformat=8e2 \
+ * -P uart:parity=even:baudrate=57600
+ * $ ./gen-values.sh | sigrok-cli \
+ * -i - -I protocoldata:protocol=uart -P uart,midi
+ *
+ * Example invocations:
+ *
+ * $ sigrok-cli -I protocoldata --show
+ *
+ * $ echo "Hello sigrok protocol values!" | \
+ * sigrok-cli \
+ * -I protocoldata:protocol=uart -i - \
+ * -P uart:format=ascii -A uart=rx-data
+ *
+ * $ sigrok-cli -i file.bin -P uart -A uart=rx-data
+ * $ sigrok-cli -i file.txt -P uart:rx=rxtx -A uart
+ * $ sigrok-cli -i file.txt --show
+ * $ sigrok-cli -i file.txt -O ascii:width=4000 | $PAGER
+ *
+ * $ echo "# -- sigrok protocol data values file --" > header.txt
+ * $ echo "# -- sigrok protocol data header start --" >> header.txt
+ * $ echo "protocol=uart" >> header.txt
+ * $ echo "bitrate=100000" >> header.txt
+ * $ echo "frameformat=8e2" >> header.txt
+ * $ echo "textinput=yes" >> header.txt
+ * $ echo "# -- sigrok protocol data header end --" >> header.txt
+ * $ echo "# textinput: radix=16" > values.txt
+ * $ echo "0f 40 a6 28 fa 78 05 19 ee c2 92 70 58 62 09 a9 f1 ca 44 90 d1 07 19 02 00" >> values.txt
+ * $ head header.txt values.txt
+ * $ cat values.txt | cat header.txt - | \
+ * sigrok-cli -i - -P uart:baudrate=100000:parity=even,sbus_futaba -A sbus_futaba
+ *
+ * $ pulseview -i file-spi-text.txt &
+ *
+ * Known issues:
+ * - Only few protocols are implemented so far. Existing handlers have
+ * suggested which infrastructure is required for future extension.
+ * But future handlers may reveal more omissions or assumptions that
+ * need addressing.
+ * - Terminology may be inconsistent, because this input module supports
+ * several protocols which often differ in how they use terms. What is
+ * available:
+ * - The input module constructs waveforms that span multiple traces.
+ * Resulting waveforms are said to have a samplerate. Data that is
+ * kept in that waveform can have a bitrate. Which is essential for
+ * asynchronous communication, but could be unimportant for clocked
+ * protocols. Protocol handlers may adjust their output to enforce
+ * a bitrate, but need not. The timing is an approximation anyway,
+ * does not reflect pauses or jitter or turnarounds which real world
+ * traffic would reveal.
+ * - Protocol handlers can generate an arbitrary number of samples for
+ * a protocol data value. A maximum number of samples per value is
+ * assumed. Variable length samples sequences per data value or per
+ * invocation is supported (and can be considered the typical case).
+ * - Protocol handlers can configure differing widths for the samples
+ * that they derived from input data. These quanta get configured
+ * when the frame format gets interpreted, and are assumed to remain
+ * as they are across data value processing.
+ * - Data values can be considered "a frame" (as seen with UART). But
+ * data values could also be "bytes" or "words" in a protocol, while
+ * "frames" or "transfers" are implemented by different means (as
+ * seen with SPI or I2C). The typical approach would be to control a
+ * "select" signal by means of pseudo comments which are interleaved
+ * with data values.
+ * - Data values need not get forwarded to decoders. They might also
+ * control the processing of the following data values as well as
+ * the waveform construction. This is at the discretion of protocol
+ * handlers, think of slave addresses, preceeding field or value
+ * counts before their data values follow, etc.
+ * - Users may need to specify more options than expected when the file
+ * content is "incomplete". The sequence of scanning builtin defaults,
+ * then file content provided specs, then user specified specs, is
+ * yet to get done. Until then it helps being explicit and thorough.
+ *
+ * TODO (arbitrary order, could partially be outdated)
+ * - Implement the most appropriate order of option scanning. Use
+ * builtin defaults first, file content then, then user specified
+ * options (when available). This shall be most robust and correct.
+ * - Switch to "submit one sample" in feed queue API when available.
+ * The current implementation of this input module uses ugly ifdefs
+ * to adjust to either feed queue API approach.
+ * - (obsoleted by the introduction of support for text format input?)
+ * Introduce TLV support for the binary input format? u32be type,
+ * u64be length, u8[] payload. The complexity of the implementation
+ * in the input module, combined with the complexity of generating
+ * the input stream which uses TLV sections, are currently considered
+ * undesirable for this input module. Do we expect huge files where
+ * the computational cost of text conversion causes pain?
+ * - Extend the UART protocol handler. Implement separate RX and TX
+ * traces. Support tx-only, rx-only, and tx-then-rx input orders.
+ * - Add a 'parallel' protocol handler, which grabs a bit pattern and
+ * derives the waveform in straight forward ways? This would be similar
+ * to the raw binary input module, but the text format could improve
+ * readability, and the input module could generate a clock signal
+ * which isn't part of the input stream. That 'parallel' protocol
+ * could be used as a vehicle to bitbang any other protocol that is
+ * unknown to the input module. The approach is only limited by the
+ * input stream generator's imagination.
+ * - Add other protocol variants. The binary input format was very
+ * limiting, the text format could cover a lot of more cases:
+ * - CAN: Pseudo comments can communicate the frame's flags (and
+ * address type etc). The first data value can be the address. The
+ * second data value or a pseudo comment can hold the CAN frame's
+ * data length (bytes count). Other data values are the 0..8 data
+ * bytes. CAN-FD might be possible with minimal adjustment?
+ * - W1: Pseudo comments can start a frame (initiate RESET). First
+ * value can carry frame length. Data bytes follow. Scans can get
+ * represented as raw bytes (bit count results in full 8bit size).
+ * - Are more than 8 traces desirable? The initial implementation was
+ * motivated by serial communication (UART). More channels were not
+ * needed so far. Even QuadSPI and Hitachi displays fit onto 8 lines.
+ *
+ * See the sigrok.org file format wiki page for details about the syntax
+ * that is supported by this input module. Or see the top of the source
+ * file and its preprocessor symbols to quickly get an idea of known
+ * keywords in input files.
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <libsigrok/libsigrok.h>
+#include <string.h>
+#include <strings.h>
+
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "input/protocoldata"
+
+#define CHUNK_SIZE (4 * 1024 * 1024)
+
+/*
+ * Support optional automatic file type detection. Support optionally
+ * embedded options in a header section after the file detection magic
+ * and before the payload data (bytes or text).
+ */
+#define MAGIC_FILE_TYPE "# -- sigrok protocol data values file --"
+#define TEXT_HEAD_START "# -- sigrok protocol data header start --"
+#define TEXT_HEAD_END "# -- sigrok protocol data header end --"
+#define TEXT_COMM_LEADER "#"
+
+#define LABEL_SAMPLERATE "samplerate="
+#define LABEL_BITRATE "bitrate="
+#define LABEL_PROTOCOL "protocol="
+#define LABEL_FRAMEFORMAT "frameformat="
+#define LABEL_TEXTINPUT "textinput="
+
+/*
+ * Options which are embedded in pseudo comments and are related to
+ * how the input module reads the input text stream. Universally
+ * applicable to all text inputs regardless of protocol choice.
+ */
+#define TEXT_INPUT_PREFIX "textinput:"
+#define TEXT_INPUT_RADIX "radix="
+
+/*
+ * Protocol dependent frame formats, the default and absolute limits.
+ * Protocol dependent keywords in pseudo-comments.
+ *
+ * UART assumes 9x2 as the longest useful frameformat. Additional STOP
+ * bits let users insert idle phases between frames, until more general
+ * support for inter-frame gaps is in place. By default the protocol
+ * handler generously adds a few more idle bit times after a UART frame.
+ *
+ * SPI assumes exactly 8 bits per "word". And leaves bit slots around
+ * the byte transmission, to have space where CS asserts or releases.
+ * Including time where SCK changes to its idle level. And requires two
+ * samples per bit time (pos and neg clock phase). The "decoration" also
+ * helps users' interactive exploration of generated waveforms.
+ *
+ * I2C generously assumes six quanta per bit slot, to gracefully allow
+ * for reliable SCL and SDA transitions regardless of samples that result
+ * from prior communication. The longest waveform is a byte (with eight
+ * data bits and an ACK slot). Special symbols like START, and STOP will
+ * fit into that memory while it is not used to communicate a byte.
+ */
+#define UART_HANDLER_NAME "uart"
+#define UART_DFLT_SAMPLERATE SR_MHZ(1)
+#define UART_DFLT_BITRATE 115200
+#define UART_DFLT_FRAMEFMT "8n1"
+#define UART_MIN_DATABITS 5
+#define UART_MAX_DATABITS 9
+#define UART_MAX_STOPBITS 20
+#define UART_ADD_IDLEBITS 2
+#define UART_MAX_WAVELEN (1 + UART_MAX_DATABITS + 1 + UART_MAX_STOPBITS \
+ + UART_ADD_IDLEBITS)
+#define UART_FORMAT_INVERT "inverted"
+/* In addition the usual '8n1' et al are supported. */
+#define UART_PSEUDO_BREAK "break"
+#define UART_PSEUDO_IDLE "idle"
+
+#define SPI_HANDLER_NAME "spi"
+#define SPI_DFLT_SAMPLERATE SR_MHZ(10)
+#define SPI_DFLT_BITRATE SR_MHZ(1)
+#define SPI_DFLT_FRAMEFMT "cs-low,bits=8,mode=0,msb-first"
+#define SPI_MIN_DATABITS 8
+#define SPI_MAX_DATABITS 8
+#define SPI_MAX_WAVELEN (2 + 2 * SPI_MAX_DATABITS + 3)
+#define SPI_FORMAT_CS_LOW "cs-low"
+#define SPI_FORMAT_CS_HIGH "cs-high"
+#define SPI_FORMAT_DATA_BITS "bits="
+#define SPI_FORMAT_SPI_MODE "mode="
+#define SPI_FORMAT_MODE_CPOL "cpol="
+#define SPI_FORMAT_MODE_CPHA "cpha="
+#define SPI_FORMAT_MSB_FIRST "msb-first"
+#define SPI_FORMAT_LSB_FIRST "lsb-first"
+#define SPI_PSEUDO_MOSI_ONLY "mosi-only"
+#define SPI_PSEUDO_MOSI_FIXED "mosi-fixed="
+#define SPI_PSEUDO_MISO_ONLY "miso-only"
+#define SPI_PSEUDO_MISO_FIXED "miso-fixed="
+#define SPI_PSEUDO_MOSI_MISO "mosi-then-miso"
+#define SPI_PSEUDO_MISO_MOSI "miso-then-mosi"
+#define SPI_PSEUDO_CS_ASSERT "cs-assert"
+#define SPI_PSEUDO_CS_RELEASE "cs-release"
+#define SPI_PSEUDO_CS_NEXT "cs-auto-next="
+#define SPI_PSEUDO_IDLE "idle"
+
+#define I2C_HANDLER_NAME "i2c"
+#define I2C_DFLT_SAMPLERATE SR_MHZ(10)
+#define I2C_DFLT_BITRATE SR_KHZ(400)
+#define I2C_DFLT_FRAMEFMT "addr-7bit"
+#define I2C_BITTIME_SLOTS (1 + 8 + 1 + 1)
+#define I2C_BITTIME_QUANTA 6
+#define I2C_ADD_IDLESLOTS 2
+#define I2C_MAX_WAVELEN (I2C_BITTIME_QUANTA * I2C_BITTIME_SLOTS + I2C_ADD_IDLESLOTS)
+#define I2C_FORMAT_ADDR_7BIT "addr-7bit"
+#define I2C_FORMAT_ADDR_10BIT "addr-10bit"
+#define I2C_PSEUDO_START "start"
+#define I2C_PSEUDO_REP_START "repeat-start"
+#define I2C_PSEUDO_STOP "stop"
+#define I2C_PSEUDO_ADDR_WRITE "addr-write="
+#define I2C_PSEUDO_ADDR_READ "addr-read="
+#define I2C_PSEUDO_ACK_NEXT "ack-next="
+#define I2C_PSEUDO_ACK_ONCE "ack-next"
+
+enum textinput_t {
+ INPUT_UNSPEC,
+ INPUT_BYTES,
+ INPUT_TEXT,
+};
+
+static const char *input_format_texts[] = {
+ [INPUT_UNSPEC] = "from-file",
+ [INPUT_BYTES] = "raw-bytes",
+ [INPUT_TEXT] = "text-format",
+};
+
+struct spi_proto_context_t {
+ gboolean needs_mosi, has_mosi;
+ gboolean needs_miso, has_miso;
+ gboolean mosi_first;
+ gboolean cs_active;
+ size_t auto_cs_remain;
+ uint8_t mosi_byte, miso_byte;
+ uint8_t mosi_fixed_value;
+ gboolean mosi_is_fixed;
+ uint8_t miso_fixed_value;
+ gboolean miso_is_fixed;
+};
+
+struct i2c_proto_context_t {
+ size_t ack_remain;
+};
+
+struct context;
+
+struct proto_handler_t {
+ const char *name;
+ struct {
+ uint64_t samplerate;
+ uint64_t bitrate;
+ const char *frame_format;
+ enum textinput_t textinput;
+ } dflt;
+ struct {
+ size_t count;
+ const char **names;
+ } chans;
+ size_t priv_size;
+ int (*check_opts)(struct context *inc);
+ int (*config_frame)(struct context *inc);
+ int (*proc_pseudo)(struct sr_input *in, char *text);
+ int (*proc_value)(struct context *inc, uint32_t value);
+ int (*get_idle_capture)(struct context *inc,
+ size_t *bits, uint8_t *lvls);
+ int (*get_idle_interframe)(struct context *inc,
+ size_t *samples, uint8_t *lvls);
+};
+
+struct context {
+ /* User provided options. */
+ struct user_opts_t {
+ uint64_t samplerate;
+ uint64_t bitrate;
+ const char *proto_name;
+ const char *fmt_text;
+ enum textinput_t textinput;
+ } user_opts;
+ /* Derived at runtime. */
+ struct {
+ uint64_t samplerate;
+ uint64_t bitrate;
+ uint64_t samples_per_bit;
+ char *proto_name;
+ char *fmt_text;
+ enum textinput_t textinput;
+ enum proto_type_t {
+ PROTO_TYPE_NONE,
+ PROTO_TYPE_UART,
+ PROTO_TYPE_SPI,
+ PROTO_TYPE_I2C,
+ PROTO_TYPE_COUNT,
+ } protocol_type;
+ const struct proto_handler_t *prot_hdl;
+ void *prot_priv;
+ union {
+ struct uart_frame_fmt_opts {
+ size_t databit_count;
+ enum {
+ UART_PARITY_NONE,
+ UART_PARITY_ODD,
+ UART_PARITY_EVEN,
+ } parity_type;
+ size_t stopbit_count;
+ gboolean half_stopbit;
+ gboolean inverted;
+ } uart;
+ struct spi_frame_fmt_opts {
+ uint8_t cs_polarity;
+ size_t databit_count;
+ gboolean msb_first;
+ gboolean spi_mode_cpol;
+ gboolean spi_mode_cpha;
+ } spi;
+ struct i2c_frame_fmt_opts {
+ gboolean addr_10bit;
+ } i2c;
+ } frame_format;
+ } curr_opts;
+ /* Module stage. Logic output channels. Session feed. */
+ gboolean scanned_magic;
+ gboolean has_magic;
+ gboolean has_header;
+ gboolean got_header;
+ gboolean started;
+ gboolean meta_sent;
+ size_t channel_count;
+ const char **channel_names;
+ struct feed_queue_logic *feed_logic;
+ /*
+ * Internal state: Allocated space for a theoretical maximum
+ * bit count. Filled in bit pattern for the current data value.
+ * (Stuffing can result in varying bit counts across frames.)
+ *
+ * Keep the bits' width in sample numbers, as well as the bits'
+ * boundaries relative to the start of the protocol frame's
+ * start. Support a number of logic bits per bit time.
+ *
+ * Implementor's note: Due to development history terminology
+ * might slip here. Strictly speaking it's "waveform sections"
+ * that hold samples for a given number of cycles. "A bit" in
+ * the protocol can occupy multiple of these slots to e.g. have
+ * a synchronous clock, or to present setup and hold phases,
+ * etc. Sample data spans several logic signal traces. You get
+ * the idea ...
+ */
+ size_t max_frame_bits; /* Reserved. */
+ size_t top_frame_bits; /* Currently filled. */
+ struct {
+ size_t mul;
+ size_t div;
+ } *bit_scale; /* Quanta scaling. */
+ size_t *sample_edges;
+ size_t *sample_widths;
+ uint8_t *sample_levels; /* Sample data, logic traces. */
+ /* Common support for samples updating by manipulation. */
+ struct {
+ uint8_t idle_levels;
+ uint8_t curr_levels;
+ } samples;
+ /* Internal state of the input text reader. */
+ struct {
+ int base;
+ } read_text;
+ /* Manage state across .reset() calls. Robustness. */
+ struct proto_prev {
+ GSList *sr_channels;
+ GSList *sr_groups;
+ } prev;
+};
+
+/* {{{ frame bits manipulation, waveform construction */
+
+/*
+ * Primitives to construct waveforms for a protocol frame, by sequencing
+ * samples after data values were seen in the input stream. Individual
+ * protocol handlers will use these common routines.
+ *
+ * The general idea is: The protocol handler's options parser determines
+ * the frame format, and derives the maximum number of time slots needed
+ * to represent the waveform. Slots can scale differintly, proportions
+ * get configured once during initialization. All remaining operation
+ * receives arbitrarily interleaved data values and pseudo comments, uses
+ * the pre-allocated and pre-scaled time slots to construct waveforms,
+ * which then get sent to the session bus as if an acquisition device
+ * had captured wire traffic. For clocked signals the "coarse" timing
+ * should never be an issue. Protocol handlers are free to use as many
+ * time slots per bit time as they please or feel necessary.
+ */
+
+static int alloc_frame_storage(struct context *inc)
+{
+ size_t bits, alloc;
+
+ if (!inc)
+ return SR_ERR_ARG;
+
+ if (!inc->max_frame_bits)
+ return SR_ERR_DATA;
+
+ inc->top_frame_bits = 0;
+ bits = inc->max_frame_bits;
+
+ alloc = bits * sizeof(inc->sample_edges[0]);
+ inc->sample_edges = g_malloc0(alloc);
+ alloc = bits * sizeof(inc->sample_widths[0]);
+ inc->sample_widths = g_malloc0(alloc);
+ alloc = bits * sizeof(inc->sample_levels[0]);
+ inc->sample_levels = g_malloc0(alloc);
+ if (!inc->sample_edges || !inc->sample_widths || !inc->sample_levels)
+ return SR_ERR_MALLOC;
+
+ alloc = bits * sizeof(inc->bit_scale[0]);
+ inc->bit_scale = g_malloc0(alloc);
+ if (!inc->bit_scale)
+ return SR_ERR_MALLOC;
+
+ return SR_OK;
+}
+
+/*
+ * Assign an equal bit width to all bits in the frame. Derive the width
+ * from the bitrate and the sampelrate. Protocol handlers optionally can
+ * arrange for "odd bit widths" (either fractions, or multiples, or when
+ * desired any rational at all). Think half-bits, or think quanta within
+ * a bit time, depends on the protocol handler really.
+ *
+ * Implementation note: The input module assumes that the position of
+ * odd length bits will never vary during frame construction. The total
+ * length may vary, 'top' can be smaller than 'max' in every iteration.
+ * It is assumed that frames with odd-length bits have constant layout,
+ * and that stuffing protocols have same-width bits. Odd lengths also
+ * can support bit time quanta, while it's assumed that these always use
+ * the same layout for all generated frames. This constraint is kept in
+ * the implementation, until one of the supported protocols genuinely
+ * requires higher flexibility and the involved complexity and runtime
+ * cost of per-samplepoint adjustment.
+ */
+static int assign_bit_widths(struct context *inc)
+{
+ const struct proto_handler_t *handler;
+ int ret;
+ double bit_edge, bit_time, this_bit_time;
+ uint64_t bit_time_int, bit_time_prev, bit_times_total;
+ size_t idx;
+
+ if (!inc)
+ return SR_ERR_ARG;
+
+ /*
+ * Run the protocol handler's optional configure routine.
+ * It derives the maximum number of "bit slots" that are needed
+ * to represent a protocol frame's waveform.
+ */
+ handler = inc->curr_opts.prot_hdl;
+ if (handler && handler->config_frame) {
+ ret = handler->config_frame(inc);
+ if (ret != SR_OK)
+ return ret;
+ }
+
+ /* Assign bit widths to the protocol frame's bit positions. */
+ bit_time = inc->curr_opts.samplerate;
+ bit_time /= inc->curr_opts.bitrate;
+ inc->curr_opts.samples_per_bit = bit_time + 0.5;
+ sr_dbg("Samplerate %" PRIu64 ", bitrate %" PRIu64 ".",
+ inc->curr_opts.samplerate, inc->curr_opts.bitrate);
+ sr_dbg("Resulting bit width %.2f samples, int %" PRIu64 ".",
+ bit_time, inc->curr_opts.samples_per_bit);
+ bit_edge = 0.0;
+ bit_time_prev = 0;
+ bit_times_total = 0;
+ for (idx = 0; idx < inc->max_frame_bits; idx++) {
+ this_bit_time = bit_time;
+ if (inc->bit_scale[idx].mul)
+ this_bit_time *= inc->bit_scale[idx].mul;
+ if (inc->bit_scale[idx].div)
+ this_bit_time /= inc->bit_scale[idx].div;
+ bit_edge += this_bit_time;
+ bit_time_int = (uint64_t)(bit_edge + 0.5);
+ inc->sample_edges[idx] = bit_time_int;
+ bit_time_int -= bit_time_prev;
+ inc->sample_widths[idx] = bit_time_int;
+ bit_time_prev = inc->sample_edges[idx];
+ bit_times_total += bit_time_int;
+ sr_spew("Bit %zu, width %" PRIu64 ".", idx, bit_time_int);
+ }
+ sr_dbg("Maximum waveform width: %zu slots, %.2f / %zu 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;
+ while (count--) {
+ ret = feed_queue_logic_submit(inc->feed_logic, &data, sizeof(data));
+ 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;
+ while (count--) {
+ ret = feed_queue_logic_submit(inc->feed_logic, &data, sizeof(data));
+ 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;
+
+ inc = in->priv;
+
+ for (index = 0; index < inc->top_frame_bits; index++) {
+ data = inc->sample_levels[index];
+ count = inc->sample_widths[index];
+ while (count--) {
+ feed_queue_logic_submit(inc->feed_logic,
+ &data, sizeof(data));
+ }
+ }
+
+ 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 %lu.", 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 %lu.", 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 %lu.", 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)
+{
+
+ /* Describe four bit times, re-use the current pin levels. */
+ if (samplecount) {
+ *samplecount = inc->curr_opts.samples_per_bit;
+ *samplecount *= 4;
+ }
+ 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 *word;
+ char *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.)
+ * 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);
+ 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,
+};