]> sigrok.org Git - libsigrok.git/commitdiff
input/protocoldata: add input module for "protocol values" files
authorGerhard Sittig <redacted>
Tue, 14 May 2019 19:27:18 +0000 (21:27 +0200)
committerGerhard Sittig <redacted>
Wed, 12 Jul 2023 18:42:48 +0000 (20:42 +0200)
Implement an input module which accepts data values that may have been
gotten by arbitrary means (external capture utility, synthesized data),
and creates logic traces which mimic the respective protocol's format.
This pretends that a logic capture had been taken by sigrok means, which
can be fed to protocol decoders. The motivation is neither to perfectly
simulate a given protocol, nor to inject arbitrary error conditions and
edge cases, but instead to make externally provided data available to
sigrok protocol decoders.

The input module supports several user servicable options to describe
raw files which exclusively contain data bytes. Optional automatic file
format detection is also supported, as is an optional file header to
embed protocol properties with the bytes stream. Text formatted input
is supported as a strict alternative to raw data bytes, also supporting
comments as well as pseudo comments. The latter carry instructions to
control some aspects that are outside of the mere stream of data bytes.
Some protocols may exclusively be useful in combination with text input.

Protocol specific handlers can generate up to eight logic channels, an
arbitrary number of bits per protocol frame, including varying bit times
within a frame, and several quanta per bit time. It is assumed that this
implementation provides enough infrastructure to mimic an arbitrary set
of protocols beyond the initially supported set of UART, SPI, and I2C.

Makefile.am
src/input/input.c
src/input/protocoldata.c [new file with mode: 0644]

index 50cd2491b6a53c2e957757278e8d9ccb1d120f64..63b7cacd82d6e8fe14b9770f3dc751d7a60b2067 100644 (file)
@@ -88,6 +88,7 @@ libsigrok_la_SOURCES += \
        src/input/chronovu_la8.c \
        src/input/csv.c \
        src/input/logicport.c \
+       src/input/protocoldata.c \
        src/input/raw_analog.c \
        src/input/saleae.c \
        src/input/trace32_ad.c \
index c4957e7656a957d84379262e847f95663837435d..43272092b2bf34b36a6c02f0e8a460cbd83786eb 100644 (file)
@@ -72,6 +72,7 @@ extern SR_PRIV struct sr_input_module input_vcd;
 extern SR_PRIV struct sr_input_module input_wav;
 extern SR_PRIV struct sr_input_module input_raw_analog;
 extern SR_PRIV struct sr_input_module input_logicport;
+extern SR_PRIV struct sr_input_module input_protocoldata;
 extern SR_PRIV struct sr_input_module input_saleae;
 extern SR_PRIV struct sr_input_module input_null;
 /** @endcond */
@@ -88,6 +89,7 @@ static const struct sr_input_module *input_module_list[] = {
        &input_wav,
        &input_raw_analog,
        &input_logicport,
+       &input_protocoldata,
        &input_saleae,
        &input_null,
        NULL,
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,
+};