blob: 905e33e4b2a3bbb2836ada750b4e42e2d94f576c [file] [log] [blame]
/*
* 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 / %" PRIu64 " samples.",
inc->max_frame_bits, bit_edge, bit_times_total);
return SR_OK;
}
/* Start accumulating the samples for a new part of the waveform. */
static int wave_clear_sequence(struct context *inc)
{
if (!inc)
return SR_ERR_ARG;
inc->top_frame_bits = 0;
return SR_OK;
}
/* Append channels' levels to the waveform for another period of samples. */
static int wave_append_pattern(struct context *inc, uint8_t sample)
{
if (!inc)
return SR_ERR_ARG;
if (inc->top_frame_bits >= inc->max_frame_bits)
return SR_ERR_DATA;
inc->sample_levels[inc->top_frame_bits++] = sample;
return SR_OK;
}
/* Initially assign idle levels, start the buffer from idle state. */
static void sample_buffer_preset(struct context *inc, uint8_t idle_sample)
{
inc->samples.idle_levels = idle_sample;
inc->samples.curr_levels = idle_sample;
}
/* Modify the samples buffer by assigning a given traces state. */
static void sample_buffer_assign(struct context *inc, uint8_t sample)
{
inc->samples.curr_levels = sample;
}
/* Modify the samples buffer by changing individual traces. */
static void sample_buffer_modify(struct context *inc,
uint8_t set_mask, uint8_t clr_mask)
{
inc->samples.curr_levels |= set_mask;
inc->samples.curr_levels &= ~clr_mask;
}
static void sample_buffer_raise(struct context *inc, uint8_t bits)
{
return sample_buffer_modify(inc, bits, 0);
}
static void sample_buffer_clear(struct context *inc, uint8_t bits)
{
return sample_buffer_modify(inc, 0, bits);
}
static void sample_buffer_setclr(struct context *inc,
gboolean level, uint8_t mask)
{
if (level)
sample_buffer_raise(inc, mask);
else
sample_buffer_clear(inc, mask);
}
static void sample_buffer_toggle(struct context *inc, uint8_t mask)
{
inc->samples.curr_levels ^= mask;
}
/* Reset current sample buffer to idle state. */
static void sample_buffer_toidle(struct context *inc)
{
inc->samples.curr_levels = inc->samples.idle_levels;
}
/* Append the buffered samples to the waveform memory. */
static int wave_append_buffer(struct context *inc)
{
return wave_append_pattern(inc, inc->samples.curr_levels);
}
/* Send idle level before the first generated frame and at end of capture. */
static int send_idle_capture(struct context *inc)
{
const struct proto_handler_t *handler;
size_t count;
uint8_t data;
int ret;
handler = inc->curr_opts.prot_hdl;
if (!handler->get_idle_capture)
return SR_OK;
ret = handler->get_idle_capture(inc, &count, &data);
if (ret != SR_OK)
return ret;
count *= inc->curr_opts.samples_per_bit;
ret = feed_queue_logic_submit_one(inc->feed_logic, &data, count);
if (ret != SR_OK)
return ret;
return SR_OK;
}
/* Optionally send idle level between protocol frames. */
static int send_idle_interframe(struct context *inc)
{
const struct proto_handler_t *handler;
size_t count;
uint8_t data;
int ret;
handler = inc->curr_opts.prot_hdl;
if (!handler->get_idle_interframe)
return SR_OK;
ret = handler->get_idle_interframe(inc, &count, &data);
if (ret != SR_OK)
return ret;
ret = feed_queue_logic_submit_one(inc->feed_logic, &data, count);
if (ret != SR_OK)
return ret;
return SR_OK;
}
/* Forward the previously accumulated samples of the waveform. */
static int send_frame(struct sr_input *in)
{
struct context *inc;
size_t count, index;
uint8_t data;
int ret;
inc = in->priv;
for (index = 0; index < inc->top_frame_bits; index++) {
data = inc->sample_levels[index];
count = inc->sample_widths[index];
ret = feed_queue_logic_submit_one(inc->feed_logic,
&data, count);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
/* }}} frame bits manipulation */
/* {{{ UART protocol handler */
enum uart_pin_t {
UART_PIN_RXTX,
};
#define UART_PINMASK_RXTX (1UL << UART_PIN_RXTX)
/* UART specific options and frame format check. */
static int uart_check_opts(struct context *inc)
{
struct uart_frame_fmt_opts *fmt_opts;
const char *fmt_text;
char **opts, *opt;
size_t opt_count, opt_idx;
int ret;
unsigned long v;
char par_text;
char *endp;
size_t total_bits;
if (!inc)
return SR_ERR_ARG;
fmt_opts = &inc->curr_opts.frame_format.uart;
/* Apply defaults before reading external spec. */
memset(fmt_opts, 0, sizeof(*fmt_opts));
fmt_opts->databit_count = 8;
fmt_opts->parity_type = UART_PARITY_NONE;
fmt_opts->stopbit_count = 1;
fmt_opts->half_stopbit = FALSE;
fmt_opts->inverted = FALSE;
/* Provide a default UART frame format. */
fmt_text = inc->curr_opts.fmt_text;
if (!fmt_text || !*fmt_text)
fmt_text = UART_DFLT_FRAMEFMT;
sr_dbg("UART frame format: %s.", fmt_text);
/* Parse the comma separated list of user provided options. */
opts = g_strsplit_set(fmt_text, ", ", 0);
opt_count = g_strv_length(opts);
for (opt_idx = 0; opt_idx < opt_count; opt_idx++) {
opt = opts[opt_idx];
if (!opt || !*opt)
continue;
sr_spew("UART format option: %s", opt);
/*
* Check for specific keywords. Before falling back to
* attempting the "8n1" et al interpretation.
*/
if (strcmp(opt, UART_FORMAT_INVERT) == 0) {
fmt_opts->inverted = TRUE;
continue;
}
/* Parse an "8n1", "8e2", "7o1", or similar input spec. */
/* Get the data bits count. */
endp = NULL;
ret = sr_atoul_base(opt, &v, &endp, 10);
if (ret != SR_OK || !endp)
return SR_ERR_DATA;
opt = endp;
if (v < UART_MIN_DATABITS || v > UART_MAX_DATABITS)
return SR_ERR_DATA;
fmt_opts->databit_count = v;
/* Get the parity type. */
par_text = tolower((int)*opt++);
switch (par_text) {
case 'n':
fmt_opts->parity_type = UART_PARITY_NONE;
break;
case 'o':
fmt_opts->parity_type = UART_PARITY_ODD;
break;
case 'e':
fmt_opts->parity_type = UART_PARITY_EVEN;
break;
default:
return SR_ERR_DATA;
}
/* Get the stop bits count. Supports half bits too. */
endp = NULL;
ret = sr_atoul_base(opt, &v, &endp, 10);
if (ret != SR_OK || !endp)
return SR_ERR_DATA;
opt = endp;
if (v > UART_MAX_STOPBITS)
return SR_ERR_DATA;
fmt_opts->stopbit_count = v;
if (g_ascii_strcasecmp(opt, ".5") == 0) {
opt += strlen(".5");
fmt_opts->half_stopbit = TRUE;
}
/* Incomplete consumption of input text is fatal. */
if (*opt) {
sr_err("Unprocessed frame format remainder: %s.", opt);
return SR_ERR_DATA;
}
continue;
}
g_strfreev(opts);
/*
* Calculate the total number of bit times in the UART frame.
* Add a few more bit times to the reserved space. They usually
* are not occupied during data transmission, but are useful to
* have for special symbols (BREAK, IDLE).
*/
total_bits = 1; /* START bit, unconditional. */
total_bits += fmt_opts->databit_count;
total_bits += (fmt_opts->parity_type != UART_PARITY_NONE) ? 1 : 0;
total_bits += fmt_opts->stopbit_count;
total_bits += fmt_opts->half_stopbit ? 1 : 0;
total_bits += UART_ADD_IDLEBITS;
sr_dbg("UART frame: total bits %zu.", total_bits);
if (total_bits > UART_MAX_WAVELEN)
return SR_ERR_DATA;
inc->max_frame_bits = total_bits;
return SR_OK;
}
/*
* Configure the frame's bit widths when not identical across the
* complete frame. Think half STOP bits.
* Preset the sample data for an idle bus.
*/
static int uart_config_frame(struct context *inc)
{
struct uart_frame_fmt_opts *fmt_opts;
size_t bit_idx;
uint8_t sample;
if (!inc)
return SR_ERR_ARG;
fmt_opts = &inc->curr_opts.frame_format.uart;
/*
* Position after the START bit. Advance over DATA, PARITY and
* (full) STOP bits. Then set the trailing STOP bit to half if
* needed. Make the trailing IDLE period after a UART frame
* wider than regular bit times. Add an even wider IDLE period
* which is used for special symbols.
*/
bit_idx = 1;
bit_idx += fmt_opts->databit_count;
bit_idx += (fmt_opts->parity_type == UART_PARITY_NONE) ? 0 : 1;
bit_idx += fmt_opts->stopbit_count;
if (fmt_opts->half_stopbit) {
sr_dbg("Setting bit index %zu to half width.", bit_idx);
inc->bit_scale[bit_idx].div = 2;
bit_idx++;
}
inc->bit_scale[bit_idx++].mul = 2;
inc->bit_scale[bit_idx++].mul = 4;
/* Start from idle signal levels (high when not inverted). */
sample = 0;
if (!fmt_opts->inverted)
sample |= UART_PINMASK_RXTX;
sample_buffer_preset(inc, sample);
return SR_OK;
}
/* Create samples for a special UART frame (IDLE, BREAK). */
static int uart_write_special(struct context *inc, uint8_t level)
{
struct uart_frame_fmt_opts *fmt_opts;
int ret;
size_t bits;
if (!inc)
return SR_ERR_ARG;
fmt_opts = &inc->curr_opts.frame_format.uart;
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
/*
* Set the same level for all bit slots, covering all of
* START and DATA (and PARITY) and STOP. This allows the
* simulation of BREAK and IDLE phases.
*/
if (fmt_opts->inverted)
level = !level;
sample_buffer_setclr(inc, level, UART_PINMASK_RXTX);
bits = 1; /* START */
bits += fmt_opts->databit_count;
bits += (fmt_opts->parity_type != UART_PARITY_NONE) ? 1 : 0;
bits += fmt_opts->stopbit_count;
bits += fmt_opts->half_stopbit ? 1 : 0;
while (bits--) {
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
/*
* Force a few more idle bit times. This does not affect a
* caller requested IDLE symbol. But helps separate (i.e.
* robustly detect) several caller requested BREAK symbols.
* Also separates those specials from subsequent data bytes.
*/
sample_buffer_toidle(inc);
bits = UART_ADD_IDLEBITS;
while (bits--) {
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
/* Process UART protocol specific pseudo comments. */
static int uart_proc_pseudo(struct sr_input *in, char *line)
{
struct context *inc;
char *word;
int ret;
inc = in->priv;
while (line) {
word = sr_text_next_word(line, &line);
if (!word)
break;
if (!*word)
continue;
if (strcmp(word, UART_PSEUDO_BREAK) == 0) {
ret = uart_write_special(inc, 0);
if (ret != SR_OK)
return ret;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
continue;
}
if (strcmp(word, UART_PSEUDO_IDLE) == 0) {
ret = uart_write_special(inc, 1);
if (ret != SR_OK)
return ret;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
continue;
}
return SR_ERR_DATA;
}
return SR_OK;
}
/*
* Create the UART frame's waveform for the given data value.
*
* In theory the protocol handler could setup START and STOP once during
* initialization. But the overhead compares to DATA and PARITY is small.
* And unconditional START/STOP would break the creation of BREAK and
* IDLE frames, or complicate their construction and recovery afterwards.
* A future implementation might as well support UART traffic on multiple
* traces, including interleaved bidirectional communication. So let's
* keep the implementation simple. Execution time is not a priority.
*/
static int uart_proc_value(struct context *inc, uint32_t value)
{
struct uart_frame_fmt_opts *fmt_opts;
int ret;
size_t bits;
int par_bit, data_bit;
if (!inc)
return SR_ERR_ARG;
fmt_opts = &inc->curr_opts.frame_format.uart;
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
/* START bit, unconditional, always 0. */
sample_buffer_clear(inc, UART_PINMASK_RXTX);
if (fmt_opts->inverted)
sample_buffer_toggle(inc, UART_PINMASK_RXTX);
ret = wave_append_buffer(inc);
/* DATA bits. Track parity here (unconditionally). */
par_bit = 0;
bits = fmt_opts->databit_count;
while (bits--) {
data_bit = value & 0x01;
value >>= 1;
par_bit ^= data_bit;
if (fmt_opts->inverted)
data_bit = !data_bit;
sample_buffer_setclr(inc, data_bit, UART_PINMASK_RXTX);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
/* PARITY bit. Emission is optional. */
switch (fmt_opts->parity_type) {
case UART_PARITY_ODD:
data_bit = par_bit ? 0 : 1;
bits = 1;
break;
case UART_PARITY_EVEN:
data_bit = par_bit ? 1 : 0;
bits = 1;
break;
default:
data_bit = 0;
bits = 0;
break;
}
if (bits) {
if (fmt_opts->inverted)
data_bit = !data_bit;
sample_buffer_setclr(inc, data_bit, UART_PINMASK_RXTX);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
/* STOP bits. Optional. */
sample_buffer_raise(inc, UART_PINMASK_RXTX);
if (fmt_opts->inverted)
sample_buffer_toggle(inc, UART_PINMASK_RXTX);
bits = fmt_opts->stopbit_count;
bits += fmt_opts->half_stopbit ? 1 : 0;
while (bits--) {
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
/*
* Force some idle time after the UART frame.
* A little shorter than for special symbols.
*/
sample_buffer_toidle(inc);
bits = UART_ADD_IDLEBITS - 1;
while (bits--) {
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
/* Start/end the logic trace with a few bit times of idle level. */
static int uart_get_idle_capture(struct context *inc,
size_t *bitcount, uint8_t *sample)
{
/* Describe a UART frame's length of idle level. */
if (bitcount)
*bitcount = inc->max_frame_bits;
if (sample)
*sample = inc->samples.idle_levels;
return SR_OK;
}
/* Arrange for a few samples of idle level between UART frames. */
static int uart_get_idle_interframe(struct context *inc,
size_t *samplecount, uint8_t *sample)
{
(void)inc;
/*
* Regular waveform creation for UART frames already includes
* padding between UART frames. That is why we don't need to
* add extra inter-frame samples. Yet prepare the implementation
* for when we need or want to add a few more idle samples.
*/
if (samplecount) {
*samplecount = inc->curr_opts.samples_per_bit;
*samplecount *= 0;
}
if (sample)
*sample = inc->samples.idle_levels;
return SR_OK;
}
/* }}} UART protocol handler */
/* {{{ SPI protocol handler */
enum spi_pin_t {
SPI_PIN_SCK,
SPI_PIN_MISO,
SPI_PIN_MOSI,
SPI_PIN_CS,
SPI_PIN_COUNT,
};
#define SPI_PINMASK_SCK (1UL << SPI_PIN_SCK)
#define SPI_PINMASK_MISO (1UL << SPI_PIN_MISO)
#define SPI_PINMASK_MOSI (1UL << SPI_PIN_MOSI)
#define SPI_PINMASK_CS (1UL << SPI_PIN_CS)
/* "Forget" data which was seen before. */
static void spi_value_discard_prev_data(struct context *inc)
{
struct spi_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
incs->has_mosi = !incs->needs_mosi;
incs->has_miso = !incs->needs_miso;
incs->mosi_byte = 0;
incs->miso_byte = 0;
}
/* Check whether all required values for the byte time were seen. */
static gboolean spi_value_is_bytes_complete(struct context *inc)
{
struct spi_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
return incs->has_mosi && incs->has_miso;
}
/* Arrange for data reception before waveform emission. */
static void spi_pseudo_data_order(struct context *inc,
gboolean needs_mosi, gboolean needs_miso, gboolean mosi_first)
{
struct spi_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
incs->needs_mosi = needs_mosi;
incs->needs_miso = needs_miso;
incs->mosi_first = mosi_first;
if (needs_mosi)
incs->mosi_is_fixed = FALSE;
if (needs_miso)
incs->miso_is_fixed = FALSE;
spi_value_discard_prev_data(inc);
}
static void spi_pseudo_mosi_fixed(struct context *inc, uint8_t v)
{
struct spi_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
incs->mosi_fixed_value = v;
incs->mosi_is_fixed = TRUE;
}
static void spi_pseudo_miso_fixed(struct context *inc, uint8_t v)
{
struct spi_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
incs->miso_fixed_value = v;
incs->miso_is_fixed = TRUE;
}
/* Explicit CS control. Arrange for next CS level, track state to keep it. */
static void spi_pseudo_select_control(struct context *inc, gboolean cs_active)
{
struct spi_frame_fmt_opts *fmt_opts;
struct spi_proto_context_t *incs;
uint8_t cs_level, sck_level;
fmt_opts = &inc->curr_opts.frame_format.spi;
incs = inc->curr_opts.prot_priv;
/* Track current "CS active" state. */
incs->cs_active = cs_active;
incs->auto_cs_remain = 0;
/* Derive current "CS pin level". Update sample data buffer. */
cs_level = 1 - fmt_opts->cs_polarity;
if (incs->cs_active)
cs_level = fmt_opts->cs_polarity;
sample_buffer_setclr(inc, cs_level, SPI_PINMASK_CS);
/* Derive the idle "SCK level" from the SPI mode's CPOL. */
sck_level = fmt_opts->spi_mode_cpol ? 1 : 0;
sample_buffer_setclr(inc, sck_level, SPI_PINMASK_SCK);
}
/* Arrange for automatic CS release after transfer length. Starts the phase. */
static void spi_pseudo_auto_select(struct context *inc, size_t length)
{
struct spi_frame_fmt_opts *fmt_opts;
struct spi_proto_context_t *incs;
uint8_t cs_level;
fmt_opts = &inc->curr_opts.frame_format.spi;
incs = inc->curr_opts.prot_priv;
/* Track current "CS active" state. */
incs->cs_active = TRUE;
incs->auto_cs_remain = length;
/* Derive current "CS pin level". Update sample data buffer. */
cs_level = 1 - fmt_opts->cs_polarity;
if (incs->cs_active)
cs_level = fmt_opts->cs_polarity;
sample_buffer_setclr(inc, cs_level, SPI_PINMASK_CS);
}
/* Check for automatic CS release. Decrements, yields result. No action here. */
static gboolean spi_auto_select_ends(struct context *inc)
{
struct spi_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
if (!incs->auto_cs_remain)
return FALSE;
incs->auto_cs_remain--;
if (incs->auto_cs_remain)
return FALSE;
/*
* DON'T release CS yet. The last data is yet to get sent.
* Keep the current "CS pin level", but tell the caller that
* CS will be released after transmission of that last data.
*/
return TRUE;
}
/* Update for automatic CS release after last data was sent. */
static void spi_auto_select_update(struct context *inc)
{
struct spi_frame_fmt_opts *fmt_opts;
struct spi_proto_context_t *incs;
uint8_t cs_level;
fmt_opts = &inc->curr_opts.frame_format.spi;
incs = inc->curr_opts.prot_priv;
/* Track current "CS active" state. */
incs->cs_active = FALSE;
incs->auto_cs_remain = 0;
/* Derive current "CS pin level". Map to bits pattern. */
cs_level = 1 - fmt_opts->cs_polarity;
sample_buffer_setclr(inc, cs_level, SPI_PINMASK_CS);
}
/*
* Create the waveforms for one SPI byte. Also cover idle periods:
* Dummy/padding bytes within a frame with clock. Idle lines outside
* of frames without clock edges. Optional automatic CS release with
* resulting inter-frame gap.
*/
static int spi_write_frame_patterns(struct context *inc,
gboolean idle, gboolean cs_release)
{
struct spi_proto_context_t *incs;
struct spi_frame_fmt_opts *fmt_opts;
int ret;
uint8_t mosi_bit, miso_bit;
size_t bits;
if (!inc)
return SR_ERR_ARG;
incs = inc->curr_opts.prot_priv;
fmt_opts = &inc->curr_opts.frame_format.spi;
/* Apply fixed values before drawing the waveform. */
if (incs->mosi_is_fixed)
incs->mosi_byte = incs->mosi_fixed_value;
if (incs->miso_is_fixed)
incs->miso_byte = incs->miso_fixed_value;
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
/* Provide two samples with idle SCK and current CS. */
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/*
* Provide two samples per DATABIT time slot. Keep CS as is.
* Toggle SCK according to CPHA specs. Shift out MOSI and MISO
* in the configured order.
*
* Force dummy MOSI/MISO bits for idle bytes within a frame.
* Skip SCK toggling for idle "frames" outside of active CS.
*/
bits = fmt_opts->databit_count;
while (bits--) {
/*
* First half-period. Provide next DATABIT values.
* Toggle SCK here when CPHA is set.
*/
if (fmt_opts->msb_first) {
mosi_bit = incs->mosi_byte & 0x80;
miso_bit = incs->miso_byte & 0x80;
incs->mosi_byte <<= 1;
incs->miso_byte <<= 1;
} else {
mosi_bit = incs->mosi_byte & 0x01;
miso_bit = incs->miso_byte & 0x01;
incs->mosi_byte >>= 1;
incs->miso_byte >>= 1;
}
if (incs->cs_active && !idle) {
sample_buffer_setclr(inc, mosi_bit, SPI_PINMASK_MOSI);
sample_buffer_setclr(inc, miso_bit, SPI_PINMASK_MISO);
}
if (fmt_opts->spi_mode_cpha && incs->cs_active)
sample_buffer_toggle(inc, SPI_PINMASK_SCK);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Second half-period. Keep DATABIT, toggle SCK. */
if (incs->cs_active)
sample_buffer_toggle(inc, SPI_PINMASK_SCK);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Toggle SCK again unless done above due to CPHA. */
if (!fmt_opts->spi_mode_cpha && incs->cs_active)
sample_buffer_toggle(inc, SPI_PINMASK_SCK);
}
/*
* Hold the waveform for another sample period. Happens to
* also communicate the most recent SCK pin level.
*
* Optionally auto-release the CS signal after sending the
* last data byte. Update the CS trace's level. Add another
* (long) bit slot to present an inter-frame gap.
*/
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
if (cs_release)
spi_auto_select_update(inc);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
if (cs_release) {
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
/* SPI specific options and frame format check. */
static int spi_check_opts(struct context *inc)
{
struct spi_frame_fmt_opts *fmt_opts;
const char *fmt_text;
char **opts, *opt;
size_t opt_count, opt_idx;
int ret;
unsigned long v;
char *endp;
size_t total_bits;
if (!inc)
return SR_ERR_ARG;
fmt_opts = &inc->curr_opts.frame_format.spi;
/* Setup defaults before reading external specs. */
fmt_opts->cs_polarity = 0;
fmt_opts->databit_count = SPI_MIN_DATABITS;
fmt_opts->msb_first = TRUE;
fmt_opts->spi_mode_cpol = FALSE;
fmt_opts->spi_mode_cpha = FALSE;
/* Provide a default SPI frame format. */
fmt_text = inc->curr_opts.fmt_text;
if (!fmt_text || !*fmt_text)
fmt_text = SPI_DFLT_FRAMEFMT;
sr_dbg("SPI frame format: %s.", fmt_text);
/* Accept comma separated key=value pairs of specs. */
opts = g_strsplit_set(fmt_text, ", ", 0);
opt_count = g_strv_length(opts);
for (opt_idx = 0; opt_idx < opt_count; opt_idx++) {
opt = opts[opt_idx];
if (!opt || !*opt)
continue;
sr_spew("SPI format option: %s.", opt);
if (strcmp(opt, SPI_FORMAT_CS_LOW) == 0) {
sr_spew("SPI chip select: low.");
fmt_opts->cs_polarity = 0;
continue;
}
if (strcmp(opt, SPI_FORMAT_CS_HIGH) == 0) {
sr_spew("SPI chip select: high.");
fmt_opts->cs_polarity = 1;
continue;
}
if (g_str_has_prefix(opt, SPI_FORMAT_DATA_BITS)) {
opt += strlen(SPI_FORMAT_DATA_BITS);
endp = NULL;
ret = sr_atoul_base(opt, &v, &endp, 10);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("SPI word size: %lu.", v);
if (v < SPI_MIN_DATABITS || v > SPI_MAX_DATABITS)
return SR_ERR_ARG;
fmt_opts->databit_count = v;
continue;
}
if (g_str_has_prefix(opt, SPI_FORMAT_SPI_MODE)) {
opt += strlen(SPI_FORMAT_SPI_MODE);
endp = NULL;
ret = sr_atoul_base(opt, &v, &endp, 10);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("SPI mode: %lu.", v);
if (v > 3)
return SR_ERR_ARG;
fmt_opts->spi_mode_cpol = v & (1UL << 1);
fmt_opts->spi_mode_cpha = v & (1UL << 0);
continue;
}
if (g_str_has_prefix(opt, SPI_FORMAT_MODE_CPOL)) {
opt += strlen(SPI_FORMAT_MODE_CPOL);
endp = NULL;
ret = sr_atoul_base(opt, &v, &endp, 10);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("SPI cpol: %lu.", v);
if (v > 1)
return SR_ERR_ARG;
fmt_opts->spi_mode_cpol = !!v;
continue;
}
if (g_str_has_prefix(opt, SPI_FORMAT_MODE_CPHA)) {
opt += strlen(SPI_FORMAT_MODE_CPHA);
endp = NULL;
ret = sr_atoul_base(opt, &v, &endp, 10);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("SPI cpha: %lu.", v);
if (v > 1)
return SR_ERR_ARG;
fmt_opts->spi_mode_cpha = !!v;
continue;
}
if (strcmp(opt, SPI_FORMAT_MSB_FIRST) == 0) {
sr_spew("SPI endianess: MSB first.");
fmt_opts->msb_first = 1;
continue;
}
if (strcmp(opt, SPI_FORMAT_LSB_FIRST) == 0) {
sr_spew("SPI endianess: LSB first.");
fmt_opts->msb_first = 0;
continue;
}
return SR_ERR_ARG;
}
g_strfreev(opts);
/*
* Get the total bit count. Add slack for CS control, and to
* visually separate bytes in frames. Multiply data bit count
* for the creation of two clock half-periods.
*/
total_bits = 2;
total_bits += 2 * fmt_opts->databit_count;
total_bits += 3;
sr_dbg("SPI frame: total bits %zu.", total_bits);
if (total_bits > SPI_MAX_WAVELEN)
return SR_ERR_DATA;
inc->max_frame_bits = total_bits;
return SR_OK;
}
/*
* Setup half-width slots for the two halves of a DATABIT time. Keep
* the "decoration" (CS control) at full width. Setup a rather long
* last slot for potential inter-frame gaps.
*
* Preset CS and SCK from their idle levels according to the frame format
* configuration. So that idle times outside of SPI transfers are covered
* with simple logic despite the protocol's flexibility.
*/
static int spi_config_frame(struct context *inc)
{
struct spi_frame_fmt_opts *fmt_opts;
size_t bit_idx, bit_count;
if (!inc)
return SR_ERR_ARG;
fmt_opts = &inc->curr_opts.frame_format.spi;
/* Configure DATABIT positions for half width (for clock period). */
bit_idx = 2;
bit_count = fmt_opts->databit_count;
while (bit_count--) {
inc->bit_scale[bit_idx + 0].div = 2;
inc->bit_scale[bit_idx + 1].div = 2;
bit_idx += 2;
}
bit_idx += 2;
inc->bit_scale[bit_idx].mul = fmt_opts->databit_count;
/*
* Seed the protocol handler's internal state before seeing
* first data values. To properly cover idle periods, and to
* operate correctly in the absence of pseudo comments.
*
* Use internal helpers for sample data initialization. Then
* grab the resulting pin levels as the idle state.
*/
spi_value_discard_prev_data(inc);
spi_pseudo_data_order(inc, TRUE, TRUE, TRUE);
spi_pseudo_select_control(inc, FALSE);
sample_buffer_preset(inc, inc->samples.curr_levels);
return SR_OK;
}
/*
* Process protocol dependent pseudo comments. Can affect future frame
* construction and submission, or can immediately emit "inter frame"
* bit patterns like chip select control.
*/
static int spi_proc_pseudo(struct sr_input *in, char *line)
{
struct context *inc;
char *word, *endp;
int ret;
unsigned long v;
inc = in->priv;
while (line) {
word = sr_text_next_word(line, &line);
if (!word)
break;
if (!*word)
continue;
if (strcmp(word, SPI_PSEUDO_MOSI_ONLY) == 0) {
sr_spew("SPI pseudo: MOSI only");
spi_pseudo_data_order(inc, TRUE, FALSE, TRUE);
continue;
}
if (g_str_has_prefix(word, SPI_PSEUDO_MOSI_FIXED)) {
word += strlen(SPI_PSEUDO_MOSI_FIXED);
endp = NULL;
ret = sr_atoul_base(word, &v, &endp, inc->read_text.base);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("SPI pseudo: MOSI fixed %lu", v);
spi_pseudo_mosi_fixed(inc, v);
continue;
}
if (strcmp(word, SPI_PSEUDO_MISO_ONLY) == 0) {
sr_spew("SPI pseudo: MISO only");
spi_pseudo_data_order(inc, FALSE, TRUE, FALSE);
continue;
}
if (g_str_has_prefix(word, SPI_PSEUDO_MISO_FIXED)) {
word += strlen(SPI_PSEUDO_MISO_FIXED);
endp = NULL;
ret = sr_atoul_base(word, &v, &endp, inc->read_text.base);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("SPI pseudo: MISO fixed %lu", v);
spi_pseudo_miso_fixed(inc, v);
continue;
}
if (strcmp(word, SPI_PSEUDO_MOSI_MISO) == 0) {
sr_spew("SPI pseudo: MOSI then MISO");
spi_pseudo_data_order(inc, TRUE, TRUE, TRUE);
continue;
}
if (strcmp(word, SPI_PSEUDO_MISO_MOSI) == 0) {
sr_spew("SPI pseudo: MISO then MOSI");
spi_pseudo_data_order(inc, TRUE, TRUE, FALSE);
continue;
}
if (strcmp(word, SPI_PSEUDO_CS_ASSERT) == 0) {
sr_spew("SPI pseudo: CS assert");
spi_pseudo_select_control(inc, TRUE);
continue;
}
if (strcmp(word, SPI_PSEUDO_CS_RELEASE) == 0) {
sr_spew("SPI pseudo: CS release");
/* Release CS. Force IDLE to display the pin change. */
spi_pseudo_select_control(inc, FALSE);
ret = spi_write_frame_patterns(inc, TRUE, FALSE);
if (ret != SR_OK)
return ret;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
continue;
}
if (g_str_has_prefix(word, SPI_PSEUDO_CS_NEXT)) {
word += strlen(SPI_PSEUDO_CS_NEXT);
endp = NULL;
ret = sr_atoul_base(word, &v, &endp, 0);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("SPI pseudo: CS auto next %lu", v);
spi_pseudo_auto_select(inc, v);
continue;
}
if (strcmp(word, SPI_PSEUDO_IDLE) == 0) {
sr_spew("SPI pseudo: idle");
ret = spi_write_frame_patterns(inc, TRUE, FALSE);
if (ret != SR_OK)
return ret;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
continue;
}
return SR_ERR_DATA;
}
return SR_OK;
}
/*
* Create the frame's waveform for the given data value. For bidirectional
* communication multiple routine invocations accumulate data bits, while
* the last invocation completes the frame preparation.
*/
static int spi_proc_value(struct context *inc, uint32_t value)
{
struct spi_proto_context_t *incs;
gboolean taken;
int ret;
gboolean auto_cs_end;
if (!inc)
return SR_ERR_ARG;
incs = inc->curr_opts.prot_priv;
/*
* Discard previous data when we get here after having completed
* a previous frame. This roundtrip from filling in to clearing
* is required to have the caller emit the waveform that we have
* constructed after receiving data values.
*/
if (spi_value_is_bytes_complete(inc)) {
sr_spew("SPI value: discarding previous data");
spi_value_discard_prev_data(inc);
}
/*
* Consume the caller provided value. Apply data in the order
* that was configured before.
*/
taken = FALSE;
if (!taken && incs->mosi_first && !incs->has_mosi) {
sr_spew("SPI value: grabbing MOSI value");
incs->mosi_byte = value & 0xff;
incs->has_mosi = TRUE;
taken = TRUE;
}
if (!taken && !incs->has_miso) {
sr_spew("SPI value: grabbing MISO value");
incs->miso_byte = value & 0xff;
incs->has_miso = TRUE;
}
if (!taken && !incs->mosi_first && !incs->has_mosi) {
sr_spew("SPI value: grabbing MOSI value");
incs->mosi_byte = value & 0xff;
incs->has_mosi = TRUE;
taken = TRUE;
}
/*
* Generate the waveform when all data values in a byte time
* were seen (all MOSI and MISO including their being optional
* or fixed values).
*
* Optionally automatically release CS after a given number of
* data bytes, when requested by the input stream.
*/
if (!spi_value_is_bytes_complete(inc)) {
sr_spew("SPI value: need more values");
return +1;
}
auto_cs_end = spi_auto_select_ends(inc);
sr_spew("SPI value: frame complete, drawing, auto CS %d", auto_cs_end);
ret = spi_write_frame_patterns(inc, FALSE, auto_cs_end);
if (ret != SR_OK)
return ret;
return 0;
}
/* Start/end the logic trace with a few bit times of idle level. */
static int spi_get_idle_capture(struct context *inc,
size_t *bitcount, uint8_t *sample)
{
/* Describe one byte time of idle level. */
if (bitcount)
*bitcount = inc->max_frame_bits;
if (sample)
*sample = inc->samples.idle_levels;
return SR_OK;
}
/* Arrange for a few samples of idle level between UART frames. */
static int spi_get_idle_interframe(struct context *inc,
size_t *samplecount, uint8_t *sample)
{
/* Describe four bit times, re-use most recent pin levels. */
if (samplecount) {
*samplecount = inc->curr_opts.samples_per_bit;
*samplecount *= 4;
}
if (sample)
*sample = inc->samples.curr_levels;
return SR_OK;
}
/* }}} SPI protocol handler */
/* {{{ I2C protocol handler */
enum i2c_pin_t {
I2C_PIN_SCL,
I2C_PIN_SDA,
I2C_PIN_COUNT,
};
#define I2C_PINMASK_SCL (1UL << I2C_PIN_SCL)
#define I2C_PINMASK_SDA (1UL << I2C_PIN_SDA)
/* Arrange for automatic ACK for a given number of data bytes. */
static void i2c_auto_ack_start(struct context *inc, size_t count)
{
struct i2c_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
incs->ack_remain = count;
}
/* Check whether automatic ACK is still applicable. Decrements. */
static gboolean i2c_auto_ack_avail(struct context *inc)
{
struct i2c_proto_context_t *incs;
incs = inc->curr_opts.prot_priv;
if (!incs->ack_remain)
return FALSE;
if (incs->ack_remain--)
return TRUE;
return FALSE;
}
/* Occupy the slots where START/STOP would be. Keep current levels. */
static int i2c_write_nothing(struct context *inc)
{
size_t reps;
int ret;
reps = I2C_BITTIME_QUANTA;
while (reps--) {
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
/*
* Construct a START symbol. Occupy a full bit time in the waveform.
* Can also be used as REPEAT START due to its conservative signalling.
*
* Definition of START: Falling SDA while SCL is high.
* Repeated START: A START without a preceeding STOP.
*/
static int i2c_write_start(struct context *inc)
{
int ret;
/*
* Important! Assumes that either SDA and SCL already are
* high (true when we come here from an idle bus). Or that
* SCL already is low before SDA potentially changes (this
* is true for preceeding START or REPEAT START or DATA BIT
* symbols).
*
* Implementation detail: This START implementation can be
* used for REPEAT START as well. The signalling sequence is
* conservatively done.
*/
/* Enforce SDA high. */
sample_buffer_raise(inc, I2C_PINMASK_SDA);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Enforce SCL high. */
sample_buffer_raise(inc, I2C_PINMASK_SCL);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Keep high SCL and high SDA for another period. */
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Falling SDA while SCL is high. */
sample_buffer_clear(inc, I2C_PINMASK_SDA);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Keep high SCL and low SDA for one more period. */
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/*
* Lower SCL here already. Which kind of prepares DATA BIT
* times (fits a data bit's start condition, does not harm).
* Improves back to back START and (repeated) START as well
* as STOP without preceeding DATA BIT.
*/
sample_buffer_clear(inc, I2C_PINMASK_SCL);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
return SR_OK;
}
/*
* Construct a STOP symbol. Occupy a full bit time in the waveform.
*
* Definition of STOP: Rising SDA while SCL is high.
*/
static int i2c_write_stop(struct context *inc)
{
int ret;
/* Enforce SCL low before SDA changes. */
sample_buffer_clear(inc, I2C_PINMASK_SCL);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Enforce SDA low (can change while SCL is low). */
sample_buffer_clear(inc, I2C_PINMASK_SDA);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Rise SCL high while SDA is low. */
sample_buffer_raise(inc, I2C_PINMASK_SCL);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Keep high SCL and low SDA for another period. */
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Rising SDA. */
sample_buffer_raise(inc, I2C_PINMASK_SDA);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Keep high SCL and high SDA for one more periods. */
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
return SR_OK;
}
/*
* Construct a DATA BIT symbol. Occupy a full bit time in the waveform.
*
* SDA can change while SCL is low. SDA must be kept while SCL is high.
*/
static int i2c_write_bit(struct context *inc, uint8_t value)
{
int ret;
/* Enforce SCL low before SDA changes. */
sample_buffer_clear(inc, I2C_PINMASK_SCL);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Setup SDA pin level while SCL is low. */
sample_buffer_setclr(inc, value, I2C_PINMASK_SDA);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Rising SCL, starting SDA validity. */
sample_buffer_raise(inc, I2C_PINMASK_SCL);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Keep SDA level with high SCL for two more periods. */
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
/* Falling SCL, terminates SDA validity. */
sample_buffer_clear(inc, I2C_PINMASK_SCL);
ret = wave_append_buffer(inc);
if (ret != SR_OK)
return ret;
return SR_OK;
}
/* Create a waveform for the eight data bits and the ACK/NAK slot. */
static int i2c_write_byte(struct context *inc, uint8_t value, uint8_t ack)
{
size_t bit_mask, bit_value;
int ret;
/* Keep an empty bit time before the data byte. */
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
/* Send 8 data bits, MSB first. */
bit_mask = 0x80;
while (bit_mask) {
bit_value = value & bit_mask;
bit_mask >>= 1;
ret = i2c_write_bit(inc, bit_value);
if (ret != SR_OK)
return ret;
}
/* Send ACK, which is low active. NAK is recessive, high. */
bit_value = !ack;
ret = i2c_write_bit(inc, bit_value);
if (ret != SR_OK)
return ret;
/* Keep an empty bit time after the data byte. */
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
return SR_OK;
}
/* Send slave address (7bit or 10bit, 1 or 2 bytes). Consumes one ACK. */
static int i2c_send_address(struct sr_input *in, uint16_t addr, gboolean read)
{
struct context *inc;
struct i2c_frame_fmt_opts *fmt_opts;
gboolean with_ack;
uint8_t addr_byte, rw_bit;
int ret;
inc = in->priv;
fmt_opts = &inc->curr_opts.frame_format.i2c;
addr &= 0x3ff;
rw_bit = read ? 1 : 0;
with_ack = i2c_auto_ack_avail(inc);
if (!fmt_opts->addr_10bit) {
/* 7 bit address, the simple case. */
addr_byte = addr & 0x7f;
addr_byte <<= 1;
addr_byte |= rw_bit;
sr_spew("I2C 7bit address, byte 0x%" PRIx8, addr_byte);
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
ret = i2c_write_byte(inc, addr_byte, with_ack);
if (ret != SR_OK)
return ret;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
} else {
/*
* 10 bit address, need to write two bytes: First byte
* with prefix 0xf0, upper most 2 address bits, and R/W.
* Second byte with lower 8 address bits.
*/
addr_byte = addr >> 8;
addr_byte <<= 1;
addr_byte |= 0xf0;
addr_byte |= rw_bit;
sr_spew("I2C 10bit address, byte 0x%" PRIx8, addr_byte);
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
ret = i2c_write_byte(inc, addr_byte, with_ack);
if (ret != SR_OK)
return ret;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
addr_byte = addr & 0xff;
sr_spew("I2C 10bit address, byte 0x%" PRIx8, addr_byte);
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
ret = i2c_write_byte(inc, addr_byte, with_ack);
if (ret != SR_OK)
return ret;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
/* I2C specific options and frame format check. */
static int i2c_check_opts(struct context *inc)
{
struct i2c_frame_fmt_opts *fmt_opts;
const char *fmt_text;
char **opts, *opt;
size_t opt_count, opt_idx;
size_t total_bits;
if (!inc)
return SR_ERR_ARG;
fmt_opts = &inc->curr_opts.frame_format.i2c;
/* Apply defaults before reading external specs. */
memset(fmt_opts, 0, sizeof(*fmt_opts));
fmt_opts->addr_10bit = FALSE;
/* Provide a default I2C frame format. */
fmt_text = inc->curr_opts.fmt_text;
if (!fmt_text || !*fmt_text)
fmt_text = I2C_DFLT_FRAMEFMT;
sr_dbg("I2C frame format: %s.", fmt_text);
/* Accept comma separated key=value pairs of specs. */
opts = g_strsplit_set(fmt_text, ", ", 0);
opt_count = g_strv_length(opts);
for (opt_idx = 0; opt_idx < opt_count; opt_idx++) {
opt = opts[opt_idx];
if (!opt || !*opt)
continue;
sr_spew("I2C format option: %s.", opt);
if (strcmp(opt, I2C_FORMAT_ADDR_7BIT) == 0) {
sr_spew("I2C address: 7 bit");
fmt_opts->addr_10bit = FALSE;
continue;
}
if (strcmp(opt, I2C_FORMAT_ADDR_10BIT) == 0) {
sr_spew("I2C address: 10 bit");
fmt_opts->addr_10bit = TRUE;
continue;
}
return SR_ERR_ARG;
}
g_strfreev(opts);
/* Get the total slot count. Leave plenty room for convenience. */
total_bits = 0;
total_bits += I2C_BITTIME_SLOTS;
total_bits *= I2C_BITTIME_QUANTA;
total_bits += I2C_ADD_IDLESLOTS;
sr_dbg("I2C frame: total bits %zu.", total_bits);
if (total_bits > I2C_MAX_WAVELEN)
return SR_ERR_DATA;
inc->max_frame_bits = total_bits;
return SR_OK;
}
/*
* Don't bother with wide and narrow slots, just assume equal size for
* them all. Edges will occupy exactly one sample, then levels are kept.
* This protocol handler's oversampling should be sufficient for decoders
* to extract the content from generated waveforms.
*
* Start with high levels on SCL and SDA for an idle bus condition.
*/
static int i2c_config_frame(struct context *inc)
{
struct i2c_proto_context_t *incs;
size_t bit_idx;
uint8_t sample;
if (!inc)
return SR_ERR_ARG;
incs = inc->curr_opts.prot_priv;
memset(incs, 0, sizeof(*incs));
incs->ack_remain = 0;
/*
* Adjust all time slots since they represent a smaller quanta
* of an I2C bit time.
*/
for (bit_idx = 0; bit_idx < inc->max_frame_bits; bit_idx++) {
inc->bit_scale[bit_idx].div = I2C_BITTIME_QUANTA;
}
sample = 0;
sample |= I2C_PINMASK_SCL;
sample |= I2C_PINMASK_SDA;
sample_buffer_preset(inc, sample);
return SR_OK;
}
/*
* Process protocol dependent pseudo comments. Can affect future frame
* construction and submission, or can immediately emit "inter frame"
* bit patterns like START/STOP control. Use wide waveforms for these
* transfer controls, put the special symbol nicely centered. Supports
* users during interactive exploration of generated waveforms.
*/
static int i2c_proc_pseudo(struct sr_input *in, char *line)
{
struct context *inc;
char *word, *endp;
int ret;
unsigned long v;
size_t bits;
inc = in->priv;
while (line) {
word = sr_text_next_word(line, &line);
if (!word)
break;
if (!*word)
continue;
sr_spew("I2C pseudo: word %s", word);
if (strcmp(word, I2C_PSEUDO_START) == 0) {
sr_spew("I2C pseudo: send START");
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
bits = I2C_BITTIME_SLOTS / 2;
while (bits--) {
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
}
ret = i2c_write_start(inc);
if (ret != SR_OK)
return ret;
bits = I2C_BITTIME_SLOTS / 2;
while (bits--) {
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
}
ret = send_frame(in);
if (ret != SR_OK)
return ret;
continue;
}
if (strcmp(word, I2C_PSEUDO_REP_START) == 0) {
sr_spew("I2C pseudo: send REPEAT START");
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
bits = I2C_BITTIME_SLOTS / 2;
while (bits--) {
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
}
ret = i2c_write_start(inc);
if (ret != SR_OK)
return ret;
bits = I2C_BITTIME_SLOTS / 2;
while (bits--) {
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
}
ret = send_frame(in);
if (ret != SR_OK)
return ret;
continue;
}
if (strcmp(word, I2C_PSEUDO_STOP) == 0) {
sr_spew("I2C pseudo: send STOP");
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
bits = I2C_BITTIME_SLOTS / 2;
while (bits--) {
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
}
ret = i2c_write_stop(inc);
if (ret != SR_OK)
return ret;
bits = I2C_BITTIME_SLOTS / 2;
while (bits--) {
ret = i2c_write_nothing(inc);
if (ret != SR_OK)
return ret;
}
ret = send_frame(in);
if (ret != SR_OK)
return ret;
continue;
}
if (g_str_has_prefix(word, I2C_PSEUDO_ADDR_WRITE)) {
word += strlen(I2C_PSEUDO_ADDR_WRITE);
endp = NULL;
ret = sr_atoul_base(word, &v, &endp, 0);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("I2C pseudo: addr write %lu", v);
ret = i2c_send_address(in, v, FALSE);
if (ret != SR_OK)
return ret;
continue;
}
if (g_str_has_prefix(word, I2C_PSEUDO_ADDR_READ)) {
word += strlen(I2C_PSEUDO_ADDR_READ);
endp = NULL;
ret = sr_atoul_base(word, &v, &endp, 0);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("I2C pseudo: addr read %lu", v);
ret = i2c_send_address(in, v, TRUE);
if (ret != SR_OK)
return ret;
continue;
}
if (g_str_has_prefix(word, I2C_PSEUDO_ACK_NEXT)) {
word += strlen(I2C_PSEUDO_ACK_NEXT);
endp = NULL;
ret = sr_atoul_base(word, &v, &endp, 0);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_ARG;
sr_spew("i2c pseudo: ack next %lu", v);
i2c_auto_ack_start(inc, v);
continue;
}
if (strcmp(word, I2C_PSEUDO_ACK_ONCE) == 0) {
sr_spew("i2c pseudo: ack once");
i2c_auto_ack_start(inc, 1);
continue;
}
return SR_ERR_DATA;
}
return SR_OK;
}
/*
* Create the frame's waveform for the given data value. Automatically
* track ACK bits, Fallback to NAK when externally specified ACK counts
* have expired. The caller sends the waveform that we created.
*/
static int i2c_proc_value(struct context *inc, uint32_t value)
{
gboolean with_ack;
int ret;
if (!inc)
return SR_ERR_ARG;
with_ack = i2c_auto_ack_avail(inc);
ret = wave_clear_sequence(inc);
if (ret != SR_OK)
return ret;
ret = i2c_write_byte(inc, value, with_ack);
if (ret != SR_OK)
return ret;
return 0;
}
/* Start/end the logic trace with a few bit times of idle level. */
static int i2c_get_idle_capture(struct context *inc,
size_t *bitcount, uint8_t *sample)
{
/* Describe a byte's time of idle level. */
if (bitcount)
*bitcount = I2C_BITTIME_SLOTS;
if (sample)
*sample = inc->samples.idle_levels;
return SR_OK;
}
/* Arrange for a few samples of idle level between UART frames. */
static int i2c_get_idle_interframe(struct context *inc,
size_t *samplecount, uint8_t *sample)
{
/*
* The space around regular bytes already is sufficient. We
* don't need to generate an inter-frame gap, but the code is
* prepared to in case we want to in the future.
*/
if (samplecount) {
*samplecount = inc->curr_opts.samples_per_bit;
*samplecount *= 0;
}
if (sample)
*sample = inc->samples.curr_levels;
return SR_OK;
}
/* }}} I2C protocol handler */
/* {{{ protocol dispatching */
/*
* The list of supported protocols and their handlers, including
* protocol specific defaults. The first item after the NONE slot
* is the default protocol, and takes effect in the absence of any
* user provided or file content provided spec.
*/
static const struct proto_handler_t protocols[PROTO_TYPE_COUNT] = {
[PROTO_TYPE_UART] = {
UART_HANDLER_NAME,
{
UART_DFLT_SAMPLERATE,
UART_DFLT_BITRATE, UART_DFLT_FRAMEFMT,
INPUT_BYTES,
},
{
1, (const char *[]){
[UART_PIN_RXTX] = "rxtx",
},
},
0,
uart_check_opts,
uart_config_frame,
uart_proc_pseudo,
uart_proc_value,
uart_get_idle_capture,
uart_get_idle_interframe,
},
[PROTO_TYPE_SPI] = {
SPI_HANDLER_NAME,
{
SPI_DFLT_SAMPLERATE,
SPI_DFLT_BITRATE, SPI_DFLT_FRAMEFMT,
INPUT_TEXT,
},
{
4, (const char *[]){
[SPI_PIN_SCK] = "sck",
[SPI_PIN_MISO] = "miso",
[SPI_PIN_MOSI] = "mosi",
[SPI_PIN_CS] = "cs",
},
},
sizeof(struct spi_proto_context_t),
spi_check_opts,
spi_config_frame,
spi_proc_pseudo,
spi_proc_value,
spi_get_idle_capture,
spi_get_idle_interframe,
},
[PROTO_TYPE_I2C] = {
I2C_HANDLER_NAME,
{
I2C_DFLT_SAMPLERATE,
I2C_DFLT_BITRATE, I2C_DFLT_FRAMEFMT,
INPUT_TEXT,
},
{
2, (const char *[]){
[I2C_PIN_SCL] = "scl",
[I2C_PIN_SDA] = "sda",
},
},
sizeof(struct i2c_proto_context_t),
i2c_check_opts,
i2c_config_frame,
i2c_proc_pseudo,
i2c_proc_value,
i2c_get_idle_capture,
i2c_get_idle_interframe,
},
};
static int lookup_protocol_name(struct context *inc)
{
const char *name;
const struct proto_handler_t *handler;
size_t idx;
void *priv;
/*
* Silence compiler warnings. Protocol handlers are free to use
* several alternative sets of primitives for their operation.
* Not using part of the API is nothing worth warning about.
*/
(void)sample_buffer_assign;
if (!inc)
return SR_ERR_ARG;
inc->curr_opts.protocol_type = PROTO_TYPE_NONE;
inc->curr_opts.prot_hdl = NULL;
name = inc->curr_opts.proto_name;
if (!name || !*name) {
/* Fallback to first item after NONE slot. */
handler = &protocols[PROTO_TYPE_NONE + 1];
name = handler->name;
}
for (idx = 0; idx < ARRAY_SIZE(protocols); idx++) {
if (idx == PROTO_TYPE_NONE)
continue;
handler = &protocols[idx];
if (!handler->name || !*handler->name)
continue;
if (strcmp(name, handler->name) != 0)
continue;
inc->curr_opts.protocol_type = idx;
inc->curr_opts.prot_hdl = handler;
if (handler->priv_size) {
priv = g_malloc0(handler->priv_size);
if (!priv)
return SR_ERR_MALLOC;
inc->curr_opts.prot_priv = priv;
}
return SR_OK;
}
return SR_ERR_DATA;
}
/* }}} protocol dispatching */
/* {{{ text/binary input file reader */
/**
* Checks for UTF BOM, removes it when found at the start of the buffer.
*
* @param[in] buf The accumulated input buffer.
*/
static void check_remove_bom(GString *buf)
{
static const char *bom_text = "\xef\xbb\xbf";
if (buf->len < strlen(bom_text))
return;
if (strncmp(buf->str, bom_text, strlen(bom_text)) != 0)
return;
g_string_erase(buf, 0, strlen(bom_text));
}
/**
* Checks for presence of a caption, yields the position after its text line.
*
* @param[in] buf The accumulated input buffer.
* @param[in] caption The text to search for (NUL terminated ASCII literal).
* @param[in] max_pos The maximum length to search for.
*
* @returns The position after the text line which contains the caption.
* Or #NULL when either the caption or the end-of-line was not found.
*/
static char *have_text_line(GString *buf, const char *caption, size_t max_pos)
{
size_t cap_len, rem_len;
char *p_read, *p_found;
cap_len = strlen(caption);
rem_len = buf->len;
p_read = buf->str;
/* Search for the occurance of the caption itself. */
if (!max_pos) {
/* Caption must be at the start of the buffer. */
if (rem_len < cap_len)
return NULL;
if (strncmp(p_read, caption, cap_len) != 0)
return NULL;
} else {
/* Caption can be anywhere up to a max position. */
p_found = g_strstr_len(p_read, rem_len, caption);
if (!p_found)
return NULL;
/* Pretend that caption had been rather long. */
cap_len += p_found - p_read;
}
/*
* Advance over the caption. Advance over end-of-line. Supports
* several end-of-line conditions, but rejects unexpected trailer
* after the caption and before the end-of-line. Always wants LF.
*/
p_read += cap_len;
rem_len -= cap_len;
while (rem_len && *p_read != '\n' && g_ascii_isspace(*p_read)) {
p_read++;
rem_len--;
}
if (rem_len && *p_read != '\n' && *p_read == '\r') {
p_read++;
rem_len--;
}
if (rem_len && *p_read == '\n') {
p_read++;
rem_len--;
return p_read;
}
return NULL;
}
/**
* Checks for the presence of the magic string at the start of the file.
*
* @param[in] buf The accumulated input buffer.
* @param[out] next_pos The text after the magic text line.
*
* @returns Boolean whether the magic was found.
*
* This implementation assumes that the magic file type marker never gets
* split across receive chunks.
*/
static gboolean have_magic(GString *buf, char **next_pos)
{
char *next_line;
if (next_pos)
*next_pos = NULL;
next_line = have_text_line(buf, MAGIC_FILE_TYPE, 0);
if (!next_line)
return FALSE;
if (next_pos)
*next_pos = next_line;
return TRUE;
}
/**
* Checks for the presence of the header section at the start of the file.
*
* @param[in] buf The accumulated input buffer.
* @param[out] next_pos The text after the header section.
*
* @returns A negative value when the answer is yet unknown (insufficient
* input data). Or boolean 0/1 when the header was found absent/present.
*
* The caller is supposed to have checked for and removed the magic text
* for the file type. This routine expects to find the header section
* boundaries right at the start of the input buffer.
*
* This implementation assumes that the header start marker never gets
* split across receive chunks.
*/
static int have_header(GString *buf, char **next_pos)
{
char *after_start, *after_end;
if (next_pos)
*next_pos = NULL;
after_start = have_text_line(buf, TEXT_HEAD_START, 0);
if (!after_start)
return 0;
after_end = have_text_line(buf, TEXT_HEAD_END, buf->len);
if (!after_end)
return -1;
if (next_pos)
*next_pos = after_end;
return 1;
}
/*
* Implementation detail: Most parse routines merely accept an input
* string or at most convert text to numbers. Actual processing of the
* values or constraints checks are done later when the header section
* ended and all data was seen, regardless of order of appearance.
*/
static int parse_samplerate(struct context *inc, const char *text)
{
uint64_t rate;
int ret;
ret = sr_parse_sizestring(text, &rate);
if (ret != SR_OK)
return SR_ERR_DATA;
inc->curr_opts.samplerate = rate;
return SR_OK;
}
static int parse_bitrate(struct context *inc, const char *text)
{
uint64_t rate;
int ret;
ret = sr_parse_sizestring(text, &rate);
if (ret != SR_OK)
return SR_ERR_DATA;
inc->curr_opts.bitrate = rate;
return SR_OK;
}
static int parse_protocol(struct context *inc, const char *line)
{
if (!line || !*line)
return SR_ERR_DATA;
if (inc->curr_opts.proto_name) {
free(inc->curr_opts.proto_name);
inc->curr_opts.proto_name = NULL;
}
inc->curr_opts.proto_name = g_strdup(line);
if (!inc->curr_opts.proto_name)
return SR_ERR_MALLOC;
line = inc->curr_opts.proto_name;
return SR_OK;
}
static int parse_frameformat(struct context *inc, const char *line)
{
if (!line || !*line)
return SR_ERR_DATA;
if (inc->curr_opts.fmt_text) {
free(inc->curr_opts.fmt_text);
inc->curr_opts.fmt_text = NULL;
}
inc->curr_opts.fmt_text = g_strdup(line);
if (!inc->curr_opts.fmt_text)
return SR_ERR_MALLOC;
line = inc->curr_opts.fmt_text;
return SR_OK;
}
static int parse_textinput(struct context *inc, const char *text)
{
gboolean is_text;
if (!text || !*text)
return SR_ERR_ARG;
is_text = sr_parse_boolstring(text);
inc->curr_opts.textinput = is_text ? INPUT_TEXT : INPUT_BYTES;
return SR_OK;
}
static int parse_header_line(struct context *inc, const char *line)
{
/* Silently ignore comment lines. Also covers start/end markers. */
if (strncmp(line, TEXT_COMM_LEADER, strlen(TEXT_COMM_LEADER)) == 0)
return SR_OK;
if (strncmp(line, LABEL_SAMPLERATE, strlen(LABEL_SAMPLERATE)) == 0) {
line += strlen(LABEL_SAMPLERATE);
return parse_samplerate(inc, line);
}
if (strncmp(line, LABEL_BITRATE, strlen(LABEL_BITRATE)) == 0) {
line += strlen(LABEL_BITRATE);
return parse_bitrate(inc, line);
}
if (strncmp(line, LABEL_PROTOCOL, strlen(LABEL_PROTOCOL)) == 0) {
line += strlen(LABEL_PROTOCOL);
return parse_protocol(inc, line);
}
if (strncmp(line, LABEL_FRAMEFORMAT, strlen(LABEL_FRAMEFORMAT)) == 0) {
line += strlen(LABEL_FRAMEFORMAT);
return parse_frameformat(inc, line);
}
if (strncmp(line, LABEL_TEXTINPUT, strlen(LABEL_TEXTINPUT)) == 0) {
line += strlen(LABEL_TEXTINPUT);
return parse_textinput(inc, line);
}
/* Unsupported directive. */
sr_err("Unsupported header directive: %s.", line);
return SR_ERR_DATA;
}
static int parse_header(struct context *inc, GString *buf, size_t hdr_len)
{
size_t remain;
char *curr, *next, *line;
int ret;
ret = SR_OK;
/* The caller determined where the header ends. Read up to there. */
remain = hdr_len;
curr = buf->str;
while (curr && remain) {
/* Get another text line. Skip empty lines. */
line = sr_text_next_line(curr, remain, &next, NULL);
if (!line)
break;
if (next)
remain -= next - curr;
else
remain = 0;
curr = next;
if (!*line)
continue;
/* Process the non-empty file header text line. */
sr_dbg("Header line: %s", line);
ret = parse_header_line(inc, line);
if (ret != SR_OK)
break;
}
return ret;
}
/* Process input text reader specific pseudo comment. */
static int process_pseudo_textinput(struct sr_input *in, char *line)
{
struct context *inc;
char *word;
unsigned long v;
char *endp;
int ret;
inc = in->priv;
while (line) {
word = sr_text_next_word(line, &line);
if (!word)
break;
if (!*word)
continue;
if (g_str_has_prefix(word, TEXT_INPUT_RADIX)) {
word += strlen(TEXT_INPUT_RADIX);
endp = NULL;
ret = sr_atoul_base(word, &v, &endp, 10);
if (ret != SR_OK)
return ret;
inc->read_text.base = v;
continue;
}
return SR_ERR_DATA;
}
return SR_OK;
}
/* Process a line of input text. */
static int process_textline(struct sr_input *in, char *line)
{
struct context *inc;
const struct proto_handler_t *handler;
gboolean is_comm, is_pseudo;
char *p, *word, *endp;
unsigned long value;
int ret;
inc = in->priv;
handler = inc->curr_opts.prot_hdl;
/*
* Check for comments, including pseudo-comments with protocol
* specific or text reader specific instructions. It's essential
* to check for "# ${PROTO}:" last, because the implementation
* of the check advances the read position, cannot rewind when
* detection fails. But we know that it is a comment and was not
* a pseudo-comment. So any non-matching data just gets discarded.
* Matching data gets processed (when handlers exist).
*/
is_comm = g_str_has_prefix(line, TEXT_COMM_LEADER);
if (is_comm) {
line += strlen(TEXT_COMM_LEADER);
while (isspace(*line))
line++;
is_pseudo = g_str_has_prefix(line, TEXT_INPUT_PREFIX);
if (is_pseudo) {
line += strlen(TEXT_INPUT_PREFIX);
while (isspace(*line))
line++;
sr_dbg("pseudo comment, textinput: %s", line);
line = sr_text_trim_spaces(line);
return process_pseudo_textinput(in, line);
}
is_pseudo = g_str_has_prefix(line, handler->name);
if (is_pseudo) {
line += strlen(handler->name);
is_pseudo = *line == ':';
if (is_pseudo)
line++;
}
if (is_pseudo) {
while (isspace(*line))
line++;
sr_dbg("pseudo comment, protocol: %s", line);
if (!handler->proc_pseudo)
return SR_OK;
return handler->proc_pseudo(in, line);
}
sr_spew("comment, skipping: %s", line);
return SR_OK;
}
/*
* Non-empty non-comment lines carry protocol values.
* (Empty lines are handled transparently when they get here.)
* Accept comma and semicolon separators for user convenience.
* Convert text according to previously received instructions.
* Pass the values to the protocol handler. Flush waveforms
* when handlers state that their construction has completed.
*/
sr_spew("got values line: %s", line);
for (p = line; *p; p++) {
if (*p == ',' || *p == ';')
*p = ' ';
}
while (line) {
word = sr_text_next_word(line, &line);
if (!word)
break;
if (!*word)
continue;
/* Get another numeric value. */
endp = NULL;
ret = sr_atoul_base(word, &value, &endp, inc->read_text.base);
if (ret != SR_OK)
return ret;
if (!endp || *endp)
return SR_ERR_DATA;
sr_spew("got a value, text [%s] -> number [%lu]", word, value);
/* Forward the value to the protocol handler. */
ret = 0;
if (handler->proc_value)
ret = handler->proc_value(inc, value);
if (ret < 0)
return ret;
/* Flush the waveform when handler signals completion. */
if (ret > 0)
continue;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
ret = send_idle_interframe(inc);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
/* }}} text/binary input file reader */
/*
* Consistency check of all previously received information. Combines
* the data file's optional header section, as well as user provided
* options that were specified during input module creation. User specs
* take precedence over file content.
*/
static int check_header_user_options(struct context *inc)
{
int ret;
const struct proto_handler_t *handler;
uint64_t rate;
const char *text;
enum textinput_t is_text;
if (!inc)
return SR_ERR_ARG;
/* Prefer user specs over file content. */
rate = inc->user_opts.samplerate;
if (rate) {
sr_dbg("Using user samplerate %" PRIu64 ".", rate);
inc->curr_opts.samplerate = rate;
}
rate = inc->user_opts.bitrate;
if (rate) {
sr_dbg("Using user bitrate %" PRIu64 ".", rate);
inc->curr_opts.bitrate = rate;
}
text = inc->user_opts.proto_name;
if (text && *text) {
sr_dbg("Using user protocol %s.", text);
ret = parse_protocol(inc, text);
if (ret != SR_OK)
return SR_ERR_DATA;
}
text = inc->user_opts.fmt_text;
if (text && *text) {
sr_dbg("Using user frame format %s.", text);
ret = parse_frameformat(inc, text);
if (ret != SR_OK)
return SR_ERR_DATA;
}
is_text = inc->user_opts.textinput;
if (is_text) {
sr_dbg("Using user textinput %d.", is_text);
inc->curr_opts.textinput = is_text;
}
/* Lookup the protocol (with fallback). Use protocol's defaults. */
text = inc->curr_opts.proto_name;
ret = lookup_protocol_name(inc);
handler = inc->curr_opts.prot_hdl;
if (ret != SR_OK || !handler) {
sr_err("Unsupported protocol: %s.", text);
return SR_ERR_DATA;
}
text = handler->name;
if (!inc->curr_opts.proto_name && text) {
sr_dbg("Using protocol handler name %s.", text);
ret = parse_protocol(inc, text);
if (ret != SR_OK)
return SR_ERR_DATA;
}
rate = handler->dflt.samplerate;
if (!inc->curr_opts.samplerate && rate) {
sr_dbg("Using protocol handler samplerate %" PRIu64 ".", rate);
inc->curr_opts.samplerate = rate;
}
rate = handler->dflt.bitrate;
if (!inc->curr_opts.bitrate && rate) {
sr_dbg("Using protocol handler bitrate %" PRIu64 ".", rate);
inc->curr_opts.bitrate = rate;
}
text = handler->dflt.frame_format;
if (!inc->curr_opts.fmt_text && text && *text) {
sr_dbg("Using protocol handler frame format %s.", text);
ret = parse_frameformat(inc, text);
if (ret != SR_OK)
return SR_ERR_DATA;
}
is_text = handler->dflt.textinput;
if (!inc->curr_opts.textinput && is_text) {
sr_dbg("Using protocol handler text format %d.", is_text);
inc->curr_opts.textinput = is_text;
}
if (!inc->curr_opts.samplerate) {
sr_err("Need a samplerate.");
return SR_ERR_DATA;
}
if (!inc->curr_opts.bitrate) {
sr_err("Need a protocol bitrate.");
return SR_ERR_DATA;
}
if (inc->curr_opts.samplerate < inc->curr_opts.bitrate) {
sr_err("Bitrate cannot exceed samplerate.");
return SR_ERR_DATA;
}
if (inc->curr_opts.samplerate / inc->curr_opts.bitrate < 3)
sr_warn("Low oversampling, consider higher samplerate.");
if (inc->curr_opts.prot_hdl->check_opts) {
ret = inc->curr_opts.prot_hdl->check_opts(inc);
if (ret != SR_OK) {
sr_err("Options failed the protocol's check.");
return SR_ERR_DATA;
}
}
return SR_OK;
}
static int create_channels(struct sr_input *in)
{
struct context *inc;
struct sr_dev_inst *sdi;
const struct proto_handler_t *handler;
size_t index;
const char *name;
if (!in)
return SR_ERR_ARG;
inc = in->priv;
if (!inc)
return SR_ERR_ARG;
sdi = in->sdi;
handler = inc->curr_opts.prot_hdl;
for (index = 0; index < handler->chans.count; index++) {
name = handler->chans.names[index];
sr_dbg("Channel %zu name %s.", index, name);
sr_channel_new(sdi, index, SR_CHANNEL_LOGIC, TRUE, name);
}
inc->feed_logic = feed_queue_logic_alloc(in->sdi,
CHUNK_SIZE, sizeof(uint8_t));
if (!inc->feed_logic) {
sr_err("Cannot create session feed.");
return SR_ERR_MALLOC;
}
return SR_OK;
}
/*
* Keep track of a previously created channel list, in preparation of
* re-reading the input file. Gets called from reset()/cleanup() paths.
*/
static void keep_header_for_reread(const struct sr_input *in)
{
struct context *inc;
inc = in->priv;
g_slist_free_full(inc->prev.sr_groups, sr_channel_group_free_cb);
inc->prev.sr_groups = in->sdi->channel_groups;
in->sdi->channel_groups = NULL;
g_slist_free_full(inc->prev.sr_channels, sr_channel_free_cb);
inc->prev.sr_channels = in->sdi->channels;
in->sdi->channels = NULL;
}
/*
* Check whether the input file is being re-read, and refuse operation
* when essential parameters of the acquisition have changed in ways
* that are unexpected to calling applications. Gets called after the
* file header got parsed (again).
*
* Changing the channel list across re-imports of the same file is not
* supported, by design and for valid reasons, see bug #1215 for details.
* Users are expected to start new sessions when they change these
* essential parameters in the acquisition's setup. When we accept the
* re-read file, then make sure to keep using the previous channel list,
* applications may still reference them.
*/
static gboolean check_header_in_reread(const struct sr_input *in)
{
struct context *inc;
if (!in)
return FALSE;
inc = in->priv;
if (!inc)
return FALSE;
if (!inc->prev.sr_channels)
return TRUE;
if (sr_channel_lists_differ(inc->prev.sr_channels, in->sdi->channels)) {
sr_err("Channel list change not supported for file re-read.");
return FALSE;
}
g_slist_free_full(in->sdi->channel_groups, sr_channel_group_free_cb);
in->sdi->channel_groups = inc->prev.sr_groups;
inc->prev.sr_groups = NULL;
g_slist_free_full(in->sdi->channels, sr_channel_free_cb);
in->sdi->channels = inc->prev.sr_channels;
inc->prev.sr_channels = NULL;
return TRUE;
}
/* Process another chunk of accumulated input data. */
static int process_buffer(struct sr_input *in, gboolean is_eof)
{
struct context *inc;
GVariant *gvar;
int ret;
GString *buf;
const struct proto_handler_t *handler;
size_t seen;
char *line, *next;
uint8_t sample;
inc = in->priv;
buf = in->buf;
handler = inc->curr_opts.prot_hdl;
/*
* Send feed header and samplerate once before any sample data.
* Communicate an idle period before the first generated frame.
*/
if (!inc->started) {
std_session_send_df_header(in->sdi);
gvar = g_variant_new_uint64(inc->curr_opts.samplerate);
ret = sr_session_send_meta(in->sdi, SR_CONF_SAMPLERATE, gvar);
inc->started = TRUE;
if (ret != SR_OK)
return ret;
ret = send_idle_capture(inc);
if (ret != SR_OK)
return ret;
}
/*
* Force proper line termination when EOF is seen and the data
* is in text format. This does not affect binary input, while
* properly terminated text input does not suffer from another
* line feed, because empty lines are considered acceptable.
* Increases robustness for text input from broken generators
* (popular editors which don't terminate the last line).
*/
if (inc->curr_opts.textinput == INPUT_TEXT && is_eof) {
g_string_append_c(buf, '\n');
}
/*
* For text input: Scan for the completion of another text line.
* Process its values (or pseudo comments). Skip comment lines.
*/
if (inc->curr_opts.textinput == INPUT_TEXT) do {
/* Get another line of text. */
seen = 0;
line = sr_text_next_line(buf->str, buf->len, &next, &seen);
if (!line)
break;
/* Process non-empty input lines. */
ret = *line ? process_textline(in, line) : 0;
if (ret < 0)
return ret;
/* Discard processed input text. */
g_string_erase(buf, 0, seen);
} while (buf->len);
/*
* For binary input: Pass data values (individual bytes) to the
* creation of protocol frames. Send the frame's waveform to
* logic channels in the session feed when the protocol handler
* signals the completion of another waveform (zero return value).
* Non-zero positive values translate to "need more input data".
* Negative values signal fatal errors. Remove processed input
* data from the receive buffer.
*/
if (inc->curr_opts.textinput == INPUT_BYTES) {
seen = 0;
while (seen < buf->len) {
sample = buf->str[seen++];
ret = 0;
if (handler->proc_value)
ret = handler->proc_value(inc, sample);
if (ret < 0)
return ret;
if (ret > 0)
continue;
ret = send_frame(in);
if (ret != SR_OK)
return ret;
ret = send_idle_interframe(inc);
if (ret != SR_OK)
return ret;
}
g_string_erase(buf, 0, seen);
}
/* Send idle level, and flush when end of input data is seen. */
if (is_eof) {
if (buf->len)
sr_warn("Unprocessed input data remains.");
ret = send_idle_capture(inc);
if (ret != SR_OK)
return ret;
ret = feed_queue_logic_flush(inc->feed_logic);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
static int format_match(GHashTable *metadata, unsigned int *confidence)
{
GString *buf, *tmpbuf;
gboolean has_magic;
buf = g_hash_table_lookup(metadata,
GINT_TO_POINTER(SR_INPUT_META_HEADER));
tmpbuf = g_string_new_len(buf->str, buf->len);
check_remove_bom(tmpbuf);
has_magic = have_magic(tmpbuf, NULL);
g_string_free(tmpbuf, TRUE);
if (!has_magic)
return SR_ERR;
*confidence = 1;
return SR_OK;
}
static int init(struct sr_input *in, GHashTable *options)
{
struct context *inc;
GVariant *gvar;
uint64_t rate;
char *copy;
const char *text;
in->sdi = g_malloc0(sizeof(*in->sdi));
inc = g_malloc0(sizeof(*inc));
in->priv = inc;
/*
* Store user specified options for later reference.
*
* TODO How to most appropriately hook up size strings with the
* input module's defaults, and applications and their input
* dialogs?
*/
gvar = g_hash_table_lookup(options, "samplerate");
if (gvar) {
rate = g_variant_get_uint64(gvar);
if (rate)
sr_dbg("User samplerate %" PRIu64 ".", rate);
inc->user_opts.samplerate = rate;
}
gvar = g_hash_table_lookup(options, "bitrate");
if (gvar) {
rate = g_variant_get_uint64(gvar);
if (rate)
sr_dbg("User bitrate %" PRIu64 ".", rate);
inc->user_opts.bitrate = rate;
}
gvar = g_hash_table_lookup(options, "protocol");
if (gvar) {
copy = g_strdup(g_variant_get_string(gvar, NULL));
if (!copy)
return SR_ERR_MALLOC;
if (*copy)
sr_dbg("User protocol %s.", copy);
inc->user_opts.proto_name = copy;
}
gvar = g_hash_table_lookup(options, "frameformat");
if (gvar) {
copy = g_strdup(g_variant_get_string(gvar, NULL));
if (!copy)
return SR_ERR_MALLOC;
if (*copy)
sr_dbg("User frame format %s.", copy);
inc->user_opts.fmt_text = copy;
}
inc->user_opts.textinput = INPUT_UNSPEC;
gvar = g_hash_table_lookup(options, "textinput");
if (gvar) {
text = g_variant_get_string(gvar, NULL);
if (!text)
return SR_ERR_DATA;
if (!*text)
return SR_ERR_DATA;
sr_dbg("User text input %s.", text);
if (strcmp(text, input_format_texts[INPUT_UNSPEC]) == 0) {
inc->user_opts.textinput = INPUT_UNSPEC;
} else if (strcmp(text, input_format_texts[INPUT_BYTES]) == 0) {
inc->user_opts.textinput = INPUT_BYTES;
} else if (strcmp(text, input_format_texts[INPUT_TEXT]) == 0) {
inc->user_opts.textinput = INPUT_TEXT;
} else {
return SR_ERR_DATA;
}
}
return SR_OK;
}
static int receive(struct sr_input *in, GString *buf)
{
struct context *inc;
char *after_magic, *after_header;
size_t consumed;
int ret;
inc = in->priv;
/*
* Accumulate all input chunks, potential deferred processing.
*
* Remove an optional BOM at the very start of the input stream.
* BEWARE! This may affect binary input, and we cannot tell if
* the input is text or binary at this stage. Though probability
* for this issue is rather low. Workarounds are available (put
* another values before the first data which happens to match
* the BOM pattern, provide text input instead).
*/
g_string_append_len(in->buf, buf->str, buf->len);
if (!inc->scanned_magic)
check_remove_bom(in->buf);
/*
* Must complete reception of the (optional) header first. Both
* end of header and absence of header will: Check options that
* were seen so far, then start processing the data part.
*/
if (!inc->got_header) {
/* Check for magic file type marker. */
if (!inc->scanned_magic) {
inc->has_magic = have_magic(in->buf, &after_magic);
inc->scanned_magic = TRUE;
if (inc->has_magic) {
consumed = after_magic - in->buf->str;
sr_dbg("File format magic found (%zu).", consumed);
g_string_erase(in->buf, 0, consumed);
}
}
/* Complete header reception and processing. */
if (inc->has_magic) {
ret = have_header(in->buf, &after_header);
if (ret < 0)
return SR_OK;
inc->has_header = ret;
if (inc->has_header) {
consumed = after_header - in->buf->str;
sr_dbg("File header found (%zu), processing.", consumed);
ret = parse_header(inc, in->buf, consumed);
if (ret != SR_OK)
return ret;
g_string_erase(in->buf, 0, consumed);
}
}
inc->got_header = TRUE;
/*
* Postprocess the combination of all options. Create
* logic channels, prepare resources for data processing.
*/
ret = check_header_user_options(inc);
if (ret != SR_OK)
return ret;
ret = create_channels(in);
if (ret != SR_OK)
return ret;
if (!check_header_in_reread(in))
return SR_ERR_DATA;
ret = alloc_frame_storage(inc);
if (ret != SR_OK)
return ret;
ret = assign_bit_widths(inc);
if (ret != SR_OK)
return ret;
/* Notify the frontend that sdi is ready. */
in->sdi_ready = TRUE;
return SR_OK;
}
/*
* Process the input file's data section after the header section
* was received and processed.
*/
ret = process_buffer(in, FALSE);
return ret;
}
static int end(struct sr_input *in)
{
struct context *inc;
int ret;
inc = in->priv;
/* Must complete processing of previously received chunks. */
if (in->sdi_ready) {
ret = process_buffer(in, TRUE);
if (ret != SR_OK)
return ret;
}
/* Must send DF_END when DF_HEADER was sent before. */
if (inc->started) {
ret = std_session_send_df_end(in->sdi);
if (ret != SR_OK)
return ret;
}
return SR_OK;
}
static void cleanup(struct sr_input *in)
{
struct context *inc;
inc = in->priv;
keep_header_for_reread(in);
g_free(inc->curr_opts.proto_name);
inc->curr_opts.proto_name = NULL;
g_free(inc->curr_opts.fmt_text);
inc->curr_opts.fmt_text = NULL;
g_free(inc->curr_opts.prot_priv);
inc->curr_opts.prot_priv = NULL;
feed_queue_logic_free(inc->feed_logic);
inc->feed_logic = NULL;
g_free(inc->sample_edges);
inc->sample_edges = NULL;
g_free(inc->sample_widths);
inc->sample_widths = NULL;
g_free(inc->sample_levels);
inc->sample_levels = NULL;
g_free(inc->bit_scale);
inc->bit_scale = NULL;
}
static int reset(struct sr_input *in)
{
struct context *inc;
struct user_opts_t save_user_opts;
struct proto_prev save_chans;
inc = in->priv;
/* Release previously allocated resources. */
cleanup(in);
g_string_truncate(in->buf, 0);
/* Restore part of the context, init() won't run again. */
save_user_opts = inc->user_opts;
save_chans = inc->prev;
memset(inc, 0, sizeof(*inc));
inc->user_opts = save_user_opts;
inc->prev = save_chans;
return SR_OK;
}
enum proto_option_t {
OPT_SAMPLERATE,
OPT_BITRATE,
OPT_PROTOCOL,
OPT_FRAME_FORMAT,
OPT_TEXTINPUT,
OPT_MAX,
};
static struct sr_option options[] = {
[OPT_SAMPLERATE] = {
"samplerate", "Logic data samplerate",
"Samplerate of generated logic traces",
NULL, NULL,
},
[OPT_BITRATE] = {
"bitrate", "Protocol bitrate",
"Bitrate used in protocol's communication",
NULL, NULL,
},
[OPT_PROTOCOL] = {
"protocol", "Protocol type",
"The type of protocol to generate waveforms for",
NULL, NULL,
},
[OPT_FRAME_FORMAT] = {
"frameformat", "Protocol frame format",
"Textual description of the protocol's frame format",
NULL, NULL,
},
[OPT_TEXTINPUT] = {
"textinput", "Input data is in text format",
"Input is not data bytes, but text formatted values",
NULL, NULL,
},
[OPT_MAX] = ALL_ZERO,
};
static const struct sr_option *get_options(void)
{
GSList *l;
enum proto_type_t p_idx;
enum textinput_t t_idx;
const char *s;
if (options[0].def)
return options;
options[OPT_SAMPLERATE].def = g_variant_ref_sink(g_variant_new_uint64(0));
options[OPT_BITRATE].def = g_variant_ref_sink(g_variant_new_uint64(0));
options[OPT_PROTOCOL].def = g_variant_ref_sink(g_variant_new_string(""));
l = NULL;
for (p_idx = 0; p_idx < ARRAY_SIZE(protocols); p_idx++) {
s = protocols[p_idx].name;
if (!s || !*s)
continue;
l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string(s)));
}
options[OPT_PROTOCOL].values = l;
options[OPT_FRAME_FORMAT].def = g_variant_ref_sink(g_variant_new_string(""));
l = NULL;
for (t_idx = INPUT_UNSPEC; t_idx <= INPUT_TEXT; t_idx++) {
s = input_format_texts[t_idx];
l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string(s)));
}
options[OPT_TEXTINPUT].values = l;
options[OPT_TEXTINPUT].def = g_variant_ref_sink(g_variant_new_string(
input_format_texts[INPUT_UNSPEC]));
return options;
}
SR_PRIV struct sr_input_module input_protocoldata = {
.id = "protocoldata",
.name = "Protocol data",
.desc = "Generate logic traces from protocol's data values",
.exts = (const char *[]){ "sr-protocol", "protocol", "bin", NULL, },
.metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
.options = get_options,
.format_match = format_match,
.init = init,
.receive = receive,
.end = end,
.reset = reset,
};