]> sigrok.org Git - libsigrok.git/blobdiff - src/input/protocoldata.c
input/protocoldata: add input module for "protocol values" files
[libsigrok.git] / src / input / protocoldata.c
diff --git a/src/input/protocoldata.c b/src/input/protocoldata.c
new file mode 100644 (file)
index 0000000..6be96f1
--- /dev/null
@@ -0,0 +1,3628 @@
+/*
+ * 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,
+};