]> sigrok.org Git - libsigrok.git/blobdiff - src/input/csv.c
input/csv: rearrange text to logic data conversion and datafeed
[libsigrok.git] / src / input / csv.c
index 000599a8d305bf9140fa78679aefb1b5cad72174..c53ca426aa8863301638c6ebf5da086d5f5ee3a6 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
+
+#include <glib.h>
 #include <stdlib.h>
 #include <string.h>
-#include <glib.h>
+
 #include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "input/csv"
 
-#define DATAFEED_MAX_SAMPLES   (128 * 1024)
+#define CHUNK_SIZE     (4 * 1024 * 1024)
 
 /*
  * The CSV input module has the following options:
  */
 
 /* Single column formats. */
-enum {
+enum single_col_format {
        FORMAT_BIN,
        FORMAT_HEX,
-       FORMAT_OCT
+       FORMAT_OCT,
 };
 
 struct context {
@@ -116,7 +118,7 @@ struct context {
        uint64_t samplerate;
 
        /* Number of channels. */
-       unsigned int num_channels;
+       size_t num_channels;
 
        /* Column delimiter character(s). */
        GString *delimiter;
@@ -131,20 +133,20 @@ struct context {
        gboolean multi_column_mode;
 
        /* Column number of the sample data in single column mode. */
-       unsigned int single_column;
+       size_t single_column;
 
        /*
         * Number of the first column to parse. Equivalent to the number of the
         * first channel in multi column mode and the single column number in
         * single column mode.
         */
-       unsigned int first_column;
+       size_t first_column;
 
        /*
         * Column number of the first channel in multi column mode and position of
         * the bit for the first channel in single column mode.
         */
-       unsigned int first_channel;
+       size_t first_channel;
 
        /* Line number to start processing. */
        size_t start_line;
@@ -156,7 +158,7 @@ struct context {
        gboolean header;
 
        /* Format sample data is stored in single column mode. */
-       int format;
+       enum single_col_format format;
 
        size_t sample_unit_size;        /**!< Byte count for a single sample. */
        uint8_t *sample_buffer;         /**!< Buffer for a single sample. */
@@ -167,8 +169,95 @@ struct context {
 
        /* Current line number. */
        size_t line_number;
+
+       /* List of previously created sigrok channels. */
+       GSList *prev_sr_channels;
 };
 
+/*
+ * Primitive operations to handle sample sets:
+ * - Keep a buffer for datafeed submission, capable of holding many
+ *   samples (reduces call overhead, improves throughput).
+ * - Have a "current sample set" pointer reference one position in that
+ *   large samples buffer.
+ * - Clear the current sample set before text line inspection, then set
+ *   the bits which are found active in the current line of text input.
+ *   Phrase the API such that call sites can be kept simple. Advance to
+ *   the next sample set between lines, flush the larger buffer as needed
+ *   (when it is full, or upon EOF).
+ */
+
+static void clear_logic_samples(struct context *inc)
+{
+       inc->sample_buffer = &inc->datafeed_buffer[inc->datafeed_buf_fill];
+       memset(inc->sample_buffer, 0, inc->sample_unit_size);
+}
+
+static void set_logic_level(struct context *inc, size_t ch_idx, int on)
+{
+       size_t byte_idx, bit_idx;
+       uint8_t bit_mask;
+
+       if (ch_idx >= inc->num_channels)
+               return;
+       if (!on)
+               return;
+
+       byte_idx = ch_idx / 8;
+       bit_idx = ch_idx % 8;
+       bit_mask = 1 << bit_idx;
+       inc->sample_buffer[byte_idx] |= bit_mask;
+}
+
+static int flush_logic_samples(const struct sr_input *in)
+{
+       struct context *inc;
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_logic logic;
+       int rc;
+
+       inc = in->priv;
+       if (!inc->datafeed_buf_fill)
+               return SR_OK;
+
+       memset(&packet, 0, sizeof(packet));
+       memset(&logic, 0, sizeof(logic));
+       packet.type = SR_DF_LOGIC;
+       packet.payload = &logic;
+       logic.unitsize = inc->sample_unit_size;
+       logic.length = inc->datafeed_buf_fill;
+       logic.data = inc->datafeed_buffer;
+
+       rc = sr_session_send(in->sdi, &packet);
+       if (rc != SR_OK)
+               return rc;
+
+       inc->datafeed_buf_fill = 0;
+       return SR_OK;
+}
+
+static int queue_logic_samples(const struct sr_input *in)
+{
+       struct context *inc;
+       int rc;
+
+       inc = in->priv;
+
+       inc->datafeed_buf_fill += inc->sample_unit_size;
+       if (inc->datafeed_buf_fill == inc->datafeed_buf_size) {
+               rc = flush_logic_samples(in);
+               if (rc != SR_OK)
+                       return rc;
+       }
+       return SR_OK;
+}
+
+/*
+ * Primitive operations for text input: Strip comments off text lines.
+ * Split text lines into columns. Process input text for individual
+ * columns.
+ */
+
 static void strip_comment(char *buf, const GString *prefix)
 {
        char *ptr;
@@ -176,32 +265,45 @@ static void strip_comment(char *buf, const GString *prefix)
        if (!prefix->len)
                return;
 
-       if ((ptr = strstr(buf, prefix->str)))
+       if ((ptr = strstr(buf, prefix->str))) {
                *ptr = '\0';
+               g_strstrip(buf);
+       }
 }
 
+/* TODO Move parse_line() here. */
+
+/**
+ * @brief Parse a text field into multiple bits, binary presentation.
+ *
+ * @param[in] str      The input text, a run of 0/1 digits.
+ * @param[in] inc      The input module's context.
+ *
+ * @retval SR_OK       Success.
+ * @retval SR_ERR      Invalid input data (empty, or format error).
+ *
+ * This routine modifies the logic levels in the current sample set,
+ * based on the text input which consists of binary digits.
+ */
 static int parse_binstr(const char *str, struct context *inc)
 {
        gsize i, j, length;
+       char c;
 
        length = strlen(str);
-
        if (!length) {
-               sr_err("Column %u in line %zu is empty.", inc->single_column,
+               sr_err("Column %zu in line %zu is empty.", inc->single_column,
                        inc->line_number);
                return SR_ERR;
        }
 
-       /* Clear buffer in order to set bits only. */
-       memset(inc->sample_buffer, 0, inc->sample_unit_size);
-
        i = inc->first_channel;
-
        for (j = 0; i < length && j < inc->num_channels; i++, j++) {
-               if (str[length - i - 1] == '1') {
-                       inc->sample_buffer[j / 8] |= (1 << (j % 8));
-               } else if (str[length - i - 1] != '0') {
-                       sr_err("Invalid value '%s' in column %u in line %zu.",
+               c = str[length - i - 1];
+               if (c == '1') {
+                       set_logic_level(inc, j, 1);
+               } else if (c != '0') {
+                       sr_err("Invalid text '%s' in binary column %zu in line %zu.",
                                str, inc->single_column, inc->line_number);
                        return SR_ERR;
                }
@@ -210,6 +312,18 @@ static int parse_binstr(const char *str, struct context *inc)
        return SR_OK;
 }
 
+/**
+ * @brief Parse a text field into multiple bits, hexadecimal presentation.
+ *
+ * @param[in] str      The input text, a run of hex digits.
+ * @param[in] inc      The input module's context.
+ *
+ * @retval SR_OK       Success.
+ * @retval SR_ERR      Invalid input data (empty, or format error).
+ *
+ * This routine modifies the logic levels in the current sample set,
+ * based on the text input which consists of hexadecimal digits.
+ */
 static int parse_hexstr(const char *str, struct context *inc)
 {
        gsize i, j, k, length;
@@ -217,43 +331,44 @@ static int parse_hexstr(const char *str, struct context *inc)
        char c;
 
        length = strlen(str);
-
        if (!length) {
-               sr_err("Column %u in line %zu is empty.", inc->single_column,
+               sr_err("Column %zu in line %zu is empty.", inc->single_column,
                        inc->line_number);
                return SR_ERR;
        }
 
-       /* Clear buffer in order to set bits only. */
-       memset(inc->sample_buffer, 0, inc->sample_unit_size);
-
        /* Calculate the position of the first hexadecimal digit. */
        i = inc->first_channel / 4;
-
        for (j = 0; i < length && j < inc->num_channels; i++) {
                c = str[length - i - 1];
-
                if (!g_ascii_isxdigit(c)) {
-                       sr_err("Invalid value '%s' in column %u in line %zu.",
+                       sr_err("Invalid text '%s' in hex column %zu in line %zu.",
                                str, inc->single_column, inc->line_number);
                        return SR_ERR;
                }
 
                value = g_ascii_xdigit_value(c);
-
                k = (inc->first_channel + j) % 4;
-
-               for (; j < inc->num_channels && k < 4; k++) {
-                       if (value & (1 << k))
-                               inc->sample_buffer[j / 8] |= (1 << (j % 8));
-
-                       j++;
+               for (; j < inc->num_channels && k < 4; j++, k++) {
+                       set_logic_level(inc, j, value & (1 << k));
                }
        }
 
        return SR_OK;
 }
 
+/**
+ * @brief Parse a text field into multiple bits, octal presentation.
+ *
+ * @param[in] str      The input text, a run of oct digits.
+ * @param[in] inc      The input module's context.
+ *
+ * @retval SR_OK       Success.
+ * @retval SR_ERR      Invalid input data (empty, or format error).
+ *
+ * This routine modifies the logic levels in the current sample set,
+ * based on the text input which consists of octal digits.
+ */
 static int parse_octstr(const char *str, struct context *inc)
 {
        gsize i, j, k, length;
@@ -261,109 +376,134 @@ static int parse_octstr(const char *str, struct context *inc)
        char c;
 
        length = strlen(str);
-
        if (!length) {
-               sr_err("Column %u in line %zu is empty.", inc->single_column,
+               sr_err("Column %zu in line %zu is empty.", inc->single_column,
                        inc->line_number);
                return SR_ERR;
        }
 
-       /* Clear buffer in order to set bits only. */
-       memset(inc->sample_buffer, 0, inc->sample_unit_size);
-
        /* Calculate the position of the first octal digit. */
        i = inc->first_channel / 3;
-
        for (j = 0; i < length && j < inc->num_channels; i++) {
                c = str[length - i - 1];
-
                if (c < '0' || c > '7') {
-                       sr_err("Invalid value '%s' in column %u in line %zu.",
+                       sr_err("Invalid text '%s' in oct column %zu in line %zu.",
                                str, inc->single_column, inc->line_number);
                        return SR_ERR;
                }
 
                value = g_ascii_xdigit_value(c);
-
                k = (inc->first_channel + j) % 3;
-
-               for (; j < inc->num_channels && k < 3; k++) {
-                       if (value & (1 << k))
-                               inc->sample_buffer[j / 8] |= (1 << (j % 8));
-
-                       j++;
+               for (; j < inc->num_channels && k < 3; j++, k++) {
+                       set_logic_level(inc, j, value & (1 << k));
                }
        }
 
        return SR_OK;
 }
 
-static char **parse_line(char *buf, struct context *inc, int max_columns)
+static int parse_single_column(const char *column, struct context *inc)
+{
+       switch (inc->format) {
+       case FORMAT_BIN:
+               return parse_binstr(column, inc);
+       case FORMAT_HEX:
+               return parse_hexstr(column, inc);
+       case FORMAT_OCT:
+               return parse_octstr(column, inc);
+       }
+
+       return SR_ERR;
+}
+
+/**
+ * @brief Splits a text line into a set of columns.
+ *
+ * @param[in] buf      The input text line to split.
+ * @param[in] inc      The input module's context.
+ * @param[in] max_cols The maximum column count, negative to get all of them.
+ *
+ * @returns An array of strings, representing the columns' text.
+ *
+ * This routine splits a text line on previously determined separators.
+ * A previously determined set of columns gets isolated (starting at a
+ * first position and spanning a given number of columns). A negative
+ * value for the maximum number of columns results in no restriction on
+ * the result set's length (the first columns still get trimmed off).
+ */
+static char **parse_line(char *buf, struct context *inc, ssize_t max_cols)
 {
        const char *str, *remainder;
        GSList *list, *l;
        char **columns;
        char *column;
-       gsize n, k;
+       gsize seen, taken;
 
-       n = 0;
-       k = 0;
+       seen = 0;
+       taken = 0;
        list = NULL;
 
        remainder = buf;
        str = strstr(remainder, inc->delimiter->str);
-
-       while (str && max_columns) {
-               if (n >= inc->first_column) {
+       while (str && max_cols) {
+               if (seen >= inc->first_column) {
                        column = g_strndup(remainder, str - remainder);
                        list = g_slist_prepend(list, g_strstrip(column));
 
-                       max_columns--;
-                       k++;
+                       max_cols--;
+                       taken++;
                }
 
                remainder = str + inc->delimiter->len;
                str = strstr(remainder, inc->delimiter->str);
-               n++;
+               seen++;
        }
 
-       if (buf[0] && max_columns && n >= inc->first_column) {
+       if (buf[0] && max_cols && seen >= inc->first_column) {
                column = g_strdup(remainder);
                list = g_slist_prepend(list, g_strstrip(column));
-               k++;
+               taken++;
        }
 
-       if (!(columns = g_try_new(char *, k + 1)))
+       if (!(columns = g_try_new(char *, taken + 1)))
                return NULL;
-
-       columns[k--] = NULL;
-
+       columns[taken--] = NULL;
        for (l = list; l; l = l->next)
-               columns[k--] = l->data;
+               columns[taken--] = l->data;
 
        g_slist_free(list);
 
        return columns;
 }
 
+/**
+ * @brief Picks logic levels from multiple binary colomns, one channel per column.
+ *
+ * @param[in] columns  The text fields which are kept in the columns.
+ * @param[in] inc      The input module's context.
+ *
+ * @retval SR_OK       Success.
+ * @retval SR_ERR      Insufficient input, or syntax errors.
+ *
+ * This routine exclusively handles binary input where one logic channel
+ * occupies one column each. All channels are expected to reside in one
+ * consequtive run of columns.
+ */
 static int parse_multi_columns(char **columns, struct context *inc)
 {
        gsize i;
        char *column;
 
-       /* Clear buffer in order to set bits only. */
-       memset(inc->sample_buffer, 0, inc->sample_unit_size);
-
        for (i = 0; i < inc->num_channels; i++) {
                column = columns[i];
-               if (column[0] == '1') {
-                       inc->sample_buffer[i / 8] |= (1 << (i % 8));
+               if (strcmp(column, "1") == 0) {
+                       set_logic_level(inc, i, 1);
                } else if (!strlen(column)) {
                        sr_err("Column %zu in line %zu is empty.",
                                inc->first_channel + i, inc->line_number);
                        return SR_ERR;
-               } else if (column[0] != '0') {
-                       sr_err("Invalid value '%s' in column %zu in line %zu.",
+               } else if (strcmp(column, "0") != 0) {
+                       sr_err("Invalid text '%s' in bit column %zu in line %zu.",
                                column, inc->first_channel + i,
                                inc->line_number);
                        return SR_ERR;
@@ -373,71 +513,6 @@ static int parse_multi_columns(char **columns, struct context *inc)
        return SR_OK;
 }
 
-static int parse_single_column(const char *column, struct context *inc)
-{
-       int res;
-
-       res = SR_ERR;
-
-       switch (inc->format) {
-       case FORMAT_BIN:
-               res = parse_binstr(column, inc);
-               break;
-       case FORMAT_HEX:
-               res = parse_hexstr(column, inc);
-               break;
-       case FORMAT_OCT:
-               res = parse_octstr(column, inc);
-               break;
-       }
-
-       return res;
-}
-
-static int flush_samples(const struct sr_input *in)
-{
-       struct context *inc;
-       struct sr_datafeed_packet packet;
-       struct sr_datafeed_logic logic;
-       int rc;
-
-       inc = in->priv;
-       if (!inc->datafeed_buf_fill)
-               return SR_OK;
-
-       memset(&packet, 0, sizeof(packet));
-       memset(&logic, 0, sizeof(logic));
-       packet.type = SR_DF_LOGIC;
-       packet.payload = &logic;
-       logic.unitsize = inc->sample_unit_size;
-       logic.length = inc->datafeed_buf_fill;
-       logic.data = inc->datafeed_buffer;
-
-       rc = sr_session_send(in->sdi, &packet);
-       if (rc != SR_OK)
-               return rc;
-
-       inc->datafeed_buf_fill = 0;
-       return SR_OK;
-}
-
-static int queue_samples(const struct sr_input *in)
-{
-       struct context *inc;
-       int rc;
-
-       inc = in->priv;
-
-       inc->datafeed_buf_fill += inc->sample_unit_size;
-       if (inc->datafeed_buf_fill == inc->datafeed_buf_size) {
-               rc = flush_samples(in);
-               if (rc != SR_OK)
-                       return rc;
-       }
-       inc->sample_buffer = &inc->datafeed_buffer[inc->datafeed_buf_fill];
-       return SR_OK;
-}
-
 static int init(struct sr_input *in, GHashTable *options)
 {
        struct context *inc;
@@ -446,10 +521,10 @@ static int init(struct sr_input *in, GHashTable *options)
        in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
        in->priv = inc = g_malloc0(sizeof(struct context));
 
-       inc->single_column = g_variant_get_int32(g_hash_table_lookup(options, "single-column"));
+       inc->single_column = g_variant_get_uint32(g_hash_table_lookup(options, "single-column"));
        inc->multi_column_mode = inc->single_column == 0;
 
-       inc->num_channels = g_variant_get_int32(g_hash_table_lookup(options, "numchannels"));
+       inc->num_channels = g_variant_get_uint32(g_hash_table_lookup(options, "numchannels"));
 
        inc->delimiter = g_string_new(g_variant_get_string(
                        g_hash_table_lookup(options, "delimiter"), NULL));
@@ -481,11 +556,11 @@ static int init(struct sr_input *in, GHashTable *options)
 
        inc->samplerate = g_variant_get_uint64(g_hash_table_lookup(options, "samplerate"));
 
-       inc->first_channel = g_variant_get_int32(g_hash_table_lookup(options, "first-channel"));
+       inc->first_channel = g_variant_get_uint32(g_hash_table_lookup(options, "first-channel"));
 
        inc->header = g_variant_get_boolean(g_hash_table_lookup(options, "header"));
 
-       inc->start_line = g_variant_get_int32(g_hash_table_lookup(options, "startline"));
+       inc->start_line = g_variant_get_uint32(g_hash_table_lookup(options, "startline"));
        if (inc->start_line < 1) {
                sr_err("Invalid start line %zu.", inc->start_line);
                return SR_ERR_ARG;
@@ -504,6 +579,44 @@ static int init(struct sr_input *in, GHashTable *options)
        return SR_OK;
 }
 
+/*
+ * Check the channel list for consistency across file re-import. See
+ * the VCD input module for more details and motivation.
+ */
+
+static void keep_header_for_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       inc = in->priv;
+       g_slist_free_full(inc->prev_sr_channels, sr_channel_free_cb);
+       inc->prev_sr_channels = in->sdi->channels;
+       in->sdi->channels = NULL;
+}
+
+static int check_header_in_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       if (!in)
+               return FALSE;
+       inc = in->priv;
+       if (!inc)
+               return FALSE;
+       if (!inc->prev_sr_channels)
+               return TRUE;
+
+       if (sr_channel_lists_differ(inc->prev_sr_channels, in->sdi->channels)) {
+               sr_err("Channel list change not supported for file re-read.");
+               return FALSE;
+       }
+       g_slist_free_full(in->sdi->channels, sr_channel_free_cb);
+       in->sdi->channels = inc->prev_sr_channels;
+       inc->prev_sr_channels = NULL;
+
+       return TRUE;
+}
+
 static const char *delim_set = "\r\n";
 
 static const char *get_line_termination(GString *buf)
@@ -525,7 +638,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
 {
        struct context *inc;
        GString *channel_name;
-       unsigned int num_columns, i;
+       size_t num_columns, i;
        size_t line_number, l;
        int ret;
        char **lines, *line, **columns, *column;
@@ -576,7 +689,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
 
        /* Ensure that the first column is not out of bounds. */
        if (!num_columns) {
-               sr_err("Column %u in line %zu is out of bounds.",
+               sr_err("Column %zu in line %zu is out of bounds.",
                        inc->first_column, line_number);
                ret = SR_ERR;
                goto out;
@@ -589,7 +702,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
                 */
                if (!inc->num_channels) {
                        inc->num_channels = num_columns;
-                       sr_dbg("Number of auto-detected channels: %u.",
+                       sr_dbg("Number of auto-detected channels: %zu.",
                                inc->num_channels);
                }
 
@@ -611,24 +724,27 @@ static int initial_parse(const struct sr_input *in, GString *buf)
                if (inc->header && inc->multi_column_mode && column[0] != '\0')
                        g_string_assign(channel_name, column);
                else
-                       g_string_printf(channel_name, "%u", i);
+                       g_string_printf(channel_name, "%zu", i);
                sr_channel_new(in->sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name->str);
        }
        g_string_free(channel_name, TRUE);
+       if (!check_header_in_reread(in)) {
+               ret = SR_ERR_DATA;
+               goto out;
+       }
 
        /*
         * Calculate the minimum buffer size to store the set of samples
         * of all channels (unit size). Determine a larger buffer size
         * for datafeed submission that is a multiple of the unit size.
-        * Allocate the larger buffer, and have the "sample buffer" point
-        * to a location within that large buffer.
+        * Allocate the larger buffer, the "sample buffer" will point
+        * to a location within that large buffer later.
         */
        inc->sample_unit_size = (inc->num_channels + 7) / 8;
-       inc->datafeed_buf_size = DATAFEED_MAX_SAMPLES;
+       inc->datafeed_buf_size = CHUNK_SIZE;
        inc->datafeed_buf_size *= inc->sample_unit_size;
        inc->datafeed_buffer = g_malloc(inc->datafeed_buf_size);
        inc->datafeed_buf_fill = 0;
-       inc->sample_buffer = &inc->datafeed_buffer[inc->datafeed_buf_fill];
 
 out:
        if (columns)
@@ -704,7 +820,8 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
        struct context *inc;
        gsize num_columns;
        uint64_t samplerate;
-       int max_columns, ret, l;
+       size_t max_columns, l;
+       int ret;
        char *p, **lines, *line, **columns;
 
        inc = in->priv;
@@ -789,7 +906,7 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
                }
                num_columns = g_strv_length(columns);
                if (!num_columns) {
-                       sr_err("Column %u in line %zu is out of bounds.",
+                       sr_err("Column %zu in line %zu is out of bounds.",
                                inc->first_column, inc->line_number);
                        g_strfreev(columns);
                        g_strfreev(lines);
@@ -807,6 +924,8 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
                        return SR_ERR;
                }
 
+               clear_logic_samples(inc);
+
                if (inc->multi_column_mode)
                        ret = parse_multi_columns(columns, inc);
                else
@@ -817,8 +936,8 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
                        return SR_ERR;
                }
 
-               /* Send sample data to the session bus. */
-               ret = queue_samples(in);
+               /* Send sample data to the session bus (buffered). */
+               ret = queue_logic_samples(in);
                if (ret != SR_OK) {
                        sr_err("Sending samples failed.");
                        g_strfreev(columns);
@@ -872,7 +991,7 @@ static int end(struct sr_input *in)
        if (ret != SR_OK)
                return ret;
 
-       ret = flush_samples(in);
+       ret = flush_logic_samples(in);
        if (ret != SR_OK)
                return ret;
 
@@ -887,16 +1006,14 @@ static void cleanup(struct sr_input *in)
 {
        struct context *inc;
 
-       inc = in->priv;
-
-       if (inc->delimiter)
-               g_string_free(inc->delimiter, TRUE);
+       keep_header_for_reread(in);
 
-       if (inc->comment)
-               g_string_free(inc->comment, TRUE);
+       inc = in->priv;
 
        g_free(inc->termination);
+       inc->termination = NULL;
        g_free(inc->datafeed_buffer);
+       inc->datafeed_buffer = NULL;
 }
 
 static int reset(struct sr_input *in)
@@ -910,31 +1027,51 @@ static int reset(struct sr_input *in)
        return SR_OK;
 }
 
+enum option_index {
+       OPT_SINGLE_COL,
+       OPT_NUM_LOGIC,
+       OPT_DELIM,
+       OPT_FORMAT,
+       OPT_COMMENT,
+       OPT_RATE,
+       OPT_FIRST_LOGIC,
+       OPT_HEADER,
+       OPT_START,
+       OPT_MAX,
+};
+
 static struct sr_option options[] = {
-       { "single-column", "Single column", "Enable/specify single column", NULL, NULL },
-       { "numchannels", "Max channels", "Number of channels", NULL, NULL },
-       { "delimiter", "Delimiter", "Column delimiter", NULL, NULL },
-       { "format", "Format", "Numeric format", NULL, NULL },
-       { "comment", "Comment", "Comment prefix character", NULL, NULL },
-       { "samplerate", "Samplerate", "Samplerate used during capture", NULL, NULL },
-       { "first-channel", "First channel", "Column number of first channel", NULL, NULL },
-       { "header", "Header", "Treat first line as header with channel names", NULL, NULL },
-       { "startline", "Start line", "Line number at which to start processing samples", NULL, NULL },
-       ALL_ZERO
+       [OPT_SINGLE_COL] = { "single-column", "Single column", "Enable single-column mode, using the specified column (>= 1); 0: multi-col. mode", NULL, NULL },
+       [OPT_NUM_LOGIC] = { "numchannels", "Number of logic channels", "The number of (logic) channels (single-col. mode: number of bits beginning at 'first channel', LSB-first)", NULL, NULL },
+       [OPT_DELIM] = { "delimiter", "Column delimiter", "The column delimiter (>= 1 characters)", NULL, NULL },
+       [OPT_FORMAT] = { "format", "Data format (single-col. mode)", "The numeric format of the data (single-col. mode): bin, hex, oct", NULL, NULL },
+       [OPT_COMMENT] = { "comment", "Comment character(s)", "The comment prefix character(s)", NULL, NULL },
+       [OPT_RATE] = { "samplerate", "Samplerate (Hz)", "The sample rate (used during capture) in Hz", NULL, NULL },
+       [OPT_FIRST_LOGIC] = { "first-channel", "First channel", "The column number of the first channel (multi-col. mode); bit position for the first channel (single-col. mode)", NULL, NULL },
+       [OPT_HEADER] = { "header", "Interpret first line as header (multi-col. mode)", "Treat the first line as header with channel names (multi-col. mode)", NULL, NULL },
+       [OPT_START] = { "startline", "Start line", "The line number at which to start processing samples (>= 1)", NULL, NULL },
+       [OPT_MAX] = ALL_ZERO,
 };
 
 static const struct sr_option *get_options(void)
 {
+       GSList *l;
+
        if (!options[0].def) {
-               options[0].def = g_variant_ref_sink(g_variant_new_int32(0));
-               options[1].def = g_variant_ref_sink(g_variant_new_int32(0));
-               options[2].def = g_variant_ref_sink(g_variant_new_string(","));
-               options[3].def = g_variant_ref_sink(g_variant_new_string("bin"));
-               options[4].def = g_variant_ref_sink(g_variant_new_string(";"));
-               options[5].def = g_variant_ref_sink(g_variant_new_uint64(0));
-               options[6].def = g_variant_ref_sink(g_variant_new_int32(0));
-               options[7].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
-               options[8].def = g_variant_ref_sink(g_variant_new_int32(1));
+               options[OPT_SINGLE_COL].def = g_variant_ref_sink(g_variant_new_int32(0));
+               options[OPT_NUM_LOGIC].def = g_variant_ref_sink(g_variant_new_int32(0));
+               options[OPT_DELIM].def = g_variant_ref_sink(g_variant_new_string(","));
+               options[OPT_FORMAT].def = g_variant_ref_sink(g_variant_new_string("bin"));
+               l = NULL;
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("bin")));
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("hex")));
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("oct")));
+               options[OPT_FORMAT].values = l;
+               options[OPT_COMMENT].def = g_variant_ref_sink(g_variant_new_string(";"));
+               options[OPT_RATE].def = g_variant_ref_sink(g_variant_new_uint64(0));
+               options[OPT_FIRST_LOGIC].def = g_variant_ref_sink(g_variant_new_int32(0));
+               options[OPT_HEADER].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[OPT_START].def = g_variant_ref_sink(g_variant_new_int32(1));
        }
 
        return options;